Vastly improve searching: all calendar types can be searched now and searching
authorJan Schneider <jan@horde.org>
Thu, 28 May 2009 12:39:53 +0000 (14:39 +0200)
committerJan Schneider <jan@horde.org>
Thu, 28 May 2009 12:47:13 +0000 (14:47 +0200)
of recurring events has been much improved.
If searching in a time range, all recurring instances are returned, if
searching without end, only the next recurrence is returned, if searching
without start, the first recurrence is returned.

kronolith/docs/CHANGES
kronolith/lib/Driver.php
kronolith/lib/Driver/Sql.php
kronolith/lib/Kronolith.php
kronolith/search.php
kronolith/templates/search/event_summaries.inc
kronolith/templates/search/search_advanced.inc

index 0c0dd84..305368c 100644 (file)
@@ -2,6 +2,8 @@
 v3.0-git
 --------
 
+[jan] Allow searching of any type of calendar and improve searching of
+      recurring events.
 [cjh] With only SHOW permissions, display event titles as "busy".
 [mjr] Replace categories and keywords by tags.
 [jan] Set colors per calendar (Request #7480).
index f2b2eb3..823f4b1 100644 (file)
@@ -122,12 +122,12 @@ class Kronolith_Driver
     /**
      * Searches a calendar.
      *
-     * @param object Kronolith_Event $query  A Kronolith_Event object with the
-     *                                       criteria to search for.
+     * @param object $query  An object with the criteria to search for.
+     * @param boolean $json  Store the results of the events' toJson() method?
      *
      * @return mixed  An array of Kronolith_Events or a PEAR_Error.
      */
-    public function search($query)
+    public function search($query, $json = false)
     {
         /* Our default implementation first gets <em>all</em> events in a
          * specific period, and then filters based on the actual values that
@@ -140,30 +140,27 @@ class Kronolith_Driver
             return $events;
         }
 
-        foreach ($events as $eventid) {
-            $event = $this->getEvent($eventid);
-            if (is_a($event, 'PEAR_Error')) {
-                return $event;
-            }
-
-            if ((((!isset($query->start) ||
-                   $event->end->compareDateTime($query->start) > 0) &&
-                  (!isset($query->end) ||
-                   $event->end->compareDateTime($query->end) < 0)) ||
-                 ($event->recurs() &&
-                  $event->end->compareDateTime($query->start) >= 0 &&
-                  $event->start->compareDateTime($query->end) <= 0)) &&
-                (empty($query->title) ||
-                 stristr($event->getTitle(), $query->title)) &&
-                (empty($query->location) ||
-                 stristr($event->getLocation(), $query->location)) &&
-                (empty($query->description) ||
-                 stristr($event->getDescription(), $query->description)) &&
-                (empty($query->creatorID) ||
-                 stristr($event->getCreatorID(), $query->creatorID))  &&
-                (!isset($query->status) ||
-                 $event->getStatus() == $query->status)) {
-                $results[] = $event;
+        foreach ($events as $day => $day_events) {
+            foreach ($day_events as $event) {
+                if ((((!isset($query->start) ||
+                       $event->end->compareDateTime($query->start) > 0) &&
+                      (!isset($query->end) ||
+                       $event->end->compareDateTime($query->end) < 0)) ||
+                     ($event->recurs() &&
+                      $event->end->compareDateTime($query->start) >= 0 &&
+                      $event->start->compareDateTime($query->end) <= 0)) &&
+                    (empty($query->title) ||
+                     stristr($event->getTitle(), $query->title)) &&
+                    (empty($query->location) ||
+                     stristr($event->getLocation(), $query->location)) &&
+                    (empty($query->description) ||
+                     stristr($event->getDescription(), $query->description)) &&
+                    (empty($query->creatorID) ||
+                     stristr($event->getCreatorID(), $query->creatorID))  &&
+                    (!isset($query->status) ||
+                     $event->getStatus() == $query->status)) {
+                    Kronolith::addEvents($results, $event, $event->start, $event->end, false, $json, false);
+                }
             }
         }
 
index fd5d437..d37a8d7 100644 (file)
@@ -95,7 +95,15 @@ class Kronolith_Driver_Sql extends Kronolith_Driver
         return $events;
     }
 
-    public function search($query)
+    /**
+     * Searches a calendar.
+     *
+     * @param object $query  An object with the criteria to search for.
+     * @param boolean $json  Store the results of the events' toJson() method?
+     *
+     * @return mixed  An array of Kronolith_Events or a PEAR_Error.
+     */
+    public function search($query, $json = false)
     {
         /* Build SQL conditions based on the query string. */
         $cond = '((';
@@ -164,13 +172,34 @@ class Kronolith_Driver_Sql extends Kronolith_Driver
             return $eventIds;
         }
 
+        $now = new Horde_Date($_SERVER['REQUEST_TIME']);
         $events = array();
         foreach ($eventIds as $eventId) {
             $event = $this->getEvent($eventId);
             if (is_a($event, 'PEAR_Error')) {
                 return $event;
             }
-            $events[] = $event;
+            $showRecurrence = true;
+            if ($event->recurs()) {
+                if (empty($query->end)) {
+                    $eventEnd = $event->recurrence->nextRecurrence($now);
+                    if (!$eventEnd) {
+                        continue;
+                    }
+                } else {
+                    $eventEnd = $query->end;
+                }
+                if (empty($query->start)) {
+                    $eventStart = $event->start;
+                    $showRecurrence = false;
+                } else {
+                    $eventStart = $query->start;
+                }
+            } else {
+                $eventStart = $event->start;
+                $eventEnd = $event->end;
+            }
+            Kronolith::addEvents($events, $event, $eventStart, $eventEnd, $showRecurrence, $json, false);
         }
 
         return $events;
index 5955235..53cd5cc 100644 (file)
@@ -566,26 +566,34 @@ class Kronolith
     /**
      * Searches for events with the given properties.
      *
-     * @param object $query  The search query.
+     * @param object $query     The search query.
+     * @param string $calendar  The calendar to search in the form
+     *                          "Driver|calendar_id".
      *
      * @return array  The events.
      */
-    public static function search($query)
+    public static function search($query, $calendar = null)
     {
-        $kronolith_driver = Kronolith::getDriver();
-
-        if (!isset($query->calendars)) {
-            $calendars = $GLOBALS['display_calendars'];
+        if ($calendar) {
+            $driver = explode('|', $calendar, 2);
+            $calendars = array($driver[0] => array($driver[1]));
         } else {
-            $calendars = $query->calendars;
+            $calendars = array(
+                String::ucfirst($GLOBALS['conf']['calendar']['driver']) => $GLOBALS['display_calendars'],
+                'Horde' => $GLOBALS['display_external_calendars'],
+                'Ical' => $GLOBALS['display_remote_calendars']);
+            if (!empty($GLOBALS['conf']['holidays']['enable'])) {
+                $calendars['Holidays'] = unserialize($GLOBALS['prefs']->getValue('holiday_drivers'));
+            }
         }
 
         $events = array();
-        foreach ($calendars as $cal) {
-            $kronolith_driver->open($cal);
-            $retevents = $kronolith_driver->search($query);
-            foreach ($retevents as $event) {
-                $events[] = $event;
+        foreach ($calendars as $type => $list) {
+            $kronolith_driver = Kronolith::getDriver($type);
+            foreach ($list as $cal) {
+                $kronolith_driver->open($cal);
+                $retevents = $kronolith_driver->search($query);
+                Kronolith::mergeEvents($events, $retevents);
             }
         }
 
@@ -692,6 +700,7 @@ class Kronolith
                 $results[$day] = $day_events;
             }
         }
+        ksort($results);
     }
 
     /**
@@ -700,7 +709,8 @@ class Kronolith
      * @access private
      */
     public static function addEvents(&$results, &$event, $startDate, $endDate,
-                                     $showRecurrence, $json)
+                                     $showRecurrence, $json,
+                                     $coverDates = false)
     {
         if ($event->recurs() && $showRecurrence) {
             /* Recurring Event. */
@@ -742,7 +752,11 @@ class Kronolith
                 if (!$event->recurrence->hasException($event->start->year,
                                                       $event->start->month,
                                                       $event->start->mday)) {
-                    Kronolith::addCoverDates($results, $event, $event->start, $event->end, $json);
+                    if ($coverDates) {
+                        Kronolith::addCoverDates($results, $event, $event->start, $event->end, $json);
+                    } else {
+                        $results[$event->start->dateString()][$event->getId()] = $json ? $event->toJson() : $event;
+                    }
                 }
 
                 /* Start searching for recurrences from the day after it
@@ -762,7 +776,15 @@ class Kronolith
                     $nextEnd->mday  += $diff[2];
                     $nextEnd->hour  += $diff[3];
                     $nextEnd->min   += $diff[4];
-                    Kronolith::addCoverDates($results, $event, $next, $nextEnd, $json);
+                    if ($coverDates) {
+                        Kronolith::addCoverDates($results, $event, $next, $nextEnd, $json);
+                    } else {
+                        $addEvent = clone $event;
+                        $addEvent->start = $next;
+                        $addEvent->end = $nextEnd;
+                        $results[$addEvent->start->dateString()][$addEvent->getId()] = $json ? $addEvent->toJson() : $addEvent;
+
+                    }
                 }
                 $next = $event->recurrence->nextRecurrence(
                     array('year' => $next->year,
@@ -773,85 +795,89 @@ class Kronolith
                           'sec' => $next->sec));
             }
         } else {
-            /* Event only occurs once. */
-            $allDay = $event->isAllDay();
-
-            /* Work out what day it starts on. */
-            if ($event->start->compareDateTime($startDate) < 0) {
-                /* It started before the beginning of the period. */
-                $eventStart = clone $startDate;
+            if (!$coverDates) {
+                $results[$event->start->dateString()][$event->getId()] = $json ? $event->toJson() : $event;
             } else {
-                $eventStart = clone $event->start;
-            }
+                /* Event only occurs once. */
+                $allDay = $event->isAllDay();
 
-            /* Work out what day it ends on. */
-            if ($event->end->compareDateTime($endDate) > 0) {
-                /* Ends after the end of the period. */
-                $eventEnd = clone $endDate;
-            } else {
-                /* If the event doesn't end at 12am set the end date to the
-                 * current end date. If it ends at 12am and does not end at
-                 * the same time that it starts (0 duration), set the end date
-                 * to the previous day's end date. */
-                if ($event->end->hour != 0 ||
-                    $event->end->min != 0 ||
-                    $event->end->sec != 0 ||
-                    $event->start->compareDateTime($event->end) == 0 ||
-                    $allDay) {
-                    $eventEnd = clone $event->end;
+                /* Work out what day it starts on. */
+                if ($event->start->compareDateTime($startDate) < 0) {
+                    /* It started before the beginning of the period. */
+                    $eventStart = clone $startDate;
                 } else {
-                    $eventEnd = new Horde_Date(
-                        array('hour' =>  23,
-                              'min' =>   59,
-                              'sec' =>   59,
-                              'month' => $event->end->month,
-                              'mday' =>  $event->end->mday - 1,
-                              'year' =>  $event->end->year));
+                    $eventStart = clone $event->start;
                 }
-            }
 
-            /* Add the event to all the days it covers. This is
-             * similar to Kronolith::addCoverDates(), but for days in
-             * between the start and end day, the range is midnight to
-             * midnight, and for the edge days it's start to midnight,
-             * and midnight to end. */
-            $i = $eventStart->mday;
-            $loopDate = new Horde_Date(array('month' => $eventStart->month,
-                                             'mday' => $i,
-                                             'year' => $eventStart->year));
-            while ($loopDate->compareDateTime($eventEnd) <= 0) {
-                if (!$allDay ||
-                    $loopDate->compareDateTime($eventEnd) != 0) {
-                    $addEvent = clone $event;
-
-                    /* If this is the start day, set the start time to
-                     * the real start time, otherwise set it to
-                     * 00:00 */
-                    if ($loopDate->compareDate($eventStart) == 0) {
-                        $addEvent->start = $eventStart;
+                /* Work out what day it ends on. */
+                if ($event->end->compareDateTime($endDate) > 0) {
+                    /* Ends after the end of the period. */
+                    $eventEnd = clone $endDate;
+                } else {
+                    /* If the event doesn't end at 12am set the end date to the
+                     * current end date. If it ends at 12am and does not end at
+                     * the same time that it starts (0 duration), set the end date
+                     * to the previous day's end date. */
+                    if ($event->end->hour != 0 ||
+                        $event->end->min != 0 ||
+                        $event->end->sec != 0 ||
+                        $event->start->compareDateTime($event->end) == 0 ||
+                        $allDay) {
+                        $eventEnd = clone $event->end;
                     } else {
-                        $addEvent->start = new Horde_Date(array(
-                            'hour' => 0, 'min' => 0, 'sec' => 0,
-                            'month' => $loopDate->month, 'mday' => $loopDate->mday, 'year' => $loopDate->year));
+                        $eventEnd = new Horde_Date(
+                            array('hour' =>  23,
+                                  'min' =>   59,
+                                  'sec' =>   59,
+                                  'month' => $event->end->month,
+                                  'mday' =>  $event->end->mday - 1,
+                                  'year' =>  $event->end->year));
                     }
+                }
 
-                    /* If this is the end day, set the end time to the
-                     * real event end, otherwise set it to 23:59. */
-                    if ($loopDate->compareDate($eventEnd) == 0) {
-                        $addEvent->end = $eventEnd;
-                    } else {
-                        $addEvent->end = new Horde_Date(array(
-                            'hour' => 23, 'min' => 59, 'sec' => 59,
-                            'month' => $loopDate->month, 'mday' => $loopDate->mday, 'year' => $loopDate->year));
+                /* Add the event to all the days it covers. This is similar to
+                 * Kronolith::addCoverDates(), but for days in between the
+                 * start and end day, the range is midnight to midnight, and
+                 * for the edge days it's start to midnight, and midnight to
+                 * end. */
+                $i = $eventStart->mday;
+                $loopDate = new Horde_Date(array('month' => $eventStart->month,
+                                                 'mday' => $i,
+                                                 'year' => $eventStart->year));
+                while ($loopDate->compareDateTime($eventEnd) <= 0) {
+                    if (!$allDay ||
+                        $loopDate->compareDateTime($eventEnd) != 0) {
+                        $addEvent = clone $event;
+
+                        /* If this is the start day, set the start time to
+                         * the real start time, otherwise set it to
+                         * 00:00 */
+                        if ($loopDate->compareDate($eventStart) == 0) {
+                            $addEvent->start = $eventStart;
+                        } else {
+                            $addEvent->start = new Horde_Date(array(
+                                'hour' => 0, 'min' => 0, 'sec' => 0,
+                                'month' => $loopDate->month, 'mday' => $loopDate->mday, 'year' => $loopDate->year));
+                        }
+
+                        /* If this is the end day, set the end time to the
+                         * real event end, otherwise set it to 23:59. */
+                        if ($loopDate->compareDate($eventEnd) == 0) {
+                            $addEvent->end = $eventEnd;
+                        } else {
+                            $addEvent->end = new Horde_Date(array(
+                                'hour' => 23, 'min' => 59, 'sec' => 59,
+                                'month' => $loopDate->month, 'mday' => $loopDate->mday, 'year' => $loopDate->year));
+                        }
+
+                        $results[$loopDate->dateString()][$addEvent->getId()] = $json ? $addEvent->toJson($allDay) : $addEvent;
                     }
 
-                    $results[$loopDate->dateString()][$addEvent->getId()] = $json ? $addEvent->toJson($allDay) : $addEvent;
+                    $loopDate = new Horde_Date(
+                        array('month' => $eventStart->month,
+                              'mday' => ++$i,
+                              'year' => $eventStart->year));
                 }
-
-                $loopDate = new Horde_Date(
-                    array('month' => $eventStart->month,
-                          'mday' => ++$i,
-                          'year' => $eventStart->year));
             }
         }
         ksort($results);
@@ -871,10 +897,7 @@ class Kronolith
     public static function addCoverDates(&$results, $event, $eventStart,
                                          $eventEnd, $json)
     {
-        $i = $eventStart->mday;
-        $loopDate = new Horde_Date(array('month' => $eventStart->month,
-                                         'mday' => $i,
-                                         'year' => $eventStart->year));
+        $loopDate = new Horde_Date($eventStart->year, $eventStart->month, $eventStart->mday);
         $allDay = $event->isAllDay();
         while ($loopDate->compareDateTime($eventEnd) <= 0) {
             if (!$allDay ||
@@ -884,10 +907,7 @@ class Kronolith
                 $addEvent->end = $eventEnd;
                 $results[$loopDate->dateString()][$addEvent->getId()] = $json ? $addEvent->toJson($allDay) : $addEvent;
             }
-            $loopDate = new Horde_Date(
-                array('month' => $eventStart->month,
-                      'mday' => ++$i,
-                      'year' => $eventStart->year));
+            $loopDate->mday++;
         }
     }
 
index ba14555..9d69873 100644 (file)
@@ -8,32 +8,42 @@
  * @author Meilof Veeningen <meilof@gmail.com>
  */
 
-/**
- * Used with usort() to sort events based on their start times.
- */
-function _sortEvents($a, $b)
-{
-    $start_a = $a->recurs() ? $a->recurrence->nextRecurrence($GLOBALS['event']->start) : $a->start;
-    $start_b = $b->recurs() ? $b->recurrence->nextRecurrence($GLOBALS['event']->start) : $b->start;
-    $diff = $start_a->compareDateTime($start_b);
-    if ($diff == 0) {
-        return strcoll($a->title, $b->title);
-    } else {
-        return $diff;
-    }
-}
-
 @define('KRONOLITH_BASE', dirname(__FILE__));
 require_once KRONOLITH_BASE . '/lib/base.php';
 
 /* Get search parameters. */
 $search_mode = Util::getFormData('search_mode', 'basic');
+$search_calendar = explode('|', Util::getFormData('calendar', '|__any'), 2);
+$events = null;
 
-if ($search_mode != 'basic') {
+if ($search_mode == 'basic') {
+    $desc = Util::getFormData('pattern_desc');
+    $title = Util::getFormData('pattern_title');
+    if (strlen($desc) || strlen($title)) {
+        $event = Kronolith::getDriver()->getEvent();
+        $event->setDescription($desc);
+        $event->setTitle($title);
+        $event->status = null;
+
+        $time1 = $_SERVER['REQUEST_TIME'];
+        $range = Util::getFormData('range');
+        if ($range == '+') {
+            $event->start = new Horde_Date($time1);
+            $event->end = null;
+        } elseif ($range == '-') {
+            $event->start = null;
+            $event->end = new Horde_Date($time1);
+        } else {
+            $time2 = $time1 + $range;
+            $event->start = new Horde_Date(min($time1, $time2));
+            $event->end = new Horde_Date(max($time1, $time2));
+        }
+        $events = Kronolith::search($event);
+    }
+} else {
     /* Make a new empty event object with default values. */
-    $event = Kronolith::getDriver()->getEvent();
-    $event->title = $event->calendars = $event->location =
-    $event->status = $event->description = null;
+    $event = Kronolith::getDriver($search_calendar[0], $search_calendar[1])->getEvent();
+    $event->title = $event->location = $event->status = $event->description = null;
 
     /* Set start on today, stop on tomorrow. */
     $event->start = new Horde_Date(mktime(0, 0, 0));
@@ -45,42 +55,39 @@ if ($search_mode != 'basic') {
     $event->initialized = true;
 
     $q_title = Util::getFormData('title');
-
-    if (isset($q_title)) {
-        /* We're returning from a previous search. */
+    if (strlen($q_title)) {
         $event->readForm();
         if (Util::getFormData('status') == Kronolith::STATUS_NONE) {
             $event->status = null;
         }
-    }
-}
 
-$desc = Util::getFormData('pattern_desc');
-$title = Util::getFormData('pattern_title');
-if ($desc || $title) {
-    /* We're doing a simple search. */
-    $event = Kronolith::getDriver()->getEvent();
-    $event->setDescription($desc);
-    $event->setTitle($title);
-    $event->status = null;
+        $events = Kronolith::search($event, $search_calendar[1] == '__any' ? null : $search_calendar[0] . '|' . $search_calendar[1]);
+    }
 
-    $time1 = $_SERVER['REQUEST_TIME'];
-    $range = Util::getFormData('range');
-    if ($range == '+') {
-        $event->start = new Horde_Date($time1);
-        $event->end = null;
-    } elseif ($range == '-') {
-        $event->start = null;
-        $event->end = new Horde_Date($time1);
-    } else {
-        $time2 = $time1 + $range;
-        $event->start = new Horde_Date(min($time1, $time2));
-        $event->end = new Horde_Date(max($time1, $time2));
+    $optgroup = $GLOBALS['browser']->hasFeature('optgroup');
+    $current_user = Auth::getAuth();
+    $calendars = array();
+    foreach (Kronolith::listCalendars(false, PERMS_READ) as $id => $cal) {
+        if ($cal->get('owner') == $current_user) {
+            $calendars[_("My Calendars:")]['|' . $id] = $cal->get('name');
+        } else {
+            $calendars[_("Shared Calendars:")]['|' . $id] = $cal->get('name');
+        }
+    }
+    foreach ($GLOBALS['all_external_calendars'] as $api => $categories) {
+        $app = $GLOBALS['registry']->get('name', $GLOBALS['registry']->hasInterface($api));
+        foreach ($categories as $id => $name) {
+            $calendars[$app . ':']['Horde|external_' . $api . '/' . $id] = $name;
+        }
+    }
+    foreach ($GLOBALS['all_remote_calendars'] as $cal) {
+        $calendars[_("Remote Calendars:")]['Ical|' . $cal['url']] = $cal['name'];
+    }
+    if (!empty($GLOBALS['conf']['holidays']['enable'])) {
+        foreach (unserialize($GLOBALS['prefs']->getValue('holiday_drivers')) as $holiday) {
+            $calendars[_("Holidays:")]['Holidays|' . $holiday] = $holiday;
+        }
     }
-    $events = Kronolith::search($event);
-} elseif (isset($q_title)) {
-    /* Advanced search. */
-    $events = Kronolith::search($event);
 }
 
 $title = _("Search");
@@ -98,18 +105,14 @@ if ($search_mode == 'basic') {
 }
 
 /* Display search results. */
-if (isset($events)) {
+if (!is_null($events)) {
     if (count($events)) {
-        usort($events, '_sortEvents');
-
         require KRONOLITH_TEMPLATES . '/search/header.inc';
         require KRONOLITH_TEMPLATES . '/search/event_headers.inc';
-
-        foreach ($events as $found) {
-            $start = $found->recurs() ? $found->recurrence->nextRecurrence($event->start) : $found->start;
-            $end = new Horde_Date($start);
-            $end->min += $found->durMin;
-            require KRONOLITH_TEMPLATES . '/search/event_summaries.inc';
+        foreach ($events as $day => $day_events) {
+            foreach ($day_events as $event) {
+                require KRONOLITH_TEMPLATES . '/search/event_summaries.inc';
+            }
         }
         require KRONOLITH_TEMPLATES . '/search/event_footers.inc';
     } else {
index 5cf2a18..6138d3e 100644 (file)
@@ -1,9 +1,9 @@
 <tr class="text">
  <td nowrap="nowrap">
-  <?php echo $found->getLink() ?>
+  <?php echo $event->getLink() ?>
  </td>
- <td class="nowrap"><?php echo htmlspecialchars($found->getLocation()) ?></td>
- <td class="nowrap"><?php echo Kronolith::statusToString($found->getStatus()) ?></td>
- <td class="nowrap"><?php echo $start->strftime($prefs->getValue('date_format')) . $start->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?></td>
- <td class="nowrap"><?php echo $end->strftime($prefs->getValue('date_format')) . $end->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?></td>
+ <td class="nowrap"><?php echo htmlspecialchars($event->getLocation()) ?></td>
+ <td class="nowrap"><?php echo Kronolith::statusToString($event->getStatus()) ?></td>
+ <td class="nowrap"><?php echo $event->start->strftime($prefs->getValue('date_format')) . $event->start->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?></td>
+ <td class="nowrap"><?php echo $event->end->strftime($prefs->getValue('date_format')) . $event->end->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?></td>
 </tr>
index 381b990..3f4973a 100644 (file)
@@ -1,4 +1,7 @@
-<?php require KRONOLITH_TEMPLATES . '/edit/javascript.inc'; ?>
+<?php
+$issearch = true;
+require KRONOLITH_TEMPLATES . '/edit/javascript.inc';
+?>
 <form method="post" name="eventform" action="search.php">
 <?php Util::pformInput() ?>
 <input type="hidden" name="actionID" value="search_calendar" />
  <strong><?php echo _("General") ?></strong>
 </td></tr>
 
-<?php
-$calendars = Kronolith::listCalendars(false, PERMS_READ);
-$issearch = true;
-?>
 <!-- title -->
 <tr>
  <td class="rightAlign"><strong><?php echo Horde::label('title', _("Tit_le")) ?></strong></td>
@@ -38,16 +37,18 @@ $issearch = true;
  </td>
  <td colspan="4">
   <select id="calendar" name="calendar">
-  <?php
-  $eCalendar = $event->getCalendar();
-  echo '<option value="__any"' .
-       (!$eCalendar ? ' selected="selected"' : '') . '>' . _("Any") .
-       '</option>';
-  foreach ($calendars as $id => $cal) {
-      $sel = ($id == $eCalendar) ? ' selected="selected"' : '';
-      printf('<option value="%s"%s>%s</option>',
-             htmlspecialchars($id), $sel, htmlspecialchars($cal->get('name'))) . "\n";
-  } ?>
+   <option value="|__any"<?php if ($search_calendar[1] == '__any') echo ' selected="selected"' ?>><?php echo _("Any") ?></option>
+   <?php foreach ($calendars as $type => $list): ?>
+   <?php if ($optgroup): ?>
+   <optgroup label="<?php echo htmlspecialchars($type) ?>">
+   <?php endif; ?>
+   <?php foreach ($list as $id => $name): ?>
+   <option value="<?php echo htmlspecialchars($id) ?>"<?php if ($search_calendar[0] . '|' . $search_calendar[1] == $id) echo ' selected="selected"' ?>><?php echo ($optgroup ? '' : htmlspecialchars($type) . ' ') . htmlspecialchars($name) ?></option>
+   <?php endforeach; ?>
+   <?php if ($optgroup): ?>
+   </optgroup>
+   <?php endif; ?>
+   <?php endforeach; ?>
   </select>
  </td>
 </tr>