1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10: class Kronolith_Api extends Horde_Registry_Api
11: {
12: 13: 14: 15: 16:
17: public $links = array(
18: 'show' => '%application%/event.php?calendar=|calendar|&eventID=|event|&uid=|uid|'
19: );
20:
21: 22: 23: 24: 25:
26: public function shareHelp()
27: {
28: return 'shares';
29: }
30:
31: 32: 33: 34: 35: 36: 37:
38: public function modified($uid)
39: {
40: $modified = $this->getActionTimestamp($uid, 'modify');
41: if (empty($modified)) {
42: $modified = $this->getActionTimestamp($uid, 'add');
43: }
44: return $modified;
45: }
46:
47: 48: 49: 50: 51: 52: 53: 54: 55: 56:
57: public function browse($path = '', $properties = array())
58: {
59: global $registry;
60:
61:
62: if (!$properties) {
63: $properties = array('name', 'icon', 'browseable');
64: }
65:
66: if (substr($path, 0, 9) == 'kronolith') {
67: $path = substr($path, 9);
68: }
69: $path = trim($path, '/');
70: $parts = explode('/', $path);
71:
72: if (empty($path)) {
73:
74:
75: $calendars = Kronolith::listInternalCalendars(false, Horde_Perms::READ);
76: $owners = array();
77: foreach ($calendars as $calendar) {
78: $owners[$calendar->get('owner')] = true;
79: }
80:
81: $results = array();
82: foreach (array_keys($owners) as $owner) {
83: $path = 'kronolith/' . $owner;
84: if (in_array('name', $properties)) {
85: $results[$path]['name'] = $owner;
86: }
87: if (in_array('icon', $properties)) {
88: $results[$path]['icon'] = Horde_Themes::img('user.png');
89: }
90: if (in_array('browseable', $properties)) {
91: $results[$path]['browseable'] = true;
92: }
93: if (in_array('contenttype', $properties)) {
94: $results[$path]['contenttype'] =
95: 'httpd/unix-directory';
96: }
97: if (in_array('contentlength', $properties)) {
98: $results[$path]['contentlength'] = 0;
99: }
100: if (in_array('modified', $properties)) {
101: $results[$path]['modified'] =
102: $_SERVER['REQUEST_TIME'];
103: }
104: if (in_array('created', $properties)) {
105: $results[$path]['created'] = 0;
106: }
107:
108:
109:
110: $caldavns = 'urn:ietf:params:xml:ns:caldav';
111: $kronolith_rpc_base = $GLOBALS['registry']->get('webroot', 'horde') . '/rpc/kronolith/';
112: if (in_array($caldavns . ':calendar-home-set', $properties)) {
113: $results[$path][$caldavns . ':calendar-home-set'] = Horde::url($kronolith_rpc_base . urlencode($owner), true);
114: }
115:
116: if (in_array($caldavns . ':calendar-user-address-set', $properties)) {
117:
118:
119: }
120: }
121: return $results;
122:
123: } elseif (count($parts) == 1) {
124:
125: $calendars = $GLOBALS['kronolith_shares']->listShares(
126: $GLOBALS['registry']->getAuth(),
127: array('perm' => Horde_Perms::SHOW,
128: 'attributes' => $parts[0]));
129: $results = array();
130: foreach ($calendars as $calendarId => $calendar) {
131: $retpath = 'kronolith/' . $parts[0] . '/' . $calendarId;
132: if (in_array('name', $properties)) {
133: $results[$retpath]['name'] = sprintf(_("Events from %s"), $calendar->get('name'));
134: $results[$retpath . '.ics']['name'] = $calendar->get('name');
135: }
136: if (in_array('displayname', $properties)) {
137: $results[$retpath]['displayname'] = rawurlencode($calendar->get('name'));
138: $results[$retpath . '.ics']['displayname'] = rawurlencode($calendar->get('name')) . '.ics';
139: }
140: if (in_array('icon', $properties)) {
141: $results[$retpath]['icon'] = Horde_Themes::img('kronolith.png');
142: $results[$retpath . '.ics']['icon'] = Horde_Themes::img('mime/icalendar.png');
143: }
144: if (in_array('browseable', $properties)) {
145: $results[$retpath]['browseable'] = $calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ);
146: $results[$retpath . '.ics']['browseable'] = false;
147: }
148: if (in_array('contenttype', $properties)) {
149: $results[$retpath]['contenttype'] = 'httpd/unix-directory';
150: $results[$retpath . '.ics']['contenttype'] = 'text/calendar';
151: }
152: if (in_array('contentlength', $properties)) {
153: $results[$retpath]['contentlength'] = 0;
154:
155:
156:
157:
158:
159:
160: $results[$retpath . '.ics']['contentlength'] = 1;
161: }
162: if (in_array('modified', $properties)) {
163: $results[$retpath]['modified'] = $_SERVER['REQUEST_TIME'];
164: $results[$retpath . '.ics']['modified'] = $_SERVER['REQUEST_TIME'];
165: }
166: if (in_array('created', $properties)) {
167: $results[$retpath]['created'] = 0;
168: $results[$retpath . '.ics']['created'] = 0;
169: }
170: }
171: return $results;
172:
173: } elseif (count($parts) == 2 &&
174: array_key_exists($parts[1], Kronolith::listInternalCalendars(false, Horde_Perms::READ))) {
175:
176:
177:
178: $kronolith_driver = Kronolith::getDriver(null, $parts[1]);
179: $events = $kronolith_driver->listEvents();
180: $icon = Horde_Themes::img('mime/icalendar.png');
181: $results = array();
182: foreach ($events as $dayevents) {
183: foreach ($dayevents as $event) {
184: $key = 'kronolith/' . $path . '/' . $event->id;
185: if (in_array('name', $properties)) {
186: $results[$key]['name'] = $event->getTitle();
187: }
188: if (in_array('icon', $properties)) {
189: $results[$key]['icon'] = $icon;
190: }
191: if (in_array('browseable', $properties)) {
192: $results[$key]['browseable'] = false;
193: }
194: if (in_array('contenttype', $properties)) {
195: $results[$key]['contenttype'] = 'text/calendar';
196: }
197: if (in_array('contentlength', $properties)) {
198:
199:
200:
201:
202:
203:
204:
205: $results[$key]['contentlength'] = 1;
206: }
207: if (in_array('modified', $properties)) {
208: $results[$key]['modified'] = $this->modified($event->uid);
209: }
210: if (in_array('created', $properties)) {
211: $results[$key]['created'] = $this->getActionTimestamp($event->uid, 'add');
212: }
213: }
214: }
215: return $results;
216: } else {
217:
218:
219: if (count($parts) == 3 &&
220: array_key_exists($parts[1], Kronolith::listInternalCalendars(false, Horde_Perms::READ))) {
221:
222: $event = Kronolith::getDriver(null, $parts[1])->getEvent($parts[2]);
223:
224: $result = array(
225: 'data' => $this->export($event->uid, 'text/calendar'),
226: 'mimetype' => 'text/calendar');
227: $modified = $this->modified($event->uid);
228: if (!empty($modified)) {
229: $result['mtime'] = $modified;
230: }
231: return $result;
232: } elseif (count($parts) == 2 &&
233: substr($parts[1], -4, 4) == '.ics' &&
234: array_key_exists(substr($parts[1], 0, -4), Kronolith::listInternalCalendars(false, Horde_Perms::READ))) {
235:
236: $ical_data = $this->exportCalendar(substr($parts[1], 0, -4), 'text/calendar');
237: $result = array('data' => $ical_data,
238: 'mimetype' => 'text/calendar',
239: 'contentlength' => strlen($ical_data),
240: 'mtime' => $_SERVER['REQUEST_TIME']);
241:
242: return $result;
243: } else {
244:
245: return false;
246: }
247: }
248: }
249:
250: 251: 252: 253: 254: 255: 256: 257: 258: 259:
260: public function put($path, $content, $content_type)
261: {
262: if (substr($path, 0, 9) == 'kronolith') {
263: $path = substr($path, 9);
264: }
265: $path = trim($path, '/');
266: $parts = explode('/', $path);
267:
268: if (count($parts) == 2 && substr($parts[1], -4) == '.ics') {
269:
270:
271: if ($content_type == 'application/octet-stream') {
272: $content_type = 'text/calendar';
273: }
274: $calendar = substr($parts[1], 0, -4);
275: } elseif (count($parts) == 3) {
276: $calendar = $parts[1];
277:
278:
279: if ($content_type == 'application/octet-stream') {
280: $content_type = 'text/calendar';
281: }
282: } else {
283: throw new Kronolith_Exception("Invalid calendar data supplied.");
284: }
285:
286: if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
287:
288:
289:
290: throw new Kronolith_Exception("Calendar does not exist or no permission to edit");
291: }
292:
293:
294:
295: $ids = array();
296: $uids_remove = array_flip($this->listUids($calendar));
297:
298: switch ($content_type) {
299: case 'text/calendar':
300: case 'text/x-vcalendar':
301: $iCal = new Horde_Icalendar();
302: if (!($content instanceof Horde_Icalendar_Vevent)) {
303: if (!$iCal->parsevCalendar($content)) {
304: throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
305: }
306: } else {
307: $iCal->addComponent($content);
308: }
309:
310: $kronolith_driver = Kronolith::getDriver();
311: foreach ($iCal->getComponents() as $content) {
312: if ($content instanceof Horde_Icalendar_Vevent) {
313: $event = $kronolith_driver->getEvent();
314: $event->fromiCalendar($content);
315: $uid = $event->uid;
316:
317:
318: if (isset($uids_remove[$uid])) {
319: unset($uids_remove[$uid]);
320: }
321: try {
322: $existing_event = $kronolith_driver->getByUID($uid, array($calendar));
323:
324:
325: $created = $modified = null;
326: try {
327: $log = $GLOBALS['injector']->getInstance('Horde_History')->getHistory('kronolith:' . $calendar . ':' . $uid);
328: foreach ($log as $entry) {
329: switch ($entry['action']) {
330: case 'add':
331: $created = $entry['ts'];
332: break;
333:
334: case 'modify':
335: $modified = $entry['ts'];
336: break;
337: }
338: }
339: } catch (Horde_Exception $e) {
340: }
341: if (empty($modified) && !empty($created)) {
342: $modified = $created;
343: }
344: try {
345: if (!empty($modified) &&
346: $modified >= $content->getAttribute('LAST-MODIFIED')) {
347:
348:
349: continue;
350: }
351: } catch (Horde_Icalendar_Exception $e) {
352: }
353:
354:
355: $event->creator = $existing_event->creator;
356: } catch (Horde_Exception_NotFound $e) {
357: }
358:
359:
360: $saved = $event->save();
361: $ids[] = $event->uid;
362: }
363: }
364: break;
365:
366: default:
367: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $content_type));
368: }
369:
370: if (Kronolith::hasPermission($calendar, Horde_Perms::DELETE)) {
371: foreach (array_keys($uids_remove) as $uid) {
372: $this->delete($uid);
373: }
374: }
375:
376: return $ids;
377: }
378:
379: 380: 381: 382: 383: 384: 385:
386: public function path_delete($path)
387: {
388: if (substr($path, 0, 9) == 'kronolith') {
389: $path = substr($path, 9);
390: }
391: $path = trim($path, '/');
392: $parts = explode('/', $path);
393:
394: if (substr($parts[1], -4) == '.ics') {
395: $calendarId = substr($parts[1], 0, -4);
396: } else {
397: $calendarId = $parts[1];
398: }
399:
400: if (!(count($parts) == 2 || count($parts) == 3) ||
401: !Kronolith::hasPermission($calendarId, Horde_Perms::DELETE)) {
402: throw new Kronolith_Exception("Calendar does not exist or no permission to delete");
403: }
404:
405: if (count($parts) == 3) {
406:
407: return Kronolith::getDriver(null, $calendarId)->deleteEvent($parts[2]);
408: } else {
409:
410: try {
411: Kronolith::getDriver()->delete($calendarId);
412:
413: $share = $GLOBALS['kronolith_shares']->getShare($calendarId);
414: $result = $GLOBALS['kronolith_shares']->removeShare($share);
415: } catch (Exception $e) {
416: throw new Kronolith_Exception(sprintf(_("Unable to delete calendar \"%s\": %s"), $calendarId, $e->getMessage()));
417: }
418: }
419: }
420:
421: 422: 423: 424: 425: 426: 427: 428: 429: 430:
431: public function listCalendars($owneronly = false, $permission = null)
432: {
433: if (is_null($permission)) {
434: $permission = Horde_Perms::SHOW;
435: }
436: return array_keys(Kronolith::listInternalCalendars($owneronly, $permission));
437: }
438:
439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451:
452: public function listUids($calendars = null, $startstamp = 0, $endstamp = 0)
453: {
454: if (empty($calendars)) {
455: $calendars = Kronolith::getSyncCalendars();
456: } elseif (!is_array($calendars)) {
457: $calendars = array($calendars);
458: }
459:
460: $driver = Kronolith::getDriver();
461: $results = array();
462: foreach ($calendars as $calendar) {
463: if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
464: Horde::logMessage(sprintf(
465: _("Permission Denied or Calendar Not Found: %s - skipping."),
466: $calendar));
467: continue;
468: }
469: try {
470: $driver->open($calendar);
471: $events = $driver->listEvents(
472: $startstamp ? new Horde_Date($startstamp) : null,
473: $endstamp ? new Horde_Date($endstamp) : null,
474: false,
475: false,
476: false,
477: false,
478: true,
479: false);
480: Kronolith::mergeEvents($results, $events);
481: } catch (Kronolith_Exception $e) {
482: Horde::logMessage($e);
483: }
484: }
485: $uids = array();
486: foreach ($results as $dayevents) {
487: foreach ($dayevents as $event) {
488: $uids[] = $event->uid;
489: }
490: }
491:
492: return $uids;
493: }
494:
495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509:
510: public function listBy($action, $timestamp, $calendar = null, $end = null)
511: {
512: if (empty($calendar)) {
513: $cs = Kronolith::getSyncCalendars($action == 'delete');
514: $results = array();
515: foreach ($cs as $c) {
516: $results = array_merge(
517: $results, $this->listBy($action, $timestamp, $c, $end));
518: }
519: return $results;
520: }
521: $filter = array(array('op' => '=', 'field' => 'action', 'value' => $action));
522: if (!empty($end)) {
523: $filter[] = array('op' => '<', 'field' => 'ts', 'value' => $end);
524: }
525: $histories = $GLOBALS['injector']
526: ->getInstance('Horde_History')
527: ->getByTimestamp('>', $timestamp, $filter, 'kronolith:' . $calendar);
528:
529:
530: return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
531: }
532:
533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546:
547: public function getChanges($start, $end, $ignoreExceptions = true)
548: {
549:
550: $cs = Kronolith::getSyncCalendars();
551: $changes = array(
552: 'add' => array(),
553: 'modify' => array(),
554: 'delete' => array());
555:
556: foreach ($cs as $c) {
557:
558: $uids = $this->listBy('add', $start, $c, $end);
559: if ($ignoreExceptions) {
560: foreach ($uids as $uid) {
561: try {
562: $event = Kronolith::getDriver()->getByUID($uid);
563: } catch (Exception $e) {
564: continue;
565: }
566: if (empty($event->baseid)) {
567: $changes['add'][] = $uid;
568: }
569: }
570: } else {
571: $changes['add'] = array_keys(array_flip(array_merge($changes['add'], $uids)));
572: }
573:
574:
575: $uids = $this->listBy('modify', $start, $c, $end);
576: if ($ignoreExceptions) {
577: foreach ($uids as $uid) {
578: try {
579: $event = Kronolith::getDriver()->getByUID($uid);
580: } catch (Exception $e) {
581: continue;
582: }
583: if (empty($event->baseid)) {
584: $changes['modify'][] = $uid;
585: }
586: }
587: } else {
588: $changes['modify'] = array_keys(array_flip(array_merge($changes['modify'], $uids)));
589: }
590:
591: $changes['delete'] = array_keys(
592: array_flip(array_merge($changes['delete'], $this->listBy('delete', $start, $c, $end))));
593: }
594:
595: return $changes;
596: }
597:
598:
599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610:
611: public function getActionTimestamp($uid, $action, $calendar = null)
612: {
613: if (empty($calendar)) {
614: $calendar = Kronolith::getDefaultCalendar();
615: } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
616: throw new Horde_Exception_PermissionDenied();
617: }
618:
619: return $GLOBALS['injector']->getInstance('Horde_History')->getActionTimestamp('kronolith:' . $calendar . ':' . $uid, $action);
620: }
621:
622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635:
636: public function import($content, $contentType, $calendar = null)
637: {
638: if (!isset($calendar)) {
639: $calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT);
640: } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
641: throw new Horde_Exception_PermissionDenied();
642: }
643:
644: $kronolith_driver = Kronolith::getDriver(null, $calendar);
645:
646: switch ($contentType) {
647: case 'text/calendar':
648: case 'text/x-vcalendar':
649: $iCal = new Horde_Icalendar();
650: if (!($content instanceof Horde_Icalendar_Vevent)) {
651: if (!$iCal->parsevCalendar($content)) {
652: throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
653: }
654: } else {
655: $iCal->addComponent($content);
656: }
657:
658: $components = $iCal->getComponents();
659: if (count($components) == 0) {
660: throw new Kronolith_Exception(_("No iCalendar data was found."));
661: }
662:
663: $ids = array();
664: $recurrences = array();
665: foreach ($components as $content) {
666: if ($content instanceof Horde_Icalendar_Vevent) {
667:
668:
669:
670: try {
671: $recurrenceId = $content->getAttribute('RECURRENCE-ID');
672: $recurrences[] = $content;
673: } catch (Horde_Icalendar_Exception $e) {
674: $ids[] = $this->_addiCalEvent($content, $kronolith_driver);
675: }
676: }
677: }
678:
679: if (count($ids) == 0) {
680: throw new Kronolith_Exception(_("No iCalendar data was found."));
681: } else if (count($ids) == 1) {
682: return $ids[0];
683: }
684:
685:
686: foreach ($recurrences as $recurrence) {
687: $ids[] = $this->_addiCalEvent($recurrence, $kronolith_driver);
688: }
689:
690: return $ids;
691:
692: case 'activesync':
693: $event = $kronolith_driver->getEvent();
694: $event->fromASAppointment($content);
695: $event->save();
696: return $event->uid;
697: }
698:
699: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
700: }
701:
702: 703: 704: 705: 706: 707: 708: 709:
710: protected function _addiCalEvent($content, $driver)
711: {
712: $event = $driver->getEvent();
713: $event->fromiCalendar($content);
714:
715:
716: $uid = $event->uid;
717: try {
718: $existing_event = $driver->getByUID($uid, array($driver->calendar));
719: throw new Kronolith_Exception(sprintf(_("%s Already Exists"), $uid));
720: } catch (Horde_Exception $e) {}
721: $result = $driver->search($event);
722:
723: if (is_array($result) && count($result) > 0) {
724: foreach($result as $match) {
725: if ($match->start == $event->start &&
726: $match->end == $event->end &&
727: $match->title == $event->title &&
728: $match->location == $event->location &&
729: $match->hasPermission(Horde_Perms::EDIT)) {
730: throw new Kronolith_Exception(sprintf(_("%s Already Exists"), $match->uid));
731: }
732: }
733: }
734: $event->save();
735:
736: return $event->uid;
737: }
738:
739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749:
750: public function quickAdd($text, $calendar = null)
751: {
752: if (!isset($calendar)) {
753: $calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT);
754: } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
755: throw new Horde_Exception_PermissionDenied();
756: }
757: $event = Kronolith::quickAdd($text, $calendar);
758: return $event->uid;
759: }
760:
761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777:
778: public function export($uid, $contentType)
779: {
780: global $kronolith_shares;
781:
782: $event = Kronolith::getDriver()->getByUID($uid);
783: if (!$event->hasPermission(Horde_Perms::READ)) {
784: throw new Horde_Exception_PermissionDenied();
785: }
786:
787: $version = '2.0';
788: switch ($contentType) {
789: case 'text/x-vcalendar':
790: $version = '1.0';
791: case 'text/calendar':
792: $share = $kronolith_shares->getShare($event->calendar);
793:
794: $iCal = new Horde_Icalendar($version);
795: $iCal->setAttribute('X-WR-CALNAME', $share->get('name'));
796:
797:
798: $iCal->addComponent($event->toiCalendar($iCal));
799:
800: return $iCal->exportvCalendar();
801:
802: case 'activesync':
803: return $event->toASAppointment();
804: }
805:
806: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
807: }
808:
809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824:
825: public function exportCalendar($calendar, $contentType)
826: {
827: global $kronolith_shares;
828:
829: if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
830: throw new Horde_Exception_PermissionDenied();
831: }
832:
833: $kronolith_driver = Kronolith::getDriver(null, $calendar);
834: $events = $kronolith_driver->listEvents(null, null, false, false, false, false, false, true);
835:
836: $version = '2.0';
837: switch ($contentType) {
838: case 'text/x-vcalendar':
839: $version = '1.0';
840: case 'text/calendar':
841: $share = $kronolith_shares->getShare($calendar);
842:
843: $iCal = new Horde_Icalendar($version);
844: $iCal->setAttribute('X-WR-CALNAME', $share->get('name'));
845: if (strlen($share->get('desc'))) {
846: $iCal->setAttribute('X-WR-CALDESC', $share->get('desc'));
847: }
848:
849: foreach ($events as $dayevents) {
850: foreach ($dayevents as $event) {
851: $iCal->addComponent($event->toiCalendar($iCal));
852: }
853: }
854:
855: return $iCal->exportvCalendar();
856: }
857:
858: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
859: }
860:
861: 862: 863: 864: 865: 866: 867: 868: 869: 870: 871: 872:
873: public function delete($uid, $recurrenceId = null)
874: {
875:
876:
877: if (is_array($uid)) {
878: foreach ($uid as $g) {
879: $result = $this->delete($g);
880: }
881: return;
882: }
883:
884: $kronolith_driver = Kronolith::getDriver();
885: $events = $kronolith_driver->getByUID($uid, null, true);
886:
887: $event = null;
888: if ($GLOBALS['registry']->isAdmin()) {
889: $event = $events[0];
890: }
891:
892:
893: if (empty($event)) {
894: $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::DELETE);
895: foreach ($events as $ev) {
896: if ($GLOBALS['registry']->isAdmin() || isset($ownerCalendars[$ev->calendar])) {
897: $kronolith_driver->open($ev->calendar);
898: $event = $ev;
899: break;
900: }
901: }
902: }
903:
904:
905: if (empty($event)) {
906: $deletableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::DELETE);
907: foreach ($events as $ev) {
908: if (isset($deletableCalendars[$ev->calendar])) {
909: $kronolith_driver->open($ev->calendar);
910: $event = $ev;
911: break;
912: }
913: }
914: }
915:
916: if (empty($event)) {
917: throw new Horde_Exception_PermissionDenied();
918: }
919:
920: if ($recurrenceId && $event->recurs()) {
921: $deleteDate = new Horde_Date($recurrenceId);
922: $event->recurrence->addException($deleteDate->format('Y'), $deleteDate->format('m'), $deleteDate->format('d'));
923: $event->save();
924: } elseif ($recurrenceId) {
925: throw new Kronolith_Exception(_("Unable to delete event. An exception date was provided but the event does not seem to be recurring."));
926: } else {
927: $kronolith_driver->deleteEvent($event->id);
928: }
929: }
930:
931: 932: 933: 934: 935: 936: 937: 938: 939: 940: 941: 942: 943: 944:
945: public function replace($uid, $content, $contentType)
946: {
947: $event = Kronolith::getDriver()->getByUID($uid);
948:
949: if (!$event->hasPermission(Horde_Perms::EDIT) ||
950: ($event->private && $event->creator != $GLOBALS['registry']->getAuth())) {
951: throw new Horde_Exception_PermissionDenied();
952: }
953:
954: if ($content instanceof Horde_Icalendar_Vevent) {
955: $component = $content;
956: } elseif ($content instanceof Horde_ActiveSync_Message_Appointment) {
957: $event->fromASAppointment($content);
958: $event->save();
959: $event->uid = $uid;
960: return;
961: } else {
962: switch ($contentType) {
963: case 'text/calendar':
964: case 'text/x-vcalendar':
965: if (!($content instanceof Horde_Icalendar_Vevent)) {
966: $iCal = new Horde_Icalendar();
967: if (!$iCal->parsevCalendar($content)) {
968: throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
969: }
970:
971: $components = $iCal->getComponents();
972: $component = null;
973: foreach ($components as $content) {
974: if ($content instanceof Horde_Icalendar_Vevent) {
975: if ($component !== null) {
976: throw new Kronolith_Exception(_("Multiple iCalendar components found; only one vEvent is supported."));
977: }
978: $component = $content;
979: }
980:
981: }
982: if ($component === null) {
983: throw new Kronolith_Exception(_("No iCalendar data was found."));
984: }
985: }
986: break;
987:
988: default:
989: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
990: }
991: }
992:
993: $event->fromiCalendar($component);
994:
995:
996: $event->uid = $uid;
997: $event->save();
998: }
999:
1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011:
1012: public function getFreeBusy($startstamp = null, $endstamp = null,
1013: $calendar = null)
1014: {
1015: if (is_null($calendar)) {
1016: $calendar = Kronolith::getDefaultCalendar();
1017: }
1018:
1019:
1020: return Kronolith_FreeBusy::generate($calendar, $startstamp, $endstamp, true);
1021: }
1022:
1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030:
1031: public function eventFromUID($uid)
1032: {
1033: $event = Kronolith::getDriver()->getByUID($uid);
1034: if (!$event->hasPermission(Horde_Perms::SHOW)) {
1035: throw new Horde_Exception_PermissionDenied();
1036: }
1037:
1038: return $event;
1039: }
1040:
1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060:
1061: public function updateAttendee($response, $sender = null)
1062: {
1063: try {
1064: $uid = $response->getAttribute('UID');
1065: } catch (Horde_Icalendar_Exception $e) {
1066: throw new Kronolith_Exception($e);
1067: }
1068:
1069: $events = Kronolith::getDriver()->getByUID($uid, null, true);
1070:
1071:
1072: $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::EDIT);
1073: $event = null;
1074: foreach ($events as $ev) {
1075: if (isset($ownerCalendars[$ev->calendar])) {
1076: $event = $ev;
1077: break;
1078: }
1079: }
1080:
1081:
1082: if (empty($event)) {
1083: $editableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::EDIT);
1084: foreach ($events as $ev) {
1085: if (isset($editableCalendars[$ev->calendar])) {
1086: $event = $ev;
1087: break;
1088: }
1089: }
1090: }
1091:
1092: if (empty($event) ||
1093: ($event->private && $event->creator != $GLOBALS['registry']->getAuth())) {
1094: throw new Horde_Exception_PermissionDenied();
1095: }
1096:
1097: $atnames = $response->getAttribute('ATTENDEE');
1098: if (!is_array($atnames)) {
1099: $atnames = array($atnames);
1100: }
1101: $atparms = $response->getAttribute('ATTENDEE', true);
1102:
1103: $found = false;
1104: $error = _("No attendees have been updated because none of the provided email addresses have been found in the event's attendees list.");
1105: $sender_lcase = Horde_String::lower($sender);
1106: foreach ($atnames as $index => $attendee) {
1107: if ($response->getAttribute('VERSION') < 2) {
1108: $addresses = Horde_Mime_Address::parseAddressList($attendee);
1109: if (!count($addresses)) {
1110: continue;
1111: }
1112: $attendee = $addresses[0]['mailbox'];
1113: if (isset($addresses[0]['host'])) {
1114: $attendee .= '@' . $addresses[0]['host'];
1115: }
1116: $attendee = Horde_String::lower($attendee);
1117: $name = isset($addresses[0]['personal']) ? $addresses[0]['personal'] : null;
1118: } else {
1119: $attendee = str_replace('mailto:', '', Horde_String::lower($attendee));
1120: $name = isset($atparms[$index]['CN']) ? $atparms[$index]['CN'] : null;
1121: }
1122: if ($event->hasAttendee($attendee)) {
1123: if (is_null($sender) || $sender_lcase == $attendee) {
1124: $event->addAttendee($attendee, Kronolith::PART_IGNORE, Kronolith::responseFromICal($atparms[$index]['PARTSTAT']), $name);
1125: $found = true;
1126: } else {
1127: $error = _("The attendee hasn't been updated because the update was not sent from the attendee.");
1128: }
1129: }
1130: }
1131: $event->save();
1132:
1133: if (!$found) {
1134: throw new Kronolith_Exception($error);
1135: }
1136: }
1137:
1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162:
1163: public function listEvents($startstamp = null, $endstamp = null,
1164: $calendars = null, $showRecurrence = true,
1165: $alarmsOnly = false, $showRemote = true,
1166: $hideExceptions = false, $coverDates = true,
1167: $fetchTags = false)
1168: {
1169: if (!isset($calendars)) {
1170: $calendars = array($GLOBALS['prefs']->getValue('default_share'));
1171: } elseif (!is_array($calendars)) {
1172: $calendars = array($calendars);
1173: }
1174: foreach ($calendars as &$calendar) {
1175: $calendar = str_replace('internal_', '', $calendar);
1176: if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
1177: throw new Horde_Exception_PermissionDenied();
1178: }
1179: }
1180:
1181: return Kronolith::listEvents(
1182: new Horde_Date($startstamp),
1183: new Horde_Date($endstamp),
1184: $calendars,
1185: $showRecurrence,
1186: $alarmsOnly,
1187: $showRemote,
1188: $hideExceptions,
1189: $coverDates,
1190: $fetchTags);
1191: }
1192:
1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201:
1202: public function subscribe($calendar)
1203: {
1204: if (!isset($calendar['type'])) {
1205: throw new Kronolith_Exception(_("Unknown calendar protocol"));
1206: }
1207:
1208: switch ($calendar['type']) {
1209: case 'http':
1210: case 'webcal':
1211: Kronolith::subscribeRemoteCalendar($calendar);
1212: break;
1213:
1214: case 'external':
1215: $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
1216: if (array_search($calendar['name'], $cals) === false) {
1217: $cals[] = $calendar['name'];
1218: $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
1219: }
1220:
1221: default:
1222: throw new Kronolith_Exception(_("Unknown calendar protocol"));
1223: }
1224: }
1225:
1226: 1227: 1228: 1229: 1230: 1231: 1232: 1233: 1234:
1235: public function unsubscribe($calendar)
1236: {
1237: if (!isset($calendar['type'])) {
1238: throw new Kronolith_Exception('Unknown calendar specification');
1239: }
1240:
1241: switch ($calendar['type']) {
1242: case 'http':
1243: case 'webcal':
1244: Kronolith::subscribeRemoteCalendar($calendar['url']);
1245: break;
1246:
1247: case 'external':
1248: $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
1249: if (($key = array_search($calendar['name'], $cals)) !== false) {
1250: unset($cals[$key]);
1251: $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
1252: }
1253:
1254: default:
1255: throw new Kronolith_Exception('Unknown calendar specification');
1256: }
1257: }
1258:
1259:
1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272:
1273: public function lock($calendar, $event = null)
1274: {
1275: if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
1276: throw new Horde_Exception_PermissionDenied();
1277: }
1278: if (!empty($event)) {
1279: $uid = $calendar . ':' . $event;
1280: }
1281:
1282: return $GLOBALS['kronolith_shares']->getShare($calendar)->lock($GLOBALS['injector']->getInstance('Horde_Lock'), $uid);
1283: }
1284:
1285: 1286: 1287: 1288: 1289: 1290: 1291: 1292:
1293: public function unlock($calendar, $lockid)
1294: {
1295: if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
1296: throw new Horde_Exception_PermissionDenied();
1297: }
1298:
1299: return $GLOBALS['kronolith_shares']->getShare($calendar)->unlock($GLOBALS['injector']->getInstance('Horde_Lock'), $lockid);
1300: }
1301:
1302: 1303: 1304: 1305: 1306: 1307: 1308: 1309:
1310: public function checkLocks($calendar, $event = null)
1311: {
1312: if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
1313: throw new Horde_Exception_PermissionDenied();
1314: }
1315: if (!empty($event)) {
1316: $uid = $calendar . ':' . $event;
1317: }
1318: return $GLOBALS['kronolith_shares']->getShare($calendar)->checkLocks($GLOBALS['injector']->getInstance('Horde_Lock'), $uid);
1319: }
1320:
1321: 1322: 1323: 1324:
1325: public function getFbCalendars()
1326: {
1327: return (unserialize($GLOBALS['prefs']->getValue('fb_cals')));
1328: }
1329:
1330: 1331: 1332: 1333: 1334: 1335: 1336: 1337: 1338: 1339:
1340: public function listTagInfo($tags = null, $user = null)
1341: {
1342: return $GLOBALS['injector']
1343: ->getInstance('Kronolith_Tagger')->getTagInfo($tags, 500, null, $user);
1344: }
1345:
1346: 1347: 1348: 1349: 1350: 1351: 1352: 1353: 1354: 1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362: 1363: 1364: 1365: 1366: 1367:
1368: public function searchTags($names, $max = 10, $from = 0,
1369: $resource_type = '', $user = null, $raw = false)
1370: {
1371: $results = $GLOBALS['injector']
1372: ->getInstance('Kronolith_Tagger')
1373: ->search(
1374: $names,
1375: array('type' => 'event', 'user' => $user));
1376:
1377:
1378: if ($raw) {
1379: return $results;
1380: }
1381:
1382: $return = array();
1383: if (!empty($results['events'])) {
1384: foreach ($results['events'] as $event_id) {
1385: $driver = Kronolith::getDriver();
1386: $event = $driver->getByUid($event_id);
1387: $view_url = $event->getViewUrl();
1388: $return[] = array(
1389: 'title' => $event->title,
1390: 'desc'=> $event->start->strftime($GLOBALS['prefs']->getValue('date_format_mini')) . ' ' . $event->start->strftime($GLOBALS['prefs']->getValue('time_format')),
1391: 'view_url' => $view_url,
1392: 'app' => 'kronolith'
1393: );
1394: }
1395: }
1396:
1397: return $return;
1398: }
1399:
1400: }
1401: