⚝
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: Kronolith.php
<?php /** * Kronolith base library. * * Copyright 1999-2017 Horde LLC (http://www.horde.org/) * * See the enclosed file COPYING for license information (GPL). If you * did not receive this file, see http://www.horde.org/licenses/gpl. * * @category Horde * @license http://www.horde.org/licenses/gpl GPL * @package Kronolith */ /** * The Kronolith:: class provides functionality common to all of Kronolith. * * @author Chuck Hagenbuch <chuck@horde.org> * @category Horde * @license http://www.horde.org/licenses/gpl GPL * @package Kronolith */ class Kronolith { /** Event status */ const STATUS_NONE = 0; const STATUS_TENTATIVE = 1; const STATUS_CONFIRMED = 2; const STATUS_CANCELLED = 3; const STATUS_FREE = 4; /** Invitation responses */ const RESPONSE_NONE = 1; const RESPONSE_ACCEPTED = 2; const RESPONSE_DECLINED = 3; const RESPONSE_TENTATIVE = 4; /** Attendee status */ const PART_REQUIRED = 1; const PART_OPTIONAL = 2; const PART_NONE = 3; const PART_IGNORE = 4; /** iTip requests */ const ITIP_REQUEST = 1; const ITIP_CANCEL = 2; const RANGE_THISANDFUTURE = 'THISANDFUTURE'; /** The event can be delegated. */ const PERMS_DELEGATE = 1024; /** Calendar Manager Constants */ const DISPLAY_CALENDARS = 'displayCalendars'; const DISPLAY_REMOTE_CALENDARS = 'displayRemote'; const DISPLAY_EXTERNAL_CALENDARS= 'displayExternal'; const DISPLAY_RESOURCE_CALENDARS= 'displayResource'; const DISPLAY_HOLIDAYS = 'displayHolidays'; const ALL_CALENDARS = 'allCalendars'; const ALL_REMOTE_CALENDARS = 'allRemote'; const ALL_EXTERNAL_CALENDARS = 'allExternal'; const ALL_HOLIDAYS = 'allHolidays'; const ALL_RESOURCE_CALENDARS = 'allResource'; /** * @var Kronolith_Tagger */ static private $_tagger; /** * Converts a permission object to a json object. * * This methods filters out any permissions for the owner and converts the * user name if necessary. * * @param Horde_Perms_Permission $perm A permission object. * * @return array A hash suitable for json. */ static public function permissionToJson(Horde_Perms_Permission $perm) { $json = $perm->data; if (isset($json['users'])) { $users = array(); foreach ($json['users'] as $user => $value) { if ($user == $GLOBALS['registry']->getAuth()) { continue; } $user = $GLOBALS['registry']->convertUsername($user, false); $users[$user] = $value; } if ($users) { $json['users'] = $users; } else { unset($json['users']); } } return $json; } /** * Returns all the alarms active on a specific date. * * @param Horde_Date $date The date to check for alarms. * @param array $calendars The calendars to check for events. * @param boolean $fullevent Whether to return complete alarm objects or * only alarm IDs. * * @return array The alarms active on the date. A hash with calendar names * as keys and arrays of events or event ids as values. * @throws Kronolith_Exception */ static public function listAlarms($date, $calendars, $fullevent = false) { $kronolith_driver = self::getDriver(); $alarms = array(); foreach ($calendars as $cal) { $kronolith_driver->open($cal); $alarms[$cal] = $kronolith_driver->listAlarms($date, $fullevent); } return $alarms; } /** * Searches for events with the given properties. * * @param object $query The search query. * @param string $calendar The calendar to search in the form * "Driver|calendar_id". * * @return array The events. * @throws Kronolith_Exception */ static public function search($query, $calendar = null) { if ($calendar) { $driver = explode('|', $calendar, 2); $calendars = array($driver[0] => array($driver[1])); } elseif (!empty($query->calendars)) { $calendars = $query->calendars; } else { $calendars = array( Horde_String::ucfirst($GLOBALS['conf']['calendar']['driver']) => $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS), 'Horde' => $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS), 'Ical' => $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_REMOTE_CALENDARS), 'Holidays' => $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_HOLIDAYS)); } $events = array(); foreach ($calendars as $type => $list) { if (!empty($list)) { $kronolith_driver = self::getDriver($type); } foreach ($list as $cal) { $kronolith_driver->open($cal); $retevents = $kronolith_driver->search($query); self::mergeEvents($events, $retevents); } } return $events; } /** * Returns all the events that happen each day within a time period * * @deprecated * * @param Horde_Date $startDate The start of the time range. * @param Horde_Date $endDate The end of the time range. * @param array $calendars The calendars to check for events. * @param array $options Additional options: * - show_recurrence: (boolean) Return every instance of a recurring * event? * DEFAULT: false (Only return recurring events once * inside $startDate - $endDate range) * - has_alarm: (boolean) Only return events with alarms. * DEFAULT: false (Return all events) * - json: (boolean) Store the results of the event's toJson() * method? * DEFAULT: false * - cover_dates: (boolean) Add the events to all days that they * cover? * DEFAULT: true * - hide_exceptions: (boolean) Hide events that represent exceptions to * a recurring event. * DEFAULT: false (Do not hide exception events) * - fetch_tags: (boolean) Fetch tags for all events. * DEFAULT: false (Do not fetch event tags) * * @return array The events happening in this time period. */ static public function listEvents( $startDate, $endDate, $calendars = null, array $options = array()) { $options = array_merge(array( 'show_recurrence' => true, 'has_alarm' => false, 'show_remote' => true, 'hide_exceptions' => false, 'cover_dates' => true, 'fetch_tags' => false), $options); $results = array(); /* Internal calendars. */ if (!isset($calendars)) { $calendars = $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS); } $driver = self::getDriver(); foreach ($calendars as $calendar) { try { $driver->open($calendar); $events = $driver->listEvents($startDate, $endDate, $options); self::mergeEvents($results, $events); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e); } } // Resource calendars if (count($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_RESOURCE_CALENDARS)) && !empty($GLOBALS['conf']['resource']['driver'])) { $driver = self::getDriver('Resource'); foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_RESOURCE_CALENDARS) as $calendar) { try { $driver->open($calendar); $events = $driver->listEvents( $startDate, $endDate, array('show_recurrence' => $options['show_recurrence'])); self::mergeEvents($results, $events); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e); } } } if ($options['show_remote']) { /* Horde applications providing listTimeObjects. */ if (count($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS))) { $driver = self::getDriver('Horde'); foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS) as $external_cal) { try { $driver->open($external_cal); $events = $driver->listEvents( $startDate, $endDate, array('show_recurrence' => $options['show_recurrence'])); self::mergeEvents($results, $events); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e); } } } /* Remote Calendars. */ foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_REMOTE_CALENDARS) as $url) { try { $driver = self::getDriver('Ical', $url); $events = $driver->listEvents( $startDate, $endDate, array('show_recurrence' => $options['show_recurrence'])); self::mergeEvents($results, $events); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e); } } /* Holidays. */ $display_holidays = $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_HOLIDAYS); if (count($display_holidays) && !empty($GLOBALS['conf']['holidays']['enable'])) { $driver = self::getDriver('Holidays'); foreach ($display_holidays as $holiday) { try { $driver->open($holiday); $events = $driver->listEvents( $startDate, $endDate, array('show_recurrence' => $options['show_recurrence'])); self::mergeEvents($results, $events); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e); } } } } /* Sort events. */ $results = self::sortEvents($results); return $results; } /** * Merges results from two listEvents() result sets. * * @param array $results First list of events. * @param array $events List of events to be merged into the first one. */ static public function mergeEvents(&$results, $events) { foreach ($events as $day => $day_events) { if (isset($results[$day])) { $results[$day] = array_merge($results[$day], $day_events); } else { $results[$day] = $day_events; } } ksort($results); } /** * Calculates recurrences of an event during a certain period. */ static public function addEvents(&$results, &$event, $startDate, $endDate, $showRecurrence, $json, $coverDates = true) { /* If the event has a custom timezone, we need to convert the * recurrence object to the event's timezone while calculating next * recurrences, to take DST changes in both the event's and the local * timezone into account. */ $convert = $event->timezone && $event->getDriver()->supportsTimezones(); if ($convert) { $timezone = date_default_timezone_get(); } // If we are adding coverDates, but have no $endDate, default to // +5 years from $startDate. This protects against hitting memory // limit and other issues due to extremely long loops if a single event // was added with a duration of thousands of years while still // providing for reasonable alarm trigger times. if ($coverDates && empty($endDate)) { $endDate = clone $startDate; $endDate->year += 5; } if ($event->recurs() && $showRecurrence) { /* Recurring Event. */ /* If the event ends at 12am and does not end at the same time * that it starts (0 duration), set the end date to the previous * day's end date. */ if ($event->end->hour == 0 && $event->end->min == 0 && $event->end->sec == 0 && $event->start->compareDateTime($event->end) != 0) { $event->end = new Horde_Date( array('hour' => 23, 'min' => 59, 'sec' => 59, 'month' => $event->end->month, 'mday' => $event->end->mday - 1, 'year' => $event->end->year)); } /* We can't use the event duration here because we might cover a * daylight saving time switch. */ $diff = array($event->end->year - $event->start->year, $event->end->month - $event->start->month, $event->end->mday - $event->start->mday, $event->end->hour - $event->start->hour, $event->end->min - $event->start->min); if ($event->start->compareDateTime($startDate) < 0) { /* The first time the event happens was before the period * started. Start searching for recurrences from the start of * the period. */ $next = new Horde_Date(array('year' => $startDate->year, 'month' => $startDate->month, 'mday' => $startDate->mday), $event->timezone); } else { /* The first time the event happens is in the range; unless * there is an exception for this ocurrence, add it. */ if (!$event->recurrence->hasException($event->start->year, $event->start->month, $event->start->mday)) { if ($coverDates) { self::addCoverDates($results, $event, $event->start, $event->end, $json, $endDate); } else { $results[$event->start->dateString()][$event->id] = $json ? $event->toJson() : $event; } } /* Start searching for recurrences from the day after it * starts. */ $next = clone $event->start; ++$next->mday; } if ($convert) { $event->recurrence->start->setTimezone($event->timezone); if ($event->recurrence->hasRecurEnd()) { $event->recurrence->recurEnd->setTimezone($event->timezone); } } /* Add all recurrences of the event. */ $next = $event->recurrence->nextRecurrence($next); if ($next && $convert) { /* Resetting after the nextRecurrence() call, because * we need to test if the next recurrence in the * event's timezone actually matches the interval we * check in the local timezone. This is done on each * nextRecurrence() further below. */ $next->setTimezone($timezone); } while ($next !== false && $next->compareDate($endDate) <= 0) { if (!$event->recurrence->hasException($next->year, $next->month, $next->mday)) { /* Add the event to all the days it covers. */ $nextEnd = clone $next; $nextEnd->year += $diff[0]; $nextEnd->month += $diff[1]; $nextEnd->mday += $diff[2]; $nextEnd->hour += $diff[3]; $nextEnd->min += $diff[4]; $addEvent = clone $event; $addEvent->start = $addEvent->originalStart = $next; $addEvent->end = $addEvent->originalEnd = $nextEnd; if ($coverDates) { self::addCoverDates($results, $addEvent, $next, $nextEnd, $json, $endDate); } else { $addEvent->start = $next; $addEvent->end = $nextEnd; $results[$addEvent->start->dateString()][$addEvent->id] = $json ? $addEvent->toJson() : $addEvent; } } if ($convert) { $next->setTimezone($event->timezone); } $next = $event->recurrence->nextRecurrence( array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec)); if ($next && $convert) { $next->setTimezone($timezone); } } } else { /* Event only occurs once. */ if (!$coverDates) { $results[$event->start->dateString()][$event->id] = $json ? $event->toJson() : $event; } else { $allDay = $event->isAllDay(); /* Work out what day it starts on. */ if ($startDate && $event->start->compareDateTime($startDate) < 0) { /* It started before the beginning of the period. */ if ($event->recurs()) { $eventStart = $event->recurrence->nextRecurrence($startDate); $originalStart = clone $eventStart; } else { $eventStart = clone $startDate; $originalStart = clone $event->start; } } else { $eventStart = clone $event->start; $originalStart = clone $event->start; } /* Work out what day it ends on. */ if ($endDate && $event->end->compareDateTime($endDate) > 0) { /* Ends after the end of the period. */ if (is_object($endDate)) { $eventEnd = clone $endDate; $originalEnd = clone $event->end; } else { $eventEnd = $endDate; $originalEnd = new Horde_Date($endDate); } } else { /* Need to perform some magic if this is a single instance * of a recurring event since $event->end would be the * original end date, not the recurrence's end date. */ if ($event->recurs()) { $diff = array($event->end->year - $event->start->year, $event->end->month - $event->start->month, $event->end->mday - $event->start->mday, $event->end->hour - $event->start->hour, $event->end->min - $event->start->min); $theEnd = $event->recurrence->nextRecurrence($eventStart); $theEnd->year += $diff[0]; $theEnd->month += $diff[1]; $theEnd->mday += $diff[2]; $theEnd->hour += $diff[3]; $theEnd->min += $diff[4]; if ($convert) { $eventStart->setTimezone($timezone); $theEnd->setTimezone($timezone); } } else { $theEnd = clone $event->end; } $originalEnd = clone $theEnd; /* If the event doesn't end at 12am set the end date to * the current end date. If it ends at 12am and does not * end at the same time that it starts (0 duration), set * the end date to the previous day's end date. */ if ($theEnd->hour != 0 || $theEnd->min != 0 || $theEnd->sec != 0 || $event->start->compareDateTime($theEnd) == 0 || $allDay) { $eventEnd = clone $theEnd; } else { $eventEnd = new Horde_Date( array('hour' => 23, 'min' => 59, 'sec' => 59, 'month' => $theEnd->month, 'mday' => $theEnd->mday - 1, 'year' => $theEnd->year)); } } /* Add the event to all the days it covers. This is similar to * Kronolith::addCoverDates(), but for days in between the * start and end day, the range is midnight to midnight, and * for the edge days it's start to midnight, and midnight to * end. */ $i = $eventStart->mday; $loopDate = new Horde_Date(array('month' => $eventStart->month, 'mday' => $i, 'year' => $eventStart->year)); while ($loopDate->compareDateTime($eventEnd) <= 0 && $loopDate->compareDateTime($endDate) <=0) { if (!$allDay || $loopDate->compareDateTime($eventEnd) != 0) { $addEvent = clone $event; $addEvent->originalStart = $originalStart; $addEvent->originalEnd = $originalEnd; /* If this is the start day, set the start time to * the real start time, otherwise set it to * 00:00 */ if ($loopDate->compareDate($eventStart) == 0) { $addEvent->start = $eventStart; } else { $addEvent->start = clone $loopDate; $addEvent->start->hour = $addEvent->start->min = $addEvent->start->sec = 0; $addEvent->first = false; } /* If this is the end day, set the end time to the * real event end, otherwise set it to 23:59. */ if ($loopDate->compareDate($eventEnd) == 0) { $addEvent->end = $eventEnd; } else { $addEvent->end = clone $loopDate; $addEvent->end->hour = 23; $addEvent->end->min = $addEvent->end->sec = 59; $addEvent->last = false; } $results[$loopDate->dateString()][$addEvent->id] = $json ? $addEvent->toJson($allDay) : $addEvent; } $loopDate = new Horde_Date( array('month' => $eventStart->month, 'mday' => ++$i, 'year' => $eventStart->year)); } } } ksort($results); } /** * Adds an event to all the days it covers. * * @param array $result The current result list. * @param Kronolith_Event $event An event object. * @param Horde_Date $eventStart The event's start at the actual * recurrence. * @param Horde_Date $eventEnd The event's end at the actual recurrence. * @param boolean $json Store the results of the events' toJson() * method? * @param Horde_Date $endDate The ending date of the current view. */ static public function addCoverDates(&$results, $event, $eventStart, $eventEnd, $json, Horde_Date $endDate = null) { $loopDate = new Horde_Date($eventStart->year, $eventStart->month, $eventStart->mday); $allDay = $event->isAllDay(); $endDate = empty($endDate) ? $eventEnd : $endDate; while ($loopDate->compareDateTime($eventEnd) <= 0 && $loopDate->compareDateTime($endDate) <= 0) { if (!$allDay || $loopDate->compareDateTime($eventEnd) != 0) { $addEvent = clone $event; $addEvent->start = $eventStart; $addEvent->end = $eventEnd; if ($loopDate->compareDate($eventStart) != 0) { $addEvent->first = false; } if ($loopDate->compareDate($eventEnd) != 0) { $addEvent->last = false; } if ($addEvent->recurs() && $addEvent->recurrence->hasCompletion($loopDate->year, $loopDate->month, $loopDate->mday)) { $addEvent->status = Kronolith::STATUS_CANCELLED; } $results[$loopDate->dateString()][$addEvent->id] = $json ? $addEvent->toJson($allDay) : $addEvent; } $loopDate->mday++; } } /** * Adds an event to set of search results. * * @param array $events The list of events to update with the new * event. * @param Kronolith_Event $event An event from a search result. * @param stdClass $query A search query. * @param boolean $json Store the results of the events' toJson() * method? */ static public function addSearchEvents(&$events, $event, $query, $json) { static $now; if (!isset($now)) { $now = new Horde_Date($_SERVER['REQUEST_TIME']); } $showRecurrence = true; if ($event->recurs()) { if (empty($query->start) && empty($query->end)) { $eventStart = $event->start; $eventEnd = $event->end; } else { if (empty($query->end)) { $convert = $event->timezone && $event->getDriver()->supportsTimezones(); if ($convert) { $timezone = date_default_timezone_get(); $event->recurrence->start->setTimezone($event->timezone); if ($event->recurrence->hasRecurEnd()) { $event->recurrence->recurEnd->setTimezone($event->timezone); } } $eventEnd = $event->recurrence->nextRecurrence($now); if (!$eventEnd) { return; } if ($convert) { $eventEnd->setTimezone($timezone); } } else { $eventEnd = $query->end; } if (empty($query->start)) { $eventStart = $event->start; $showRecurrence = false; } else { $eventStart = $query->start; } } } else { // Don't include any results that are outside the query range. if ((!empty($query->end) && $event->start->after($query->end)) || (!empty($query->start) && $event->end->before($query->start))) { return; } $eventStart = $event->start; $eventEnd = $event->end; } self::addEvents($events, $event, $eventStart, $eventEnd, $showRecurrence, $json, false); } /** * Returns the number of events in calendars that the current user owns. * * @return integer The number of events. */ static public function countEvents() { static $count; if (isset($count)) { return $count; } $kronolith_driver = self::getDriver(); $calendars = self::listInternalCalendars(true, Horde_Perms::ALL); $current_calendar = $kronolith_driver->calendar; $count = 0; foreach (array_keys($calendars) as $calendar) { $kronolith_driver->open($calendar); try { $count += $kronolith_driver->countEvents(); } catch (Exception $e) { } } /* Reopen last calendar. */ $kronolith_driver->open($current_calendar); return $count; } /** * 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) { $text = trim($text); if (strpos($text, "\n") !== false) { list($title, $description) = explode($text, "\n", 2); } else { $title = $text; $description = ''; } $title = trim($title); $description = trim($description); $dateParser = Horde_Date_Parser::factory(array('locale' => $GLOBALS['language'])); $r = $dateParser->parse($title, array('return' => 'result')); if (!($d = $r->guess())) { throw new Horde_Exception(sprintf(_("Cannot parse event description \"%s\""), Horde_String::truncate($text))); } $title = $r->untaggedText(); $kronolith_driver = self::getDriver(null, $calendar); $event = $kronolith_driver->getEvent(); $event->initialized = true; $event->title = $title; $event->description = $description; $event->start = $d; $event->end = $d->add(array('hour' => 1)); $event->save(); return $event; } /** * Initial app setup code. * * Globals defined: */ static public function initialize() { $GLOBALS['calendar_manager'] = $GLOBALS['injector']->createInstance('Kronolith_CalendarsManager'); } /** * Returns the real name, if available, of a user. */ static public function getUserName($uid) { static $names = array(); if (!isset($names[$uid])) { $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid); $ident->setDefault($ident->getDefault()); $names[$uid] = $ident->getValue('fullname'); if (empty($names[$uid])) { $names[$uid] = $uid; } } return $names[$uid]; } /** * Returns the email address, if available, of a user. */ static public function getUserEmail($uid) { static $emails = array(); if (!isset($emails[$uid])) { $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid); $emails[$uid] = $ident->getValue('from_addr'); if (empty($emails[$uid])) { $emails[$uid] = $uid; } } return $emails[$uid]; } /** * Checks if an email address belongs to a user. */ static public function isUserEmail($uid, $email) { static $emails = array(); if (!isset($emails[$uid])) { $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid); $addrs = $ident->getAll('from_addr'); $addrs[] = $uid; $emails[$uid] = $addrs; } return in_array($email, $emails[$uid]); } /** * Maps a Kronolith recurrence value to a translated string suitable for * display. * * @param integer $type The recurrence value; one of the * Horde_Date_Recurrence::RECUR_XXX constants. * * @return string The translated displayable recurrence value string. */ static public function recurToString($type) { switch ($type) { case Horde_Date_Recurrence::RECUR_NONE: return _("Does not recur"); case Horde_Date_Recurrence::RECUR_DAILY: return _("Recurs daily"); case Horde_Date_Recurrence::RECUR_WEEKLY: return _("Recurs weekly"); case Horde_Date_Recurrence::RECUR_MONTHLY_DATE: case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY: return _("Recurs monthly"); case Horde_Date_Recurrence::RECUR_YEARLY_DATE: case Horde_Date_Recurrence::RECUR_YEARLY_DAY: case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY: return _("Recurs yearly"); } } /** * Maps a Kronolith meeting status string to a translated string suitable * for display. * * @param integer $status The meeting status; one of the * Kronolith::STATUS_XXX constants. * * @return string The translated displayable meeting status string. */ static public function statusToString($status) { switch ($status) { case self::STATUS_CONFIRMED: return _("Confirmed"); case self::STATUS_CANCELLED: return _("Cancelled"); case self::STATUS_FREE: return _("Free"); case self::STATUS_TENTATIVE: default: return _("Tentative"); } } /** * Maps a Kronolith attendee response string to a translated string * suitable for display. * * @param integer $response The attendee response; one of the * Kronolith::RESPONSE_XXX constants. * * @return string The translated displayable attendee response string. */ static public function responseToString($response) { switch ($response) { case self::RESPONSE_ACCEPTED: return _("Accepted"); case self::RESPONSE_DECLINED: return _("Declined"); case self::RESPONSE_TENTATIVE: return _("Tentative"); case self::RESPONSE_NONE: default: return _("None"); } } /** * Maps a Kronolith attendee participation string to a translated string * suitable for display. * * @param integer $part The attendee participation; one of the * Kronolith::PART_XXX constants. * * @return string The translated displayable attendee participation * string. */ static public function partToString($part) { switch ($part) { case self::PART_OPTIONAL: return _("Optional"); case self::PART_NONE: return _("None"); case self::PART_REQUIRED: default: return _("Required"); } } /** * Maps an iCalendar attendee response string to the corresponding * Kronolith value. * * @param string $response The attendee response. * * @return string The Kronolith response value. */ static public function responseFromICal($response) { switch (Horde_String::upper($response)) { case 'ACCEPTED': return self::RESPONSE_ACCEPTED; case 'DECLINED': return self::RESPONSE_DECLINED; case 'TENTATIVE': return self::RESPONSE_TENTATIVE; case 'NEEDS-ACTION': default: return self::RESPONSE_NONE; } } /** * Builds the HTML for an event status widget. * * @param string $name The name of the widget. * @param string $current The selected status value. * @param string $any Whether an 'any' item should be added * * @return string The HTML <select> widget. */ static public function buildStatusWidget($name, $current = self::STATUS_CONFIRMED, $any = false) { $html = "<select id=\"$name\" name=\"$name\">"; $statii = array( self::STATUS_FREE, self::STATUS_TENTATIVE, self::STATUS_CONFIRMED, self::STATUS_CANCELLED ); if (!isset($current)) { $current = self::STATUS_NONE; } if ($any) { $html .= "<option value=\"" . self::STATUS_NONE . "\""; $html .= ($current == self::STATUS_NONE) ? ' selected="selected">' : '>'; $html .= _("Any") . "</option>"; } foreach ($statii as $status) { $html .= "<option value=\"$status\""; $html .= ($status == $current) ? ' selected="selected">' : '>'; $html .= self::statusToString($status) . "</option>"; } $html .= '</select>'; return $html; } /** * Returns all internal calendars a user has access to, according * to several parameters/permission levels. * * This method takes the $conf['share']['hidden'] setting into account. If * this setting is enabled, even if requesting permissions different than * SHOW, it will only return calendars that the user owns or has SHOW * permissions for. For checking individual calendar's permissions, use * hasPermission() instead. * * @param boolean $owneronly Only return calenders that this user owns? * Defaults to false. * @param integer $permission The permission to filter calendars by. * @param string $user The user to list calendars for, if not * the current. * * @return array The calendar list. */ public static function listInternalCalendars($owneronly = false, $permission = Horde_Perms::SHOW, $user = null) { if ($owneronly && !$GLOBALS['registry']->getAuth()) { return array(); } if (empty($user)) { $user = $GLOBALS['registry']->getAuth(); } $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares'); if ($owneronly || empty($GLOBALS['conf']['share']['hidden'])) { try { $calendars = $kronolith_shares->listShares( $user, array('perm' => $permission, 'attributes' => $owneronly ? $user : null, 'sort_by' => 'name')); } catch (Horde_Share_Exception $e) { Horde::log($e); return array(); } } else { try { $calendars = $kronolith_shares->listShares( $GLOBALS['registry']->getAuth(), array('perm' => $permission, 'attributes' => $user, 'sort_by' => 'name')); } catch (Horde_Share_Exception $e) { Horde::log($e); return array(); } $display_calendars = @unserialize($GLOBALS['prefs']->getValue('display_cals')); if (is_array($display_calendars)) { foreach ($display_calendars as $id) { try { $calendar = $kronolith_shares->getShare($id); if ($calendar->hasPermission($user, $permission)) { $calendars[$id] = $calendar; } } catch (Horde_Exception_NotFound $e) { } catch (Horde_Share_Exception $e) { Horde::log($e); return array(); } } } } $default_share = $GLOBALS['prefs']->getValue('default_share'); if (isset($calendars[$default_share])) { $calendar = $calendars[$default_share]; unset($calendars[$default_share]); if (!$owneronly || ($owneronly && $calendar->get('owner') == $GLOBALS['registry']->getAuth())) { $calendars = array($default_share => $calendar) + $calendars; } } return $calendars; } /** * Returns all calendars a user has access to, according to several * parameters/permission levels. * * @param boolean $owneronly Only return calenders that this user owns? * Defaults to false. * @param boolean $display Only return calendars that are supposed to * be displayed per configuration and user * preference. * @param integer $permission The permission to filter calendars by. * * @return array The calendar list. */ static public function listCalendars($permission = Horde_Perms::SHOW, $display = false, $flat = true) { $calendars = array(); foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS) as $id => $calendar) { if ($calendar->hasPermission($permission) && (!$display || $calendar->display())) { if ($flat) { $calendars['internal_' . $id] = $calendar; } } } foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_REMOTE_CALENDARS) as $id => $calendar) { try { if ($calendar->hasPermission($permission) && (!$display || $calendar->display())) { if ($flat) { $calendars['remote_' . $id] = $calendar; } } } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push(sprintf(_("The calendar %s returned the error: %s"), $calendar->name(), $e->getMessage()), 'horde.error'); } } foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS) as $id => $calendar) { if ($calendar->hasPermission($permission) && (!$display || $calendar->display())) { if ($flat) { $calendars['external_' . $id] = $calendar; } } } foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_HOLIDAYS) as $id => $calendar) { if ($calendar->hasPermission($permission) && (!$display || $calendar->display())) { if ($flat) { $calendars['holiday_' . $id] = $calendar; } } } return $calendars; } /** * Returns the default calendar for the current user at the specified * permissions level. * * @param integer $permission Horde_Perms constant for permission level * required. * @param boolean $owner_only Only consider owner-owned calendars. * * @return string The calendar id, or null if none. */ static public function getDefaultCalendar($permission = Horde_Perms::SHOW, $owner_only = false) { $calendars = self::listInternalCalendars($owner_only, $permission); $default_share = $GLOBALS['prefs']->getValue('default_share'); if (isset($calendars[$default_share])) { return $default_share; } $default_share = $GLOBALS['injector'] ->getInstance('Kronolith_Factory_Calendars') ->create() ->getDefaultShare(); // If no default share identified via share backend, use the // first found share, and set it in the prefs to make it stick. if (!isset($calendars[$default_share])) { reset($calendars); $default_share = key($calendars); } $GLOBALS['prefs']->setValue('default_share', $default_share); return $default_share; } /** * Returns the calendars that should be used for syncing. * * @param boolean $prune Remove calendar ids from the sync list that no * longer exist. The values are pruned *after* the * results are passed back to the client to give * sync clients a chance to remove their entries. * * @return array An array of calendar ids */ static public function getSyncCalendars($prune = false) { $haveRemoved = false; $cs = unserialize($GLOBALS['prefs']->getValue('sync_calendars')); if (!empty($cs)) { if ($prune) { $calendars = self::listInternalCalendars(false, Horde_Perms::DELETE); $cscopy = array_flip($cs); foreach ($cs as $c) { if (empty($calendars[$c])) { unset($cscopy[$c]); $haveRemoved = true; } } if ($haveRemoved) { $GLOBALS['prefs']->setValue('sync_calendars', serialize(array_flip($cscopy))); } } return $cs; } if ($cs = self::getDefaultCalendar(Horde_Perms::EDIT, true)) { return array($cs); } return array(); } /** * Adds <link> tags for calendar feeds to the HTML header. */ static public function addCalendarLinks() { foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS) as $calendar) { $GLOBALS['page_output']->addLinkTag(array( 'href' => Kronolith::feedUrl($calendar), 'type' => 'application/atom+xml' )); } } /** * Returns the label to be used for a calendar. * * Attaches the owner name of shared calendars if necessary. * * @param Horde_Share_Object A calendar. * * @return string The calendar's label. */ public static function getLabel($calendar) { $label = $calendar->get('name'); if ($calendar->get('owner') && $calendar->get('owner') != $GLOBALS['registry']->getAuth()) { $label .= ' [' . $GLOBALS['registry']->convertUsername($calendar->get('owner'), false) . ']'; } return $label; } /** * Returns whether the current user has certain permissions on a calendar. * * @param string $calendar A calendar id. * @param integer $perm A Horde_Perms permission mask. * * @return boolean True if the current user has the requested permissions. */ static public function hasPermission($calendar, $perm) { try { $share = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar); if (!$share->hasPermission($GLOBALS['registry']->getAuth(), $perm)) { throw new Horde_Exception_NotFound(); } } catch (Horde_Exception_NotFound $e) { return false; } return true; } /** * Creates a new share. * * @param array $info Hash with calendar information. * * @return Horde_Share The new share. * @throws Kronolith_Exception */ static public function addShare($info) { global $calendar_manager, $injector, $prefs, $registry; $kronolith_shares = $injector->getInstance('Kronolith_Shares'); try { $calendar = $kronolith_shares->newShare( $registry->getAuth(), strval(new Horde_Support_Randomid()), $info['name'] ); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception($e); } $calendar->set('color', $info['color']); $calendar->set('desc', $info['description']); if (!empty($info['system'])) { $calendar->set('owner', null); } $tagger = self::getTagger(); $tagger->tag( $calendar->getName(), $info['tags'], $calendar->get('owner'), Kronolith_Tagger::TYPE_CALENDAR ); try { $kronolith_shares->addShare($calendar); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception($e); } $all_cals = $calendar_manager->get(Kronolith::ALL_CALENDARS); $all_cals[$calendar->getName()] = new Kronolith_Calendar_Internal(array('share' => $calendar)); $calendar_manager->set(Kronolith::ALL_CALENDARS, $all_cals); $display_cals = $calendar_manager->get(Kronolith::DISPLAY_CALENDARS); $display_cals[] = $calendar->getName(); $calendar_manager->set(Kronolith::DISPLAY_CALENDARS, $display_cals); $prefs->setValue('display_cals', serialize($display_cals)); return $calendar; } /** * Updates an existing share. * * @param Horde_Share $share The share to update. * @param array $info Hash with calendar information. * * @throws Kronolith_Exception */ static public function updateShare(&$calendar, $info) { if (!$GLOBALS['registry']->getAuth() || ($calendar->get('owner') != $GLOBALS['registry']->getAuth() && (!is_null($calendar->get('owner')) || !$GLOBALS['registry']->isAdmin()))) { throw new Kronolith_Exception(_("You are not allowed to change this calendar.")); } $calendar->set('name', $info['name']); $calendar->set('color', $info['color']); $calendar->set('desc', $info['description']); $calendar->set('owner', empty($info['system']) ? $GLOBALS['registry']->getAuth() : null); try { $calendar->save(); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception(sprintf(_("Unable to save calendar \"%s\": %s"), $info['name'], $e->getMessage())); } $tagger = self::getTagger(); $tagger->replaceTags($calendar->getName(), $info['tags'], $calendar->get('owner'), Kronolith_Tagger::TYPE_CALENDAR); } /** * Deletes a share and removes all events associated with it. * * @param Horde_Share $calendar The share to delete. * * @throws Kronolith_Exception */ static public function deleteShare($calendar) { if (!$GLOBALS['registry']->getAuth() || ($calendar->get('owner') != $GLOBALS['registry']->getAuth() && (!is_null($calendar->get('owner')) || !$GLOBALS['registry']->isAdmin()))) { throw new Kronolith_Exception(_("You are not allowed to delete this calendar.")); } // Delete the calendar. try { self::getDriver()->delete($calendar->getName()); } catch (Exception $e) { throw new Kronolith_Exception(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $e->getMessage())); } // Remove share and all groups/permissions. try { $GLOBALS['injector'] ->getInstance('Kronolith_Shares') ->removeShare($calendar); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception($e); } } /** * Returns a list of user with read access to a share. * * @param Horde_Share_Object $share A share object. * * @return array A hash of share users. */ static public function listShareUsers($share) { global $injector; $users = $share->listUsers(Horde_Perms::READ); $groups = $share->listGroups(Horde_Perms::READ); if (count($groups)) { $horde_group = $injector->getInstance('Horde_Group'); foreach ($groups as $group) { $users = array_merge( $users, $horde_group->listUsers($group) ); } } $users = array_flip($users); $factory = $injector->getInstance('Horde_Core_Factory_Identity'); foreach (array_keys($users) as $user) { $fullname = $factory->create($user)->getValue('fullname'); $users[$user] = strlen($fullname) ? $fullname : $user; } return $users; } /** * Reads a submitted permissions form and updates the share permissions. * * @param Horde_Share_Object $share The share to update. * * @return array A list of error messages. * @throws Kronolith_Exception */ static public function readPermsForm($share) { $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create(); $perm = $share->getPermission(); $errors = array(); if ($GLOBALS['conf']['share']['notify']) { $identity = $GLOBALS['injector'] ->getInstance('Horde_Core_Factory_Identity') ->create(); $mail = new Horde_Mime_Mail(array( 'From' => $identity->getDefaultFromAddress(true), 'User-Agent' => 'Kronolith ' . $GLOBALS['registry']->getVersion())); $image = self::getImagePart('big_share.png'); $view = new Horde_View(array('templatePath' => KRONOLITH_TEMPLATES . '/share')); new Horde_View_Helper_Text($view); $view->identity = $identity; $view->calendar = $share->get('name'); $view->imageId = $image->getContentId(); } // Process owner and owner permissions. $old_owner = $share->get('owner'); $new_owner_backend = Horde_Util::getFormData('owner_select', Horde_Util::getFormData('owner_input', $old_owner)); $new_owner = $GLOBALS['registry']->convertUsername($new_owner_backend, true); if ($old_owner !== $new_owner && !empty($new_owner)) { if ($old_owner != $GLOBALS['registry']->getAuth() && !$GLOBALS['registry']->isAdmin()) { $errors[] = _("Only the owner or system administrator may change ownership or owner permissions for a share"); } elseif ($auth->hasCapability('list') && !$auth->exists($new_owner_backend)) { $errors[] = sprintf(_("The user \"%s\" does not exist."), $new_owner_backend); } else { $share->set('owner', $new_owner); $share->save(); if ($GLOBALS['conf']['share']['notify']) { $view->ownerChange = true; $multipart = self::buildMimeMessage($view, 'notification', $image); $to = $GLOBALS['injector'] ->getInstance('Horde_Core_Factory_Identity') ->create($new_owner) ->getDefaultFromAddress(true); $mail->addHeader('Subject', _("Ownership assignment")); $mail->addHeader('To', $to); $mail->setBasePart($multipart); $mail->send($GLOBALS['injector']->getInstance('Horde_Mail')); $view->ownerChange = false; } } } if ($GLOBALS['conf']['share']['notify']) { if ($GLOBALS['conf']['share']['hidden']) { $view->subscribe = Horde::url('calendars/subscribe.php', true)->add('calendar', $share->getName()); } $multipart = self::buildMimeMessage($view, 'notification', $image); } if ($GLOBALS['registry']->isAdmin() || !empty($GLOBALS['conf']['share']['world'])) { // Process default permissions. if (Horde_Util::getFormData('default_show')) { $perm->addDefaultPermission(Horde_Perms::SHOW, false); } else { $perm->removeDefaultPermission(Horde_Perms::SHOW, false); } if (Horde_Util::getFormData('default_read')) { $perm->addDefaultPermission(Horde_Perms::READ, false); } else { $perm->removeDefaultPermission(Horde_Perms::READ, false); } if (Horde_Util::getFormData('default_edit')) { $perm->addDefaultPermission(Horde_Perms::EDIT, false); } else { $perm->removeDefaultPermission(Horde_Perms::EDIT, false); } if (Horde_Util::getFormData('default_delete')) { $perm->addDefaultPermission(Horde_Perms::DELETE, false); } else { $perm->removeDefaultPermission(Horde_Perms::DELETE, false); } if (Horde_Util::getFormData('default_delegate')) { $perm->addDefaultPermission(self::PERMS_DELEGATE, false); } else { $perm->removeDefaultPermission(self::PERMS_DELEGATE, false); } // Process guest permissions. if (Horde_Util::getFormData('guest_show')) { $perm->addGuestPermission(Horde_Perms::SHOW, false); } else { $perm->removeGuestPermission(Horde_Perms::SHOW, false); } if (Horde_Util::getFormData('guest_read')) { $perm->addGuestPermission(Horde_Perms::READ, false); } else { $perm->removeGuestPermission(Horde_Perms::READ, false); } if (Horde_Util::getFormData('guest_edit')) { $perm->addGuestPermission(Horde_Perms::EDIT, false); } else { $perm->removeGuestPermission(Horde_Perms::EDIT, false); } if (Horde_Util::getFormData('guest_delete')) { $perm->addGuestPermission(Horde_Perms::DELETE, false); } else { $perm->removeGuestPermission(Horde_Perms::DELETE, false); } if (Horde_Util::getFormData('guest_delegate')) { $perm->addGuestPermission(self::PERMS_DELEGATE, false); } else { $perm->removeGuestPermission(self::PERMS_DELEGATE, false); } // Process creator permissions. if (Horde_Util::getFormData('creator_show')) { $perm->addCreatorPermission(Horde_Perms::SHOW, false); } else { $perm->removeCreatorPermission(Horde_Perms::SHOW, false); } if (Horde_Util::getFormData('creator_read')) { $perm->addCreatorPermission(Horde_Perms::READ, false); } else { $perm->removeCreatorPermission(Horde_Perms::READ, false); } if (Horde_Util::getFormData('creator_edit')) { $perm->addCreatorPermission(Horde_Perms::EDIT, false); } else { $perm->removeCreatorPermission(Horde_Perms::EDIT, false); } if (Horde_Util::getFormData('creator_delete')) { $perm->addCreatorPermission(Horde_Perms::DELETE, false); } else { $perm->removeCreatorPermission(Horde_Perms::DELETE, false); } if (Horde_Util::getFormData('creator_delegate')) { $perm->addCreatorPermission(self::PERMS_DELEGATE, false); } else { $perm->removeCreatorPermission(self::PERMS_DELEGATE, false); } } // Process user permissions. $u_names = Horde_Util::getFormData('u_names'); $u_show = Horde_Util::getFormData('u_show'); $u_read = Horde_Util::getFormData('u_read'); $u_edit = Horde_Util::getFormData('u_edit'); $u_delete = Horde_Util::getFormData('u_delete'); $u_delegate = Horde_Util::getFormData('u_delegate'); $current = $perm->getUserPermissions(); if ($GLOBALS['conf']['share']['notify']) { $mail->addHeader('Subject', _("Access permissions")); } $perm->removeUserPermission(null, null, false); foreach ($u_names as $key => $user_backend) { // Apply backend hooks $user = $GLOBALS['registry']->convertUsername($user_backend, true); // If the user is empty, or we've already set permissions // via the owner_ options, don't do anything here. if (empty($user) || $user == $new_owner) { continue; } if ($auth->hasCapability('list') && !$auth->exists($user_backend)) { $errors[] = sprintf(_("The user \"%s\" does not exist."), $user_backend); continue; } $has_perms = false; if (!empty($u_show[$key])) { $perm->addUserPermission($user, Horde_Perms::SHOW, false); $has_perms = true; } if (!empty($u_read[$key])) { $perm->addUserPermission($user, Horde_Perms::READ, false); $has_perms = true; } if (!empty($u_edit[$key])) { $perm->addUserPermission($user, Horde_Perms::EDIT, false); $has_perms = true; } if (!empty($u_delete[$key])) { $perm->addUserPermission($user, Horde_Perms::DELETE, false); $has_perms = true; } if (!empty($u_delegate[$key])) { $perm->addUserPermission($user, self::PERMS_DELEGATE, false); $has_perms = true; } // Notify users that have been added. if ($GLOBALS['conf']['share']['notify'] && !isset($current[$user]) && $has_perms) { $to = $GLOBALS['injector'] ->getInstance('Horde_Core_Factory_Identity') ->create($user) ->getDefaultFromAddress(true); $mail->addHeader('To', $to); $mail->setBasePart($multipart); $mail->send($GLOBALS['injector']->getInstance('Horde_Mail')); } } // Process group permissions. $g_names = Horde_Util::getFormData('g_names'); $g_show = Horde_Util::getFormData('g_show'); $g_read = Horde_Util::getFormData('g_read'); $g_edit = Horde_Util::getFormData('g_edit'); $g_delete = Horde_Util::getFormData('g_delete'); $g_delegate = Horde_Util::getFormData('g_delegate'); $current = $perm->getGroupPermissions(); $perm->removeGroupPermission(null, null, false); foreach ($g_names as $key => $group) { if (empty($group)) { continue; } $has_perms = false; if (!empty($g_show[$key])) { $perm->addGroupPermission($group, Horde_Perms::SHOW, false); $has_perms = true; } if (!empty($g_read[$key])) { $perm->addGroupPermission($group, Horde_Perms::READ, false); $has_perms = true; } if (!empty($g_edit[$key])) { $perm->addGroupPermission($group, Horde_Perms::EDIT, false); $has_perms = true; } if (!empty($g_delete[$key])) { $perm->addGroupPermission($group, Horde_Perms::DELETE, false); $has_perms = true; } if (!empty($g_delegate[$key])) { $perm->addGroupPermission($group, self::PERMS_DELEGATE, false); $has_perms = true; } // Notify users that have been added. if ($GLOBALS['conf']['share']['notify'] && !isset($current[$group]) && $has_perms) { $groupOb = $GLOBALS['injector'] ->getInstance('Horde_Group') ->getData($group); if (!empty($groupOb['email'])) { $mail->addHeader('To', $groupOb['name'] . ' <' . $groupOb['email'] . '>'); $mail->setBasePart($multipart); $mail->send($GLOBALS['injector']->getInstance('Horde_Mail')); } } } try { $share->setPermission($perm); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception($e); } return $errors; } /** * Subscribes to or updates a remote calendar. * * @param array $info Hash with calendar information. * @param string $update If present, the original URL of the calendar to * update. * * @throws Kronolith_Exception */ static public function subscribeRemoteCalendar(&$info, $update = false) { if (!(strlen($info['name']) && strlen($info['url']))) { throw new Kronolith_Exception(_("You must specify a name and a URL.")); } if (!empty($info['user']) || !empty($info['password'])) { $key = $GLOBALS['registry']->getAuthCredential('password'); if ($key) { $secret = $GLOBALS['injector']->getInstance('Horde_Secret'); $info['user'] = base64_encode($secret->write($key, $info['user'])); $info['password'] = base64_encode($secret->write($key, $info['password'])); } } $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); if ($update) { foreach ($remote_calendars as $key => $calendar) { if ($calendar['url'] == $update) { $remote_calendars[$key] = $info; break; } } } else { $remote_calendars[] = $info; $display_remote = $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_REMOTE_CALENDARS); $display_remote[] = $info['url']; $GLOBALS['calendar_manager']->set(Kronolith::DISPLAY_REMOTE_CALENDARS, $display_remote); $GLOBALS['prefs']->setValue('display_remote_cals', serialize($display_remote)); } $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars)); } /** * Unsubscribes from a remote calendar. * * @param string $url The calendar URL. * * @return array Hash with the deleted calendar's information. * @throws Kronolith_Exception */ static public function unsubscribeRemoteCalendar($url) { $url = trim($url); if (!strlen($url)) { return false; } $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); $remote_calendar = null; foreach ($remote_calendars as $key => $calendar) { if ($calendar['url'] == $url) { $remote_calendar = $calendar; unset($remote_calendars[$key]); break; } } if (!$remote_calendar) { throw new Kronolith_Exception(_("The remote calendar was not found.")); } $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars)); return $remote_calendar; } /** * Returns the feed URL for a calendar. * * @param string $calendar A calendar name. * * @return string The calendar's feed URL. */ static public function feedUrl($calendar) { if (isset($GLOBALS['conf']['urls']['pretty']) && $GLOBALS['conf']['urls']['pretty'] == 'rewrite') { return Horde::url('feed/' . $calendar, true, -1); } return Horde::url('feed/index.php', true, -1) ->add('c', $calendar); } /** * Returs the HTML/javascript snippit needed to embed a calendar in an * external website. * * @param string $calendar A calendar name. * * @return string The calendar's embed snippit. */ static public function embedCode($calendar) { /* Get the base url */ $url = $GLOBALS['registry']->getServiceLink('ajax', 'kronolith', true)->add(array( 'calendar' => 'internal_' . $calendar, 'container' => 'kronolithCal', 'view' => 'Month' )); $url->url .= 'embed'; return '<div id="kronolithCal"></div><script src="' . $url . '" type="text/javascript"></script>'; } /** * Parses a comma separated list of names and e-mail addresses into a list * of attendee hashes. * * @param string $newAttendees A comma separated attendee list. * * @return array The attendee list with e-mail addresses as keys and * attendee information as values. */ static public function parseAttendees($newAttendees) { global $injector, $notification; if (empty($newAttendees)) { return array(); } $parser = $injector->getInstance('Horde_Mail_Rfc822'); $attendees = array(); /* Parse the address without validation to see what we can get out * of it. We allow email addresses (john@example.com), email * address with user information (John Doe <john@example.com>), * and plain names (John Doe). */ $result = $parser->parseAddressList($newAttendees); $result->setIteratorFilter(Horde_Mail_Rfc822_List::HIDE_GROUPS); foreach ($result as $newAttendee) { if (!$newAttendee->valid) { // If we can't even get a mailbox out of the address, then it // is likely unuseable. Reject it entirely. $notification->push( sprintf(_("Unable to recognize \"%s\" as an email address."), $newAttendee), 'horde.error' ); continue; } // If there is only a mailbox part, then it is just a local name. if (!is_null($newAttendee->host)) { // Build a full email address again and validate it. try { $parser->parseAddressList($newAttendee->writeAddress(true)); } catch (Horde_Mail_Exception $e) { $notification->push($e, 'horde.error'); continue; } $name = $newAttendee->label != $newAttendee->bare_address ? $newAttendee->label : ''; } else { $name = $newAttendee->bare_address; } // Avoid overwriting existing attendees with the default // values. $attendees[$newAttendee->bare_address] = array( 'attendance' => self::PART_REQUIRED, 'response' => self::RESPONSE_NONE, 'name' => $name ); } return $attendees; } /** * Returns a comma separated list of attendees and resources * * @return string Attendee/Resource list. */ static public function attendeeList() { /* Attendees */ $attendees = self::getAttendeeEmailList($GLOBALS['session']->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY))->addresses; /* Resources */ foreach ($GLOBALS['session']->get('kronolith', 'resources', Horde_Session::TYPE_ARRAY) as $resource) { $attendees[] = $resource['name']; } return implode(', ', $attendees); } /** * Sends out iTip event notifications to all attendees of a specific * event. * * Can be used to send event invitations, event updates as well as event * cancellations. * * @param Kronolith_Event $event * The event in question. * @param Horde_Notification_Handler $notification * A notification object used to show result status. * @param integer $action * The type of notification to send. One of the Kronolith::ITIP_* * values. * @param Horde_Date $instance * If cancelling a single instance of a recurring event, the date of * this instance. * @param string $range The range parameter if this is a recurring event. * Possible values are self::RANGE_THISANDFUTURE * @param array $cancellations If $action is 'CANCEL', but it is due to * removing attendees and not canceling the * entire event, these are the email addresses * of the uninvited attendees and are the ONLY * people that will receive the CANCEL iTIP. * @since 4.2.10 * */ static public function sendITipNotifications( Kronolith_Event $event, Horde_Notification_Handler $notification, $action, Horde_Date $instance = null, $range = null, array $cancellations = array()) { global $injector, $registry; if (!$event->attendees) { return; } $ident = $injector->getInstance('Horde_Core_Factory_Identity')->create($event->creator); if (!$ident->getValue('from_addr')) { $notification->push(sprintf(_("You do not have an email address configured in your Personal Information Preferences. You must set one %shere%s before event notifications can be sent."), $registry->getServiceLink('prefs', 'kronolith')->add(array('app' => 'horde', 'group' => 'identities'))->link(), '</a>'), 'horde.error', array('content.raw')); return; } // Generate image mime part first and only once, because we // need the Content-ID. $image = self::getImagePart('big_invitation.png'); $share = $injector->getInstance('Kronolith_Shares')->getShare($event->calendar); $view = new Horde_View(array('templatePath' => KRONOLITH_TEMPLATES . '/itip')); new Horde_View_Helper_Text($view); $view->identity = $ident; $view->event = clone $event; $view->imageId = $image->getContentId(); if ($action == self::ITIP_CANCEL && !empty($cancellations)) { $mail_attendees = $cancellations; } else { $mail_attendees = $event->attendees; } foreach ($mail_attendees as $email => $status) { /* Don't bother sending an invitation/update if the recipient does * not need to participate, or has declined participating, or * doesn't have an email address. */ if (strpos($email, '@') === false || $status['response'] == self::RESPONSE_DECLINED) { continue; } /* Determine all notification-specific strings. */ switch ($action) { case self::ITIP_CANCEL: /* Cancellation. */ $method = 'CANCEL'; $filename = 'event-cancellation.ics'; $view->subject = sprintf(_("Cancelled: %s"), $event->getTitle()); if (empty($instance)) { $view->header = sprintf(_("%s has cancelled \"%s\"."), $ident->getName(), $event->getTitle()); } else { $view->header = sprintf(_("%s has cancelled an instance of the recurring \"%s\"."), $ident->getName(), $event->getTitle()); } break; case self::ITIP_REQUEST: default: $method = 'REQUEST'; if ($status['response'] == self::RESPONSE_NONE) { /* Invitation. */ $filename = 'event-invitation.ics'; $view->subject = $event->getTitle(); $view->header = sprintf(_("%s wishes to make you aware of \"%s\"."), $ident->getName(), $event->getTitle()); } else { /* Update. */ $filename = 'event-update.ics'; $view->subject = sprintf(_("Updated: %s."), $event->getTitle()); $view->header = sprintf(_("%s wants to notify you about changes of \"%s\"."), $ident->getName(), $event->getTitle()); } break; } if ($event->attendees) { $view->attendees = strval(self::getAttendeeEmailList($event->attendees)); $view->organizer = $registry->convertUserName($event->creator, false); } if ($action == self::ITIP_REQUEST) { $attend_link = Horde::url('attend.php', true, -1) ->add(array('c' => $event->calendar, 'e' => $event->id, 'u' => $email)); $view->linkAccept = (string)$attend_link->add('a', 'accept'); $view->linkTentative = (string)$attend_link->add('a', 'tentative'); $view->linkDecline = (string)$attend_link->add('a', 'decline'); } /* Build the iCalendar data */ $iCal = new Horde_Icalendar(); $iCal->setAttribute('METHOD', $method); $iCal->setAttribute('X-WR-CALNAME', $share->get('name')); $vevent = $event->toiCalendar($iCal); if ($action == self::ITIP_CANCEL && !empty($instance)) { // Recurring event instance deletion, need to specify the // RECURRENCE-ID but NOT the EXDATE. foreach($vevent as &$ve) { try { $uid = $ve->getAttribute('UID'); } catch (Horde_Icalendar_Exception $e) { continue; } if ($event->uid == $uid) { $ve->setAttribute('RECURRENCE-ID', $instance); if (!empty($range)) { $ve->setParameter('RECURRENCE-ID', array('RANGE' => $range)); } $ve->setAttribute('DTSTART', $instance, array(), false); $diff = $event->end->timestamp() - $event->start->timestamp(); $end = clone $instance; $end->sec += $diff; $ve->setAttribute('DTEND', $end, array(), false); $ve->removeAttribute('EXDATE'); $view->event->fromiCalendar($ve); break; } } } $iCal->addComponent($vevent); /* text/calendar part */ $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setContents($iCal->exportvCalendar()); $ics->setName($filename); $ics->setContentTypeParameter('METHOD', $method); $ics->setCharset('UTF-8'); $ics->setEOL("\r\n"); /* application/ics part */ $ics2 = clone $ics; $ics2->setType('application/ics'); /* multipart/mixed part */ $multipart = new Horde_Mime_Part(); $multipart->setType('multipart/mixed'); $inner = self::buildMimeMessage($view, 'notification', $image); $inner->addPart($ics); $multipart->addPart($inner); $multipart->addPart($ics2); $recipient = new Horde_Mail_Rfc822_Address($email); if (!empty($status['name'])) { $recipient->personal = $status['name']; } $mail = new Horde_Mime_Mail( array('Subject' => $view->subject, 'To' => $recipient, 'From' => $ident->getDefaultFromAddress(true), 'User-Agent' => 'Kronolith ' . $registry->getVersion())); $mail->setBasePart($multipart); try { $mail->send($injector->getInstance('Horde_Mail')); $notification->push( sprintf(_("The event notification to %s was successfully sent."), $recipient), 'horde.success' ); } catch (Horde_Mime_Exception $e) { $notification->push( sprintf(_("There was an error sending an event notification to %s: %s"), $recipient, $e->getMessage(), $e->getCode()), 'horde.error' ); } } } /** * Sends email notifications that a event has been added, edited, or * deleted to users that want such notifications. * * @param Kronolith_Event $event An event. * @param string $action The event action. One of "add", "edit", * or "delete". * * @throws Horde_Mime_Exception * @throws Kronolith_Exception */ static public function sendNotification($event, $action) { global $injector, $registry; if (!in_array($action, array('add', 'edit', 'delete'))) { throw new Kronolith_Exception('Unknown event action: ' . $action); } // @TODO: Send notifications to the email addresses stored in the // resource object? if ($event->calendarType == 'resource') { return; } $groups = $injector->getInstance('Horde_Group'); $calendar = $event->calendar; $recipients = array(); try { $share = $injector->getInstance('Kronolith_Shares')->getShare($calendar); } catch (Horde_Share_Exception $e) { throw new Kronolith_Exception($e); } $owner = $share->get('owner'); if ($owner) { if ($notificationPrefs = self::_notificationPref($owner, 'owner')) { $recipients[$owner] = $notificationPrefs; $recipients[$owner]['private'] = $event->isPrivate($owner); } } $senderIdentity = $injector->getInstance('Horde_Core_Factory_Identity') ->create($registry->getAuth() ?: $event->creator ?: $owner); foreach ($share->listUsers(Horde_Perms::READ) as $user) { if (empty($recipients[$user]) && ($notificationPrefs = self::_notificationPref($user, 'read', $calendar))) { $recipients[$user] = $notificationPrefs; $recipients[$user]['private'] = $event->isPrivate($user); } } foreach ($share->listGroups(Horde_Perms::READ) as $group) { try { $group_users = $groups->listUsers($group); } catch (Horde_Group_Exception $e) { Horde::log($e, 'ERR'); continue; } foreach ($group_users as $user) { if (empty($recipients[$user]) && ($notificationPrefs = self::_notificationPref($user, 'read', $calendar))) { $recipients[$user] = $notificationPrefs; $recipients[$user]['private'] = $event->isPrivate($user); } } } $addresses = array(); foreach ($recipients as $user => $vals) { if (!$vals) { continue; } $identity = $injector->getInstance('Horde_Core_Factory_Identity')->create($user); $email = $identity->getValue('from_addr'); if (strpos($email, '@') === false) { continue; } if (!isset($addresses[$vals['lang']][$vals['tf']][$vals['df']][$vals['private']])) { $addresses[$vals['lang']][$vals['tf']][$vals['df']][$vals['private']] = array(); } $tmp = new Horde_Mail_Rfc822_Address($email); $tmp->personal = $identity->getValue('fullname'); $addresses[$vals['lang']][$vals['tf']][$vals['df']][$vals['private']][] = strval($tmp); } if (!$addresses) { return; } foreach ($addresses as $lang => $twentyFour) { $registry->setLanguageEnvironment($lang); switch ($action) { case 'add': $subject = _("Event added:"); $notification_message = _("You requested to be notified when events are added to your calendars.") . "\n\n" . _("The event \"%s\" has been added to \"%s\" calendar, which is on %s at %s."); break; case 'edit': $subject = _("Event edited:"); $notification_message = _("You requested to be notified when events are edited in your calendars.") . "\n\n" . _("The event \"%s\" has been edited on \"%s\" calendar, which is on %s at %s."); break; case 'delete': $subject = _("Event deleted:"); $notification_message = _("You requested to be notified when events are deleted from your calendars.") . "\n\n" . _("The event \"%s\" has been deleted from \"%s\" calendar, which was on %s at %s."); break; } foreach ($twentyFour as $tf => $dateFormat) { foreach ($dateFormat as $df => $recipients) { foreach ($recipients as $is_private => $df_recipients) { $event_title = $is_private ? _("busy") : $event->title; $message = "\n" . sprintf($notification_message, $event_title, Kronolith::getLabel($share), $event->start->strftime($df), $event->start->strftime($tf ? '%R' : '%I:%M%p')) . "\n\n" . ($is_private ? "" : $event->description); $mime_mail = new Horde_Mime_Mail(array( 'Subject' => $subject . ' ' . $event_title, 'To' => implode(',', $df_recipients), 'From' => $senderIdentity->getDefaultFromAddress(true), 'User-Agent' => 'Kronolith ' . $registry->getVersion(), 'body' => $message)); Horde::log(sprintf('Sending event notifications for %s to %s', $event_title, implode(', ', $df_recipients)), 'DEBUG'); try { $mime_mail->send($injector->getInstance('Horde_Mail')); } catch (Horde_Mime_Exception $e) { Horde::log(sprintf('Could not send notification to all recipients %s: %s', implode(', ', $df_recipients), $e->getMessage()), 'ERR'); } } } } } } /** * Check for resource declines and push notice to stack if found. * * @param Kronolith_Event $event * * @throws Kronolith_Exception */ static public function notifyOfResourceRejection($event) { $accepted = $declined = array(); foreach ($event->getResources() as $id => $resource) { if ($resource['response'] == self::RESPONSE_DECLINED) { $r = self::getDriver('Resource')->getResource($id); $declined[] = $r->get('name'); } elseif ($resource['response'] == self::RESPONSE_ACCEPTED) { $r = self::getDriver('Resource')->getResource($id); $accepted[] = $r->get('name'); } } if (count($declined)) { $GLOBALS['notification']->push( sprintf( ngettext( "The following resource has declined your request: %s", "The following resources have declined your request: %s", count($declined) ), implode(", ", $declined) ), 'horde.error' ); } if (count($accepted)) { $GLOBALS['notification']->push( sprintf( ngettext( "The following resource has accepted your request: %s", "The following resources have accepted your request: %s", count($accepted) ), implode(", ", $accepted) ), 'horde.success' ); } } /** * Returns whether a user wants email notifications for a calendar. * * @access private * * @todo This method is causing a memory leak somewhere, noticeable if * importing a large amount of events. * * @param string $user A user name. * @param string $mode The check "mode". If "owner", the method checks * if the user wants notifications only for * calendars he owns. If "read", the method checks * if the user wants notifications for all * calendars he has read access to, or only for * shown calendars and the specified calendar is * currently shown. * @param string $calendar The name of the calendar if mode is "read". * * @return array|boolen The user's email, time, and language preferences if * they want a notification for this calendar. False * if no notification should be sent. */ static public function _notificationPref($user, $mode, $calendar = null) { $prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('kronolith', array( 'cache' => false, 'user' => $user )); $vals = array('lang' => $prefs->getValue('language'), 'tf' => $prefs->getValue('twentyFour'), 'df' => $prefs->getValue('date_format')); if ($prefs->getValue('event_notification_exclude_self') && $user == $GLOBALS['registry']->getAuth()) { return false; } switch ($prefs->getValue('event_notification')) { case 'owner': return $mode == 'owner' ? $vals : false; case 'read': return $mode == 'read' ? $vals : false; case 'show': if ($mode == 'read') { $display_calendars = unserialize($prefs->getValue('display_cals')); return in_array($calendar, $display_calendars) ? $vals : false; } } return false; } /** * Builds the body MIME part of a multipart message. * * @param Horde_View $view A view to render the HTML and plain text * templates for the messate. * @param string $template The template base name for the view. * @param Horde_Mime_Part $image The MIME part of a related image. * * @return Horde_Mime_Part A multipart/alternative MIME part. */ static public function buildMimeMessage(Horde_View $view, $template, Horde_Mime_Part $image) { $multipart = new Horde_Mime_Part(); $multipart->setType('multipart/alternative'); $bodyText = new Horde_Mime_Part(); $bodyText->setType('text/plain'); $bodyText->setCharset('UTF-8'); $bodyText->setContents($view->render($template . '.plain.php')); $bodyText->setDisposition('inline'); $multipart->addPart($bodyText); $bodyHtml = new Horde_Mime_Part(); $bodyHtml->setType('text/html'); $bodyHtml->setCharset('UTF-8'); $bodyHtml->setContents($view->render($template . '.html.php')); $bodyHtml->setDisposition('inline'); $related = new Horde_Mime_Part(); $related->setType('multipart/related'); $related->setContentTypeParameter('start', $bodyHtml->setContentId()); $related->addPart($bodyHtml); $related->addPart($image); $multipart->addPart($related); return $multipart; } /** * Returns a MIME part for an image to be embedded into a HTML document. * * @param string $file An image file name. * * @return Horde_Mime_Part A MIME part representing the image. */ static public function getImagePart($file) { $background = Horde_Themes::img($file); $image = new Horde_Mime_Part(); $image->setType('image/png'); $image->setContents(file_get_contents($background->fs)); $image->setContentId(); $image->setDisposition('attachment'); return $image; } /** * @return Horde_Date */ static public function currentDate() { if ($date = Horde_Util::getFormData('date')) { return new Horde_Date($date . '000000'); } if ($date = Horde_Util::getFormData('datetime')) { return new Horde_Date($date); } return new Horde_Date($_SERVER['REQUEST_TIME']); } /** * Parses a complete date-time string into a Horde_Date object. * * @param string $date The date-time string to parse. * @param boolean $withtime Whether time is included in the string. * @þaram string $timezone The timezone of the string. * * @return Horde_Date The parsed date. * @throws Horde_Date_Exception */ static public function parseDate($date, $withtime = true, $timezone = null) { // strptime() is not available on Windows. if (!function_exists('strptime')) { return new Horde_Date($date, $timezone); } // strptime() is locale dependent, i.e. %p is not always matching // AM/PM. Set the locale to C to workaround this, but grab the // locale's D_FMT before that. $format = Horde_Nls::getLangInfo(D_FMT); if ($withtime) { $format .= ' ' . ($GLOBALS['prefs']->getValue('twentyFour') ? '%H:%M' : '%I:%M %p'); } $old_locale = setlocale(LC_TIME, 0); setlocale(LC_TIME, 'C'); // Try exact format match first. $date_arr = strptime($date, $format); setlocale(LC_TIME, $old_locale); if (!$date_arr) { // Try with locale dependent parsing next. $date_arr = strptime($date, $format); if (!$date_arr) { // Try throwing at Horde_Date finally. return new Horde_Date($date, $timezone); } } return new Horde_Date( array('year' => $date_arr['tm_year'] + 1900, 'month' => $date_arr['tm_mon'] + 1, 'mday' => $date_arr['tm_mday'], 'hour' => $date_arr['tm_hour'], 'min' => $date_arr['tm_min'], 'sec' => $date_arr['tm_sec']), $timezone); } /** * @param object $renderer A Kronolith view. */ static public function tabs($renderer) { global $injector, $prefs; $view = $injector->createInstance('Horde_View'); $date = self::currentDate(); $date_stamp = array('date' => $date->dateString()); $tabname = basename($_SERVER['PHP_SELF']) == 'index.php' ? $GLOBALS['prefs']->getValue('defaultview') : str_replace('.php', '', basename($_SERVER['PHP_SELF'])); $view->active = $tabname; $view->previous = $renderer->link(-1); $view->next = $renderer->link(1); switch ($tabname) { case 'day': $view->current = $renderer->getTime($prefs->getValue('date_format')); break; case 'workweek': case 'week': $view->current = $renderer->days[$renderer->startDay] ->getTime($prefs->getValue('date_format')) . ' - ' . $renderer->days[$renderer->endDay] ->getTime($prefs->getValue('date_format')); break; case 'month': $view->current = $renderer->date->strftime('%B %Y'); break; case 'year': $view->current = $renderer->year; break; } $view->today = Horde::url($prefs->getValue('defaultview') . '.php') ->link(Horde::getAccessKeyAndTitle(_("_Today"), false, true)) . _("Today") . '</a>'; $view->day = Horde::widget(array( 'url' => Horde::url('day.php')->add($date_stamp), 'id' => 'kronolithNavDay', 'accesskey' => '1', 'title' => _("Day") )); $view->workWeek = Horde::widget(array( 'url' => Horde::url('workweek.php')->add($date_stamp), 'id' => 'kronolithNavWorkweek', 'accesskey' => '2', 'title' => _("Work Week") )); $view->week = Horde::widget(array( 'url' => Horde::url('week.php')->add($date_stamp), 'id' => 'kronolithNavWeek', 'accesskey' => '3', 'title' => _("Week") )); $view->month = Horde::widget(array( 'url' => Horde::url('month.php')->add($date_stamp), 'id' => 'kronolithNavMonth', 'accesskey' => '4', 'title' => _("Month") )); $view->year = Horde::widget(array( 'url' => Horde::url('year.php')->add($date_stamp), 'id' => 'kronolithNavYear', 'accesskey' => '5', 'title' => _("Year") )); echo $view->render('buttonbar'); } /** * @param string $tabname * @param Kronolith_Event $event */ static public function eventTabs($tabname, $event) { if (!$event->initialized) { return; } $GLOBALS['page_output']->addScriptFile('views.js'); $tabs = new Horde_Core_Ui_Tabs('event', Horde_Variables::getDefaultVariables()); $date = self::currentDate(); $tabs->preserve('datetime', $date->dateString()); $tabs->addTab( htmlspecialchars($event->getTitle()), $event->getViewUrl(), array('tabname' => 'Event', 'id' => 'tabEvent', 'onclick' => 'return ShowTab(\'Event\');')); /* We check for read permissions, because we can always save a copy if * we can read the event. */ if ((!$event->private || $event->creator == $GLOBALS['registry']->getAuth()) && $event->hasPermission(Horde_Perms::READ) && self::getDefaultCalendar(Horde_Perms::EDIT)) { $tabs->addTab( $event->hasPermission(Horde_Perms::EDIT) ? _("_Edit") : _("Save As New"), $event->getEditUrl(), array('tabname' => 'EditEvent', 'id' => 'tabEditEvent', 'onclick' => 'return ShowTab(\'EditEvent\');')); } if ($event->hasPermission(Horde_Perms::DELETE)) { $tabs->addTab( _("De_lete"), $event->getDeleteUrl(array('confirm' => 1)), array('tabname' => 'DeleteEvent', 'id' => 'tabDeleteEvent', 'onclick' => 'return ShowTab(\'DeleteEvent\');')); } $tabs->addTab( _("Export"), $event->getExportUrl(), array('tabname' => 'ExportEvent', 'id' => 'tabExportEvent')); echo $tabs->render($tabname); } /** * Attempts to return a single, concrete Kronolith_Driver instance based * on a driver name. * * This singleton method automatically retrieves all parameters required * for the specified driver. * * @param string $driver The type of concrete Kronolith_Driver subclass * to return. * @param string $calendar The calendar name. The format depends on the * driver being used. * * @return Kronolith_Driver The newly created concrete Kronolith_Driver * instance. * @throws Kronolith_Exception */ static public function getDriver($driver = null, $calendar = null) { $instance = $GLOBALS['injector'] ->getInstance('Kronolith_Factory_Driver') ->create($driver); if (!is_null($calendar)) { $instance->open($calendar); /* Remote calendar parameters are per calendar. */ if ($instance instanceof Kronolith_Driver_Ical) { $instance->setParams(self::getRemoteParams($calendar)); } } return $instance; } /** * Returns a Kronolith_Calendar object for a driver instance. * * @since Kronolith 4.0.1 * * @param Kronolith_Driver A driver instance. * * @return Kronolith_Calendar The matching calendar instance. */ static public function getCalendar(Kronolith_Driver $driver) { global $calendar_manager; switch (true) { case $driver instanceof Kronolith_Driver_Sql: case $driver instanceof Kronolith_Driver_Kolab: return $calendar_manager->getEntry(Kronolith::ALL_CALENDARS, $driver->calendar); case $driver instanceof Kronolith_Driver_Ical: return $calendar_manager->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $driver->calendar); case $driver instanceof Kronolith_Driver_Horde: $all = $calendar_manager->get(Kronolith::ALL_EXTERNAL_CALENDARS); return $all[$driver->calendar]; case $driver instanceof Kronolith_Driver_Holidays: return $calendar_manager->getEntry(Kronolith::ALL_HOLIDAYS, $driver->calendar); case $driver instanceof Kronolith_Driver_Resource_Sql: return $calendar_manager->getEntry(Kronolith::ALL_RESOURCE_CALENDARS, $driver->calendar); } } /** * Check for HTTP authentication credentials */ static public function getRemoteParams($calendar) { if (empty($calendar)) { return array(); } $cals = unserialize($GLOBALS['prefs']->getValue('remote_cals')); foreach ($cals as $cal) { if ($cal['url'] == $calendar) { $user = isset($cal['user']) ? $cal['user'] : ''; $password = isset($cal['password']) ? $cal['password'] : ''; $key = $GLOBALS['registry']->getAuthCredential('password'); if ($key && $password) { $secret = $GLOBALS['injector']->getInstance('Horde_Secret'); $user = $secret->read($key, base64_decode($user)); $password = $secret->read($key, base64_decode($password)); } if (!empty($user)) { return array('user' => $user, 'password' => $password); } return array(); } } return array(); } /** * Returns a list of currently displayed calendars. * * @return array Currently displayed calendars. */ static public function displayedCalendars() { $calendars = array(); foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS) as $calendarId) { $calendars[] = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $calendarId); } if (count($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_RESOURCE_CALENDARS))) { $r_driver = self::getDriver('Resource'); foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_RESOURCE_CALENDARS) as $c) { try { $resource = $r_driver->getResource($r_driver->getResourceIdByCalendar($c)); $calendars[] = new Kronolith_Calendar_Resource(array('resource' => $resource)); } catch (Horde_Exception_NotFound $e) { } } } return $calendars; } /** * Get a named Kronolith_View_* object and load it with the * appropriate date parameters. * * @param string $view The name of the view. */ static public function getView($view) { switch ($view) { case 'Day': case 'Month': case 'Week': case 'WorkWeek': case 'Year': $class = 'Kronolith_View_' . $view; return new $class(self::currentDate()); case 'Event': case 'EditEvent': case 'DeleteEvent': case 'ExportEvent': try { if ($uid = Horde_Util::getFormData('uid')) { $event = self::getDriver()->getByUID($uid); } else { $event = self::getDriver(Horde_Util::getFormData('type'), Horde_Util::getFormData('calendar')) ->getEvent(Horde_Util::getFormData('eventID'), Horde_Util::getFormData('datetime')); } } catch (Horde_Exception $e) { $event = $e->getMessage(); } switch ($view) { case 'Event': if (!is_string($event) && !$event->hasPermission(Horde_Perms::READ)) { $event = _("Permission Denied"); } return new Kronolith_View_Event($event); case 'EditEvent': /* We check for read permissions, because we can always save a * copy if we can read the event. */ if (!is_string($event) && !$event->hasPermission(Horde_Perms::READ)) { $event = _("Permission Denied"); } return new Kronolith_View_EditEvent($event); case 'DeleteEvent': if (!is_string($event) && !$event->hasPermission(Horde_Perms::DELETE)) { $event = _("Permission Denied"); } return new Kronolith_View_DeleteEvent($event); case 'ExportEvent': if (!is_string($event) && !$event->hasPermission(Horde_Perms::READ)) { $event = _("Permission Denied"); } return new Kronolith_View_ExportEvent($event); } } } /** * Should we show event location, based on the show_location pref? */ static public function viewShowLocation() { $show = @unserialize($GLOBALS['prefs']->getValue('show_location')); return @in_array('screen', $show); } /** * Should we show event time, based on the show_time preference? */ static public function viewShowTime() { $show = @unserialize($GLOBALS['prefs']->getValue('show_time')); return @in_array('screen', $show); } /** * Returns the background color for a calendar. * * @param array|Horde_Share_Object $calendar A calendar share or a hash * from a remote calender * definition. * * @return string A HTML color code. */ static public function backgroundColor($calendar) { $color = ''; if (!is_array($calendar)) { $color = $calendar->get('color'); } elseif (isset($calendar['color'])) { $color = $calendar['color']; } return empty($color) ? '#dddddd' : $color; } /** * Returns the foreground color for a calendar or a background color. * * @param array|Horde_Share_Object|string $calendar A color string, a * calendar share or a * hash from a remote * calender definition. * * @return string A HTML color code. */ static public function foregroundColor($calendar) { return Horde_Image::brightness(is_string($calendar) ? $calendar : self::backgroundColor($calendar)) < 128 ? '#fff' : '#000'; } /** * Returns the CSS color definition for a calendar. * * @param array|Horde_Share_Object $calendar A calendar share or a hash * from a remote calender * definition. * @param boolean $with_attribute Whether to wrap the colors * inside a "style" attribute. * * @return string A CSS string with color definitions. */ static public function getCSSColors($calendar, $with_attribute = true) { $css = 'background-color:' . self::backgroundColor($calendar) . ';color:' . self::foregroundColor($calendar); if ($with_attribute) { $css = ' style="' . $css . '"'; } return $css; } /** * Returns a random CSS color. * * @return string A random CSS color string. */ static public function randomColor() { $color = '#'; for ($i = 0; $i < 3; $i++) { $color .= sprintf('%02x', mt_rand(0, 255)); } return $color; } /** * Returns whether to display the ajax view. * * return boolean True if the ajax view should be displayed. */ static public function showAjaxView() { return $GLOBALS['registry']->getView() == Horde_Registry::VIEW_DYNAMIC && $GLOBALS['prefs']->getValue('dynamic_view'); } /** * Sorts an event list. * * @param array $days A list of days with events. * * @return array The sorted day list. */ static public function sortEvents($days) { foreach ($days as $day => $devents) { if (count($devents)) { uasort($devents, array('Kronolith', '_sortEventStartTime')); $days[$day] = $devents; } } return $days; } /** * Used with usort() to sort events based on their start times. */ static protected function _sortEventStartTime($a, $b) { $diff = $a->start->compareDateTime($b->start); if ($diff == 0) { return strcoll($a->title, $b->title); } else { return $diff; } } /** * Obtain a Kronolith_Tagger instance * * @return Kronolith_Tagger */ static public function getTagger() { if (empty(self::$_tagger)) { self::$_tagger = new Kronolith_Tagger(); } return self::$_tagger; } /** * Obtain an internal calendar. Use this where we don't know if we will * have a Horde_Share or a Kronolith_Resource based calendar. * * @param string $target The calendar id to retrieve. * * @return Kronolith_Resource|Horde_Share_Object * @throws Kronolith_Exception */ static public function getInternalCalendar($target) { if ($GLOBALS['conf']['resource']['driver'] && self::getDriver('Resource')->isResourceCalendar($target)) { $driver = self::getDriver('Resource'); $id = $driver->getResourceIdByCalendar($target); return $driver->getResource($id); } else { return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($target); } } /** * Determines parameters needed to do an address search * * @return array An array with two keys: 'fields' and 'sources'. */ static public function getAddressbookSearchParams() { $src = json_decode($GLOBALS['prefs']->getValue('search_sources')); if (empty($src)) { $src = array(); } $fields = json_decode($GLOBALS['prefs']->getValue('search_fields'), true); if (empty($fields)) { $fields = array(); } return array( 'fields' => $fields, 'sources' => $src ); } /** * Checks whether an API (application) exists and the user has permission * to access it. * * @param string $api The API (application) to check. * @param integer $perm The permission to check. * * @return boolean True if the API can be accessed. */ static public function hasApiPermission($api, $perm = Horde_Perms::READ) { $app = $GLOBALS['registry']->hasInterface($api); return ($app && $GLOBALS['registry']->hasPermission($app, $perm)); } /** * Remove all events owned by the specified user in all calendars. * * @param string $user The user name to delete events for. * * @throws Kronolith_Exception * @throws Horde_Exception_NotFound * @throws Horde_Exception_PermissionDenied */ static public function removeUserEvents($user) { if (!$GLOBALS['registry']->isAdmin()) { throw new Horde_Exception_PermissionDenied(); } try { $shares = $GLOBALS['injector'] ->getInstance('Kronolith_Shares') ->listShares($user, array('perm' => Horde_Perms::EDIT)); } catch (Horde_Share_Exception $e) { Horde::log($shares, 'ERR'); throw new Kronolith_Exception($shares); } foreach (array_keys($shares) as $calendar) { $driver = self::getDriver(null, $calendar); $events = $driver->listEvents(null, null, array('cover_dates' => false)); $uids = array(); foreach ($events as $dayevents) { foreach ($dayevents as $event) { if (!$event->baseid) { $uids[] = $event->uid; } } } foreach ($uids as $uid) { $event = $driver->getByUID($uid, array($calendar)); if ($event->creator == $user) { $driver->deleteEvent($event->id); } } } } /** * TODO * * @param array $attendees * * @return Horde_Mail_Rfc822_List */ static public function getAttendeeEmailList($attendees) { $a_list = new Horde_Mail_Rfc822_List(); foreach ($attendees as $mail => $attendee) { $tmp = new Horde_Mail_Rfc822_Address($mail); if (!empty($attendee['name'])) { $tmp->personal = $attendee['name']; } $a_list->add($tmp); } return $a_list; } /** * Exports an event to a timeslice. * * * @param Kronolith_Event $event An event. * @param integer $type A job type ID. * @param string $client A client ID. */ static public function toTimeslice(Kronolith_Event $event, $type, $client) { global $registry; if (!$registry->hasMethod('time/recordTime')) { throw new Kronolith_Exception(); } $data = array( 'date' => $event->start, 'type' => $type, 'client' => $client, 'hours' => ($event->end->timestamp() - $event->start->timestamp()) / 3600, 'description' => $event->title, 'note' => $event->description ); try { $registry->time->recordTime($data); } catch (Horde_Exception $e) { throw new Kronolith_Exception($e->getMessage()); } } }
Simpan