Overview

Packages

  • Kronolith
  • None

Classes

  • Kronolith
  • Kronolith_Ajax_Application
  • Kronolith_Ajax_Imple_ContactAutoCompleter
  • Kronolith_Ajax_Imple_Embed
  • Kronolith_Ajax_Imple_TagActions
  • Kronolith_Ajax_Imple_TagAutoCompleter
  • Kronolith_Api
  • Kronolith_Calendar
  • Kronolith_Calendar_External
  • Kronolith_Calendar_External_Tasks
  • Kronolith_Calendar_Holiday
  • Kronolith_Calendar_Internal
  • Kronolith_Calendar_Remote
  • Kronolith_Calendar_Resource
  • Kronolith_Calendars_Base
  • Kronolith_Calendars_Default
  • Kronolith_Calendars_Kolab
  • Kronolith_Day
  • Kronolith_Driver
  • Kronolith_Driver_Holidays
  • Kronolith_Driver_Horde
  • Kronolith_Driver_Ical
  • Kronolith_Driver_Kolab
  • Kronolith_Driver_Mock
  • Kronolith_Driver_Resource
  • Kronolith_Driver_Sql
  • Kronolith_Event
  • Kronolith_Event_Holidays
  • Kronolith_Event_Horde
  • Kronolith_Event_Ical
  • Kronolith_Event_Kolab
  • Kronolith_Event_Resource
  • Kronolith_Event_Sql
  • Kronolith_Exception
  • Kronolith_Factory_Calendars
  • Kronolith_Factory_Geo
  • Kronolith_Form_CreateCalendar
  • Kronolith_Form_CreateResource
  • Kronolith_Form_CreateResourceGroup
  • Kronolith_Form_DeleteCalendar
  • Kronolith_Form_DeleteResource
  • Kronolith_Form_DeleteResourceGroup
  • Kronolith_Form_EditCalendar
  • Kronolith_Form_EditRemoteCalendar
  • Kronolith_Form_EditResource
  • Kronolith_Form_EditResourceGroup
  • Kronolith_Form_SubscribeRemoteCalendar
  • Kronolith_Form_UnsubscribeRemoteCalendar
  • Kronolith_FreeBusy
  • Kronolith_FreeBusy_View
  • Kronolith_FreeBusy_View_Day
  • Kronolith_FreeBusy_View_Month
  • Kronolith_FreeBusy_View_Week
  • Kronolith_FreeBusy_View_Workweek
  • Kronolith_Geo_Base
  • Kronolith_Geo_Mysql
  • Kronolith_Geo_Sql
  • Kronolith_LoginTasks_SystemTask_Upgrade
  • Kronolith_LoginTasks_Task_PurgeEvents
  • Kronolith_Notification_Listener_AjaxStatus
  • Kronolith_Resource
  • Kronolith_Resource_Base
  • Kronolith_Resource_Group
  • Kronolith_Resource_Single
  • Kronolith_Storage
  • Kronolith_Storage_Kolab
  • Kronolith_Storage_Sql
  • Kronolith_Tagger
  • Kronolith_Test
  • Kronolith_View_Day
  • Kronolith_View_DeleteEvent
  • Kronolith_View_EditEvent
  • Kronolith_View_Event
  • Kronolith_View_ExportEvent
  • Kronolith_View_Month
  • Kronolith_View_Week
  • Kronolith_View_WorkWeek
  • Kronolith_View_Year
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
   4:  *
   5:  * See the enclosed file COPYING for license information (GPL). If you
   6:  * did not receive this file, see http://www.horde.org/licenses/gpl.
   7:  *
   8:  * @package Kronolith
   9:  */
  10: 
  11: /**
  12:  * The Kronolith:: class provides functionality common to all of Kronolith.
  13:  *
  14:  * @author  Chuck Hagenbuch <chuck@horde.org>
  15:  * @package Kronolith
  16:  */
  17: class Kronolith
  18: {
  19:     /** Event status */
  20:     const STATUS_NONE      = 0;
  21:     const STATUS_TENTATIVE = 1;
  22:     const STATUS_CONFIRMED = 2;
  23:     const STATUS_CANCELLED = 3;
  24:     const STATUS_FREE      = 4;
  25: 
  26:     /** Invitation responses */
  27:     const RESPONSE_NONE      = 1;
  28:     const RESPONSE_ACCEPTED  = 2;
  29:     const RESPONSE_DECLINED  = 3;
  30:     const RESPONSE_TENTATIVE = 4;
  31: 
  32:     /** Attendee status */
  33:     const PART_REQUIRED = 1;
  34:     const PART_OPTIONAL = 2;
  35:     const PART_NONE     = 3;
  36:     const PART_IGNORE   = 4;
  37: 
  38:     /** iTip requests */
  39:     const ITIP_REQUEST = 1;
  40:     const ITIP_CANCEL  = 2;
  41: 
  42:     /** The event can be delegated. */
  43:     const PERMS_DELEGATE = 1024;
  44: 
  45:     /**
  46:      * Driver singleton instances.
  47:      *
  48:      * @var array
  49:      */
  50:     static private $_instances = array();
  51: 
  52:     /**
  53:      * @var Kronolith_Tagger
  54:      */
  55:     static private $_tagger;
  56: 
  57:     /**
  58:      * Output everything for the AJAX interface up to but not including the
  59:      * <body> tag.
  60:      */
  61:     static public function header()
  62:     {
  63:         // Need to include script files before we start output
  64:         $datejs = str_replace('_', '-', $GLOBALS['language']) . '.js';
  65:         if (!file_exists($GLOBALS['registry']->get('jsfs', 'horde') . '/date/' . $datejs)) {
  66:             $datejs = 'en-US.js';
  67:         }
  68:         Horde::addScriptFile('effects.js', 'horde');
  69:         Horde::addScriptFile('sound.js', 'horde');
  70:         Horde::addScriptFile('horde.js', 'horde');
  71:         Horde::addScriptFile('dragdrop2.js', 'kronolith');
  72:         Horde::addScriptFile('growler.js', 'horde');
  73:         Horde::addScriptFile('redbox.js', 'horde');
  74:         Horde::addScriptFile('tooltips.js', 'horde');
  75:         Horde::addScriptFile('colorpicker.js', 'horde');
  76:         Horde::addScriptFile('date/' . $datejs, 'horde');
  77:         Horde::addScriptFile('date/date.js', 'horde');
  78:         Horde::addScriptFile('kronolith.js', 'kronolith');
  79:         Horde_Core_Ui_JsCalendar::init(array('short_weekdays' => true));
  80: 
  81:         if (isset($GLOBALS['language'])) {
  82:             header('Content-type: text/html; charset=UTF-8');
  83:             header('Vary: Accept-Language');
  84:         }
  85: 
  86:         echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">' . "\n" .
  87:              (!empty($GLOBALS['language']) ? '<html lang="' . strtr($GLOBALS['language'], '_', '-') . '"' : '<html') . ">\n".
  88:              "<head>\n" .
  89:              '<title>' . htmlspecialchars($GLOBALS['registry']->get('name')) . "</title>\n";
  90: 
  91:         Horde::includeFavicon();
  92:         echo Horde::wrapInlineScript(self::includeJSVars());
  93:         Horde::includeStylesheetFiles();
  94: 
  95:         echo "</head>\n";
  96: 
  97:         // Send what we have currently output so the browser can start
  98:         // loading CSS/JS. See:
  99:         // http://developer.yahoo.com/performance/rules.html#flush
 100:         echo Horde::endBuffer();
 101:         flush();
 102:     }
 103: 
 104:     /**
 105:      * Outputs the javascript code which defines all javascript variables
 106:      * that are dependent on the local user's account.
 107:      *
 108:      * @private
 109:      *
 110:      * @return string
 111:      */
 112:     static public function includeJSVars()
 113:     {
 114:         global $prefs, $registry;
 115: 
 116:         $kronolith_webroot = $registry->get('webroot');
 117:         $horde_webroot = $registry->get('webroot', 'horde');
 118:         $has_tasks = self::hasApiPermission('tasks');
 119:         $app_urls = array();
 120:         if (isset($GLOBALS['conf']['menu']['apps']) &&
 121:             is_array($GLOBALS['conf']['menu']['apps'])) {
 122:             foreach ($GLOBALS['conf']['menu']['apps'] as $app) {
 123:                 $app_urls[$app] = (string)Horde::url($registry->getInitialPage($app), true)->add('ajaxui', 1);
 124:             }
 125:         }
 126: 
 127:         /* Variables used in core javascript files. */
 128:         $code['conf'] = array(
 129:             'URI_AJAX' => (string)Horde::getServiceLink('ajax', 'kronolith'),
 130:             'URI_SNOOZE' => (string)Horde::url($registry->get('webroot', 'horde') . '/services/snooze.php', true, -1),
 131:             'URI_CALENDAR_EXPORT' => (string)Horde::url('data.php', true)->add(array('actionID' => 'export', 'all_events' => 1, 'exportID' => Horde_Data::EXPORT_ICALENDAR, 'exportCal' => 'internal_')),
 132:             'URI_EVENT_EXPORT' => str_replace(array('%23', '%7B', '%7D'), array('#', '{', '}'), Horde::url('event.php', true)->add(array('view' => 'ExportEvent', 'eventID' => '#{id}', 'calendar' => '#{calendar}', 'type' => '#{type}'))),
 133:             'SESSION_ID' => defined('SID') ? SID : '',
 134:             'images' => array(
 135:                 'attendees' => (string)Horde_Themes::img('attendees-fff.png'),
 136:                 'alarm'     => (string)Horde_Themes::img('alarm-fff.png'),
 137:                 'recur'     => (string)Horde_Themes::img('recur-fff.png'),
 138:                 'exception' => (string)Horde_Themes::img('exception-fff.png'),
 139:             ),
 140:             'user' => $GLOBALS['registry']->convertUsername($GLOBALS['registry']->getAuth(), false),
 141:             'prefs_url' => (string)Horde::getServiceLink('prefs', 'kronolith')->setRaw(true)->add('ajaxui', 1),
 142:             'app_urls' => $app_urls,
 143:             'use_iframe' => !empty($GLOBALS['conf']['menu']['apps_iframe']),
 144:             'name' => $registry->get('name'),
 145:             'has_tasks' => (bool)$has_tasks,
 146:             'is_ie6' => ($GLOBALS['browser']->isBrowser('msie') && ($GLOBALS['browser']->getMajor() < 7)),
 147:             'login_view' => $prefs->getValue('defaultview') == 'workweek' ? 'week' : $prefs->getValue('defaultview'),
 148:             'default_calendar' => 'internal|' . self::getDefaultCalendar(Horde_Perms::EDIT),
 149:             'week_start' => (int)$prefs->getValue('week_start_monday'),
 150:             'max_events' => (int)$prefs->getValue('max_events'),
 151:             'date_format' => str_replace(array('%e', '%d', '%a', '%A', '%m', '%h', '%b', '%B', '%y', '%Y'),
 152:                                          array('d', 'dd', 'ddd', 'dddd', 'MM', 'MMM', 'MMM', 'MMMM', 'yy', 'yyyy'),
 153:                                          Horde_Nls::getLangInfo(D_FMT)),
 154:             'time_format' => $prefs->getValue('twentyFour') ? 'HH:mm' : 'hh:mm tt',
 155:             'show_time' => self::viewShowTime(),
 156:             'default_alarm' => (int)$prefs->getValue('default_alarm'),
 157:             'status' => array('tentative' => self::STATUS_TENTATIVE,
 158:                               'confirmed' => self::STATUS_CONFIRMED,
 159:                               'cancelled' => self::STATUS_CANCELLED,
 160:                               'free' => self::STATUS_FREE),
 161:             'recur' => array(Horde_Date_Recurrence::RECUR_NONE => 'None',
 162:                              Horde_Date_Recurrence::RECUR_DAILY => 'Daily',
 163:                              Horde_Date_Recurrence::RECUR_WEEKLY => 'Weekly',
 164:                              Horde_Date_Recurrence::RECUR_MONTHLY_DATE => 'Monthly',
 165:                              Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY => 'Monthly',
 166:                              Horde_Date_Recurrence::RECUR_YEARLY_DATE => 'Yearly',
 167:                              Horde_Date_Recurrence::RECUR_YEARLY_DAY => 'Yearly',
 168:                              Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY => 'Yearly'),
 169:             'perms' => array('all' => Horde_Perms::ALL,
 170:                              'show' => Horde_Perms::SHOW,
 171:                              'read' => Horde_Perms::READ,
 172:                              'edit' => Horde_Perms::EDIT,
 173:                              'delete' => Horde_Perms::DELETE,
 174:                              'delegate' => self::PERMS_DELEGATE),
 175:             'snooze' => array('0' => _("select..."),
 176:                               '5' => _("5 minutes"),
 177:                               '15' => _("15 minutes"),
 178:                               '60' => _("1 hour"),
 179:                               '360' => _("6 hours"),
 180:                               '1440' => _("1 day")),
 181:         );
 182:         if (!empty($GLOBALS['conf']['logo']['link'])) {
 183:             $code['conf']['URI_HOME'] = $GLOBALS['conf']['logo']['link'];
 184:         }
 185: 
 186:         if ($has_tasks) {
 187:             $code['conf']['tasks'] = $registry->tasks->ajaxDefaults();
 188:         }
 189: 
 190:         // Calendars. Do some twisting to sort own calendar before shared
 191:         // calendars.
 192:         foreach (array(true, false) as $my) {
 193:             foreach ($GLOBALS['all_calendars'] as $id => $calendar) {
 194:                 $owner = $GLOBALS['registry']->getAuth() &&
 195:                     $calendar->owner() == $GLOBALS['registry']->getAuth();
 196:                 if (($my && $owner) || (!$my && !$owner)) {
 197:                     $code['conf']['calendars']['internal'][$id] = $calendar->toHash();
 198:                 }
 199:             }
 200: 
 201:             // Tasklists
 202:             if (!$has_tasks) {
 203:                 continue;
 204:             }
 205:             foreach ($registry->tasks->listTasklists($my, Horde_Perms::SHOW) as $id => $tasklist) {
 206:                 if (!isset($GLOBALS['all_external_calendars']['tasks/' . $id])) {
 207:                     continue;
 208:                 }
 209:                 $owner = $GLOBALS['registry']->getAuth() &&
 210:                     $tasklist->get('owner') == $GLOBALS['registry']->getAuth();
 211:                 if (($my && $owner) || (!$my && !$owner)) {
 212:                     $code['conf']['calendars']['tasklists']['tasks/' . $id] = $GLOBALS['all_external_calendars']['tasks/' . $id]->toHash();
 213:                 }
 214:             }
 215:         }
 216: 
 217:         // Timeobjects
 218:         foreach ($GLOBALS['all_external_calendars'] as $id => $calendar) {
 219:             if ($calendar->api() == 'tasks') {
 220:                 continue;
 221:             }
 222:             if (!empty($GLOBALS['conf']['share']['hidden']) &&
 223:                 !in_array($id, $GLOBALS['display_external_calendars'])) {
 224:                 continue;
 225:             }
 226:             $code['conf']['calendars']['external'][$id] = $calendar->toHash();
 227:         }
 228: 
 229:         // Remote calendars
 230:         foreach ($GLOBALS['all_remote_calendars'] as $url => $calendar) {
 231:             $code['conf']['calendars']['remote'][$url] = $calendar->toHash();
 232:         }
 233: 
 234:         // Holidays
 235:         foreach ($GLOBALS['all_holidays'] as $id => $calendar) {
 236:             $code['conf']['calendars']['holiday'][$id] = $calendar->toHash();
 237:         }
 238: 
 239:         /* Gettext strings used in core javascript files. */
 240:         $code['text'] = array(
 241:             'ajax_error' => _("Error when communicating with the server."),
 242:             'ajax_timeout' => _("There has been no contact with the server for several minutes. The server may be temporarily unavailable or network problems may be interrupting your session. You will not see any updates until the connection is restored."),
 243:             'ajax_recover' => _("The connection to the server has been restored."),
 244:             'alarm' => _("Alarm:"),
 245:             'snooze' => sprintf(_("You can snooze it for %s or %s dismiss %s it entirely"), '#{time}', '#{dismiss_start}', '#{dismiss_end}'),
 246:             'noalerts' => _("No Notifications"),
 247:             'alerts' => sprintf(_("%s notifications"), '#{count}'),
 248:             'hidelog' => _("Hide Notifications"),
 249:             'growlerinfo' => _("This is the notification backlog"),
 250:             'agenda' => _("Agenda"),
 251:             'tasks' => _("Tasks"),
 252:             'searching' => sprintf(_("Events matching \"%s\""), '#{term}'),
 253:             'allday' => _("All day"),
 254:             'more' => _("more..."),
 255:             'prefs' => _("Preferences"),
 256:             'shared' => _("Shared"),
 257:             'external_category' => _("Other events"),
 258:             'no_url' => _("You must specify a URL."),
 259:             'no_calendar_title' => _("The calendar title must not be empty."),
 260:             'no_tasklist_title' => _("The task list title must not be empty."),
 261:             'delete_calendar' => _("Are you sure you want to delete this calendar and all the events in it?"),
 262:             'delete_tasklist' => _("Are you sure you want to delete this task list and all the tasks in it?"),
 263:             'wrong_auth' => _("The authentication information you specified wasn't accepted."),
 264:             'geocode_error' => _("Unable to locate requested address"),
 265:             'wrong_date_format' => sprintf(_("You used an unknown date format \"%s\". Please try something like \"%s\"."), '#{wrong}', '#{right}'),
 266:             'wrong_time_format' => sprintf(_("You used an unknown time format \"%s\". Please try something like \"%s\"."), '#{wrong}', '#{right}'),
 267:             'fix_form_values' => _("Please enter correct values in the form first."),
 268:         );
 269:         for ($i = 1; $i <= 12; ++$i) {
 270:             $code['text']['month'][$i - 1] = Horde_Nls::getLangInfo(constant('MON_' . $i));
 271:         }
 272:         for ($i = 1; $i <= 7; ++$i) {
 273:             $code['text']['weekday'][$i] = Horde_Nls::getLangInfo(constant('DAY_' . $i));
 274:         }
 275:         foreach (array(Horde_Date_Recurrence::RECUR_DAILY,
 276:                        Horde_Date_Recurrence::RECUR_WEEKLY,
 277:                        Horde_Date_Recurrence::RECUR_MONTHLY_DATE,
 278:                        Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY,
 279:                        Horde_Date_Recurrence::RECUR_YEARLY_DATE,
 280:                        Horde_Date_Recurrence::RECUR_YEARLY_DAY,
 281:                        Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY) as $recurType) {
 282:             $code['text']['recur'][$recurType] = self::recurToString($recurType);
 283:         }
 284: 
 285:         $code['text']['recur']['exception'] = _("Exception");
 286: 
 287:         // Maps
 288:         $code['conf']['maps'] = $GLOBALS['conf']['maps'];
 289: 
 290:         return Horde::addInlineJsVars(array(
 291:             'var Kronolith' => $code
 292:         ), array('ret_vars' => true));
 293:     }
 294: 
 295:     /**
 296:      * Converts a permission object to a json object.
 297:      *
 298:      * This methods filters out any permissions for the owner and converts the
 299:      * user name if necessary.
 300:      *
 301:      * @param Horde_Perms_Permission $perm  A permission object.
 302:      *
 303:      * @return array  A hash suitable for json.
 304:      */
 305:     static public function permissionToJson(Horde_Perms_Permission $perm)
 306:     {
 307:         $json = $perm->data;
 308:         if (isset($json['users'])) {
 309:             $users = array();
 310:             foreach ($json['users'] as $user => $value) {
 311:                 if ($user == $GLOBALS['registry']->getAuth()) {
 312:                     continue;
 313:                 }
 314:                 $user = $GLOBALS['registry']->convertUsername($user, false);
 315:                 $users[$user] = $value;
 316:             }
 317:             if ($users) {
 318:                 $json['users'] = $users;
 319:             } else {
 320:                 unset($json['users']);
 321:             }
 322:         }
 323:         return $json;
 324:    }
 325: 
 326:     /**
 327:      * Returns all the alarms active on a specific date.
 328:      *
 329:      * @param Horde_Date $date    The date to check for alarms.
 330:      * @param array $calendars    The calendars to check for events.
 331:      * @param boolean $fullevent  Whether to return complete alarm objects or
 332:      *                            only alarm IDs.
 333:      *
 334:      * @return array  The alarms active on the date. A hash with calendar names
 335:      *                as keys and arrays of events or event ids as values.
 336:      * @throws Kronolith_Exception
 337:      */
 338:     static public function listAlarms($date, $calendars, $fullevent = false)
 339:     {
 340:         $kronolith_driver = self::getDriver();
 341: 
 342:         $alarms = array();
 343:         foreach ($calendars as $cal) {
 344:             $kronolith_driver->open($cal);
 345:             $alarms[$cal] = $kronolith_driver->listAlarms($date, $fullevent);
 346:         }
 347: 
 348:         return $alarms;
 349:     }
 350: 
 351:     /**
 352:      * Searches for events with the given properties.
 353:      *
 354:      * @param object $query     The search query.
 355:      * @param string $calendar  The calendar to search in the form
 356:      *                          "Driver|calendar_id".
 357:      *
 358:      * @return array  The events.
 359:      * @throws Kronolith_Exception
 360:      */
 361:     static public function search($query, $calendar = null)
 362:     {
 363:         if ($calendar) {
 364:             $driver = explode('|', $calendar, 2);
 365:             $calendars = array($driver[0] => array($driver[1]));
 366:         } elseif (!empty($query->calendars)) {
 367:             $calendars = $query->calendars;
 368:         } else {
 369:             $calendars = array(
 370:                 Horde_String::ucfirst($GLOBALS['conf']['calendar']['driver']) => $GLOBALS['display_calendars'],
 371:                 'Horde' => $GLOBALS['display_external_calendars'],
 372:                 'Ical' => $GLOBALS['display_remote_calendars'],
 373:                 'Holidays' => $GLOBALS['display_holidays']);
 374:         }
 375: 
 376:         $events = array();
 377:         foreach ($calendars as $type => $list) {
 378:             $kronolith_driver = self::getDriver($type);
 379:             foreach ($list as $cal) {
 380:                 $kronolith_driver->open($cal);
 381:                 $retevents = $kronolith_driver->search($query);
 382:                 self::mergeEvents($events, $retevents);
 383:             }
 384:         }
 385: 
 386:         return $events;
 387:     }
 388: 
 389:     /**
 390:      * Returns all the events that happen each day within a time period
 391:      *
 392:      * @deprecated
 393:      *
 394:      * @param Horde_Date $startDate    The start of the time range.
 395:      * @param Horde_Date $endDate      The end of the time range.
 396:      * @param array $calendars         The calendars to check for events.
 397:      * @param boolean $showRecurrence  Return every instance of a recurring
 398:      *                                 event? If false, will only return
 399:      *                                 recurring events once inside the
 400:      *                                 $startDate - $endDate range.
 401:      *                                 Defaults to true
 402:      * @param boolean $alarmsOnly      Filter results for events with alarms
 403:      *                                 Defaults to false
 404:      * @param boolean $showRemote      Return events from remote and
 405:      *                                 listTimeObjects as well?
 406:      * @param boolean $hideExceptions  Hide events that represent exceptions to
 407:      *                                 a recurring event?
 408:      * @param boolean $fetchTags       Should we fetch each event's tags from
 409:      *                                 storage?
 410:      *
 411:      * @return array  The events happening in this time period.
 412:      */
 413:     static public function listEvents($startDate, $endDate, $calendars = null,
 414:                                       $showRecurrence = true,
 415:                                       $alarmsOnly = false, $showRemote = true,
 416:                                       $hideExceptions = false,
 417:                                       $coverDates = true, $fetchTags = false)
 418:     {
 419:         $results = array();
 420: 
 421:         /* Internal calendars. */
 422:         if (!isset($calendars)) {
 423:             $calendars = $GLOBALS['display_calendars'];
 424:         }
 425:         $driver = self::getDriver();
 426:         foreach ($calendars as $calendar) {
 427:             try {
 428:                 $driver->open($calendar);
 429:                 $events = $driver->listEvents($startDate, $endDate,
 430:                                               $showRecurrence, $alarmsOnly,
 431:                                               false, $coverDates,
 432:                                               $hideExceptions, $fetchTags);
 433:                 self::mergeEvents($results, $events);
 434:             } catch (Kronolith_Exception $e) {
 435:                 $GLOBALS['notification']->push($e);
 436:             }
 437:         }
 438: 
 439:         /* Resource calendars (this would only be populated if explicitly
 440:          * requested in the request, so include them if this is set regardless
 441:          * of $calendars value).
 442:          *
 443:          * @TODO: Should we disallow even *viewing* these if the user is not an
 444:          *        admin?
 445:          */
 446:         if (!empty($GLOBALS['display_resource_calendars'])) {
 447:             $driver = self::getDriver('Resource');
 448:             foreach ($GLOBALS['display_resource_calendars'] as $calendar) {
 449:                 try {
 450:                     $driver->open($calendar);
 451:                     $events = $driver->listEvents($startDate, $endDate, $showRecurrence);
 452:                     self::mergeEvents($results, $events);
 453:                 } catch (Kronolith_Exception $e) {
 454:                     $GLOBALS['notification']->push($e);
 455:                 }
 456:             }
 457:         }
 458: 
 459:         if ($showRemote) {
 460:             /* Horde applications providing listTimeObjects. */
 461:             if (count($GLOBALS['display_external_calendars'])) {
 462:                 $driver = self::getDriver('Horde');
 463:                 foreach ($GLOBALS['display_external_calendars'] as $external_cal) {
 464:                     try {
 465:                         $driver->open($external_cal);
 466:                         $events = $driver->listEvents($startDate, $endDate, $showRecurrence);
 467:                         self::mergeEvents($results, $events);
 468:                     } catch (Kronolith_Exception $e) {
 469:                         $GLOBALS['notification']->push($e);
 470:                     }
 471:                 }
 472:             }
 473: 
 474:             /* Remote Calendars. */
 475:             foreach ($GLOBALS['display_remote_calendars'] as $url) {
 476:                 try {
 477:                     $driver = self::getDriver('Ical', $url);
 478:                     $events = $driver->listEvents($startDate, $endDate, $showRecurrence);
 479:                     self::mergeEvents($results, $events);
 480:                 } catch (Kronolith_Exception $e) {
 481:                     $GLOBALS['notification']->push($e);
 482:                 }
 483:             }
 484: 
 485:             /* Holidays. */
 486:             if (count($GLOBALS['display_holidays']) && !empty($GLOBALS['conf']['holidays']['enable'])) {
 487:                 $driver = self::getDriver('Holidays');
 488:                 foreach ($GLOBALS['display_holidays'] as $holiday) {
 489:                     try {
 490:                         $driver->open($holiday);
 491:                         $events = $driver->listEvents($startDate, $endDate, $showRecurrence);
 492:                         self::mergeEvents($results, $events);
 493:                     } catch (Kronolith_Exception $e) {
 494:                         $GLOBALS['notification']->push($e);
 495:                     }
 496:                 }
 497:             }
 498:         }
 499: 
 500:         /* Sort events. */
 501:         $results = Kronolith::sortEvents($results);
 502: 
 503:         return $results;
 504:     }
 505: 
 506:     /**
 507:      * Merges results from two listEvents() result sets.
 508:      *
 509:      * @param array $results  First list of events.
 510:      * @param array $events   List of events to be merged into the first one.
 511:      */
 512:     static public function mergeEvents(&$results, $events)
 513:     {
 514:         foreach ($events as $day => $day_events) {
 515:             if (isset($results[$day])) {
 516:                 $results[$day] = array_merge($results[$day], $day_events);
 517:             } else {
 518:                 $results[$day] = $day_events;
 519:             }
 520:         }
 521:         ksort($results);
 522:     }
 523: 
 524:     /**
 525:      * Calculates recurrences of an event during a certain period.
 526:      */
 527:     static public function addEvents(&$results, &$event, $startDate, $endDate,
 528:                                      $showRecurrence, $json, $coverDates = true)
 529:     {
 530:         if ($event->recurs() && $showRecurrence) {
 531:             /* Recurring Event. */
 532: 
 533:             /* If the event ends at 12am and does not end at the same time
 534:              * that it starts (0 duration), set the end date to the previous
 535:              * day's end date. */
 536:             if ($event->end->hour == 0 &&
 537:                 $event->end->min == 0 &&
 538:                 $event->end->sec == 0 &&
 539:                 $event->start->compareDateTime($event->end) != 0) {
 540:                 $event->end = new Horde_Date(
 541:                     array('hour' =>  23,
 542:                           'min' =>   59,
 543:                           'sec' =>   59,
 544:                           'month' => $event->end->month,
 545:                           'mday' =>  $event->end->mday - 1,
 546:                           'year' =>  $event->end->year));
 547:             }
 548: 
 549:             /* We can't use the event duration here because we might cover a
 550:              * daylight saving time switch. */
 551:             $diff = array($event->end->year - $event->start->year,
 552:                           $event->end->month - $event->start->month,
 553:                           $event->end->mday - $event->start->mday,
 554:                           $event->end->hour - $event->start->hour,
 555:                           $event->end->min - $event->start->min);
 556: 
 557:             if ($event->start->compareDateTime($startDate) < 0) {
 558:                 /* The first time the event happens was before the period
 559:                  * started. Start searching for recurrences from the start of
 560:                  * the period. */
 561:                 $next = array('year' => $startDate->year,
 562:                               'month' => $startDate->month,
 563:                               'mday' => $startDate->mday);
 564:             } else {
 565:                 /* The first time the event happens is in the range; unless
 566:                  * there is an exception for this ocurrence, add it. */
 567:                 if (!$event->recurrence->hasException($event->start->year,
 568:                                                       $event->start->month,
 569:                                                       $event->start->mday)) {
 570:                     if ($coverDates) {
 571:                         self::addCoverDates($results, $event, $event->start, $event->end, $json);
 572:                     } else {
 573:                         $results[$event->start->dateString()][$event->id] = $json ? $event->toJson() : $event;
 574:                     }
 575:                 }
 576: 
 577:                 /* Start searching for recurrences from the day after it
 578:                  * starts. */
 579:                 $next = clone $event->start;
 580:                 ++$next->mday;
 581:             }
 582: 
 583:             /* Add all recurrences of the event. */
 584:             $next = $event->recurrence->nextRecurrence($next);
 585:             while ($next !== false && $next->compareDate($endDate) <= 0) {
 586:                 if (!$event->recurrence->hasException($next->year, $next->month, $next->mday)) {
 587:                     /* Add the event to all the days it covers. */
 588:                     $nextEnd = clone $next;
 589:                     $nextEnd->year  += $diff[0];
 590:                     $nextEnd->month += $diff[1];
 591:                     $nextEnd->mday  += $diff[2];
 592:                     $nextEnd->hour  += $diff[3];
 593:                     $nextEnd->min   += $diff[4];
 594:                     if ($coverDates) {
 595:                         self::addCoverDates($results, $event, $next, $nextEnd, $json);
 596:                     } else {
 597:                         $addEvent = clone $event;
 598:                         $addEvent->start = $next;
 599:                         $addEvent->end = $nextEnd;
 600:                         $results[$addEvent->start->dateString()][$addEvent->id] = $json ? $addEvent->toJson() : $addEvent;
 601: 
 602:                     }
 603:                 }
 604:                 $next = $event->recurrence->nextRecurrence(
 605:                     array('year' => $next->year,
 606:                           'month' => $next->month,
 607:                           'mday' => $next->mday + 1,
 608:                           'hour' => $next->hour,
 609:                           'min' => $next->min,
 610:                           'sec' => $next->sec));
 611:             }
 612:         } else {
 613:             /* Event only occurs once. */
 614:             if (!$coverDates) {
 615:                 $results[$event->start->dateString()][$event->id] = $json ? $event->toJson() : $event;
 616:             } else {
 617:                 $allDay = $event->isAllDay();
 618: 
 619:                 /* Work out what day it starts on. */
 620:                 if ($startDate &&
 621:                     $event->start->compareDateTime($startDate) < 0) {
 622:                     /* It started before the beginning of the period. */
 623:                     if ($event->recurs()) {
 624:                         $eventStart = $event->recurrence->nextRecurrence($startDate);
 625:                     } else {
 626:                         $eventStart = clone $startDate;
 627:                     }
 628:                 } else {
 629:                     $eventStart = clone $event->start;
 630:                 }
 631: 
 632:                 /* Work out what day it ends on. */
 633:                 if ($endDate &&
 634:                     $event->end->compareDateTime($endDate) > 0) {
 635:                     /* Ends after the end of the period. */
 636:                     if (is_object($endDate)) {
 637:                         $eventEnd = clone $endDate;
 638:                     } else {
 639:                         $eventEnd = $endDate;
 640:                     }
 641:                 } else {
 642:                     /* Need to perform some magic if this is a single instance
 643:                      * of a recurring event since $event->end would be the
 644:                      * original end date, not the recurrence's end date. */
 645:                     if ($event->recurs()) {
 646: 
 647:                         $diff = array($event->end->year - $event->start->year,
 648:                                       $event->end->month - $event->start->month,
 649:                                       $event->end->mday - $event->start->mday,
 650:                                       $event->end->hour - $event->start->hour,
 651:                                       $event->end->min - $event->start->min);
 652: 
 653:                         $theEnd = $event->recurrence->nextRecurrence($eventStart);
 654:                         $theEnd->year  += $diff[0];
 655:                         $theEnd->month += $diff[1];
 656:                         $theEnd->mday  += $diff[2];
 657:                         $theEnd->hour  += $diff[3];
 658:                         $theEnd->min   += $diff[4];
 659:                     } else {
 660:                         $theEnd = clone $event->end;
 661:                     }
 662: 
 663:                     /* If the event doesn't end at 12am set the end date to
 664:                      * the current end date. If it ends at 12am and does not
 665:                      * end at the same time that it starts (0 duration), set
 666:                      * the end date to the previous day's end date. */
 667:                     if ($theEnd->hour != 0 ||
 668:                         $theEnd->min != 0 ||
 669:                         $theEnd->sec != 0 ||
 670:                         $event->start->compareDateTime($theEnd) == 0 ||
 671:                         $allDay) {
 672:                         $eventEnd = clone $theEnd;
 673:                     } else {
 674:                         $eventEnd = new Horde_Date(
 675:                             array('hour' =>  23,
 676:                                   'min' =>   59,
 677:                                   'sec' =>   59,
 678:                                   'month' => $theEnd->month,
 679:                                   'mday' =>  $theEnd->mday - 1,
 680:                                   'year' =>  $theEnd->year));
 681:                     }
 682:                 }
 683: 
 684:                 /* Add the event to all the days it covers. This is similar to
 685:                  * Kronolith::addCoverDates(), but for days in between the
 686:                  * start and end day, the range is midnight to midnight, and
 687:                  * for the edge days it's start to midnight, and midnight to
 688:                  * end. */
 689:                 $i = $eventStart->mday;
 690:                 $loopDate = new Horde_Date(array('month' => $eventStart->month,
 691:                                                  'mday' => $i,
 692:                                                  'year' => $eventStart->year));
 693:                 while ($loopDate->compareDateTime($eventEnd) <= 0) {
 694:                     if (!$allDay ||
 695:                         $loopDate->compareDateTime($eventEnd) != 0) {
 696:                         $addEvent = clone $event;
 697: 
 698:                         /* If this is the start day, set the start time to
 699:                          * the real start time, otherwise set it to
 700:                          * 00:00 */
 701:                         if ($loopDate->compareDate($eventStart) == 0) {
 702:                             $addEvent->start = $eventStart;
 703:                         } else {
 704:                             $addEvent->start = clone $loopDate;
 705:                             $addEvent->start->hour = $addEvent->start->min = $addEvent->start->sec = 0;
 706:                             $addEvent->first = false;
 707:                         }
 708: 
 709:                         /* If this is the end day, set the end time to the
 710:                          * real event end, otherwise set it to 23:59. */
 711:                         if ($loopDate->compareDate($eventEnd) == 0) {
 712:                             $addEvent->end = $eventEnd;
 713:                         } else {
 714:                             $addEvent->end = clone $loopDate;
 715:                             $addEvent->end->hour = 23;
 716:                             $addEvent->end->min = $addEvent->end->sec = 59;
 717:                             $addEvent->last = false;
 718:                         }
 719: 
 720:                         $results[$loopDate->dateString()][$addEvent->id] = $json ? $addEvent->toJson($allDay) : $addEvent;
 721:                     }
 722: 
 723:                     $loopDate = new Horde_Date(
 724:                         array('month' => $eventStart->month,
 725:                               'mday' => ++$i,
 726:                               'year' => $eventStart->year));
 727:                 }
 728:             }
 729:         }
 730:         ksort($results);
 731:     }
 732: 
 733:     /**
 734:      * Adds an event to all the days it covers.
 735:      *
 736:      * @param array $result           The current result list.
 737:      * @param Kronolith_Event $event  An event object.
 738:      * @param Horde_Date $eventStart  The event's start at the actual
 739:      *                                recurrence.
 740:      * @param Horde_Date $eventEnd    The event's end at the actual recurrence.
 741:      * @param boolean $json           Store the results of the events' toJson()
 742:      *                                method?
 743:      */
 744:     static public function addCoverDates(&$results, $event, $eventStart,
 745:                                          $eventEnd, $json)
 746:     {
 747:         $loopDate = new Horde_Date($eventStart->year, $eventStart->month, $eventStart->mday);
 748:         $allDay = $event->isAllDay();
 749:         $first = true;
 750:         while ($loopDate->compareDateTime($eventEnd) <= 0) {
 751:             if (!$allDay ||
 752:                 $loopDate->compareDateTime($eventEnd) != 0) {
 753:                 $addEvent = clone $event;
 754:                 $addEvent->start = $eventStart;
 755:                 $addEvent->end = $eventEnd;
 756:                 if ($loopDate->compareDate($eventStart) != 0) {
 757:                     $addEvent->first = false;
 758:                 }
 759:                 if ($loopDate->compareDate($eventEnd) != 0) {
 760:                     $addEvent->last = false;
 761:                 }
 762:                 $results[$loopDate->dateString()][$addEvent->id] = $json ? $addEvent->toJson($allDay) : $addEvent;
 763:             }
 764:             $loopDate->mday++;
 765:         }
 766:     }
 767: 
 768:     /**
 769:      * Adds an event to set of search results.
 770:      *
 771:      * @param array $events           The list of events to update with the new
 772:      *                                event.
 773:      * @param Kronolith_Event $event  An event from a search result.
 774:      * @param stdClass $query         A search query.
 775:      * @param boolean $json           Store the results of the events' toJson()
 776:      *                                method?
 777:      */
 778:     static public function addSearchEvents(&$events, $event, $query, $json)
 779:     {
 780:         static $now;
 781:         if (!isset($now)) {
 782:             $now = new Horde_Date($_SERVER['REQUEST_TIME']);
 783:         }
 784:         $showRecurrence = true;
 785:         if ($event->recurs()) {
 786:             if (empty($query->start) && empty($query->end)) {
 787:                 $eventStart = $event->start;
 788:                 $eventEnd = $event->end;
 789:             } else {
 790:                 if (empty($query->end)) {
 791:                     $eventEnd = $event->recurrence->nextRecurrence($now);
 792:                     if (!$eventEnd) {
 793:                         return;
 794:                     }
 795:                 } else {
 796:                     $eventEnd = $query->end;
 797:                 }
 798:                 if (empty($query->start)) {
 799:                     $eventStart = $event->start;
 800:                     $showRecurrence = false;
 801:                 } else {
 802:                     $eventStart = $query->start;
 803:                 }
 804:             }
 805:         } else {
 806:             $eventStart = $event->start;
 807:             $eventEnd = $event->end;
 808:         }
 809:         self::addEvents($events, $event, $eventStart, $eventEnd, $showRecurrence, $json, false);
 810:     }
 811: 
 812:     /**
 813:      * Returns the number of events in calendars that the current user owns.
 814:      *
 815:      * @return integer  The number of events.
 816:      */
 817:     static public function countEvents()
 818:     {
 819:         static $count;
 820:         if (isset($count)) {
 821:             return $count;
 822:         }
 823: 
 824:         $kronolith_driver = self::getDriver();
 825:         $calendars = self::listInternalCalendars(true, Horde_Perms::ALL);
 826:         $current_calendar = $kronolith_driver->calendar;
 827: 
 828:         $count = 0;
 829:         foreach (array_keys($calendars) as $calendar) {
 830:             $kronolith_driver->open($calendar);
 831:             try {
 832:                 $count += $kronolith_driver->countEvents();
 833:             } catch (Exception $e) {
 834:             }
 835:         }
 836: 
 837:         /* Reopen last calendar. */
 838:         $kronolith_driver->open($current_calendar);
 839: 
 840:         return $count;
 841:     }
 842: 
 843:     /**
 844:      * Imports an event parsed from a string.
 845:      *
 846:      * @param string $text      The text to parse into an event
 847:      * @param string $calendar  The calendar into which the event will be
 848:      *                          imported.  If 'null', the user's default
 849:      *                          calendar will be used.
 850:      *
 851:      * @return array  The UID of all events that were added.
 852:      * @throws Kronolith_Exception
 853:      */
 854:     public function quickAdd($text, $calendar = null)
 855:     {
 856:         $text = trim($text);
 857:         if (strpos($text, "\n") !== false) {
 858:             list($title, $description) = explode($text, "\n", 2);
 859:         } else {
 860:             $title = $text;
 861:             $description = '';
 862:         }
 863:         $title = trim($title);
 864:         $description = trim($description);
 865: 
 866:         $dateParser = Horde_Date_Parser::factory(array('locale' => $GLOBALS['language']));
 867:         $r = $dateParser->parse($title, array('return' => 'result'));
 868:         if (!($d = $r->guess())) {
 869:             throw new Horde_Exception(sprintf(_("Cannot parse event description \"%s\""), Horde_String::truncate($text)));
 870:         }
 871: 
 872:         $title = $r->untaggedText();
 873:         $start = $d->timestamp();
 874: 
 875:         $kronolith_driver = self::getDriver(null, $calendar);
 876:         $event = $kronolith_driver->getEvent();
 877:         $event->initialized = true;
 878:         $event->title = $title;
 879:         $event->description = $description;
 880:         $event->start = $d;
 881:         $event->end = $d->add(array('hour' => 1));
 882:         $event->save();
 883: 
 884:         return $event;
 885:     }
 886: 
 887:     /**
 888:      * Initial app setup code.
 889:      */
 890:     static public function initialize()
 891:     {
 892:         /* Store the request timestamp if it's not already present. */
 893:         if (!isset($_SERVER['REQUEST_TIME'])) {
 894:             $_SERVER['REQUEST_TIME'] = time();
 895:         }
 896: 
 897:         /* Fetch display preferences. */
 898:         $GLOBALS['display_calendars'] = @unserialize($GLOBALS['prefs']->getValue('display_cals'));
 899:         $GLOBALS['display_remote_calendars'] = @unserialize($GLOBALS['prefs']->getValue('display_remote_cals'));
 900:         $GLOBALS['display_external_calendars'] = @unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
 901:         $GLOBALS['display_holidays'] = @unserialize($GLOBALS['prefs']->getValue('holiday_drivers'));
 902: 
 903:         if (!is_array($GLOBALS['display_calendars'])) {
 904:             $GLOBALS['display_calendars'] = array();
 905:         }
 906:         if (!is_array($GLOBALS['display_remote_calendars'])) {
 907:             $GLOBALS['display_remote_calendars'] = array();
 908:         }
 909:         if (!is_array($GLOBALS['display_external_calendars'])) {
 910:             $GLOBALS['display_external_calendars'] = array();
 911:         }
 912:         if (!is_array($GLOBALS['display_holidays']) ||
 913:             empty($GLOBALS['conf']['holidays']['enable'])) {
 914:             $GLOBALS['display_holidays'] = array();
 915:         }
 916: 
 917:         /* Update preferences for which calendars to display. If the
 918:          * user doesn't have any selected calendars to view then fall
 919:          * back to an available calendar. An empty string passed in this
 920:          * parameter will clear any existing session value.*/
 921:         if (($calendarId = Horde_Util::getFormData('display_cal')) !== null) {
 922:             $GLOBALS['session']->set('kronolith', 'display_cal', $calendarId);
 923:         }
 924: 
 925:         if (strlen($GLOBALS['session']->get('kronolith', 'display_cal'))) {
 926:             /* Specifying a value for display_cal is always to make sure
 927:              * that only the specified calendars are shown. Use the
 928:              * "toggle_calendar" argument  to toggle the state of a single
 929:              * calendar. */
 930:             $GLOBALS['display_calendars'] = array();
 931:             $GLOBALS['display_remote_calendars'] = array();
 932:             $GLOBALS['display_external_calendars'] = array();
 933:             $GLOBALS['display_resource_calendars'] = array();
 934:             $GLOBALS['display_holidays'] = array();
 935:             $calendars = $GLOBALS['session']->get('kronolith', 'display_cal');
 936:             if (!is_array($calendars)) {
 937:                 $calendars = array($calendars);
 938:             }
 939:             foreach ($calendars as $calendarId) {
 940:                 if (strncmp($calendarId, 'remote_', 7) === 0) {
 941:                     $calendarId = substr($calendarId, 7);
 942:                     if (!in_array($calendarId, $GLOBALS['display_remote_calendars'])) {
 943:                         $GLOBALS['display_remote_calendars'][] = $calendarId;
 944:                     }
 945:                 } elseif (strncmp($calendarId, 'external_', 9) === 0) {
 946:                     $calendarId = substr($calendarId, 9);
 947:                     if (!in_array($calendarId, $GLOBALS['display_external_calendars'])) {
 948:                         $GLOBALS['display_external_calendars'][] = $calendarId;
 949:                     }
 950:                 } elseif (strncmp($calendarId, 'resource_', 9) === 0) {
 951:                     $calendarId = substr($calendarId, 9);
 952:                     if (!in_array($calendarId, $GLOBALS['display_resource_calendars'])) {
 953:                         $GLOBALS['display_resource_calendars'][] = $calendarId;
 954:                     }
 955:                 } elseif (strncmp($calendarId, 'holidays_', 9) === 0) {
 956:                     $calendarId = substr($calendarId, 9);
 957:                     if (!in_array($calendarId, $GLOBALS['display_holidays'])) {
 958:                         $GLOBALS['display_holidays'][] = $calendarId;
 959:                     }
 960:                 } else {
 961:                     if (strncmp($calendarId, 'internal_', 9) === 0) {
 962:                         $calendarId = substr($calendarId, 9);
 963:                     }
 964:                     if (!in_array($calendarId, $GLOBALS['display_calendars'])) {
 965:                         $GLOBALS['display_calendars'][] = $calendarId;
 966:                     }
 967:                 }
 968:             }
 969:         }
 970: 
 971:         /* Check for single "toggle" calendars. */
 972:         if (($calendarId = Horde_Util::getFormData('toggle_calendar')) !== null) {
 973:             if (strncmp($calendarId, 'remote_', 7) === 0) {
 974:                 $calendarId = substr($calendarId, 7);
 975:                 if (in_array($calendarId, $GLOBALS['display_remote_calendars'])) {
 976:                     $key = array_search($calendarId, $GLOBALS['display_remote_calendars']);
 977:                     unset($GLOBALS['display_remote_calendars'][$key]);
 978:                 } else {
 979:                     $GLOBALS['display_remote_calendars'][] = $calendarId;
 980:                 }
 981:             } elseif ((strncmp($calendarId, 'external_', 9) === 0 &&
 982:                        $calendarId = substr($calendarId, 9)) ||
 983:                       (strncmp($calendarId, 'tasklists_', 10) === 0 &&
 984:                        $calendarId = substr($calendarId, 10))) {
 985:                 if (in_array($calendarId, $GLOBALS['display_external_calendars'])) {
 986:                     $key = array_search($calendarId, $GLOBALS['display_external_calendars']);
 987:                     unset($GLOBALS['display_external_calendars'][$key]);
 988:                 } else {
 989:                     $GLOBALS['display_external_calendars'][] = $calendarId;
 990:                 }
 991:                 if (strpos($calendarId, 'tasks/') === 0) {
 992:                     $tasklists = array();
 993:                     foreach ($GLOBALS['display_external_calendars'] as $id) {
 994:                         if (strpos($id, 'tasks/') === 0) {
 995:                             $tasklists[] = substr($id, 6);
 996:                         }
 997:                     }
 998:                     try {
 999:                         $GLOBALS['registry']->tasks->setDisplayedTasklists($tasklists);
1000:                     } catch (Horde_Exception $e) {
1001:                     }
1002:                 }
1003:             } elseif (strncmp($calendarId, 'holiday_', 8) === 0) {
1004:                 $calendarId = substr($calendarId, 8);
1005:                 if (in_array($calendarId, $GLOBALS['display_holidays'])) {
1006:                     $key = array_search($calendarId, $GLOBALS['display_holidays']);
1007:                     unset($GLOBALS['display_holidays'][$key]);
1008:                 } else {
1009:                     $GLOBALS['display_holidays'][] = $calendarId;
1010:                 }
1011:             } else {
1012:                 if (in_array($calendarId, $GLOBALS['display_calendars'])) {
1013:                     $key = array_search($calendarId, $GLOBALS['display_calendars']);
1014:                     unset($GLOBALS['display_calendars'][$key]);
1015:                 } else {
1016:                     $GLOBALS['display_calendars'][] = $calendarId;
1017:                 }
1018:             }
1019: 
1020:             $GLOBALS['prefs']->setValue('display_cals', serialize($GLOBALS['display_calendars']));
1021:         }
1022: 
1023:         /* Make sure all shares exists now to save on checking later. */
1024:         $GLOBALS['all_calendars'] = array();
1025:         foreach (self::listInternalCalendars() as $id => $calendar) {
1026:             $GLOBALS['all_calendars'][$id] = new Kronolith_Calendar_Internal(array('share' => $calendar));
1027:         }
1028:         $calendar_keys = array_values($GLOBALS['display_calendars']);
1029:         $GLOBALS['display_calendars'] = array();
1030:         foreach ($calendar_keys as $id) {
1031:             if (isset($GLOBALS['all_calendars'][$id])) {
1032:                 $GLOBALS['display_calendars'][] = $id;
1033:             }
1034:         }
1035: 
1036:         /* Make sure all the remote calendars still exist. */
1037:         $_temp = $GLOBALS['display_remote_calendars'];
1038:         $GLOBALS['display_remote_calendars'] = array();
1039:         $GLOBALS['all_remote_calendars'] = array();
1040:         $calendars = @unserialize($GLOBALS['prefs']->getValue('remote_cals'));
1041:         if (!is_array($calendars)) {
1042:             $calendars = array();
1043:         }
1044:         foreach ($calendars as $calendar) {
1045:             $GLOBALS['all_remote_calendars'][$calendar['url']] = new Kronolith_Calendar_Remote($calendar);
1046:             if (in_array($calendar['url'], $_temp)) {
1047:                 $GLOBALS['display_remote_calendars'][] = $calendar['url'];
1048:             }
1049:         }
1050:         $GLOBALS['prefs']->setValue('display_remote_cals', serialize($GLOBALS['display_remote_calendars']));
1051: 
1052:         /* Make sure all the holiday drivers still exist. */
1053:         $GLOBALS['all_holidays'] = array();
1054:         if (!empty($GLOBALS['conf']['holidays']['enable'])) {
1055:             if (class_exists('Date_Holidays')) {
1056:                 foreach (Date_Holidays::getInstalledDrivers() as $driver) {
1057:                     if ($driver['id'] == 'Composite') {
1058:                         continue;
1059:                     }
1060:                     $GLOBALS['all_holidays'][$driver['id']] = new Kronolith_Calendar_Holiday(array('driver' => $driver));
1061:                     ksort($GLOBALS['all_holidays']);
1062:                 }
1063:             }
1064:         }
1065:         $_temp = $GLOBALS['display_holidays'];
1066:         $GLOBALS['display_holidays'] = array();
1067:         foreach (array_keys($GLOBALS['all_holidays']) as $id) {
1068:             if (in_array($id, $_temp)) {
1069:                 $GLOBALS['display_holidays'][] = $id;
1070:             }
1071:         }
1072:         $GLOBALS['prefs']->setValue('holiday_drivers', serialize($GLOBALS['display_holidays']));
1073: 
1074:         /* Get a list of external calendars. */
1075:         $GLOBALS['all_external_calendars'] = array();
1076: 
1077:         /* Make sure all task lists exist. */
1078:         if (self::hasApiPermission('tasks') &&
1079:             $GLOBALS['registry']->hasMethod('tasks/listTimeObjects')) {
1080:             try {
1081:                 $tasklists = $GLOBALS['registry']->tasks->listTasklists();
1082:                 $categories = $GLOBALS['registry']->call('tasks/listTimeObjectCategories');
1083:                 foreach ($categories as $name => $description) {
1084:                     if (!isset($tasklists[$name])) {
1085:                         continue;
1086:                     }
1087:                     $GLOBALS['all_external_calendars']['tasks/' . $name] = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $description, 'share' => $tasklists[$name]));
1088:                 }
1089:             } catch (Horde_Exception $e) {
1090:                 Horde::logMessage($e, 'DEBUG');
1091:             }
1092:         }
1093: 
1094:         if ($GLOBALS['session']->exists('kronolith', 'all_external_calendars')) {
1095:             foreach ($GLOBALS['session']->get('kronolith', 'all_external_calendars') as $calendar) {
1096:                 if (!self::hasApiPermission($calendar['a']) ||
1097:                     $calendar['a'] == 'tasks') {
1098:                     continue;
1099:                 }
1100:                 $GLOBALS['all_external_calendars'][$calendar['a'] . '/' . $calendar['n']] = new Kronolith_Calendar_External(array('api' => $calendar['a'], 'name' => $calendar['d'], 'id' => $calendar['n']));
1101:             }
1102:         } else {
1103:             $apis = array_unique($GLOBALS['registry']->listAPIs());
1104:             $ext_cals = array();
1105: 
1106:             foreach ($apis as $api) {
1107:                 if ($api == 'tasks' ||
1108:                     !self::hasApiPermission($api) ||
1109:                     !$GLOBALS['registry']->hasMethod($api . '/listTimeObjects')) {
1110:                     continue;
1111:                 }
1112:                 try {
1113:                     $categories = $GLOBALS['registry']->call($api . '/listTimeObjectCategories');
1114:                 } catch (Horde_Exception $e) {
1115:                     Horde::logMessage($e, 'DEBUG');
1116:                     continue;
1117:                 }
1118: 
1119:                 foreach ($categories as $name => $description) {
1120:                     $GLOBALS['all_external_calendars'][$api . '/' . $name] = new Kronolith_Calendar_External(array('api' => $api, 'name' => $description, 'id' => $name));
1121:                     $ext_cals[] = array(
1122:                         'a' => $api,
1123:                         'n' => $name,
1124:                         'd' => $description
1125:                     );
1126:                 }
1127:             }
1128: 
1129:             $GLOBALS['session']->set('kronolith', 'all_external_calendars', $ext_cals);
1130:         }
1131: 
1132:         /* Make sure all the external calendars still exist. */
1133:         $_tasklists = $_temp = $GLOBALS['display_external_calendars'];
1134:         if (self::hasApiPermission('tasks')) {
1135:             try {
1136:                 $_tasklists = $GLOBALS['registry']->tasks->getDisplayedTasklists();
1137:             } catch (Horde_Exception $e) {
1138:             }
1139:         }
1140:         $GLOBALS['display_external_calendars'] = array();
1141:         foreach ($GLOBALS['all_external_calendars'] as $id => $calendar) {
1142:             if ((substr($id, 0, 6) == 'tasks/' &&
1143:                  in_array(substr($id, 6), $_tasklists)) ||
1144:                 in_array($id, $_temp)) {
1145:                 $GLOBALS['display_external_calendars'][] = $id;
1146:             } else {
1147:                 /* Convert Kronolith 2 preferences.
1148:                  * @todo: remove in Kronolith 3.1. */
1149:                 list(,$oldid,) = explode('/', $id);
1150:                 if (in_array($calendar->api() . '/' . $oldid, $_temp)) {
1151:                     $GLOBALS['display_external_calendars'][] = $calendarId;
1152:                 }
1153:             }
1154:         }
1155:         $GLOBALS['prefs']->setValue('display_external_cals', serialize($GLOBALS['display_external_calendars']));
1156: 
1157:         /* If an authenticated user doesn't own a calendar, create it. */
1158:         if (!empty($GLOBALS['conf']['share']['auto_create']) &&
1159:             $GLOBALS['registry']->getAuth() &&
1160:             !count(self::listInternalCalendars(true))) {
1161:             $calendars = $GLOBALS['injector']->getInstance('Kronolith_Factory_Calendars')
1162:                 ->create();
1163: 
1164:             $share = $calendars->createDefaultShare();
1165:             $GLOBALS['all_calendars'][$share->getName()] = new Kronolith_Calendar_Internal(array('share' => $share));
1166:             $GLOBALS['display_calendars'][] = $share->getName();
1167: 
1168:             /* Calendar auto-sharing with the user's groups */
1169:             if ($GLOBALS['conf']['autoshare']['shareperms'] != 'none') {
1170:                 $perm_value = 0;
1171:                 switch ($GLOBALS['conf']['autoshare']['shareperms']) {
1172:                 case 'read':
1173:                     $perm_value = Horde_Perms::READ | Horde_Perms::SHOW;
1174:                     break;
1175:                 case 'edit':
1176:                     $perm_value = Horde_Perms::READ | Horde_Perms::SHOW | Horde_Perms::EDIT;
1177:                     break;
1178:                 case 'full':
1179:                     $perm_value = Horde_Perms::READ | Horde_Perms::SHOW | Horde_Perms::EDIT | Horde_Perms::DELETE;
1180:                     break;
1181:                 }
1182: 
1183:                 try {
1184:                     $group_list = $GLOBALS['injector']
1185:                         ->getInstance('Horde_Group')
1186:                         ->listGroups($GLOBALS['registry']->getAuth());
1187:                     if (count($group_list)) {
1188:                         $perm = $share->getPermission();
1189:                         // Add the default perm, not added otherwise
1190:                         foreach ($group_list as $group_id => $group_name) {
1191:                             $perm->addGroupPermission($group_id, $perm_value, false);
1192:                         }
1193:                         $share->setPermission($perm);
1194:                         $GLOBALS['notification']->push(sprintf(_("New calendar created and automatically shared with the following group(s): %s."), implode(', ', $group_list)), 'horde.success');
1195:                     }
1196:                 } catch (Horde_Group_Exception $e) {}
1197:             }
1198: 
1199:             $GLOBALS['prefs']->setValue('display_cals', serialize($GLOBALS['display_calendars']));
1200:         }
1201:     }
1202: 
1203:     /**
1204:      * Initialize the event map.
1205:      *
1206:      * @param array $params Parameters to pass the the map
1207:      *
1208:      * @return void
1209:      */
1210:     static public function initEventMap($params)
1211:     {
1212:         // Add the apikeys
1213:         if (!empty($params['providers'])) {
1214:             /* It is safe to put configuration specific to horde driver inside
1215:             this block since horde driver *must* contain a provider array */
1216: 
1217:             // Language specific file needed?
1218:             $language = str_replace('_', '-', $GLOBALS['language']);
1219:             if (!file_exists($GLOBALS['registry']->get('jsfs', 'horde') . '/map/lang/' . $language . '.js')) {
1220:                 $language = 'en-US';
1221:             }
1222:             $params['conf'] = array(
1223:                 'markerImage' => (string)Horde_Themes::img('map/marker.png'),
1224:                 'markerBackground' => (string)Horde_Themes::img('map/marker-shadow.png'),
1225:                 'useMarkerLayer' => true,
1226:                 'language' => $language,
1227:             );
1228: 
1229:             foreach ($params['providers'] as $layer) {
1230:                 switch ($layer) {
1231:                 case 'Google':
1232:                     $params['conf']['apikeys']['google'] = $GLOBALS['conf']['api']['googlemaps'];
1233:                     break;
1234:                 case 'Yahoo':
1235:                     $params['conf']['apikeys']['yahoo'] = $GLOBALS['conf']['api']['yahoomaps'];
1236:                     break;
1237:                 case 'Cloudmade':
1238:                     $params['conf']['apikeys']['cloudmade'] = $GLOBALS['conf']['api']['cloudmade'];
1239:                     break;
1240:                 }
1241:             }
1242:         }
1243: 
1244:         if (!empty($params['geocoder'])) {
1245:             switch ($params['geocoder']) {
1246:             case 'Google':
1247:                 $params['conf']['apikeys']['google'] = $GLOBALS['conf']['api']['googlemaps'];
1248:                 break;
1249:             case 'Yahoo':
1250:                 $params['conf']['apikeys']['yahoo'] = $GLOBALS['conf']['api']['yahoomaps'];
1251:                 break;
1252:             case 'Cloudmade':
1253:                 $params['conf']['apikeys']['cloudmade'] = $GLOBALS['conf']['api']['cloudmade'];
1254:                 break;
1255:             }
1256:         }
1257:         $params['jsuri'] = $GLOBALS['registry']->get('jsuri', 'horde') . '/map/';
1258:         $params['ssl'] = $GLOBALS['browser']->usingSSLConnection();
1259:         Horde::addScriptFile('map/map.js', 'horde');
1260:         $js = 'HordeMap.initialize(' . Horde_Serialize::serialize($params, HORDE_SERIALIZE::JSON) . ');';
1261:         Horde::addinlineScript($js);
1262:     }
1263: 
1264:     /**
1265:      * Returns the real name, if available, of a user.
1266:      */
1267:     static public function getUserName($uid)
1268:     {
1269:         static $names = array();
1270: 
1271:         if (!isset($names[$uid])) {
1272:             $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid);
1273:             $ident->setDefault($ident->getDefault());
1274:             $names[$uid] = $ident->getValue('fullname');
1275:             if (empty($names[$uid])) {
1276:                 $names[$uid] = $uid;
1277:             }
1278:         }
1279: 
1280:         return $names[$uid];
1281:     }
1282: 
1283:     /**
1284:      * Returns the email address, if available, of a user.
1285:      */
1286:     static public function getUserEmail($uid)
1287:     {
1288:         static $emails = array();
1289: 
1290:         if (!isset($emails[$uid])) {
1291:             $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid);
1292:             $emails[$uid] = $ident->getValue('from_addr');
1293:             if (empty($emails[$uid])) {
1294:                 $emails[$uid] = $uid;
1295:             }
1296:         }
1297: 
1298:         return $emails[$uid];
1299:     }
1300: 
1301:     /**
1302:      * Checks if an email address belongs to a user.
1303:      */
1304:     static public function isUserEmail($uid, $email)
1305:     {
1306:         static $emails = array();
1307: 
1308:         if (!isset($emails[$uid])) {
1309:             $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid);
1310: 
1311:             $addrs = $ident->getAll('from_addr');
1312:             $addrs[] = $uid;
1313: 
1314:             $emails[$uid] = $addrs;
1315:         }
1316: 
1317:         return in_array($email, $emails[$uid]);
1318:     }
1319: 
1320:     /**
1321:      * Maps a Kronolith recurrence value to a translated string suitable for
1322:      * display.
1323:      *
1324:      * @param integer $type  The recurrence value; one of the
1325:      *                       Horde_Date_Recurrence::RECUR_XXX constants.
1326:      *
1327:      * @return string  The translated displayable recurrence value string.
1328:      */
1329:     static public function recurToString($type)
1330:     {
1331:         switch ($type) {
1332:         case Horde_Date_Recurrence::RECUR_NONE:
1333:             return _("Does not recur");
1334: 
1335:         case Horde_Date_Recurrence::RECUR_DAILY:
1336:             return _("Recurs daily");
1337: 
1338:         case Horde_Date_Recurrence::RECUR_WEEKLY:
1339:             return _("Recurs weekly");
1340: 
1341:         case Horde_Date_Recurrence::RECUR_MONTHLY_DATE:
1342:         case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY:
1343:             return _("Recurs monthly");
1344: 
1345:         case Horde_Date_Recurrence::RECUR_YEARLY_DATE:
1346:         case Horde_Date_Recurrence::RECUR_YEARLY_DAY:
1347:         case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY:
1348:             return _("Recurs yearly");
1349:         }
1350:     }
1351: 
1352:     /**
1353:      * Maps a Kronolith meeting status string to a translated string suitable
1354:      * for display.
1355:      *
1356:      * @param integer $status  The meeting status; one of the
1357:      *                         Kronolith::STATUS_XXX constants.
1358:      *
1359:      * @return string  The translated displayable meeting status string.
1360:      */
1361:     static public function statusToString($status)
1362:     {
1363:         switch ($status) {
1364:         case self::STATUS_CONFIRMED:
1365:             return _("Confirmed");
1366: 
1367:         case self::STATUS_CANCELLED:
1368:             return _("Cancelled");
1369: 
1370:         case self::STATUS_FREE:
1371:             return _("Free");
1372: 
1373:         case self::STATUS_TENTATIVE:
1374:         default:
1375:             return _("Tentative");
1376:         }
1377:     }
1378: 
1379:     /**
1380:      * Maps a Kronolith attendee response string to a translated string
1381:      * suitable for display.
1382:      *
1383:      * @param integer $response  The attendee response; one of the
1384:      *                           Kronolith::RESPONSE_XXX constants.
1385:      *
1386:      * @return string  The translated displayable attendee response string.
1387:      */
1388:     static public function responseToString($response)
1389:     {
1390:         switch ($response) {
1391:         case self::RESPONSE_ACCEPTED:
1392:             return _("Accepted");
1393: 
1394:         case self::RESPONSE_DECLINED:
1395:             return _("Declined");
1396: 
1397:         case self::RESPONSE_TENTATIVE:
1398:             return _("Tentative");
1399: 
1400:         case self::RESPONSE_NONE:
1401:         default:
1402:             return _("None");
1403:         }
1404:     }
1405: 
1406:     /**
1407:      * Maps a Kronolith attendee participation string to a translated string
1408:      * suitable for display.
1409:      *
1410:      * @param integer $part  The attendee participation; one of the
1411:      *                       Kronolith::PART_XXX constants.
1412:      *
1413:      * @return string  The translated displayable attendee participation
1414:      *                 string.
1415:      */
1416:     static public function partToString($part)
1417:     {
1418:         switch ($part) {
1419:         case self::PART_OPTIONAL:
1420:             return _("Optional");
1421: 
1422:         case self::PART_NONE:
1423:             return _("None");
1424: 
1425:         case self::PART_REQUIRED:
1426:         default:
1427:             return _("Required");
1428:         }
1429:     }
1430: 
1431:     /**
1432:      * Maps an iCalendar attendee response string to the corresponding
1433:      * Kronolith value.
1434:      *
1435:      * @param string $response  The attendee response.
1436:      *
1437:      * @return string  The Kronolith response value.
1438:      */
1439:     static public function responseFromICal($response)
1440:     {
1441:         switch (Horde_String::upper($response)) {
1442:         case 'ACCEPTED':
1443:             return self::RESPONSE_ACCEPTED;
1444: 
1445:         case 'DECLINED':
1446:             return self::RESPONSE_DECLINED;
1447: 
1448:         case 'TENTATIVE':
1449:             return self::RESPONSE_TENTATIVE;
1450: 
1451:         case 'NEEDS-ACTION':
1452:         default:
1453:             return self::RESPONSE_NONE;
1454:         }
1455:     }
1456: 
1457:     /**
1458:      * Builds the HTML for an event status widget.
1459:      *
1460:      * @param string $name     The name of the widget.
1461:      * @param string $current  The selected status value.
1462:      * @param string $any      Whether an 'any' item should be added
1463:      *
1464:      * @return string  The HTML <select> widget.
1465:      */
1466:     static public function buildStatusWidget($name,
1467:                                              $current = self::STATUS_CONFIRMED,
1468:                                              $any = false)
1469:     {
1470:         $html = "<select id=\"$name\" name=\"$name\">";
1471: 
1472:         $statii = array(
1473:             self::STATUS_FREE,
1474:             self::STATUS_TENTATIVE,
1475:             self::STATUS_CONFIRMED,
1476:             self::STATUS_CANCELLED
1477:         );
1478: 
1479:         if (!isset($current)) {
1480:             $current = self::STATUS_NONE;
1481:         }
1482: 
1483:         if ($any) {
1484:             $html .= "<option value=\"" . self::STATUS_NONE . "\"";
1485:             $html .= ($current == self::STATUS_NONE) ? ' selected="selected">' : '>';
1486:             $html .= _("Any") . "</option>";
1487:         }
1488: 
1489:         foreach ($statii as $status) {
1490:             $html .= "<option value=\"$status\"";
1491:             $html .= ($status == $current) ? ' selected="selected">' : '>';
1492:             $html .= self::statusToString($status) . "</option>";
1493:         }
1494:         $html .= '</select>';
1495: 
1496:         return $html;
1497:     }
1498: 
1499:     /**
1500:      * Returns all internal calendars a user has access to, according
1501:      * to several parameters/permission levels.
1502:      *
1503:      * This method takes the $conf['share']['hidden'] setting into account. If
1504:      * this setting is enabled, even if requesting permissions different than
1505:      * SHOW, it will only return calendars that the user owns or has SHOW
1506:      * permissions for. For checking individual calendar's permissions, use
1507:      * hasPermission() instead.
1508:      *
1509:      * @param boolean $owneronly   Only return calenders that this user owns?
1510:      *                             Defaults to false.
1511:      * @param integer $permission  The permission to filter calendars by.
1512:      *
1513:      * @return array  The calendar list.
1514:      */
1515:     static public function listInternalCalendars($owneronly = false, $permission = Horde_Perms::SHOW)
1516:     {
1517:         if ($owneronly && !$GLOBALS['registry']->getAuth()) {
1518:             return array();
1519:         }
1520: 
1521:         if ($owneronly || empty($GLOBALS['conf']['share']['hidden'])) {
1522:             try {
1523:                 $calendars = $GLOBALS['kronolith_shares']->listShares(
1524:                     $GLOBALS['registry']->getAuth(),
1525:                     array('perm' => $permission,
1526:                           'attributes' => $owneronly ? $GLOBALS['registry']->getAuth() : null,
1527:                           'sort_by' => 'name'));
1528:             } catch (Horde_Share_Exception $e) {
1529:                 Horde::logMessage($e);
1530:                 return array();
1531:             }
1532:         } else {
1533:             try {
1534:                 $calendars = $GLOBALS['kronolith_shares']->listShares(
1535:                     $GLOBALS['registry']->getAuth(),
1536:                     array('perm' => $permission,
1537:                           'attributes' => $GLOBALS['registry']->getAuth(),
1538:                           'sort_by' => 'name'));
1539:             } catch (Horde_Share_Exception $e) {
1540:                 Horde::logMessage($e);
1541:                 return array();
1542:             }
1543:             $display_calendars = @unserialize($GLOBALS['prefs']->getValue('display_cals'));
1544:             if (is_array($display_calendars)) {
1545:                 foreach ($display_calendars as $id) {
1546:                     try {
1547:                         $calendar = $GLOBALS['kronolith_shares']->getShare($id);
1548:                         if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), $permission)) {
1549:                             $calendars[$id] = $calendar;
1550:                         }
1551:                     } catch (Horde_Exception_NotFound $e) {
1552:                     } catch (Horde_Share_Exception $e) {
1553:                         Horde::logMessage($e);
1554:                         return array();
1555:                     }
1556:                 }
1557:             }
1558:         }
1559: 
1560:         $default_share = $GLOBALS['prefs']->getValue('default_share');
1561:         if (isset($calendars[$default_share])) {
1562:             $calendar = $calendars[$default_share];
1563:             unset($calendars[$default_share]);
1564:             $calendars = array($default_share => $calendar) + $calendars;
1565:         }
1566: 
1567:         return $calendars;
1568:     }
1569: 
1570:     /**
1571:      * Returns all calendars a user has access to, according to several
1572:      * parameters/permission levels.
1573:      *
1574:      * @param boolean $owneronly   Only return calenders that this user owns?
1575:      *                             Defaults to false.
1576:      * @param boolean $display     Only return calendars that are supposed to
1577:      *                             be displayed per configuration and user
1578:      *                             preference.
1579:      * @param integer $permission  The permission to filter calendars by.
1580:      *
1581:      * @return array  The calendar list.
1582:      */
1583:     static public function listCalendars($permission = Horde_Perms::SHOW,
1584:                                          $display = false,
1585:                                          $flat = true)
1586:     {
1587:         $calendars = array();
1588:         foreach ($GLOBALS['all_calendars'] as $id => $calendar) {
1589:             if ($calendar->hasPermission($permission) &&
1590:                 (!$display || $calendar->display())) {
1591:                 if ($flat) {
1592:                     $calendars['internal_' . $id] = $calendar;
1593:                 }
1594:             }
1595:         }
1596: 
1597:         foreach ($GLOBALS['all_remote_calendars'] as $id => $calendar) {
1598:             try {
1599:                 if ($calendar->hasPermission($permission) &&
1600:                     (!$display || $calendar->display())) {
1601:                     if ($flat) {
1602:                         $calendars['remote_' . $id] = $calendar;
1603:                     }
1604:                 }
1605:             } catch (Kronolith_Exception $e) {
1606:                 $GLOBALS['notification']->push(sprintf(_("The calendar %s returned the error: %s"), $calendar->name(), $e->getMessage()), 'horde.error');
1607:             }
1608:         }
1609: 
1610:         foreach ($GLOBALS['all_external_calendars'] as $id => $calendar) {
1611:             if ($calendar->hasPermission($permission) &&
1612:                 (!$display || $calendar->display())) {
1613:                 if ($flat) {
1614:                     $calendars['external_' . $id] = $calendar;
1615:                 }
1616:             }
1617:         }
1618: 
1619:         foreach ($GLOBALS['all_holidays'] as $id => $calendar) {
1620:             if ($calendar->hasPermission($permission) &&
1621:                 (!$display || $calendar->display())) {
1622:                 if ($flat) {
1623:                     $calendars['holiday_' . $id] = $calendar;
1624:                 }
1625:             }
1626:         }
1627: 
1628:         return $calendars;
1629:     }
1630: 
1631:     /**
1632:      * Returns the default calendar for the current user at the specified
1633:      * permissions level.
1634:      *
1635:      * @param integer $permission  Horde_Perms constant for permission level required.
1636:      * @param boolean $owner_only  Only consider owner-owned calendars.
1637:      *
1638:      * @return mixed  The calendar id, or false if none found.
1639:      */
1640:     static public function getDefaultCalendar($permission = Horde_Perms::SHOW, $owner_only = false)
1641:     {
1642:         global $prefs;
1643: 
1644:         $default_share = $prefs->getValue('default_share');
1645:         $calendars = self::listInternalCalendars($owner_only, $permission);
1646: 
1647:         if (isset($calendars[$default_share]) ||
1648:             $prefs->isLocked('default_share')) {
1649:             return $default_share;
1650:         } elseif (isset($GLOBALS['all_calendars'][$GLOBALS['registry']->getAuth()]) &&
1651:                   $GLOBALS['all_calendars'][$GLOBALS['registry']->getAuth()]->hasPermission($permission)) {
1652:             // This is for older, existing default shares. New default shares
1653:             // are not named as the username.
1654:             return $GLOBALS['registry']->getAuth();
1655:         } elseif (count($calendars)) {
1656:             return key($calendars);
1657:         }
1658: 
1659:         return false;
1660:     }
1661: 
1662:     /**
1663:      * Returns the calendars that should be used for syncing.
1664:      *
1665:      * @param boolean $prune  Remove calendar ids from the sync list that no
1666:      *                        longer exist. The values are pruned *after* the
1667:      *                        results are passed back to the client to give
1668:      *                        sync clients a chance to remove their entries.
1669:      *
1670:      * @return array  An array of calendar ids
1671:      */
1672:     static public function getSyncCalendars($prune = false)
1673:     {
1674:         $haveRemoved = false;
1675:         $cs = unserialize($GLOBALS['prefs']->getValue('sync_calendars'));
1676:         if (!empty($cs)) {
1677:             if ($prune) {
1678:                 $calendars = self::listInternalCalendars(true, Horde_Perms::EDIT);
1679:                 $cscopy = array_flip($cs);
1680:                 foreach ($cs as $c) {
1681:                     if (empty($calendars[$c])) {
1682:                         unset($cscopy[$c]);
1683:                         $haveRemoved = true;
1684:                     }
1685:                 }
1686:                 if ($haveRemoved) {
1687:                     $GLOBALS['prefs']->setValue('sync_calendars', serialize(array_flip($cscopy)));
1688:                 }
1689:             }
1690:             return $cs;
1691:         }
1692: 
1693:         if ($cs = self::getDefaultCalendar(Horde_Perms::EDIT, true)) {
1694:             return array($cs);
1695:         }
1696: 
1697:         return array();
1698:     }
1699: 
1700:     /**
1701:      * Returns whether the current user has certain permissions on a calendar.
1702:      *
1703:      * @since Kronolith 3.0.6
1704:      *
1705:      * @param string $calendar  A calendar id.
1706:      * @param integer $perm     A Horde_Perms permission mask.
1707:      *
1708:      * @return boolean  True if the current user has the requested permissions.
1709:      */
1710:     static public function hasPermission($calendar, $perm)
1711:     {
1712:         try {
1713:             $share = $GLOBALS['kronolith_shares']->getShare($calendar);
1714:             if (!$share->hasPermission($GLOBALS['registry']->getAuth(), $perm)) {
1715:                 throw new Horde_Exception_NotFound();
1716:             }
1717:         } catch (Horde_Exception_NotFound $e) {
1718:             return false;
1719:         }
1720:         return true;
1721:     }
1722: 
1723:     /**
1724:      * Creates a new share.
1725:      *
1726:      * @param array $info  Hash with calendar information.
1727:      *
1728:      * @return Horde_Share  The new share.
1729:      * @throws Kronolith_Exception
1730:      */
1731:     static public function addShare($info)
1732:     {
1733:         try {
1734:             $calendar = $GLOBALS['kronolith_shares']->newShare($GLOBALS['registry']->getAuth(), strval(new Horde_Support_Randomid()), $info['name']);
1735:         } catch (Horde_Share_Exception $e) {
1736:             throw new Kronolith_Exception($e);
1737:         }
1738: 
1739:         $calendar->set('color', $info['color']);
1740:         $calendar->set('desc', $info['description']);
1741:         if (!empty($info['system'])) {
1742:             $calendar->set('owner', null);
1743:         }
1744:         $tagger = self::getTagger();
1745:         $tagger->tag($calendar->getName(), $info['tags'], $calendar->get('owner'), 'calendar');
1746: 
1747:         try {
1748:             $GLOBALS['kronolith_shares']->addShare($calendar);
1749:         } catch (Horde_Share_Exception $e) {
1750:             throw new Kronolith_Exception($e);
1751:         }
1752: 
1753:         $GLOBALS['display_calendars'][] = $calendar->getName();
1754:         $GLOBALS['prefs']->setValue('display_cals', serialize($GLOBALS['display_calendars']));
1755: 
1756:         return $calendar;
1757:     }
1758: 
1759:     /**
1760:      * Updates an existing share.
1761:      *
1762:      * @param Horde_Share $share  The share to update.
1763:      * @param array $info         Hash with calendar information.
1764:      *
1765:      * @throws Kronolith_Exception
1766:      */
1767:     static public function updateShare(&$calendar, $info)
1768:     {
1769:         if (!$GLOBALS['registry']->getAuth() ||
1770:             ($calendar->get('owner') != $GLOBALS['registry']->getAuth() &&
1771:              (!is_null($calendar->get('owner')) || !$GLOBALS['registry']->isAdmin()))) {
1772:             throw new Kronolith_Exception(_("You are not allowed to change this calendar."));
1773:         }
1774: 
1775:         $original_name = $calendar->get('name');
1776:         $calendar->set('name', $info['name']);
1777:         $calendar->set('color', $info['color']);
1778:         $calendar->set('desc', $info['description']);
1779:         $calendar->set('owner', empty($info['system']) ? $GLOBALS['registry']->getAuth() : null);
1780: 
1781:         try {
1782:             $result = $calendar->save();
1783:         } catch (Horde_Share_Exception $e) {
1784:             throw new Kronolith_Exception(sprintf(_("Unable to save calendar \"%s\": %s"), $info['name'], $e->getMessage()));
1785:         }
1786: 
1787:         $tagger = self::getTagger();
1788:         $tagger->replaceTags($calendar->getName(), $info['tags'], $calendar->get('owner'), 'calendar');
1789:     }
1790: 
1791:     /**
1792:      * Deletes a share.
1793:      *
1794:      * @param Horde_Share $calendar  The share to delete.
1795:      *
1796:      * @throws Kronolith_Exception
1797:      */
1798:     static public function deleteShare($calendar)
1799:     {
1800:         if (!$GLOBALS['registry']->getAuth() ||
1801:             ($calendar->get('owner') != $GLOBALS['registry']->getAuth() &&
1802:              (!is_null($calendar->get('owner')) || !$GLOBALS['registry']->isAdmin()))) {
1803:             throw new Kronolith_Exception(_("You are not allowed to delete this calendar."));
1804:         }
1805: 
1806:         // Delete the calendar.
1807:         try {
1808:             self::getDriver()->delete($calendar->getName());
1809:         } catch (Exception $e) {
1810:             throw new Kronolith_Exception(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $e->getMessage()));
1811:         }
1812: 
1813:         // Remove share and all groups/permissions.
1814:         try {
1815:             $result = $GLOBALS['kronolith_shares']->removeShare($calendar);
1816:         } catch (Horde_Share_Exception $e) {
1817:             throw new Kronolith_Exception($e);
1818:         }
1819:     }
1820: 
1821:     /**
1822:      * Reads a submitted permissions form and updates the share permissions.
1823:      *
1824:      * @param Horde_Share_Object $share  The share to update.
1825:      *
1826:      * @return array  A list of error messages.
1827:      * @throws Kronolith_Exception
1828:      */
1829:     static public function readPermsForm($share)
1830:     {
1831:         $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
1832:         $perm = $share->getPermission();
1833:         $errors = array();
1834: 
1835:         if ($GLOBALS['conf']['share']['notify']) {
1836:             $identity = $GLOBALS['injector']
1837:                 ->getInstance('Horde_Core_Factory_Identity')
1838:                 ->create();
1839:             $mail = new Horde_Mime_Mail(array(
1840:                 'From' => $identity->getDefaultFromAddress(true),
1841:                 'User-Agent' => 'Kronolith ' . $GLOBALS['registry']->getVersion()));
1842:             $image = self::getImagePart('big_share.png');
1843:             $view = new Horde_View(array('templatePath' => KRONOLITH_TEMPLATES . '/share'));
1844:             new Horde_View_Helper_Text($view);
1845:             $view->identity = $identity;
1846:             $view->calendar = $share->get('name');
1847:             $view->imageId = $image->getContentId();
1848:         }
1849: 
1850:         // Process owner and owner permissions.
1851:         $old_owner = $share->get('owner');
1852:         $new_owner_backend = Horde_Util::getFormData('owner_select', Horde_Util::getFormData('owner_input', $old_owner));
1853:         $new_owner = $GLOBALS['registry']->convertUsername($new_owner_backend, true);
1854:         if ($old_owner !== $new_owner && !empty($new_owner)) {
1855:             if ($old_owner != $GLOBALS['registry']->getAuth() && !$GLOBALS['registry']->isAdmin()) {
1856:                 $errors[] = _("Only the owner or system administrator may change ownership or owner permissions for a share");
1857:             } elseif ($auth->hasCapability('list') && !$auth->exists($new_owner_backend)) {
1858:                 $errors[] = sprintf(_("The user \"%s\" does not exist."), $new_owner_backend);
1859:             } else {
1860:                 $share->set('owner', $new_owner);
1861:                 $share->save();
1862:                 if ($GLOBALS['conf']['share']['notify']) {
1863:                     $view->ownerChange = true;
1864:                     $multipart = self::buildMimeMessage($view, 'notification', $image);
1865:                     $to = $GLOBALS['injector']
1866:                         ->getInstance('Horde_Core_Factory_Identity')
1867:                         ->create($new_owner)
1868:                         ->getDefaultFromAddress(true);
1869:                     $mail->addHeader('Subject', _("Ownership assignment"));
1870:                     $mail->addHeader('To', $to);
1871:                     $mail->setBasePart($multipart);
1872:                     $mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
1873:                     $view->ownerChange = false;
1874:                 }
1875:             }
1876:         }
1877: 
1878:         if ($GLOBALS['conf']['share']['notify']) {
1879:             if ($GLOBALS['conf']['share']['hidden']) {
1880:                 $view->subscribe = Horde::url('calendars/subscribe.php', true)->add('calendar', $share->getName());
1881:             }
1882:             $multipart = self::buildMimeMessage($view, 'notification', $image);
1883:         }
1884: 
1885:         if ($GLOBALS['registry']->isAdmin() ||
1886:             !empty($GLOBALS['conf']['share']['world'])) {
1887:             // Process default permissions.
1888:             if (Horde_Util::getFormData('default_show')) {
1889:                 $perm->addDefaultPermission(Horde_Perms::SHOW, false);
1890:             } else {
1891:                 $perm->removeDefaultPermission(Horde_Perms::SHOW, false);
1892:             }
1893:             if (Horde_Util::getFormData('default_read')) {
1894:                 $perm->addDefaultPermission(Horde_Perms::READ, false);
1895:             } else {
1896:                 $perm->removeDefaultPermission(Horde_Perms::READ, false);
1897:             }
1898:             if (Horde_Util::getFormData('default_edit')) {
1899:                 $perm->addDefaultPermission(Horde_Perms::EDIT, false);
1900:             } else {
1901:                 $perm->removeDefaultPermission(Horde_Perms::EDIT, false);
1902:             }
1903:             if (Horde_Util::getFormData('default_delete')) {
1904:                 $perm->addDefaultPermission(Horde_Perms::DELETE, false);
1905:             } else {
1906:                 $perm->removeDefaultPermission(Horde_Perms::DELETE, false);
1907:             }
1908:             if (Horde_Util::getFormData('default_delegate')) {
1909:                 $perm->addDefaultPermission(self::PERMS_DELEGATE, false);
1910:             } else {
1911:                 $perm->removeDefaultPermission(self::PERMS_DELEGATE, false);
1912:             }
1913: 
1914:             // Process guest permissions.
1915:             if (Horde_Util::getFormData('guest_show')) {
1916:                 $perm->addGuestPermission(Horde_Perms::SHOW, false);
1917:             } else {
1918:                 $perm->removeGuestPermission(Horde_Perms::SHOW, false);
1919:             }
1920:             if (Horde_Util::getFormData('guest_read')) {
1921:                 $perm->addGuestPermission(Horde_Perms::READ, false);
1922:             } else {
1923:                 $perm->removeGuestPermission(Horde_Perms::READ, false);
1924:             }
1925:             if (Horde_Util::getFormData('guest_edit')) {
1926:                 $perm->addGuestPermission(Horde_Perms::EDIT, false);
1927:             } else {
1928:                 $perm->removeGuestPermission(Horde_Perms::EDIT, false);
1929:             }
1930:             if (Horde_Util::getFormData('guest_delete')) {
1931:                 $perm->addGuestPermission(Horde_Perms::DELETE, false);
1932:             } else {
1933:                 $perm->removeGuestPermission(Horde_Perms::DELETE, false);
1934:             }
1935:             if (Horde_Util::getFormData('guest_delegate')) {
1936:                 $perm->addGuestPermission(self::PERMS_DELEGATE, false);
1937:             } else {
1938:                 $perm->removeGuestPermission(self::PERMS_DELEGATE, false);
1939:             }
1940:         }
1941: 
1942:         // Process creator permissions.
1943:         if (Horde_Util::getFormData('creator_show')) {
1944:             $perm->addCreatorPermission(Horde_Perms::SHOW, false);
1945:         } else {
1946:             $perm->removeCreatorPermission(Horde_Perms::SHOW, false);
1947:         }
1948:         if (Horde_Util::getFormData('creator_read')) {
1949:             $perm->addCreatorPermission(Horde_Perms::READ, false);
1950:         } else {
1951:             $perm->removeCreatorPermission(Horde_Perms::READ, false);
1952:         }
1953:         if (Horde_Util::getFormData('creator_edit')) {
1954:             $perm->addCreatorPermission(Horde_Perms::EDIT, false);
1955:         } else {
1956:             $perm->removeCreatorPermission(Horde_Perms::EDIT, false);
1957:         }
1958:         if (Horde_Util::getFormData('creator_delete')) {
1959:             $perm->addCreatorPermission(Horde_Perms::DELETE, false);
1960:         } else {
1961:             $perm->removeCreatorPermission(Horde_Perms::DELETE, false);
1962:         }
1963:         if (Horde_Util::getFormData('creator_delegate')) {
1964:             $perm->addCreatorPermission(self::PERMS_DELEGATE, false);
1965:         } else {
1966:             $perm->removeCreatorPermission(self::PERMS_DELEGATE, false);
1967:         }
1968: 
1969:         // Process user permissions.
1970:         $u_names = Horde_Util::getFormData('u_names');
1971:         $u_show = Horde_Util::getFormData('u_show');
1972:         $u_read = Horde_Util::getFormData('u_read');
1973:         $u_edit = Horde_Util::getFormData('u_edit');
1974:         $u_delete = Horde_Util::getFormData('u_delete');
1975:         $u_delegate = Horde_Util::getFormData('u_delegate');
1976: 
1977:         $current = $perm->getUserPermissions();
1978:         if ($GLOBALS['conf']['share']['notify']) {
1979:             $mail->addHeader('Subject', _("Access permissions"));
1980:         }
1981: 
1982:         $perm->removeUserPermission(null, null, false);
1983:         foreach ($u_names as $key => $user_backend) {
1984:             // Apply backend hooks
1985:             $user = $GLOBALS['registry']->convertUsername($user_backend, true);
1986:             // If the user is empty, or we've already set permissions
1987:             // via the owner_ options, don't do anything here.
1988:             if (empty($user) || $user == $new_owner) {
1989:                 continue;
1990:             }
1991:             if ($auth->hasCapability('list') && !$auth->exists($user_backend)) {
1992:                 $errors[] = sprintf(_("The user \"%s\" does not exist."), $user_backend);
1993:                 continue;
1994:             }
1995: 
1996:             $has_perms = false;
1997:             if (!empty($u_show[$key])) {
1998:                 $perm->addUserPermission($user, Horde_Perms::SHOW, false);
1999:                 $has_perms = true;
2000:             }
2001:             if (!empty($u_read[$key])) {
2002:                 $perm->addUserPermission($user, Horde_Perms::READ, false);
2003:                 $has_perms = true;
2004:             }
2005:             if (!empty($u_edit[$key])) {
2006:                 $perm->addUserPermission($user, Horde_Perms::EDIT, false);
2007:                 $has_perms = true;
2008:             }
2009:             if (!empty($u_delete[$key])) {
2010:                 $perm->addUserPermission($user, Horde_Perms::DELETE, false);
2011:                 $has_perms = true;
2012:             }
2013:             if (!empty($u_delegate[$key])) {
2014:                 $perm->addUserPermission($user, self::PERMS_DELEGATE, false);
2015:                 $has_perms = true;
2016:             }
2017: 
2018:             // Notify users that have been added.
2019:             if ($GLOBALS['conf']['share']['notify'] &&
2020:                 !isset($current[$user]) && $has_perms) {
2021:                 $to = $GLOBALS['injector']
2022:                     ->getInstance('Horde_Core_Factory_Identity')
2023:                     ->create($user)
2024:                     ->getDefaultFromAddress(true);
2025:                 $mail->addHeader('To', $to);
2026:                 $mail->setBasePart($multipart);
2027:                 $mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
2028:             }
2029:         }
2030: 
2031:         // Process group permissions.
2032:         $g_names = Horde_Util::getFormData('g_names');
2033:         $g_show = Horde_Util::getFormData('g_show');
2034:         $g_read = Horde_Util::getFormData('g_read');
2035:         $g_edit = Horde_Util::getFormData('g_edit');
2036:         $g_delete = Horde_Util::getFormData('g_delete');
2037:         $g_delegate = Horde_Util::getFormData('g_delegate');
2038: 
2039:         $current = $perm->getGroupPermissions();
2040:         $perm->removeGroupPermission(null, null, false);
2041:         foreach ($g_names as $key => $group) {
2042:             if (empty($group)) {
2043:                 continue;
2044:             }
2045: 
2046:             $has_perms = false;
2047:             if (!empty($g_show[$key])) {
2048:                 $perm->addGroupPermission($group, Horde_Perms::SHOW, false);
2049:                 $has_perms = true;
2050:             }
2051:             if (!empty($g_read[$key])) {
2052:                 $perm->addGroupPermission($group, Horde_Perms::READ, false);
2053:                 $has_perms = true;
2054:             }
2055:             if (!empty($g_edit[$key])) {
2056:                 $perm->addGroupPermission($group, Horde_Perms::EDIT, false);
2057:                 $has_perms = true;
2058:             }
2059:             if (!empty($g_delete[$key])) {
2060:                 $perm->addGroupPermission($group, Horde_Perms::DELETE, false);
2061:                 $has_perms = true;
2062:             }
2063:             if (!empty($g_delegate[$key])) {
2064:                 $perm->addGroupPermission($group, self::PERMS_DELEGATE, false);
2065:                 $has_perms = true;
2066:             }
2067: 
2068:             // Notify users that have been added.
2069:             if ($GLOBALS['conf']['share']['notify'] &&
2070:                 !isset($current[$group]) && $has_perms) {
2071:                 $groupOb = $GLOBALS['injector']
2072:                     ->getInstance('Horde_Group')
2073:                     ->getData($group);
2074:                 if (!empty($groupOb['email'])) {
2075:                     $mail->addHeader('To', $groupOb['name'] . ' <' . $groupOb['email'] . '>');
2076:                     $mail->setBasePart($multipart);
2077:                     $mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
2078:                 }
2079:             }
2080:         }
2081:         try {
2082:             $share->setPermission($perm);
2083:         } catch (Horde_Share_Exception $e) {
2084:             throw new Kronolith_Exception($e);
2085:         }
2086: 
2087:         return $errors;
2088:     }
2089: 
2090:     /**
2091:      * Subscribes to or updates a remote calendar.
2092:      *
2093:      * @param array $info     Hash with calendar information.
2094:      * @param string $update  If present, the original URL of the calendar to
2095:      *                        update.
2096:      *
2097:      * @throws Kronolith_Exception
2098:      */
2099:     static public function subscribeRemoteCalendar(&$info, $update = false)
2100:     {
2101:         if (!(strlen($info['name']) && strlen($info['url']))) {
2102:             throw new Kronolith_Exception(_("You must specify a name and a URL."));
2103:         }
2104: 
2105:         if (!empty($info['user']) || !empty($info['password'])) {
2106:             $key = $GLOBALS['registry']->getAuthCredential('password');
2107:             if ($key) {
2108:                 $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
2109:                 $info['user'] = base64_encode($secret->write($key, $info['user']));
2110:                 $info['password'] = base64_encode($secret->write($key, $info['password']));
2111:             }
2112:         }
2113: 
2114:         $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
2115:         if ($update) {
2116:             foreach ($remote_calendars as $key => $calendar) {
2117:                 if ($calendar['url'] == $update) {
2118:                     $remote_calendars[$key] = $info;
2119:                     break;
2120:                 }
2121:             }
2122:         } else {
2123:             $remote_calendars[] = $info;
2124:             $GLOBALS['display_remote_calendars'][] = $info['url'];
2125:             $GLOBALS['prefs']->setValue('display_remote_cals', serialize($GLOBALS['display_remote_calendars']));
2126:         }
2127: 
2128:         $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars));
2129:     }
2130: 
2131:     /**
2132:      * Unsubscribes from a remote calendar.
2133:      *
2134:      * @param string $url  The calendar URL.
2135:      *
2136:      * @return array  Hash with the deleted calendar's information.
2137:      * @throws Kronolith_Exception
2138:      */
2139:     static public function unsubscribeRemoteCalendar($url)
2140:     {
2141:         $url = trim($url);
2142:         if (!strlen($url)) {
2143:             return false;
2144:         }
2145: 
2146:         $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
2147:         $remote_calendar = null;
2148:         foreach ($remote_calendars as $key => $calendar) {
2149:             if ($calendar['url'] == $url) {
2150:                 $remote_calendar = $calendar;
2151:                 unset($remote_calendars[$key]);
2152:                 break;
2153:             }
2154:         }
2155:         if (!$remote_calendar) {
2156:             throw new Kronolith_Exception(_("The remote calendar was not found."));
2157:         }
2158: 
2159:         $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars));
2160: 
2161:         return $remote_calendar;
2162:     }
2163: 
2164:     /**
2165:      * Returns the feed URL for a calendar.
2166:      *
2167:      * @param string $calendar  A calendar name.
2168:      *
2169:      * @return string  The calendar's feed URL.
2170:      */
2171:     static public function feedUrl($calendar)
2172:     {
2173:         if (isset($GLOBALS['conf']['urls']['pretty']) &&
2174:             $GLOBALS['conf']['urls']['pretty'] == 'rewrite') {
2175:             return Horde::url('feed/' . $calendar, true, -1);
2176:         }
2177:         return Horde::url('feed/index.php', true, -1)
2178:             ->add('c', $calendar);
2179:     }
2180: 
2181:     /**
2182:      * Returs the HTML/javascript snippit needed to embed a calendar in an
2183:      * external website.
2184:      *
2185:      * @param string $calendar  A calendar name.
2186:      *
2187:      * @return string  The calendar's embed snippit.
2188:      */
2189:     static public function embedCode($calendar)
2190:     {
2191:         /* Get the base url */
2192:         $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create(array('kronolith', 'Embed'), array(
2193:             'calendar' => 'internal_' . $calendar,
2194:             'container' => 'kronolithCal',
2195:             'view' => 'month'
2196:         ), true);
2197: 
2198:         $html = '<div id="kronolithCal"></div><script src="' . $imple->getUrl()
2199:             . '" type="text/javascript"></script>';
2200: 
2201:         return $html;
2202:     }
2203: 
2204:     /**
2205:      * Parses a comma separated list of names and e-mail addresses into a list
2206:      * of attendee hashes.
2207:      *
2208:      * @param string $newAttendees  A comma separated attendee list.
2209:      *
2210:      * @return array  The attendee list with e-mail addresses as keys and
2211:      *                attendee information as values.
2212:      */
2213:     static public function parseAttendees($newAttendees)
2214:     {
2215:         global $notification;
2216: 
2217:         if (empty($newAttendees)) {
2218:             return array();
2219:         }
2220: 
2221:         $parser = new Horde_Mail_Rfc822();
2222:         $attendees = array();
2223: 
2224:         foreach (Horde_Mime_Address::explode($newAttendees) as $newAttendee) {
2225:             // Parse the address without validation to see what we can get out
2226:             // of it. We allow email addresses (john@example.com), email
2227:             // address with user information (John Doe <john@example.com>),
2228:             // and plain names (John Doe).
2229:             try {
2230:                 $newAttendeeParsed = $parser->parseAddressList($newAttendee, array(
2231:                     'default_domain' => null,
2232:                     'nest_groups' => false,
2233:                     'validate' => false
2234:                 ));
2235:                 $error = (!isset($newAttendeeParsed[0]) || !isset($newAttendeeParsed[0]->mailbox));
2236:             } catch (Horde_Mail_Exception $e) {
2237:                 $error = true;
2238:             }
2239: 
2240:             // If we can't even get a mailbox out of the address, then it is
2241:             // likely unuseable. Reject it entirely.
2242:             if ($error) {
2243:                 $notification->push(
2244:                     sprintf(_("Unable to recognize \"%s\" as an email address."), $newAttendee),
2245:                     'horde.error');
2246:                 continue;
2247:             }
2248: 
2249:             // Loop through any addresses we found.
2250:             foreach ($newAttendeeParsed as $newAttendeeParsedPart) {
2251:                 // If there is only a mailbox part, then it is just a local
2252:                 // name.
2253:                 if (empty($newAttendeeParsedPart->host)) {
2254:                     $attendees[] = array(
2255:                         'attendance' => self::PART_REQUIRED,
2256:                         'response'   => self::RESPONSE_NONE,
2257:                         'name'       => $newAttendee,
2258:                     );
2259:                     continue;
2260:                 }
2261: 
2262:                 // Build a full email address again and validate it.
2263:                 $name = empty($newAttendeeParsedPart->personal)
2264:                     ? ''
2265:                     : $newAttendeeParsedPart->personal;
2266: 
2267:                 try {
2268:                     $newAttendeeParsedPartNew = Horde_Mime::encodeAddress(Horde_Mime_Address::writeAddress($newAttendeeParsedPart->mailbox, $newAttendeeParsedPart->host, $name), 'UTF-8');
2269:                     $newAttendeeParsedPartValidated = $parser->parseAddressList($newAttendeeParsedPartNew, array(
2270:                         'default_domain' => null
2271:                     ));
2272: 
2273:                     $email = $newAttendeeParsedPart->mailbox . '@'
2274:                         . $newAttendeeParsedPart->host;
2275:                     // Avoid overwriting existing attendees with the default
2276:                     // values.
2277:                     $attendees[Horde_String::lower($email)] = array(
2278:                         'attendance' => self::PART_REQUIRED,
2279:                         'response'   => self::RESPONSE_NONE,
2280:                         'name'       => $name);
2281:                 } catch (Horde_Mime_Exception $e) {
2282:                     $notification->push($e, 'horde.error');
2283:                 }
2284:             }
2285:         }
2286: 
2287:         return $attendees;
2288:     }
2289: 
2290:     /**
2291:      * Returns a comma separated list of attendees and resources
2292:      *
2293:      * @return string  Attendee/Resource list.
2294:      */
2295:     static public function attendeeList()
2296:     {
2297:         /* Attendees */
2298:         $attendees = array();
2299:         foreach ($GLOBALS['session']->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY) as $email => $attendee) {
2300:             $attendees[] = empty($attendee['name']) ? $email : Horde_Mime_Address::trimAddress($attendee['name'] . (strpos($email, '@') === false ? '' : ' <' . $email . '>'));
2301:         }
2302: 
2303:         /* Resources */
2304:         foreach ($GLOBALS['session']->get('kronolith', 'resources', Horde_Session::TYPE_ARRAY) as $resource) {
2305:             $attendees[] = $resource['name'];
2306:         }
2307: 
2308:         return implode(', ', $attendees);
2309:     }
2310: 
2311:     /**
2312:      * Sends out iTip event notifications to all attendees of a specific
2313:      * event.
2314:      *
2315:      * Can be used to send event invitations, event updates as well as event
2316:      * cancellations.
2317:      *
2318:      * @param Kronolith_Event $event
2319:      *        The event in question.
2320:      * @param Horde_Notification_Handler $notification
2321:      *        A notification object used to show result status.
2322:      * @param integer $action
2323:      *        The type of notification to send. One of the Kronolith::ITIP_*
2324:      *        values.
2325:      * @param Horde_Date $instance
2326:      *        If cancelling a single instance of a recurring event, the date of
2327:      *        this intance.
2328:      */
2329:     static public function sendITipNotifications($event, $notification,
2330:                                                  $action, $instance = null)
2331:     {
2332:         global $conf, $registry;
2333: 
2334:         if (!$event->attendees) {
2335:             return;
2336:         }
2337: 
2338:         $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($event->creator);
2339:         if (!$ident->getValue('from_addr')) {
2340:             $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."), Horde::getServiceLink('prefs', 'kronolith')->add(array('app' => 'horde', 'group' => 'identities'))->link(), '</a>'), 'horde.error', array('content.raw'));
2341:             return;
2342:         }
2343: 
2344:         // Generate image mime part first and only once, because we
2345:         // need the Content-ID.
2346:         $image = self::getImagePart('big_invitation.png');
2347: 
2348:         $share = $GLOBALS['kronolith_shares']->getShare($event->calendar);
2349:         $view = new Horde_View(array('templatePath' => KRONOLITH_TEMPLATES . '/itip'));
2350:         new Horde_View_Helper_Text($view);
2351:         $view->identity = $ident;
2352:         $view->event = $event;
2353:         $view->imageId = $image->getContentId();
2354: 
2355:         foreach ($event->attendees as $email => $status) {
2356:             /* Don't bother sending an invitation/update if the recipient does
2357:              * not need to participate, or has declined participating, or
2358:              * doesn't have an email address. */
2359:             if (strpos($email, '@') === false ||
2360:                 $status['attendance'] == self::PART_NONE ||
2361:                 $status['response'] == self::RESPONSE_DECLINED) {
2362:                 continue;
2363:             }
2364: 
2365:             /* Determine all notification-specific strings. */
2366:             switch ($action) {
2367:             case self::ITIP_CANCEL:
2368:                 /* Cancellation. */
2369:                 $method = 'CANCEL';
2370:                 $filename = 'event-cancellation.ics';
2371:                 $view->subject = sprintf(_("Cancelled: %s"), $event->getTitle());
2372:                 if (empty($instance)) {
2373:                     $view->header = sprintf(_("%s has cancelled \"%s\"."), $ident->getName(), $event->getTitle());
2374:                 } else {
2375:                     $view->header = sprintf(_("%s has cancelled an instance of the recurring \"%s\"."), $ident->getName(), $event->getTitle());
2376:                 }
2377:                 break;
2378: 
2379:             case self::ITIP_REQUEST:
2380:             default:
2381:                 $method = 'REQUEST';
2382:                 if ($status['response'] == self::RESPONSE_NONE) {
2383:                     /* Invitation. */
2384:                     $filename = 'event-invitation.ics';
2385:                     $view->subject = $event->getTitle();
2386:                     $view->header = sprintf(_("%s wishes to make you aware of \"%s\"."), $ident->getName(), $event->getTitle());
2387:                 } else {
2388:                     /* Update. */
2389:                     $filename = 'event-update.ics';
2390:                     $view->subject = sprintf(_("Updated: %s."), $event->getTitle());
2391:                     $view->header = sprintf(_("%s wants to notify you about changes of \"%s\"."), $ident->getName(), $event->getTitle());
2392:                 }
2393:                 break;
2394:             }
2395: 
2396:             if ($event->attendees) {
2397:                 $attendees = array();
2398:                 foreach ($event->attendees as $mail => $attendee) {
2399:                     $attendees[] = empty($attendee['name']) ? $mail : Horde_Mime_Address::trimAddress($attendee['name'] . (strpos($mail, '@') === false ? '' : ' <' . $mail . '>'));
2400:                 }
2401:                 $view->organizer = $GLOBALS['registry']->convertUserName($event->creator, false);
2402:                 $view->attendees = $attendees;
2403:             }
2404: 
2405:             if ($action == self::ITIP_REQUEST) {
2406:                 $attend_link = Horde::url('attend.php', true, -1)
2407:                     ->add(array('c' => $event->calendar,
2408:                                 'e' => $event->id,
2409:                                 'u' => $email));
2410:                 $view->linkAccept    = (string)$attend_link->add('a', 'accept');
2411:                 $view->linkTentative = (string)$attend_link->add('a', 'tentative');
2412:                 $view->linkDecline   = (string)$attend_link->add('a', 'decline');
2413:             }
2414: 
2415:             /* Build the iCalendar data */
2416:             $iCal = new Horde_Icalendar();
2417:             $iCal->setAttribute('METHOD', $method);
2418:             $iCal->setAttribute('X-WR-CALNAME', $share->get('name'));
2419:             $vevent = $event->toiCalendar($iCal);
2420:             if ($action == self::ITIP_CANCEL && !empty($instance)) {
2421:                 // Recurring event instance deletion, need to specify the
2422:                 // RECURRENCE-ID but NOT the EXDATE.
2423:                 $vevent = array_pop($vevent);
2424:                 $vevent->setAttribute('RECURRENCE-ID', $instance, array('VALUE' => 'DATE'));
2425:                 $vevent->removeAttribute('EXDATE');
2426:             }
2427:             $iCal->addComponent($vevent);
2428: 
2429:             /* text/calendar part */
2430:             $ics = new Horde_Mime_Part();
2431:             $ics->setType('text/calendar');
2432:             $ics->setContents($iCal->exportvCalendar());
2433:             $ics->setName($filename);
2434:             $ics->setContentTypeParameter('METHOD', $method);
2435:             $ics->setCharset('UTF-8');
2436:             $ics->setEOL("\r\n");
2437: 
2438:             $multipart = self::buildMimeMessage($view, 'notification', $image);
2439:             $multipart->addPart($ics);
2440:             $recipient = empty($status['name']) ? $email : Horde_Mime_Address::trimAddress($status['name'] . ' <' . $email . '>');
2441:             $mail = new Horde_Mime_Mail(
2442:                 array('Subject' => $view->subject,
2443:                       'To' => $recipient,
2444:                       'From' => $ident->getDefaultFromAddress(true),
2445:                       'User-Agent' => 'Kronolith ' . $GLOBALS['registry']->getVersion()));
2446:             $mail->setBasePart($multipart);
2447: 
2448:             try {
2449:                 $mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
2450:                 $notification->push(
2451:                     sprintf(_("The event notification to %s was successfully sent."), $recipient),
2452:                     'horde.success'
2453:                 );
2454:             } catch (Horde_Mime_Exception $e) {
2455:                 $notification->push(
2456:                     sprintf(_("There was an error sending an event notification to %s: %s"), $recipient, $e->getMessage(), $e->getCode()),
2457:                     'horde.error'
2458:                 );
2459:             }
2460:         }
2461:     }
2462: 
2463:     /**
2464:      * Sends email notifications that a event has been added, edited, or
2465:      * deleted to users that want such notifications.
2466:      *
2467:      * @param Kronolith_Event $event  An event.
2468:      * @param string $action          The event action. One of "add", "edit",
2469:      *                                or "delete".
2470:      *
2471:      * @throws Horde_Mime_Exception
2472:      * @throws Kronolith_Exception
2473:      */
2474:     static public function sendNotification($event, $action)
2475:     {
2476:         global $conf;
2477: 
2478:         if (!in_array($action, array('add', 'edit', 'delete'))) {
2479:             throw new Kronolith_Exception('Unknown event action: ' . $action);
2480:         }
2481: 
2482:         $groups = $GLOBALS['injector']->getInstance('Horde_Group');
2483:         $calendar = $event->calendar;
2484:         $recipients = array();
2485:         try {
2486:             $share = $GLOBALS['kronolith_shares']->getShare($calendar);
2487:         } catch (Horde_Share_Exception $e) {
2488:             throw new Kronolith_Exception($e);
2489:         }
2490: 
2491:         $senderIdentity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create();
2492: 
2493:         $owner = $share->get('owner');
2494:         if ($owner) {
2495:             $recipients[$owner] = self::_notificationPref($owner, 'owner');
2496:         }
2497: 
2498:         foreach ($share->listUsers(Horde_Perms::READ) as $user) {
2499:             if (!isset($recipients[$user])) {
2500:                 $recipients[$user] = self::_notificationPref($user, 'read', $calendar);
2501:             }
2502:         }
2503: 
2504:         foreach ($share->listGroups(Horde_Perms::READ) as $group) {
2505:             try {
2506:                 $group_users = $groups->listUsers($group);
2507:             } catch (Horde_Group_Exception $e) {
2508:                 Horde::logMessage($e, 'ERR');
2509:                 continue;
2510:             }
2511: 
2512:             foreach ($group_users as $user) {
2513:                 if (!isset($recipients[$user])) {
2514:                     $recipients[$user] = self::_notificationPref($user, 'read', $calendar);
2515:                 }
2516:             }
2517:         }
2518: 
2519:         $addresses = array();
2520:         foreach ($recipients as $user => $vals) {
2521:             if (!$vals) {
2522:                 continue;
2523:             }
2524:             $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($user);
2525:             $email = $identity->getValue('from_addr');
2526:             if (strpos($email, '@') === false) {
2527:                 continue;
2528:             }
2529:             list($mailbox, $host) = explode('@', $email);
2530:             if (!isset($addresses[$vals['lang']][$vals['tf']][$vals['df']])) {
2531:                 $addresses[$vals['lang']][$vals['tf']][$vals['df']] = array();
2532:             }
2533:             $addresses[$vals['lang']][$vals['tf']][$vals['df']][] = Horde_Mime_Address::writeAddress($mailbox, $host, $identity->getValue('fullname'));
2534:         }
2535: 
2536:         if (!$addresses) {
2537:             return;
2538:         }
2539: 
2540:         foreach ($addresses as $lang => $twentyFour) {
2541:             $GLOBALS['registry']->setLanguageEnvironment($lang);
2542: 
2543:             switch ($action) {
2544:             case 'add':
2545:                 $subject = _("Event added:");
2546:                 $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.");
2547:                 break;
2548: 
2549:             case 'edit':
2550:                 $subject = _("Event edited:");
2551:                 $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.");
2552:                 break;
2553: 
2554:             case 'delete':
2555:                 $subject = _("Event deleted:");
2556:                 $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.");
2557:                 break;
2558:             }
2559: 
2560:             foreach ($twentyFour as $tf => $dateFormat) {
2561:                 foreach ($dateFormat as $df => $df_recipients) {
2562:                     $message = "\n"
2563:                         . sprintf($notification_message,
2564:                                   $event->title,
2565:                                   $share->get('name'),
2566:                                   $event->start->strftime($df),
2567:                                   $event->start->strftime($tf ? '%R' : '%I:%M%p'))
2568:                         . "\n\n" . $event->description;
2569: 
2570:                     $mime_mail = new Horde_Mime_Mail(array(
2571:                         'Subject' => $subject . ' ' . $event->title,
2572:                         'To' => implode(',', $df_recipients),
2573:                         'From' => $senderIdentity->getDefaultFromAddress(true),
2574:                         'User-Agent' => 'Kronolith ' . $GLOBALS['registry']->getVersion(),
2575:                         'body' => $message));
2576:                     Horde::logMessage(sprintf('Sending event notifications for %s to %s', $event->title, implode(', ', $df_recipients)), 'DEBUG');
2577:                     $mime_mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
2578:                 }
2579:             }
2580:         }
2581:     }
2582: 
2583:     /**
2584:      * Check for resource declines and push notice to stack if found.
2585:      *
2586:      * @param Kronolith_Event $event
2587:      *
2588:      * @throws Kronolith_Exception
2589:      */
2590:     static public function notifyOfResourceRejection($event)
2591:     {
2592:         $declined = array();
2593:         $accepted = array();
2594:         foreach ($event->getResources() as $id => $resource) {
2595:             if ($resource['response'] == self::RESPONSE_DECLINED) {
2596:                 $r = self::getDriver('Resource')->getResource($id);
2597:                 $declined[] = $r->get('name');
2598:             } elseif ($resource['response'] == self::RESPONSE_ACCEPTED) {
2599:                 $r = self::getDriver('Resource')->getResource($id);
2600:                 $accepted[] = $r->get('name');
2601:             }
2602: 
2603: 
2604:         }
2605:         if (count($declined)) {
2606:             $GLOBALS['notification']->push(sprintf(ngettext("The following resource has declined your request: %s",
2607:                                                             "The following resources have declined your request: %s",
2608:                                                             count($declined)),
2609:                                                     implode(", ", $declined)),
2610:                                            'horde.error');
2611:         }
2612:         if (count($accepted)) {
2613:              $GLOBALS['notification']->push(sprintf(ngettext("The following resource has accepted your request: %s",
2614:                                                             "The following resources have accepted your request: %s",
2615:                                                             count($accepted)),
2616:                                                     implode(", ", $accepted)),
2617:                                            'horde.success');
2618:         }
2619:     }
2620: 
2621:     /**
2622:      * Returns whether a user wants email notifications for a calendar.
2623:      *
2624:      * @access private
2625:      *
2626:      * @todo This method is causing a memory leak somewhere, noticeable if
2627:      *       importing a large amount of events.
2628:      *
2629:      * @param string $user      A user name.
2630:      * @param string $mode      The check "mode". If "owner", the method checks
2631:      *                          if the user wants notifications only for
2632:      *                          calendars he owns. If "read", the method checks
2633:      *                          if the user wants notifications for all
2634:      *                          calendars he has read access to, or only for
2635:      *                          shown calendars and the specified calendar is
2636:      *                          currently shown.
2637:      * @param string $calendar  The name of the calendar if mode is "read".
2638:      *
2639:      * @return mixed  The user's email, time, and language preferences if they
2640:      *                want a notification for this calendar.
2641:      */
2642:     static public function _notificationPref($user, $mode, $calendar = null)
2643:     {
2644:         $prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('kronolith', array(
2645:             'cache' => false,
2646:             'user' => $user
2647:         ));
2648:         $vals = array('lang' => $prefs->getValue('language'),
2649:                       'tf' => $prefs->getValue('twentyFour'),
2650:                       'df' => $prefs->getValue('date_format'));
2651: 
2652:         if ($prefs->getValue('event_notification_exclude_self') &&
2653:             $user == $GLOBALS['registry']->getAuth()) {
2654:             return false;
2655:         }
2656: 
2657:         switch ($prefs->getValue('event_notification')) {
2658:         case 'owner':
2659:             return $mode == 'owner' ? $vals : false;
2660: 
2661:         case 'read':
2662:             return $mode == 'read' ? $vals : false;
2663: 
2664:         case 'show':
2665:             if ($mode == 'read') {
2666:                 $display_calendars = unserialize($prefs->getValue('display_cals'));
2667:                 return in_array($calendar, $display_calendars) ? $vals : false;
2668:             }
2669:         }
2670: 
2671:         return false;
2672:     }
2673: 
2674:     /**
2675:      * Builds the body MIME part of a multipart message.
2676:      *
2677:      * @param Horde_View $view        A view to render the HTML and plain text
2678:      *                                templates for the messate.
2679:      * @param string $template        The template base name for the view.
2680:      * @param Horde_Mime_Part $image  The MIME part of a related image.
2681:      *
2682:      * @return Horde_Mime_Part  A multipart/alternative MIME part.
2683:      */
2684:     static public function buildMimeMessage(Horde_View $view, $template,
2685:                                             Horde_Mime_Part $image)
2686:     {
2687:         $multipart = new Horde_Mime_Part();
2688:         $multipart->setType('multipart/alternative');
2689:         $bodyText = new Horde_Mime_Part();
2690:         $bodyText->setType('text/plain');
2691:         $bodyText->setCharset('UTF-8');
2692:         $bodyText->setContents($view->render($template . '.plain.php'));
2693:         $bodyText->setDisposition('inline');
2694:         $multipart->addPart($bodyText);
2695:         $bodyHtml = new Horde_Mime_Part();
2696:         $bodyHtml->setType('text/html');
2697:         $bodyHtml->setCharset('UTF-8');
2698:         $bodyHtml->setContents($view->render($template . '.html.php'));
2699:         $bodyHtml->setDisposition('inline');
2700:         $related = new Horde_Mime_Part();
2701:         $related->setType('multipart/related');
2702:         $related->setContentTypeParameter('start', $bodyHtml->setContentId());
2703:         $related->addPart($bodyHtml);
2704:         $related->addPart($image);
2705:         $multipart->addPart($related);
2706:         return $multipart;
2707:     }
2708: 
2709:     /**
2710:      * Returns a MIME part for an image to be embedded into a HTML document.
2711:      *
2712:      * @param string $file  An image file name.
2713:      *
2714:      * @return Horde_Mime_Part  A MIME part representing the image.
2715:      */
2716:     static public function getImagePart($file)
2717:     {
2718:         $background = Horde_Themes::img($file);
2719:         $image = new Horde_Mime_Part();
2720:         $image->setType('image/png');
2721:         $image->setContents(file_get_contents($background->fs));
2722:         $image->setContentId();
2723:         $image->setDisposition('attachment');
2724:         return $image;
2725:     }
2726: 
2727:     /**
2728:      * @return Horde_Date
2729:      */
2730:     static public function currentDate()
2731:     {
2732:         if ($date = Horde_Util::getFormData('date')) {
2733:             return new Horde_Date($date . '000000');
2734:         }
2735:         if ($date = Horde_Util::getFormData('datetime')) {
2736:             return new Horde_Date($date);
2737:         }
2738: 
2739:         return new Horde_Date($_SERVER['REQUEST_TIME']);
2740:     }
2741: 
2742:     /**
2743:      * Parses a complete date-time string into a Horde_Date object.
2744:      *
2745:      * @param string $date       The date-time string to parse.
2746:      * @param boolean $withtime  Whether time is included in the string.
2747:      *
2748:      * @return Horde_Date  The parsed date.
2749:      * @throws Horde_Date_Exception
2750:      */
2751:     static public function parseDate($date, $withtime = true)
2752:     {
2753:         // strptime() is not available on Windows.
2754:         if (!function_exists('strptime')) {
2755:             return new Horde_Date($date);
2756:         }
2757: 
2758:         // strptime() is locale dependent, i.e. %p is not always matching
2759:         // AM/PM. Set the locale to C to workaround this, but grab the
2760:         // locale's D_FMT before that.
2761:         $format = Horde_Nls::getLangInfo(D_FMT);
2762:         if ($withtime) {
2763:             $format .= ' '
2764:                 . ($GLOBALS['prefs']->getValue('twentyFour') ? '%H:%M' : '%I:%M %p');
2765:         }
2766:         $old_locale = setlocale(LC_TIME, 0);
2767:         setlocale(LC_TIME, 'C');
2768: 
2769:         // Try exact format match first.
2770:         $date_arr = strptime($date, $format);
2771:         setlocale(LC_TIME, $old_locale);
2772: 
2773:         if (!$date_arr) {
2774:             // Try with locale dependent parsing next.
2775:             $date_arr = strptime($date, $format);
2776:             if (!$date_arr) {
2777:                 // Try throwing at Horde_Date finally.
2778:                 return new Horde_Date($date);
2779:             }
2780:         }
2781: 
2782:         return new Horde_Date(
2783:             array('year'  => $date_arr['tm_year'] + 1900,
2784:                   'month' => $date_arr['tm_mon'] + 1,
2785:                   'mday'  => $date_arr['tm_mday'],
2786:                   'hour'  => $date_arr['tm_hour'],
2787:                   'min'   => $date_arr['tm_min'],
2788:                   'sec'   => $date_arr['tm_sec']));
2789:     }
2790: 
2791:     /**
2792:      * @param string $tabname
2793:      */
2794:     static public function tabs($tabname = null)
2795:     {
2796:         $date = self::currentDate();
2797:         $date_stamp = $date->dateString();
2798: 
2799:         $tabs = new Horde_Core_Ui_Tabs('view', Horde_Variables::getDefaultVariables());
2800:         $tabs->preserve('date', $date_stamp);
2801: 
2802:         $tabs->addTab(_("Day"), Horde::url('day.php'),
2803:                       array('tabname' => 'day', 'id' => 'tabday', 'onclick' => 'return ShowView(\'Day\', \'' . $date_stamp . '\');'));
2804:         $tabs->addTab(_("Work Week"), Horde::url('workweek.php'),
2805:                       array('tabname' => 'workweek', 'id' => 'tabworkweek', 'onclick' => 'return ShowView(\'WorkWeek\', \'' . $date_stamp . '\');'));
2806:         $tabs->addTab(_("Week"), Horde::url('week.php'),
2807:                       array('tabname' => 'week', 'id' => 'tabweek', 'onclick' => 'return ShowView(\'Week\', \'' . $date_stamp . '\');'));
2808:         $tabs->addTab(_("Month"), Horde::url('month.php'),
2809:                       array('tabname' => 'month', 'id' => 'tabmonth', 'onclick' => 'return ShowView(\'Month\', \'' . $date_stamp . '\');'));
2810:         $tabs->addTab(_("Year"), Horde::url('year.php'),
2811:                       array('tabname' => 'year', 'id' => 'tabyear', 'onclick' => 'return ShowView(\'Year\', \'' . $date_stamp . '\');'));
2812: 
2813:         if ($tabname === null) {
2814:             $tabname = basename($_SERVER['PHP_SELF']) == 'index.php' ? $GLOBALS['prefs']->getValue('defaultview') : str_replace('.php', '', basename($_SERVER['PHP_SELF']));
2815:         }
2816:         echo $tabs->render($tabname);
2817:     }
2818: 
2819:     /**
2820:      * @param string $tabname
2821:      * @param Kronolith_Event $event
2822:      */
2823:     static public function eventTabs($tabname, $event)
2824:     {
2825:         if (!$event->initialized) {
2826:             return;
2827:         }
2828: 
2829:         $tabs = new Horde_Core_Ui_Tabs('event', Horde_Variables::getDefaultVariables());
2830: 
2831:         $date = self::currentDate();
2832:         $tabs->preserve('datetime', $date->dateString());
2833: 
2834:         $tabs->addTab(
2835:             htmlspecialchars($event->getTitle()),
2836:             $event->getViewUrl(),
2837:             array('tabname' => 'Event',
2838:                   'id' => 'tabEvent',
2839:                   'onclick' => 'return ShowTab(\'Event\');'));
2840:         /* We check for read permissions, because we can always save a copy if
2841:          * we can read the event. */
2842:         if ((!$event->private ||
2843:              $event->creator == $GLOBALS['registry']->getAuth()) &&
2844:             $event->hasPermission(Horde_Perms::READ) &&
2845:             self::getDefaultCalendar(Horde_Perms::EDIT)) {
2846:             $tabs->addTab(
2847:                 $event->hasPermission(Horde_Perms::EDIT) ? _("_Edit") : _("Save As New"),
2848:                 $event->getEditUrl(),
2849:                 array('tabname' => 'EditEvent',
2850:                       'id' => 'tabEditEvent',
2851:                       'onclick' => 'return ShowTab(\'EditEvent\');'));
2852:         }
2853:         if ($event->hasPermission(Horde_Perms::DELETE)) {
2854:             $tabs->addTab(
2855:                 _("De_lete"),
2856:                 $event->getDeleteUrl(array('confirm' => 1)),
2857:                 array('tabname' => 'DeleteEvent',
2858:                       'id' => 'tabDeleteEvent',
2859:                       'onclick' => 'return ShowTab(\'DeleteEvent\');'));
2860:         }
2861:         $tabs->addTab(
2862:             _("Export"),
2863:             $event->getExportUrl(),
2864:             array('tabname' => 'ExportEvent',
2865:                   'id' => 'tabExportEvent'));
2866: 
2867:         echo $tabs->render($tabname);
2868:     }
2869: 
2870:     /**
2871:      * Attempts to return a single, concrete Kronolith_Driver instance based
2872:      * on a driver name.
2873:      *
2874:      * This singleton method automatically retrieves all parameters required
2875:      * for the specified driver.
2876:      *
2877:      * @param string $driver    The type of concrete Kronolith_Driver subclass
2878:      *                          to return.
2879:      * @param string $calendar  The calendar name. The format depends on the
2880:      *                          driver being used.
2881:      *
2882:      * @return Kronolith_Driver  The newly created concrete Kronolith_Driver
2883:      *                           instance.
2884:      * @throws Kronolith_Exception
2885:      */
2886:     static public function getDriver($driver = null, $calendar = null)
2887:     {
2888:         switch ($driver) {
2889:         case 'internal':
2890:             $driver = '';
2891:             break;
2892:         case 'external':
2893:         case 'tasklists':
2894:             $driver = 'Horde';
2895:             break;
2896:         case 'remote':
2897:             $driver = 'Ical';
2898:             break;
2899:         case 'holiday':
2900:             $driver = 'Holidays';
2901:             break;
2902:         case 'resource':
2903:             $driver = 'Resource';
2904:             break;
2905:         }
2906: 
2907:         if (empty($driver)) {
2908:             $driver = Horde_String::ucfirst($GLOBALS['conf']['calendar']['driver']);
2909:         }
2910: 
2911:         if (!isset(self::$_instances[$driver])) {
2912:             switch ($driver) {
2913:             case 'Sql':
2914:             case 'Resource':
2915:                 $params = Horde::getDriverConfig('calendar', 'sql');
2916:                 if ($params['driverconfig'] != 'Horde') {
2917:                     $customParams = $params;
2918:                     unset($customParams['driverconfig'], $customParams['table'], $customParams['utc']);
2919:                     $params['db'] = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create('kronolith', $customParams);
2920:                 } else {
2921:                     $params['db'] = $GLOBALS['injector']->getInstance('Horde_Db_Adapter');
2922:                 }
2923:                 break;
2924: 
2925:             case 'Kolab':
2926:                 $params['storage'] = $GLOBALS['injector']->getInstance('Horde_Kolab_Storage');
2927:                 break;
2928: 
2929:             case 'Ical':
2930:             case 'Mock':
2931:                 $params = array();
2932:                 break;
2933: 
2934:             case 'Horde':
2935:                 $params['registry'] = $GLOBALS['registry'];
2936:                 break;
2937: 
2938:             case 'Holidays':
2939:                 if (empty($GLOBALS['conf']['holidays']['enable'])) {
2940:                     throw new Kronolith_Exception(_("Holidays are disabled"));
2941:                 }
2942:                 $params['language'] = $GLOBALS['language'];
2943:                 break;
2944: 
2945:             default:
2946:                 throw new Kronolith_Exception('No calendar driver specified');
2947:                 break;
2948:             }
2949: 
2950:             self::$_instances[$driver] = $GLOBALS['injector']->getInstance('Kronolith_Factory_Driver')->create($driver, $params);
2951:         }
2952: 
2953:         if (!is_null($calendar)) {
2954:             self::$_instances[$driver]->open($calendar);
2955:             /* Remote calendar parameters are per calendar. */
2956:             if ($driver == 'Ical') {
2957:                 self::$_instances[$driver]->setParams(self::getRemoteParams($calendar));
2958:             }
2959:         }
2960: 
2961:         return self::$_instances[$driver];
2962:     }
2963: 
2964:     /**
2965:      * Check for HTTP authentication credentials
2966:      */
2967:     static public function getRemoteParams($calendar)
2968:     {
2969:         if (empty($calendar)) {
2970:             return array();
2971:         }
2972: 
2973:         $cals = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
2974:         foreach ($cals as $cal) {
2975:             if ($cal['url'] == $calendar) {
2976:                 $user = isset($cal['user']) ? $cal['user'] : '';
2977:                 $password = isset($cal['password']) ? $cal['password'] : '';
2978:                 $key = $GLOBALS['registry']->getAuthCredential('password');
2979:                 if ($key && $password) {
2980:                     $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
2981:                     $user = $secret->read($key, base64_decode($user));
2982:                     $password = $secret->read($key, base64_decode($password));
2983:                 }
2984:                 if (!empty($user)) {
2985:                     return array('user' => $user, 'password' => $password);
2986:                 }
2987:                 return array();
2988:             }
2989:         }
2990: 
2991:         return array();
2992:     }
2993: 
2994:     /**
2995:      * Get a named Kronolith_View_* object and load it with the
2996:      * appropriate date parameters.
2997:      *
2998:      * @param string $view The name of the view.
2999:      */
3000:     static public function getView($view)
3001:     {
3002:         switch ($view) {
3003:         case 'Day':
3004:         case 'Month':
3005:         case 'Week':
3006:         case 'WorkWeek':
3007:         case 'Year':
3008:             $class = 'Kronolith_View_' . $view;
3009:             return new $class(self::currentDate());
3010: 
3011:         case 'Event':
3012:         case 'EditEvent':
3013:         case 'DeleteEvent':
3014:         case 'ExportEvent':
3015:             try {
3016:                 if ($uid = Horde_Util::getFormData('uid')) {
3017:                     $event = self::getDriver()->getByUID($uid);
3018:                 } else {
3019:                     $event = self::getDriver(Horde_Util::getFormData('type'),
3020:                                              Horde_Util::getFormData('calendar'))
3021:                         ->getEvent(Horde_Util::getFormData('eventID'),
3022:                                    Horde_Util::getFormData('datetime'));
3023:                 }
3024:             } catch (Horde_Exception $e) {
3025:                 $event = $e->getMessage();
3026:             }
3027:             switch ($view) {
3028:             case 'Event':
3029:                 if (!is_string($event) &&
3030:                     !$event->hasPermission(Horde_Perms::READ)) {
3031:                     $event = _("Permission Denied");
3032:                 }
3033:                 return new Kronolith_View_Event($event);
3034:             case 'EditEvent':
3035:                 /* We check for read permissions, because we can always save a
3036:                  * copy if we can read the event. */
3037:                 if (!is_string($event) &&
3038:                     !$event->hasPermission(Horde_Perms::READ)) {
3039:                     $event = _("Permission Denied");
3040:                 }
3041:                 return new Kronolith_View_EditEvent($event);
3042:             case 'DeleteEvent':
3043:                 if (!is_string($event) &&
3044:                     !$event->hasPermission(Horde_Perms::DELETE)) {
3045:                     $event = _("Permission Denied");
3046:                 }
3047:                 return new Kronolith_View_DeleteEvent($event);
3048:             case 'ExportEvent':
3049:                 if (!is_string($event) &&
3050:                     !$event->hasPermission(Horde_Perms::READ)) {
3051:                     $event = _("Permission Denied");
3052:                 }
3053:                 return new Kronolith_View_ExportEvent($event);
3054:             }
3055:         }
3056:     }
3057: 
3058:     /**
3059:      * Should we show event location, based on the show_location pref?
3060:      */
3061:     static public function viewShowLocation()
3062:     {
3063:         $show = @unserialize($GLOBALS['prefs']->getValue('show_location'));
3064:         return @in_array('screen', $show);
3065:     }
3066: 
3067:     /**
3068:      * Should we show event time, based on the show_time preference?
3069:      */
3070:     static public function viewShowTime()
3071:     {
3072:         $show = @unserialize($GLOBALS['prefs']->getValue('show_time'));
3073:         return @in_array('screen', $show);
3074:     }
3075: 
3076:     /**
3077:      * Returns the background color for a calendar.
3078:      *
3079:      * @param array|Horde_Share_Object $calendar  A calendar share or a hash
3080:      *                                            from a remote calender
3081:      *                                            definition.
3082:      *
3083:      * @return string  A HTML color code.
3084:      */
3085:     static public function backgroundColor($calendar)
3086:     {
3087:         $color = '';
3088:         if (!is_array($calendar)) {
3089:             $color = $calendar->get('color');
3090:         } elseif (isset($calendar['color'])) {
3091:             $color = $calendar['color'];
3092:         }
3093:         return empty($color) ? '#dddddd' : $color;
3094:     }
3095: 
3096:     /**
3097:      * Returns the foreground color for a calendar or a background color.
3098:      *
3099:      * @param array|Horde_Share_Object|string $calendar  A color string, a
3100:      *                                                   calendar share or a
3101:      *                                                   hash from a remote
3102:      *                                                   calender definition.
3103:      *
3104:      * @return string  A HTML color code.
3105:      */
3106:     static public function foregroundColor($calendar)
3107:     {
3108:         return Horde_Image::brightness(is_string($calendar) ? $calendar : self::backgroundColor($calendar)) < 128 ? '#fff' : '#000';
3109:     }
3110: 
3111:     /**
3112:      * Returns the CSS color definition for a calendar.
3113:      *
3114:      * @param array|Horde_Share_Object $calendar  A calendar share or a hash
3115:      *                                            from a remote calender
3116:      *                                            definition.
3117:      * @param boolean $with_attribute             Whether to wrap the colors
3118:      *                                            inside a "style" attribute.
3119:      *
3120:      * @return string  A CSS string with color definitions.
3121:      */
3122:     static public function getCSSColors($calendar, $with_attribute = true)
3123:     {
3124:         $css = 'background-color:' . self::backgroundColor($calendar) . ';color:' . self::foregroundColor($calendar);
3125:         if ($with_attribute) {
3126:             $css = ' style="' . $css . '"';
3127:         }
3128:         return $css;
3129:     }
3130: 
3131:     /**
3132:      * Returns whether to display the ajax view.
3133:      *
3134:      * return boolean  True if the ajax view should be displayed.
3135:      */
3136:     static public function showAjaxView()
3137:     {
3138:         global $prefs, $session;
3139: 
3140:         $mode = $session->get('horde', 'mode');
3141:         return ($mode == 'dynamic' || ($prefs->getValue('dynamic_view') && $mode == 'auto')) && Horde::ajaxAvailable();
3142:     }
3143: 
3144:     /**
3145:      * Sorts an event list.
3146:      *
3147:      * @since Kronolith 3.0.5
3148:      *
3149:      * @param array $days  A list of days with events.
3150:      *
3151:      * @return array  The sorted day list.
3152:      */
3153:     static public function sortEvents($days)
3154:     {
3155:         foreach ($days as $day => $devents) {
3156:             if (count($devents)) {
3157:                 uasort($devents, array('Kronolith', '_sortEventStartTime'));
3158:                 $days[$day] = $devents;
3159:             }
3160:         }
3161:         return $days;
3162:     }
3163: 
3164:     /**
3165:      * Used with usort() to sort events based on their start times.
3166:      */
3167:     static protected function _sortEventStartTime($a, $b)
3168:     {
3169:         $diff = $a->start->compareDateTime($b->start);
3170:         if ($diff == 0) {
3171:             return strcoll($a->title, $b->title);
3172:         } else {
3173:             return $diff;
3174:         }
3175:     }
3176: 
3177:     /**
3178:      * Obtain a Kronolith_Tagger instance
3179:      *
3180:      * @return Kronolith_Tagger
3181:      */
3182:     static public function getTagger()
3183:     {
3184:         if (empty(self::$_tagger)) {
3185:             self::$_tagger = new Kronolith_Tagger();
3186:         }
3187:         return self::$_tagger;
3188:     }
3189: 
3190:     /**
3191:      * Obtain an internal calendar. Use this where we don't know if we will
3192:      * have a Horde_Share or a Kronolith_Resource based calendar.
3193:      *
3194:      * @param string $target  The calendar id to retrieve.
3195:      *
3196:      * @return Kronolith_Resource|Horde_Share_Object
3197:      * @throws Kronolith_Exception
3198:      */
3199:     static public function getInternalCalendar($target)
3200:     {
3201:         if (Kronolith::getDriver('Resource')->isResourceCalendar($target)) {
3202:             $driver = self::getDriver('Resource');
3203:             $id = $driver->getResourceIdByCalendar($target);
3204:             return $driver->getResource($id);
3205:         } else {
3206:             return $GLOBALS['kronolith_shares']->getShare($target);
3207:         }
3208:     }
3209: 
3210:     /**
3211:      * Determines parameters needed to do an address search
3212:      *
3213:      * @return array  An array with two keys: 'fields' and 'sources'.
3214:      */
3215:     static public function getAddressbookSearchParams()
3216:     {
3217:         $src = json_decode($GLOBALS['prefs']->getValue('search_sources'));
3218:         if (empty($src)) {
3219:             $src = array();
3220:         }
3221: 
3222:         $fields = json_decode($GLOBALS['prefs']->getValue('search_fields'), true);
3223:         if (empty($fields)) {
3224:             $fields = array();
3225:         }
3226: 
3227:         return array(
3228:             'fields' => $fields,
3229:             'sources' => $src
3230:         );
3231:     }
3232: 
3233:     /**
3234:      * Checks whether an API (application) exists and the user has permission
3235:      * to access it.
3236:      *
3237:      * @param string $api    The API (application) to check.
3238:      * @param integer $perm  The permission to check.
3239:      *
3240:      * @return boolean  True if the API can be accessed.
3241:      */
3242:     static public function hasApiPermission($api, $perm = Horde_Perms::READ)
3243:     {
3244:         $app = $GLOBALS['registry']->hasInterface($api);
3245:         return ($app && $GLOBALS['registry']->hasPermission($app, $perm));
3246:     }
3247: 
3248:     /**
3249:      * Remove all events owned by the specified user in all calendars.
3250:      *
3251:      * @since  Kronolith 3.0.10
3252:      *
3253:      * @param string $user  The user name to delete events for.
3254:      *
3255:      * @throws Kronolith_Exception
3256:      * @throws Horde_Exception_NotFound
3257:      * @throws Horde_Exception_PermissionDenied
3258:      */
3259:     static public function removeUserEvents($user)
3260:     {
3261:         if (!$GLOBALS['registry']->isAdmin()) {
3262:             throw new Horde_Exception_PermissionDenied();
3263:         }
3264: 
3265:         try {
3266:             $shares = $GLOBALS['kronolith_shares']->listShares(
3267:                 $user, array('perm' => Horde_Perms::EDIT));
3268:         } catch (Horde_Share_Exception $e) {
3269:             Horde::logMessage($shares, 'ERR');
3270:             throw new Kronolith_Exception($shares);
3271:         }
3272: 
3273:         foreach (array_keys($shares) as $calendar) {
3274:             $driver = Kronolith::getDriver(null, $calendar);
3275:             $events = $driver->listEvents(null, null, false, false, false);
3276:             $uids = array();
3277:             foreach ($events as $dayevents) {
3278:                 foreach ($dayevents as $event) {
3279:                     $uids[] = $event->uid;
3280:                 }
3281:             }
3282:             foreach ($uids as $uid) {
3283:                 $event = $driver->getByUID($uid, array($calendar));
3284:                 $driver->deleteEvent($event->id);
3285:             }
3286:         }
3287:     }
3288: 
3289: }
3290: 
API documentation generated by ApiGen