From 7c068474beb913598aa72764fa1bb89d1fb5db96 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Thu, 28 May 2009 14:39:53 +0200 Subject: [PATCH] Vastly improve searching: all calendar types can be searched now and searching 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 | 2 + kronolith/lib/Driver.php | 51 +++---- kronolith/lib/Driver/Sql.php | 33 +++- kronolith/lib/Kronolith.php | 202 ++++++++++++++----------- kronolith/search.php | 117 +++++++------- kronolith/templates/search/event_summaries.inc | 10 +- kronolith/templates/search/search_advanced.inc | 31 ++-- 7 files changed, 249 insertions(+), 197 deletions(-) diff --git a/kronolith/docs/CHANGES b/kronolith/docs/CHANGES index 0c0dd84c3..305368c6e 100644 --- a/kronolith/docs/CHANGES +++ b/kronolith/docs/CHANGES @@ -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). diff --git a/kronolith/lib/Driver.php b/kronolith/lib/Driver.php index f2b2eb32d..823f4b17b 100644 --- a/kronolith/lib/Driver.php +++ b/kronolith/lib/Driver.php @@ -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 all 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); + } } } diff --git a/kronolith/lib/Driver/Sql.php b/kronolith/lib/Driver/Sql.php index fd5d43798..d37a8d74e 100644 --- a/kronolith/lib/Driver/Sql.php +++ b/kronolith/lib/Driver/Sql.php @@ -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; diff --git a/kronolith/lib/Kronolith.php b/kronolith/lib/Kronolith.php index 5955235fc..53cd5cc90 100644 --- a/kronolith/lib/Kronolith.php +++ b/kronolith/lib/Kronolith.php @@ -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++; } } diff --git a/kronolith/search.php b/kronolith/search.php index ba1455599..9d6987371 100644 --- a/kronolith/search.php +++ b/kronolith/search.php @@ -8,32 +8,42 @@ * @author Meilof Veeningen */ -/** - * 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 { diff --git a/kronolith/templates/search/event_summaries.inc b/kronolith/templates/search/event_summaries.inc index 5cf2a18d7..6138d3ec1 100644 --- a/kronolith/templates/search/event_summaries.inc +++ b/kronolith/templates/search/event_summaries.inc @@ -1,9 +1,9 @@ - getLink() ?> + getLink() ?> - getLocation()) ?> - getStatus()) ?> - strftime($prefs->getValue('date_format')) . $start->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?> - strftime($prefs->getValue('date_format')) . $end->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?> + getLocation()) ?> + getStatus()) ?> + start->strftime($prefs->getValue('date_format')) . $event->start->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?> + end->strftime($prefs->getValue('date_format')) . $event->end->strftime($prefs->getValue('twentyFour') ? ' %H:%M' : ' %I:%M %p') ?> diff --git a/kronolith/templates/search/search_advanced.inc b/kronolith/templates/search/search_advanced.inc index 381b99090..3f4973a41 100644 --- a/kronolith/templates/search/search_advanced.inc +++ b/kronolith/templates/search/search_advanced.inc @@ -1,4 +1,7 @@ - +
@@ -19,10 +22,6 @@ - @@ -38,16 +37,18 @@ $issearch = true; -- 2.11.0