⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.97
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-horde
/
kronolith
/
lib
/
Edit File: Api.php
<?php /** * Kronolith external API interface. * * This file defines Kronolith's external API interface. Other applications * can interact with Kronolith through this API. * * @package Kronolith */ class Kronolith_Api extends Horde_Registry_Api { /** * Links. * * @var array */ protected $_links = array( 'show' => '%application%/event.php?calendar=|calendar|&eventID=|event|&uid=|uid|' ); /** * Returns the share helper prefix * * @return string */ public function shareHelp() { return 'shares'; } /** * Returns the last modification timestamp for the given uid. * * @param string $uid The uid to look for. * @param string $calendar The calendar to search in. * * @return integer The timestamp for the last modification of $uid. */ public function modified($uid, $calendar = null) { $modified = $this->getActionTimestamp($uid, 'modify', $calendar); if (empty($modified)) { $modified = $this->getActionTimestamp($uid, 'add', $calendar); } return $modified; } /** * Browse through Kronolith's object tree. * * @param string $path The level of the tree to browse. * @param array $properties The item properties to return. Defaults to 'name', * 'icon', and 'browseable'. * * @return array The contents of $path * @throws Kronolith_Exception */ public function browse($path = '', $properties = array()) { global $injector, $registry; // Default properties. if (!$properties) { $properties = array('name', 'icon', 'browseable'); } if (substr($path, 0, 9) == 'kronolith') { $path = substr($path, 9); } $path = trim($path, '/'); $parts = explode('/', $path); $currentUser = $registry->getAuth(); if (empty($path)) { // This request is for a list of all users who have calendars // visible to the requesting user. $calendars = Kronolith::listInternalCalendars(false, Horde_Perms::READ); $owners = array(); foreach ($calendars as $calendar) { $owners[$calendar->get('owner') ? $calendar->get('owner') : '-system-'] = true; } $results = array(); foreach (array_keys($owners) as $owner) { $path = 'kronolith/' . $registry->convertUsername($owner, false); if (in_array('name', $properties)) { $results[$path]['name'] = $injector ->getInstance('Horde_Core_Factory_Identity') ->create($owner) ->getName(); } if (in_array('icon', $properties)) { $results[$path]['icon'] = Horde_Themes::img('user.png'); } if (in_array('browseable', $properties)) { $results[$path]['browseable'] = true; } } return $results; } elseif (count($parts) == 1) { // This request is for all calendars owned by the requested user $owner = $parts[0] == '-system-' ? '' : $registry->convertUsername($parts[0], true); $calendars = $injector->getInstance('Kronolith_Shares') ->listShares( $currentUser, array('perm' => Horde_Perms::SHOW, 'attributes' => $owner) ); $results = array(); foreach ($calendars as $calendarId => $calendar) { if ($parts[0] == '-system-' && $calendar->get('owner')) { continue; } $retpath = 'kronolith/' . $parts[0] . '/' . $calendarId; if (in_array('name', $properties)) { $results[$retpath]['name'] = sprintf(_("Events from %s"), Kronolith::getLabel($calendar)); $results[$retpath . '.ics']['name'] = Kronolith::getLabel($calendar); } if (in_array('displayname', $properties)) { $results[$retpath]['displayname'] = Kronolith::getLabel($calendar); $results[$retpath . '.ics']['displayname'] = Kronolith::getLabel($calendar) . '.ics'; } if (in_array('owner', $properties)) { $results[$retpath]['owner'] = $results[$retpath . '.ics']['owner'] = $calendar->get('owner') ? $registry->convertUsername($calendar->get('owner'), false) : '-system-'; } if (in_array('icon', $properties)) { $results[$retpath]['icon'] = Horde_Themes::img('kronolith.png'); $results[$retpath . '.ics']['icon'] = Horde_Themes::img('mime/icalendar.png'); } if (in_array('browseable', $properties)) { $results[$retpath]['browseable'] = $calendar->hasPermission($currentUser, Horde_Perms::READ); $results[$retpath . '.ics']['browseable'] = false; } if (in_array('read-only', $properties)) { $results[$retpath]['read-only'] = $results[$retpath . '.ics']['read-only'] = !$calendar->hasPermission($currentUser, Horde_Perms::EDIT); } if (in_array('contenttype', $properties)) { $results[$retpath . '.ics']['contenttype'] = 'text/calendar'; } } return $results; } elseif (count($parts) == 2 && array_key_exists($parts[1], Kronolith::listInternalCalendars(false, Horde_Perms::READ))) { // This request is browsing into a specific calendar. Generate // the list of items and represent them as files within the // directory. try { $calendar = $injector->getInstance('Kronolith_Shares') ->getShare($parts[1]); } catch (Horde_Exception_NotFound $e) { throw new Kronolith_Exception(_("Invalid calendar requested."), 404); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception($e->getMessage, 500); } $kronolith_driver = Kronolith::getDriver(null, $parts[1]); $events = $kronolith_driver->listEvents(); $icon = Horde_Themes::img('mime/icalendar.png'); $owner = $calendar->get('owner') ? $registry->convertUsername($calendar->get('owner'), false) : '-system-'; $results = array(); foreach ($events as $dayevents) { foreach ($dayevents as $event) { $key = 'kronolith/' . $path . '/' . $event->id; if (in_array('name', $properties)) { $results[$key]['name'] = $event->getTitle(); } if (in_array('owner', $properties)) { $results[$key]['owner'] = $owner; } if (in_array('icon', $properties)) { $results[$key]['icon'] = $icon; } if (in_array('browseable', $properties)) { $results[$key]['browseable'] = false; } if (in_array('contenttype', $properties)) { $results[$key]['contenttype'] = 'text/calendar'; } if (in_array('modified', $properties)) { $results[$key]['modified'] = $this->modified($event->uid, $parts[1]); } if (in_array('created', $properties)) { $results[$key]['created'] = $this->getActionTimestamp($event->uid, 'add'); } } } return $results; } else { // The only valid request left is for either a specific event or // for the entire calendar. if (count($parts) == 3 && array_key_exists($parts[1], Kronolith::listInternalCalendars(false, Horde_Perms::READ))) { // This request is for a specific item within a given calendar. $event = Kronolith::getDriver(null, $parts[1])->getEvent($parts[2]); $result = array( 'data' => $this->export($event->uid, 'text/calendar'), 'mimetype' => 'text/calendar'); $modified = $this->modified($event->uid, $parts[1]); if (!empty($modified)) { $result['mtime'] = $modified; } return $result; } elseif (count($parts) == 2 && substr($parts[1], -4, 4) == '.ics' && array_key_exists(substr($parts[1], 0, -4), Kronolith::listInternalCalendars(false, Horde_Perms::READ))) { // This request is for an entire calendar (calendar.ics). $ical_data = $this->exportCalendar(substr($parts[1], 0, -4), 'text/calendar'); return array( 'data' => $ical_data, 'mimetype' => 'text/calendar', 'contentlength' => strlen($ical_data), 'mtime' => $_SERVER['REQUEST_TIME'] ); } else { // All other requests are a 404: Not Found return false; } } } /** * Saves a file into the Kronolith tree. * * @param string $path The path where to PUT the file. * @param string $content The file content. * @param string $content_type The file's content type. * * @return array The event UIDs. * @throws Kronolith_Exception */ public function put($path, $content, $content_type) { if (substr($path, 0, 9) == 'kronolith') { $path = substr($path, 9); } $path = trim($path, '/'); $parts = explode('/', $path); if (count($parts) == 2 && substr($parts[1], -4) == '.ics') { // Workaround for WebDAV clients that are not smart enough to send // the right content type. Assume text/calendar. if ($content_type == 'application/octet-stream') { $content_type = 'text/calendar'; } $calendar = substr($parts[1], 0, -4); } elseif (count($parts) == 3) { $calendar = $parts[1]; // Workaround for WebDAV clients that are not smart enough to send // the right content type. Assume text/calendar. if ($content_type == 'application/octet-stream') { $content_type = 'text/calendar'; } } else { throw new Kronolith_Exception(_("Invalid calendar data supplied.")); } if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) { // FIXME: Should we attempt to create a calendar based on the // filename in the case that the requested calendar does not // exist? throw new Kronolith_Exception(_("Calendar does not exist or no permission to edit")); } // Store all currently existings UIDs. Use this info to delete UIDs not // present in $content after processing. $ids = array(); if (count($parts) == 2) { $uids_remove = array_flip($this->listUids($calendar)); } else { $uids_remove = array(); } switch ($content_type) { case 'text/calendar': case 'text/x-vcalendar': $iCal = new Horde_Icalendar(); if (!($content instanceof Horde_Icalendar_Vevent)) { if (!$iCal->parsevCalendar($content)) { throw new Kronolith_Exception(_("There was an error importing the iCalendar data.")); } } else { $iCal->addComponent($content); } $kronolith_driver = Kronolith::getDriver(); $kronolith_driver->open($parts[1]); $history = $GLOBALS['injector']->getInstance('Horde_History'); foreach ($iCal->getComponents() as $content) { if ($content instanceof Horde_Icalendar_Vevent) { $event = $kronolith_driver->getEvent(); $event->fromiCalendar($content); $uid = $event->uid; if ($uid) { // Remove from uids_remove list so we won't delete in // the end. unset($uids_remove[$uid]); try { $existing_event = $kronolith_driver->getByUID( $uid, array($calendar) ); // Check if our event is newer then the existing - // get the event's history. $created = $modified = null; try { $created = $history->getActionTimestamp( 'kronolith:' . $calendar . ':' . $uid, 'add' ); $modified = $history->getActionTimestamp( 'kronolith:' . $calendar . ':' . $uid, 'modify' ); /* The history driver returns 0 for not * found. If 0 or null does not matter, strip * this */ if ($created == 0) { $created = null; } if ($modified == 0) { $modified = null; } } catch (Horde_Exception $e) { } if (empty($modified) && !empty($created)) { $modified = $created; } try { if (!empty($modified) && $modified >= $content->getAttribute('LAST-MODIFIED')) { // LAST-MODIFIED timestamp of existing // entry is newer: don't replace it. continue; } } catch (Horde_Icalendar_Exception $e) { } // Don't change creator/owner. $event->creator = $existing_event->creator; } catch (Horde_Exception_NotFound $e) { } } // Save entry. $event->save(); $ids[] = $event->uid; } } break; default: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $content_type)); } if (Kronolith::hasPermission($calendar, Horde_Perms::DELETE)) { foreach (array_keys($uids_remove) as $uid) { $this->delete($uid); } } return $ids; } /** * Deletes a file from the Kronolith tree. * * @param string $path The path to the file. * * @throws Kronolith_Exception */ public function path_delete($path) { if (substr($path, 0, 9) == 'kronolith') { $path = substr($path, 9); } $path = trim($path, '/'); $parts = explode('/', $path); if (substr($parts[1], -4) == '.ics') { $calendarId = substr($parts[1], 0, -4); } else { $calendarId = $parts[1]; } if (!(count($parts) == 2 || count($parts) == 3) || !Kronolith::hasPermission($calendarId, Horde_Perms::DELETE)) { throw new Kronolith_Exception(_("Calendar does not exist or no permission to delete")); } if (count($parts) == 3) { // Delete just a single entry Kronolith::getDriver(null, $calendarId)->deleteEvent($parts[2]); } else { // Delete the entire calendar try { Kronolith::getDriver()->delete($calendarId); // Remove share and all groups/permissions. $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares'); $share = $kronolith_shares->getShare($calendarId); $kronolith_shares->removeShare($share); } catch (Exception $e) { throw new Kronolith_Exception(sprintf(_("Unable to delete calendar \"%s\": %s"), $calendarId, $e->getMessage())); } } } /** * Returns all calendars a user has access to, according to several * parameters/permission levels. * * @param boolean $owneronly Only return calendars that this user owns? * Defaults to false. * @param integer $permission The permission to filter calendars by. * * @return array The calendar list. */ public function listCalendars($owneronly = false, $permission = null) { if (is_null($permission)) { $permission = Horde_Perms::SHOW; } return array_keys(Kronolith::listInternalCalendars($owneronly, $permission)); } /** * Returns a list of available sources. * * @param boolean $writeable If true, limits to writeable sources. * @param boolean $sync_only Only include syncable sources. * * @return array An array of the available sources. Keys are source IDs, * values are source titles. * @since 4.2.0 */ public function sources($writeable = false, $sync_only = false) { $out = array(); foreach (Kronolith::listInternalCalendars(false, $writeable ? Horde_Perms::EDIT : Horde_Perms::READ) as $id => $data) { $out[$id] = $data->get('name'); } if ($sync_only) { $syncable = Kronolith::getSyncCalendars(); $out = array_intersect_key($out, array_flip($syncable)); } return $out; } /** * Retrieve the UID for the current user's default calendar. * * @return string UID. * @since 4.2.0 */ public function getDefaultShare() { return Kronolith::getDefaultCalendar(Horde_Perms::EDIT, true); } /** * Returns the ids of all the events that happen within a time period. * Only includes recurring events once per time period, and does not include * events that represent exceptions, making this method useful for syncing * purposes. For more control, use the listEvents method. * * @param string|array $calendars The calendar to check for events. * @param object $startstamp The start of the time range. * @param object $endstamp The end of the time range. * * @return array The event ids happening in this time period. * @throws Kronolith_Exception */ public function listUids($calendars = null, $startstamp = 0, $endstamp = 0) { if (empty($calendars)) { $calendars = Kronolith::getSyncCalendars(); } elseif (!is_array($calendars)) { $calendars = array($calendars); } $driver = Kronolith::getDriver(); $results = array(); foreach ($calendars as $calendar) { if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) { Horde::log(sprintf( _("Permission Denied or Calendar Not Found: %s - skipping."), $calendar)); continue; } try { $driver->open($calendar); $events = $driver->listEvents( $startstamp ? new Horde_Date($startstamp) : null, $endstamp ? new Horde_Date($endstamp) : null, array('cover_dates' => false, 'hide_exceptions' => true) ); Kronolith::mergeEvents($results, $events); } catch (Kronolith_Exception $e) { Horde::log($e); } } $uids = array(); foreach ($results as $dayevents) { foreach ($dayevents as $event) { $uids[] = $event->uid; } } return $uids; } /** * Returns an array of UIDs for events that have had $action happen since * $timestamp. * * @param string $action The action to check for - add, modify, or delete. * @param integer $timestamp The time to start the search. * @param string $calendar The calendar to search in. * @param integer $end The optional ending timestamp * @param boolean $isModSeq If true, $timestamp and $end are modification * sequences and not timestamps. @since 4.1.1 * * @return array An array of UIDs matching the action and time criteria. * * @throws Kronolith_Exception * @throws Horde_History_Exception * @throws InvalidArgumentException */ public function listBy($action, $timestamp, $calendar = null, $end = null, $isModSeq = false) { if (empty($calendar)) { $cs = Kronolith::getSyncCalendars($action == 'delete'); $results = array(); foreach ($cs as $c) { $results = array_merge( $results, $this->listBy($action, $timestamp, $c, $end, $isModSeq)); } return $results; } $filter = array(array('op' => '=', 'field' => 'action', 'value' => $action)); if (!empty($end) && !$isModSeq) { $filter[] = array('op' => '<', 'field' => 'ts', 'value' => $end); } if (!$isModSeq) { $histories = $GLOBALS['injector'] ->getInstance('Horde_History') ->getByTimestamp('>', $timestamp, $filter, 'kronolith:' . $calendar); } else { $histories = $GLOBALS['injector'] ->getInstance('Horde_History') ->getByModSeq($timestamp, $end, $filter, 'kronolith:' . $calendar); } // Strip leading kronolith:username:. return preg_replace('/^([^:]*:){2}/', '', array_keys($histories)); } /** * Method for obtaining all server changes between two timestamps. Basically * a wrapper around listBy(), but returns an array containing all adds, * edits and deletions. If $ignoreExceptions is true, events representing * recurring event exceptions will not be included in the results. * * @param integer $start The starting timestamp * @param integer $end The ending timestamp. * @param boolean $ignoreExceptions Do not include exceptions in results. * @param boolean $isModSeq If true, $timestamp and $end are * modification sequences and not * timestamps. @since 4.1.1 * @param string|array $calendars The sources to check. @since 4.2.0 * * @return array An hash with 'add', 'modify' and 'delete' arrays. * @throws Horde_Exception_PermissionDenied * @throws Kronolith_Exception */ public function getChanges( $start, $end, $ignoreExceptions = true, $isModSeq = false, $calendars = null) { // Only get the calendar once if (is_null($calendars)) { $cs = Kronolith::getSyncCalendars(); } else { if (!is_array($calendars)) { $calendars = array($calendars); } $cs = $calendars; } $changes = array( 'add' => array(), 'modify' => array(), 'delete' => array()); foreach ($cs as $c) { // New events $uids = $this->listBy('add', $start, $c, $end, $isModSeq); if ($ignoreExceptions) { foreach ($uids as $uid) { try { $event = Kronolith::getDriver()->getByUID($uid, array($c)); } catch (Exception $e) { continue; } if (empty($event->baseid)) { $changes['add'][] = $uid; } } } else { $changes['add'] = array_keys(array_flip(array_merge($changes['add'], $uids))); } // Edits $uids = $this->listBy('modify', $start, $c, $end, $isModSeq); if ($ignoreExceptions) { foreach ($uids as $uid) { try { $event = Kronolith::getDriver()->getByUID($uid, array($c)); } catch (Exception $e) { continue; } if (empty($event->baseid)) { $changes['modify'][] = $uid; } } } else { $changes['modify'] = array_keys(array_flip(array_merge($changes['modify'], $uids))); } // No way to figure out if this was an exception, so we must include all $changes['delete'] = array_keys( array_flip(array_merge($changes['delete'], $this->listBy('delete', $start, $c, $end, $isModSeq)))); } return $changes; } /** * Return all changes occuring between the specified modification * sequences. * * @param integer $start The starting modseq. * @param integer $end The ending modseq. * @param string|array $calendars The sources to check. @since 4.2.0 * * @return array The changes @see getChanges() * @since 4.1.1 */ public function getChangesByModSeq($start, $end, $calendars = null) { return $this->getChanges($start, $end, true, true, $calendars); } /** * Returns the timestamp of an operation for a given uid an action * * @param string $uid The uid to look for. * @param string $action The action to check for - add, modify, or delete. * @param string $calendar The calendar to search in. * @param boolean $modSeq Request a modification sequence instead of a * timestamp. @since 4.1.1 * * @return integer The timestamp or modseq for this action. * * @throws Kronolith_Exception * @throws InvalidArgumentException */ public function getActionTimestamp($uid, $action, $calendar = null, $modSeq = false) { if (empty($calendar)) { $calendar = Kronolith::getDefaultCalendar(); } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) { throw new Horde_Exception_PermissionDenied(); } if (!$modSeq) { return $GLOBALS['injector']->getInstance('Horde_History')->getActionTimestamp('kronolith:' . $calendar . ':' . $uid, $action); } return $GLOBALS['injector']->getInstance('Horde_History')->getActionModSeq('kronolith:' . $calendar . ':' . $uid, $action); } /** * Return the largest modification sequence from the history backend. * * @param string $id The calendar id to return the hightest MDOSEQ for. If * null, the highest MODSEQ across all calendars is * returned. @since 4.2.0 * * @return integer The MODSEQ value. * @since 4.1.1 */ public function getHighestModSeq($id = null) { $parent = 'kronolith'; if (!empty($id)) { $parent .= ':' . $id; } return $GLOBALS['injector']->getInstance('Horde_History')->getHighestModSeq($parent); } /** * Imports an event represented in the specified content type. * * @param string $content The content of the event. * @param string $contentType What format is the data in? Currently supports: * <pre> * text/calendar * text/x-vcalendar * activesync * </pre> * @param string $calendar What calendar should the event be added to? * * @return array The event's UID. * @throws Kronolith_Exception */ public function import($content, $contentType, $calendar = null) { if (!isset($calendar)) { $calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT); } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) { throw new Horde_Exception_PermissionDenied(); } $kronolith_driver = Kronolith::getDriver(null, $calendar); switch ($contentType) { case 'text/calendar': case 'text/x-vcalendar': $iCal = new Horde_Icalendar(); if (!($content instanceof Horde_Icalendar_Vevent)) { if (!$iCal->parsevCalendar($content)) { throw new Kronolith_Exception(_("There was an error importing the iCalendar data.")); } } else { $iCal->addComponent($content); } $ical_importer = new Kronolith_Icalendar_Handler_Base($iCal, $kronolith_driver); $result = array_flip($ical_importer->process()); return current($result); case 'activesync': $event = $kronolith_driver->getEvent(); $event->fromASAppointment($content); $event->save(); return $event->uid; } throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } /** * Imports a single vEvent part to storage. * * @param Horde_Icalendar_Vevent $content The vEvent part * @param Kronolith_Driver $driver The kronolith driver * @param boolean $exception Content represents an exception * in a recurrence series. * * @return string The new event's uid */ protected function _addiCalEvent($content, $driver, $exception = false) { $event = $driver->getEvent(); $event->fromiCalendar($content, true); // Check if the entry already exists in the data source, first by UID. if (!$exception) { try { $driver->getByUID($event->uid, array($driver->calendar)); throw new Kronolith_Exception(sprintf(_("%s Already Exists"), $event->uid)); } catch (Horde_Exception $e) {} } $result = $driver->search($event); // Check if the match really is an exact match: foreach ($result as $days) { foreach ($days as $match) { if ($match->start == $event->start && $match->end == $event->end && $match->title == $event->title && $match->location == $event->location && $match->hasPermission(Horde_Perms::EDIT)) { throw new Kronolith_Exception(sprintf(_("%s Already Exists"), $match->uid)); } } } $event->save(); return $event->uid; } /** * Imports an event parsed from a string. * * @param string $text The text to parse into an event * @param string $calendar The calendar into which the event will be * imported. If 'null', the user's default * calendar will be used. * * @return array The UID of all events that were added. * @throws Kronolith_Exception */ public function quickAdd($text, $calendar = null) { if (!isset($calendar)) { $calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT); } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) { throw new Horde_Exception_PermissionDenied(); } $event = Kronolith::quickAdd($text, $calendar); return $event->uid; } /** * Exports an event, identified by UID, in the requested content type. * * @param string $uid Identify the event to export. * @param string $contentType What format should the data be in? * A string with one of: * <pre> * text/calendar (VCALENDAR 2.0. Recommended as * this is specified in rfc2445) * text/x-vcalendar (old VCALENDAR 1.0 format. * Still in wide use) * activesync (Horde_ActiveSync_Message_Appointment) * </pre> * @param array $options Any additional options to be passed to the * exporter. * @param array $calendars Require event to be in these calendars. * @since 4.2.0 * * @return string The requested data. * @throws Kronolith_Exception * @throws Horde_Exception_NotFound */ public function export($uid, $contentType, array $options = array(), array $calendars = null) { $event = Kronolith::getDriver()->getByUID($uid, $calendars); if (!$event->hasPermission(Horde_Perms::READ)) { throw new Horde_Exception_PermissionDenied(); } $version = '2.0'; switch ($contentType) { case 'text/x-vcalendar': $version = '1.0'; case 'text/calendar': $share = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($event->calendar); $iCal = new Horde_Icalendar($version); $iCal->setAttribute('X-WR-CALNAME', $share->get('name')); // Create a new vEvent. $iCal->addComponent($event->toiCalendar($iCal)); return $iCal->exportvCalendar(); case 'activesync': return $event->toASAppointment($options); } throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } /** * Exports a calendar in the requested content type. * * @param string $calendar The calendar to export. * @param string $contentType What format should the data be in? * A string with one of: * <pre> * text/calendar (VCALENDAR 2.0. Recommended as * this is specified in rfc2445) * text/x-vcalendar (old VCALENDAR 1.0 format. * Still in wide use) * </pre> * * @return string The iCalendar representation of the calendar. * @throws Kronolith_Exception */ public function exportCalendar($calendar, $contentType) { if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) { throw new Horde_Exception_PermissionDenied(); } $kronolith_driver = Kronolith::getDriver(null, $calendar); $events = $kronolith_driver->listEvents(null, null, array( 'cover_dates' => false, 'hide_exceptions' => true) ); $version = '2.0'; switch ($contentType) { case 'text/x-vcalendar': $version = '1.0'; case 'text/calendar': $share = $GLOBALS['injector'] ->getInstance('Kronolith_Shares') ->getShare($calendar); $iCal = new Horde_Icalendar($version); $iCal->setAttribute('X-WR-CALNAME', $share->get('name')); if (strlen($share->get('desc'))) { $iCal->setAttribute('X-WR-CALDESC', $share->get('desc')); } foreach ($events as $dayevents) { foreach ($dayevents as $event) { $iCal->addComponent($event->toiCalendar($iCal)); } } return $iCal->exportvCalendar(); } throw new Kronolith_Exception(sprintf( _("Unsupported Content-Type: %s"), $contentType) ); } /** * Deletes an event identified by UID. * * @param string|array $uid A single UID or an array identifying the * event(s) to delete. * * @param string $recurrenceId The reccurenceId for the event instance, if * this is a deletion of a recurring event * instance ($uid must not be an array). * @param string $range The range value if deleting a recurring * event instance. Only supported values are * null or Kronolith::RANGE_THISANDFUTURE. * @since 4.1.5 * * @throws Kronolith_Exception */ public function delete($uid, $recurrenceId = null, $range = null) { // Handle an array of UIDs for convenience of deleting multiple events // at once. if (is_array($uid)) { foreach ($uid as $g) { $this->delete($g); } return; } $kronolith_driver = Kronolith::getDriver(); $events = $kronolith_driver->getByUID($uid, null, true); $event = null; // First try the user's own calendars. if (empty($event)) { $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::DELETE); foreach ($events as $ev) { if (isset($ownerCalendars[$ev->calendar])) { $kronolith_driver->open($ev->calendar); $event = $ev; break; } } } // If not successful, try all calendars the user has access to. if (empty($event)) { $deletableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::DELETE); foreach ($events as $ev) { if (isset($deletableCalendars[$ev->calendar])) { $kronolith_driver->open($ev->calendar); $event = $ev; break; } } } // Are we an admin cleaing up user data? if (empty($event) && $GLOBALS['registry']->isAdmin()) { $event = $events[0]; } if (empty($event)) { throw new Horde_Exception_PermissionDenied(); } if ($recurrenceId && $event->recurs() && empty($range)) { $deleteDate = new Horde_Date($recurrenceId); $event->recurrence->addException($deleteDate->format('Y'), $deleteDate->format('m'), $deleteDate->format('d')); $event->save(); } elseif ($range == Kronolith::RANGE_THISANDFUTURE) { // Deleting the instance and remaining series. $instance = new Horde_Date($recurrenceId); $recurEnd = clone($instance); $recurEnd->mday--; if ($event->end->compareDate($recurEnd) > 0) { $kronolith_driver->deleteEvent($event->id); } else { $event->recurrence->setRecurEnd($recurEnd); $result = $event->save(); } } elseif ($recurrenceId) { throw new Kronolith_Exception(_("Unable to delete event. An exception date was provided but the event does not seem to be recurring.")); } else { $kronolith_driver->deleteEvent($event->id); } } /** * Replaces the event identified by UID with the content represented in the * specified contentType. * * @param string $uid Idenfity the event to replace. * @param mixed $content The content of the event. String or * Horde_Icalendar_Vevent * @param string $contentType What format is the data in? Currently supports: * text/calendar * text/x-vcalendar * (Ignored if content is Horde_Icalendar_Vevent) * activesync (Horde_ActiveSync_Message_Appointment) * @param string $calendar Ensure the event is replaced in the specified * calendar. @since 4.2.0 * * @throws Kronolith_Exception */ public function replace($uid, $content, $contentType, $calendar = null) { $event = Kronolith::getDriver(null, $calendar)->getByUID($uid); if (!$event->hasPermission(Horde_Perms::EDIT) || ($event->private && $event->creator != $GLOBALS['registry']->getAuth())) { throw new Horde_Exception_PermissionDenied(); } if ($content instanceof Horde_Icalendar_Vevent) { $component = $content; } elseif ($content instanceof Horde_ActiveSync_Message_Appointment) { $event->fromASAppointment($content); $event->save(); $event->uid = $uid; return; } else { switch ($contentType) { case 'text/calendar': case 'text/x-vcalendar': if (!($content instanceof Horde_Icalendar_Vevent)) { $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($content)) { throw new Kronolith_Exception(_("There was an error importing the iCalendar data.")); } $components = $iCal->getComponents(); $component = null; foreach ($components as $content) { if ($content instanceof Horde_Icalendar_Vevent) { if ($component !== null) { throw new Kronolith_Exception(_("Multiple iCalendar components found; only one vEvent is supported.")); } $component = $content; } } if ($component === null) { throw new Kronolith_Exception(_("No iCalendar data was found.")); } } break; default: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } } try { $component->getAttribute('RECURRENCE-ID'); $this->_addiCalEvent($component, Kronolith::getDriver(null, $calendar), true); } catch (Horde_Icalendar_Exception $e) { $event->fromiCalendar($component, true); // Ensure we keep the original UID, even when content does not // contain one and fromiCalendar creates a new one. $event->uid = $uid; $event->save(); } } /** * Generates free/busy information for a given time period. * * @param integer $startstamp The start of the time period to retrieve. * @param integer $endstamp The end of the time period to retrieve. * @param string $calendar The calendar to view free/busy slots for. * Defaults to the user's default calendar. * * @return Horde_Icalendar_Vfreebusy A freebusy object that covers the * specified time period. * @throws Kronolith_Exception */ public function getFreeBusy($startstamp = null, $endstamp = null, $calendar = null) { if (is_null($calendar)) { $calendar = Kronolith::getDefaultCalendar(); } // Free/Busy information is globally available; no permission // check is needed. return Kronolith_FreeBusy::generate($calendar, $startstamp, $endstamp, true); } /** * Attempt to lookup the free/busy information for the given email address. * * @param string $email The email to lookup free/busy information for. * @param boolean $json Return the data in a simple json format. If false, * returns the vCalander object. * @since 4.1.0 */ public function lookupFreeBusy($email, $json = false) { return Kronolith_FreeBusy::get($email, $json); } /** * Retrieves a Kronolith_Event object, given an event UID. * * @param string $uid The event's UID. * * @return Kronolith_Event A valid Kronolith_Event. * @throws Kronolith_Exception */ public function eventFromUID($uid) { $event = Kronolith::getDriver()->getByUID($uid); if (!$event->hasPermission(Horde_Perms::SHOW)) { throw new Horde_Exception_PermissionDenied(); } return $event; } /** * Updates an attendee's response status for a specified event. * * @param Horde_Icalendar_Vevent $response A Horde_Icalendar_Vevent * object, with a valid UID * attribute that points to an * existing event. This is * typically the vEvent portion * of an iTip meeting-request * response, with the attendee's * response in an ATTENDEE * parameter. * @param string $sender The email address of the * person initiating the * update. Attendees are only * updated if this address * matches. * * @throws Kronolith_Exception */ public function updateAttendee($response, $sender = null) { try { $uid = $response->getAttribute('UID'); } catch (Horde_Icalendar_Exception $e) { throw new Kronolith_Exception($e); } $events = Kronolith::getDriver()->getByUID($uid, null, true); /* First try the user's own calendars. */ $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::EDIT); $event = null; foreach ($events as $ev) { if (isset($ownerCalendars[$ev->calendar])) { $event = $ev; break; } } /* If not successful, try all calendars the user has access to. */ if (empty($event)) { $editableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::EDIT); foreach ($events as $ev) { if (isset($editableCalendars[$ev->calendar])) { $event = $ev; break; } } } if (empty($event) || ($event->private && $event->creator != $GLOBALS['registry']->getAuth())) { throw new Horde_Exception_PermissionDenied(); } $atnames = $response->getAttribute('ATTENDEE'); if (!is_array($atnames)) { $atnames = array($atnames); } $atparms = $response->getAttribute('ATTENDEE', true); $found = false; $error = _("No attendees have been updated because none of the provided email addresses have been found in the event's attendees list."); foreach ($atnames as $index => $attendee) { if ($response->getAttribute('VERSION') < 2) { $addr_ob = new Horde_Mail_Rfc822_Address($attendee); if (!$addr_ob->valid) { continue; } $attendee = $addr_ob->bare_address; $name = $addr_ob->personal; } else { $attendee = str_ireplace('mailto:', '', $attendee); $name = isset($atparms[$index]['CN']) ? $atparms[$index]['CN'] : null; } if ($event->hasAttendee($attendee)) { if (is_null($sender) || $sender == $attendee) { $event->addAttendee($attendee, Kronolith::PART_IGNORE, Kronolith::responseFromICal($atparms[$index]['PARTSTAT']), $name); $found = true; } else { $error = _("The attendee hasn't been updated because the update was not sent from the attendee."); } } } $event->save(); if (!$found) { throw new Kronolith_Exception($error); } } /** * Lists events for a given time period. * * @param integer $startstamp The start of the time period to * retrieve. * @param integer $endstamp The end of the time period to retrieve. * @param array $calendars The calendars to view events from. * Defaults to the user's default calendar. * @param boolean $showRecurrence Return every instance of a recurring * event? If false, will only return * recurring events once inside the * $startDate - $endDate range. * @param boolean $alarmsOnly Filter results for events with alarms. * Defaults to false. * @param boolean $showRemote Return events from remote calendars and * listTimeObject API as well? * * @param boolean $hideExceptions Hide events that represent exceptions to * a recurring event (events with baseid * set)? * @param boolean $coverDates Add multi-day events to all dates? * * @return array A list of event hashes. * @throws Kronolith_Exception */ public function listEvents($startstamp = null, $endstamp = null, $calendars = null, $showRecurrence = true, $alarmsOnly = false, $showRemote = true, $hideExceptions = false, $coverDates = true, $fetchTags = false) { if (!isset($calendars)) { $calendars = array($GLOBALS['prefs']->getValue('default_share')); } elseif (!is_array($calendars)) { $calendars = array($calendars); } foreach ($calendars as &$calendar) { $calendar = str_replace('internal_', '', $calendar); if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) { throw new Horde_Exception_PermissionDenied(); } } return Kronolith::listEvents( new Horde_Date($startstamp), new Horde_Date($endstamp), $calendars, array( 'show_recurrence' => $showRecurrence, 'has_alarm' => $alarmsOnly, 'show_remote' => $showRemote, 'hide_exceptions' => $hideExceptions, 'cover_dates' => $coverDates, 'fetch_tags' => $fetchTags) ); } /** * Subscribe to a calendar. * * @param array $calendar Calendar description hash, with required 'type' * parameter. Currently supports 'http' and * 'webcal' for remote calendars. * * @throws Kronolith_Exception */ public function subscribe($calendar) { if (!isset($calendar['type'])) { throw new Kronolith_Exception(_("Unknown calendar protocol")); } switch ($calendar['type']) { case 'http': case 'webcal': Kronolith::subscribeRemoteCalendar($calendar); break; case 'external': $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals')); if (array_search($calendar['name'], $cals) === false) { $cals[] = $calendar['name']; $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals)); } default: throw new Kronolith_Exception(_("Unknown calendar protocol")); } } /** * Unsubscribe from a calendar. * * @param array $calendar Calendar description array, with required 'type' * parameter. Currently supports 'http' and * 'webcal' for remote calendars. * * @throws Kronolith_Exception */ public function unsubscribe($calendar) { if (!isset($calendar['type'])) { throw new Kronolith_Exception('Unknown calendar specification'); } switch ($calendar['type']) { case 'http': case 'webcal': Kronolith::subscribeRemoteCalendar($calendar['url']); break; case 'external': $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals')); if (($key = array_search($calendar['name'], $cals)) !== false) { unset($cals[$key]); $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals)); } default: throw new Kronolith_Exception('Unknown calendar specification'); } } /** * Places an exclusive lock for a calendar or an event. * * @param string $calendar The id of the calendar to lock * @param string $event The uid for the event to lock * * @return mixed A lock ID on success, false if: * - The calendar is already locked * - The event is already locked * - A calendar lock was requested and an event is * already locked in the calendar * @throws Kronolith_Exception */ public function lock($calendar, $event = null) { if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) { throw new Horde_Exception_PermissionDenied(); } if (!empty($event)) { $uid = $calendar . ':' . $event; } return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar)->lock($GLOBALS['injector']->getInstance('Horde_Lock'), $uid); } /** * Releases a lock. * * @param array $calendar The event to lock. * @param array $lockid The lock id to unlock. * * @throws Kronolith_Exception */ public function unlock($calendar, $lockid) { if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) { throw new Horde_Exception_PermissionDenied(); } return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar)->unlock($GLOBALS['injector']->getInstance('Horde_Lock'), $lockid); } /** * Check for existing calendar or event locks. * * @param array $calendar The calendar to check locks for. * @param array $event The event to check locks for. * * @throws Kronolith_Exception */ public function checkLocks($calendar, $event = null) { if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) { throw new Horde_Exception_PermissionDenied(); } if (!empty($event)) { $uid = $calendar . ':' . $event; } return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar)->checkLocks($GLOBALS['injector']->getInstance('Horde_Lock'), $uid); } /** * * @return array A list of calendars used to display free/busy information */ public function getFbCalendars() { return (unserialize($GLOBALS['prefs']->getValue('fb_cals'))); } /** * Retrieve the list of used tag_names, tag_ids and the total number * of resources that are linked to that tag. * * @param array $tags An optional array of tag_ids. If omitted, all tags * will be included. * @param string $user Restrict result to those tagged by $user. * * @return array An array containing tag_name, and total */ public function listTagInfo($tags = null, $user = null) { return $GLOBALS['injector'] ->getInstance('Kronolith_Tagger')->getTagInfo($tags, 500, null, $user); } /** * SearchTags API: * Returns an application-agnostic array (useful for when doing a tag search * across multiple applications) * * The 'raw' results array can be returned instead by setting $raw = true. * * @param array $names An array of tag_names to search for. * @param integer $max The maximum number of resources to return. * @param integer $from The number of the resource to start with. * @param string $resource_type The resource type [event, calendar, ''] * @param string $user Restrict results to resources owned by $user. * @param boolean $raw Return the raw data? * * @return array An array of results: * <pre> * 'title' - The title for this resource. * 'desc' - A terse description of this resource. * 'view_url' - The URL to view this resource. * 'app' - The Horde application this resource belongs to. * </pre> */ public function searchTags($names, $max = 10, $from = 0, $resource_type = '', $user = null, $raw = false) { // TODO: $max, $from, $resource_type not honored $results = $GLOBALS['injector'] ->getInstance('Kronolith_Tagger') ->search( $names, array('type' => 'event', 'user' => $user)); // Check for error or if we requested the raw data array. if ($raw) { return $results; } $return = array(); if (!empty($results['events'])) { foreach ($results['events'] as $event_id) { $driver = Kronolith::getDriver(); $event = $driver->getByUid($event_id); $view_url = $event->getViewUrl(); $return[] = array( 'title' => $event->title, 'desc'=> $event->start->strftime($GLOBALS['prefs']->getValue('date_format_mini')) . ' ' . $event->start->strftime($GLOBALS['prefs']->getValue('time_format')), 'view_url' => $view_url, 'app' => 'kronolith' ); } } return $return; } /** * Create a new calendar for the existing user. * * @param string $name The calendar's display name. * @param array $param Any additional parameters. May include: * - color: (string) The color to associate with the calendar. * DEFAULT: none (color will be randomly assigned). * - description: (string) The calendar description. * DEFAULT: none (empty description). * - tags: (array) An array of tags to apply to the new calendar. * * - synchronize: (boolean) If true, add calendar to the list of * calendars to syncronize. * DEFAULT: false (do not add to the list of calendars). * @return string The new calendar's UID. * @since 4.2.0 */ public function addCalendar($name, array $params = array()) { global $prefs; $info = array( 'name' => $name, 'color' => empty($params['color']) ? null : $params['color'], 'description' => empty($params['description']) ? null : $params['description'], 'tags' => empty($params['tags']) ? null : $params['tags'] ); $share = Kronolith::addShare($info); if (!empty($params['synchronize'])) { $sync = @unserialize($prefs->getValue('sync_calendars')); $sync[] = $share->getName(); $prefs->setValue('sync_calendars', serialize($sync)); } return $share->getName(); } /** * Delete the specified calendar. * * @param string $id The calendar id. */ public function deleteCalendar($id) { $calendar = $GLOBALS['injector'] ->getInstance('Kronolith_Shares') ->getShare($calendar); Kronolith::deleteShare($calendar); } /** * Return an internal calendar. * * @todo Note: This returns a Kronolith_Calendar_Object object instead of a hash * to be consistent with other application APIs. For H6 we need to normalize * the APIs to always return non-objects and/or implement some mechanism to * mark API methods as non-RPC safe. * * @param string $id The calendar uid (share name). * * @return Kronolith_Calendar The calendar object. * @since 4.2.0 */ public function getCalendar($id = null) { $driver = Kronolith::getDriver(null, $id); return Kronolith::getCalendar($driver); } /** * Update an internal calendar's information. * * @param string $id The calendar id. * @param array $info An array of calendar information. * @see self::addCalendar() * @since 4.2.0 */ public function updateCalendar($id, array $info) { $calendar = $this->getCalendar(null, $id); // Prevent wiping tags if they were not passed. if (!array_key_exists('tags', $info)) { $info['tags'] = Kronolith::getTagger()->getTags($id, Kronolith_Tagger::TYPE_CALENDAR); } Kronolith::updateShare($calendar->share(), $info); } }
Simpan