diff --git a/plugins/calendar/calendar.js b/plugins/calendar/calendar.js index 3e66d963..aaa2b181 100644 --- a/plugins/calendar/calendar.js +++ b/plugins/calendar/calendar.js @@ -33,6 +33,8 @@ function rcube_calendar(settings) this.dismiss_link = null; this.selected_event = null; this.selected_calendar = null; + this.search_request = null; + this.search_source = null; this.eventcount = []; @@ -684,6 +686,76 @@ function rcube_calendar(settings) } }; + + /*** event searching ***/ + + // execute search + this.quicksearch = function() + { + if (rcmail.gui_objects.qsearchbox) { + var q = rcmail.gui_objects.qsearchbox.value; + if (q != '') { + var id = 'search-'+q; + var fc = $(fcselector); + var sources = []; + + for (var sid in this.calendars) { + if (this.calendars[sid] && this.calendars[sid].active) { + fc.fullCalendar('removeEventSource', this.calendars[sid]); + sources.push(sid); + } + } + id += '@'+sources.join(','); + + // just refetch events if query didn't change + if (this.search_request == id) { + fc.fullCalendar('refetchEvents'); + return; + } + // remove old search results + else if (this.search_source) { + fc.fullCalendar('removeEventSource', this.search_source); + } + else { + this.default_view = fc.fullCalendar('getView').name; + } + + // replace event source from fullcalendar + this.search_request = id; + this.search_source = { + url: "./?_task=calendar&_action=search_events&q="+escape(q)+'&source='+escape(sources.join(',')), + editable: false + }; + + fc.fullCalendar('option', 'smartSections', false); + fc.fullCalendar('addEventSource', this.search_source); + fc.fullCalendar('changeView', 'list'); + } + } + }; + + // reset search and get back to normal event listing + this.reset_quicksearch = function() + { + $(rcmail.gui_objects.qsearchbox).val(''); + if (this.search_request) { + // restore original event sources and view mode from fullcalendar + var fc = $(fcselector); + fc.fullCalendar('option', 'smartSections', true); + fc.fullCalendar('removeEventSource', this.search_source); + for (var sid in this.calendars) { + if (this.calendars[sid] && this.calendars[sid].active) + fc.fullCalendar('addEventSource', this.calendars[sid]); + } + console.log(this.default_view); + if (this.default_view) + fc.fullCalendar('changeView', this.default_view); + + this.search_request = this.search_source = null; + } + }; + + /*** startup code ***/ // create list of event sources AKA calendars @@ -698,8 +770,10 @@ function rcube_calendar(settings) id: id }, cal); - if ((active = ($.inArray(String(id), settings.hidden_calendars) < 0))) + if ((active = ($.inArray(String(id), settings.hidden_calendars) < 0))) { + this.calendars[id].active = true; event_sources.push(this.calendars[id]); + } // init event handler on calendar list checkbox if ((li = rcmail.get_folder_li(id, 'rcmlical'))) { @@ -709,21 +783,29 @@ function rcube_calendar(settings) var action; if (this.checked) { action = 'addEventSource'; + me.calendars[id].active = true; settings.hidden_calendars = $.map(settings.hidden_calendars, function(v){ return v == id ? null : v; }); } else { action = 'removeEventSource'; + me.calendars[id].active = false; settings.hidden_calendars.push(id); } - $(fcselector).fullCalendar(action, me.calendars[id]); - rcmail.save_pref({ name:'hidden_calendars', value:settings.hidden_calendars.join(',') }); + // just trigger search again (don't save prefs?) + if (me.search_request) { + me.quicksearch(); + } + else { // add/remove event source + $(fcselector).fullCalendar(action, me.calendars[id]); + rcmail.save_pref({ name:'hidden_calendars', value:settings.hidden_calendars.join(',') }); + } } }).data('id', id).get(0).checked = active; $(li).click(function(e){ var id = $(this).data('id'); rcmail.select_folder(id, me.selected_calendar, 'rcmlical'); - rcmail.enable_command('calendar-edit','calendar-remove', true); + rcmail.enable_command('calendar-edit','calendar-remove', !me.calendars[id].readonly); me.selected_calendar = id; }).data('id', id); } @@ -1046,8 +1128,10 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { rcmail.register_command('calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false); rcmail.register_command('calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false); - // export events + // search and export events rcmail.register_command('export', function(){ rcmail.goto_url('export_events', { source:cal.selected_calendar }); }, true); + rcmail.register_command('search', function(){ cal.quicksearch(); }, true); + rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true); // register callback commands rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); }); diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 03d65d6d..3dfd9d4d 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -138,6 +138,7 @@ class calendar extends rcube_plugin $this->register_handler('plugin.snooze_select', array($this->ui, 'snooze_select')); $this->register_handler('plugin.recurrence_form', array($this->ui, 'recurrence_form')); $this->register_handler('plugin.edit_recurring_warning', array($this->ui, 'recurring_event_warning')); + $this->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template $this->rc->output->set_env('calendar_settings', $this->load_settings()); $this->rc->output->add_label('low','normal','high'); @@ -457,12 +458,13 @@ class calendar extends rcube_plugin */ function search_events() { - $events = $this->driver->load_events( + $events = $this->driver->search_events( get_input_value('start', RCUBE_INPUT_GET), get_input_value('end', RCUBE_INPUT_GET), + get_input_value('q', RCUBE_INPUT_GET), get_input_value('source', RCUBE_INPUT_GET) ); - echo $this->encode($events); + echo $this->encode($events, true); exit; } @@ -586,7 +588,7 @@ class calendar extends rcube_plugin * @param array Events as array * @return string JSON encoded events */ - function encode($events) + function encode($events, $addcss = false) { $json = array(); foreach ($events as $event) { @@ -601,7 +603,7 @@ class calendar extends rcube_plugin 'end' => date('c', $event['end']), // ISO 8601 date (added in PHP 5) 'description' => $event['description'], 'location' => $event['location'], - 'className' => 'cat-' . asciiwords($event['categories'], true), + 'className' => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'cat-' . asciiwords($event['categories'], true), 'allDay' => ($event['all_day'] == 1)?true:false, ) + $event; } diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index f230ddd7..f79f5c63 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -597,7 +597,7 @@ class database_driver extends calendar_driver * * @see Driver:load_events() */ - public function load_events($start, $end, $calendars = null) + public function load_events($start, $end, $calendars = null, $sql_add = '') { if (empty($calendars)) $calendars = array_keys($this->calendars); @@ -613,10 +613,12 @@ class database_driver extends calendar_driver $result = $this->rc->db->query(sprintf( "SELECT * FROM " . $this->db_events . " WHERE calendar_id IN (%s) - AND start <= %s AND end >= %s", + AND start <= %s AND end >= %s + %s", join(',', $calendar_ids), $this->rc->db->fromunixtime($end), - $this->rc->db->fromunixtime($start) + $this->rc->db->fromunixtime($start), + $sql_add )); while ($result && ($event = $this->rc->db->fetch_assoc($result))) { @@ -665,7 +667,13 @@ class database_driver extends calendar_driver */ public function search_events($start, $end, $query, $calendars = null) { + // compose (slow) SQL query for searching + // FIXME: improve searching using a dedicated col and normalized values + foreach (array('title','location','description','categories','attendees') as $col) { + $sql_query[] = $this->rc->db->ilike($col, '%'.$query.'%'); + } + return $this->load_events($start, $end, $calendars, 'AND (' . join(' OR ', $sql_query) . ')'); } /** diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index b2cc1c25..a3de2cbd 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -106,18 +106,23 @@ class kolab_calendar /** * @param integer Event's new start (unix timestamp) * @param integer Event's new end (unix timestamp) + * @param string Search query (optional) * @return array A list of event records */ - public function list_events($start, $end) + public function list_events($start, $end, $search = null) { // use Horde classes to compute recurring instances require_once 'Horde/Date/Recurrence.php'; $this->_fetch_events(); - - + $events = array(); foreach ($this->events as $id => $event) { + // TODO: filter events by search query + if (!empty($search)) { + + } + // list events in requested time window if ($event['start'] <= $end && $event['end'] >= $start) { $events[] = $event; diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index cd528ff9..5cd177ea 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -216,9 +216,10 @@ class kolab_driver extends calendar_driver * @param integer Event's new start (unix timestamp) * @param integer Event's new end (unix timestamp) * @param mixed List of calendar IDs to load events from (either as array or comma-separated string) + * @param string Search query (optional) * @return array A list of event records */ - public function load_events($start, $end, $calendars = null) + public function load_events($start, $end, $calendars = null, $search = null) { if ($calendars && is_string($calendars)) $calendars = explode(',', $calendars); @@ -228,7 +229,7 @@ class kolab_driver extends calendar_driver if ($calendars && !in_array($cid, $calendars)) continue; - $events = array_merge($this->folders[$cid]->list_events($start, $end)); + $events = array_merge($this->folders[$cid]->list_events($start, $end, $search)); } return $events; @@ -242,7 +243,8 @@ class kolab_driver extends calendar_driver */ public function search_events($start, $end, $query, $calendars = null) { - return array(); + // delegate request to load_events() + return $this->load_events($start, $end, $calendars, $query); } /** diff --git a/plugins/calendar/lib/js/fullcalendar.js b/plugins/calendar/lib/js/fullcalendar.js index ec12db8e..a997d5e1 100644 --- a/plugins/calendar/lib/js/fullcalendar.js +++ b/plugins/calendar/lib/js/fullcalendar.js @@ -650,8 +650,8 @@ function Calendar(element, options, eventSources) { if (value === undefined) { return options[name]; } + options[name] = value; if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { - options[name] = value; updateSize(); } } diff --git a/plugins/calendar/skins/default/fullcalendar.css b/plugins/calendar/skins/default/fullcalendar.css index e14cad45..34b17d43 100644 --- a/plugins/calendar/skins/default/fullcalendar.css +++ b/plugins/calendar/skins/default/fullcalendar.css @@ -622,7 +622,7 @@ table.fc-border-separate { .fc-view-list, .fc-view-table { border: 1px solid #ccc; - width: 99%; + width: auto; } .fc-view-list .fc-list-header, diff --git a/plugins/calendar/skins/default/templates/calendar.html b/plugins/calendar/skins/default/templates/calendar.html index 119dde57..fb732147 100644 --- a/plugins/calendar/skins/default/templates/calendar.html +++ b/plugins/calendar/skins/default/templates/calendar.html @@ -197,7 +197,7 @@