First shot at the birthdays calendar feature

This commit is contained in:
Thomas Bruederli 2014-01-27 19:12:29 +01:00
parent 01c6a75d16
commit 4112437fe9
5 changed files with 215 additions and 21 deletions

View file

@ -31,6 +31,9 @@ $rcmail_config['calendar_driver'] = "database";
// default calendar view (agendaDay, agendaWeek, month)
$rcmail_config['calendar_default_view'] = "agendaWeek";
// show a birthdays calendar from the user's address book(s)
$rcmail_config['calendar_contact_birthdays'] = false;
// mapping of Roundcube date formats to calendar formats (long/short/agenda)
// should be in sync with 'date_formats' in main config
$rcmail_config['calendar_date_format_sets'] = array(

View file

@ -8,7 +8,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -81,6 +81,8 @@
*/
abstract class calendar_driver
{
const BIRTHDAY_CALENDAR_ID = '__bdays__';
// features supported by backend
public $alarms = false;
public $attendees = false;
@ -398,7 +400,120 @@ abstract class calendar_driver
*/
public function get_color_values()
{
return false;
return false;
}
/**
* Compose a list of birthday events from the contact records in the user's address books.
*
* This is a default implementation using Roundcube's address book API.
* It can be overriden with a more optimized version by the individual drivers.
*
* @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 load_birthday_events($start, $end, $search = null)
{
// convert to DateTime for comparisons
$start = new DateTime('@'.$start);
$end = new DateTime('@'.$end);
// extract the current year
$year = $start->format('Y');
$year2 = $end->format('Y');
$events = array();
$search = mb_strtolower($search);
$rcmail = rcmail::get_instance();
$cache = $rcmail->get_cache('calendar.birthdays', 'db', 3600);
$cache->expunge();
// TODO: let the user select the address books to consider in prefs
foreach ($rcmail->get_address_sources(false, true) as $source) {
$abook = $rcmail->get_address_book($source['id']);
$abook->set_pagesize(10000);
// skip LDAP address books (really?)
if ($abook instanceof rcube_ldap) {
continue;
}
// check for cached results
$cache_records = array();
$cached = $cache->get($source['id']);
// iterate over (cached) contacts
foreach ((array)($cached ?: $abook->list_records()) as $contact) {
if (!empty($contact['birthday'])) {
try {
if (is_array($contact['birthday']))
$contact['birthday'] = reset($contact['birthday']);
$bday = $contact['birthday'] instanceof DateTime ? $contact['birthday'] :
new DateTime($contact['birthday'], new DateTimezone('UTC'));
$birthyear = $bday->format('Y');
}
catch (Exception $e) {
// console('BIRTHDAY PARSE ERROR: ' . $e);
continue;
}
$display_name = rcube_addressbook::compose_display_name($contact);
$event_title = $rcmail->gettext(array('name' => 'birthdayeventtitle', 'vars' => array('name' => $display_name)), 'calendar');
// add stripped record to cache
if (empty($cached)) {
$cache_records[] = array(
'id' => $contact['ID'],
'name' => $display_name,
'birthday' => $bday->format('Y-m-d'),
);
}
// filter by search term (only name is involved here)
if (!empty($search) && strpos(mb_strtolower($event_title), $search) === false) {
continue;
}
// quick-and-dirty recurrence computation: just replace the year
$bday->setDate($year, $bday->format('n'), $bday->format('j'));
$bday->setTime(12, 0, 0);
// date range reaches over multiple years: use end year if not in range
if (($bday > $end || $bday < $start) && $year2 != $year) {
$bday->setDate($year2, $bday->format('n'), $bday->format('j'));
$year = $year2;
}
// birthday is within requested range
if ($bday <= $end && $bday >= $start) {
$age = $year - $birthyear;
$event = array(
'id' => md5('bday_' . $contact['id']),
'calendar' => self::BIRTHDAY_CALENDAR_ID,
'title' => $event_title,
'description' => $rcmail->gettext(array('name' => 'birthdayage', 'vars' => array('age' => $age)), 'calendar'),
// Add more contact information to description block?
'allday' => true,
'start' => $bday,
// TODO: add alarms (configurable?)
);
$event['end'] = clone $bday;
$event['end']->add(new DateInterval('PT1H'));
$events[] = $event;
}
}
}
// store collected contacts in cache
if (empty($cached)) {
$cache->write($source['id'], $cache_records);
}
}
return $events;
}
}

View file

@ -8,7 +8,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -127,6 +127,28 @@ class database_driver extends calendar_driver
// 'personal' is unsupported in this driver
// append the virtual birthdays calendar
if ($this->rc->config->get('calendar_contact_birthdays', false)) {
$prefs = $this->rc->config->get('birthday_calendar', array('color' => '87CEFA'));
$hidden = array_filter(explode(',', $this->rc->config->get('hidden_calendars', '')));
$id = self::BIRTHDAY_CALENDAR_ID;
if (!$active || !in_array($id, $hidden)) {
$calendars[$id] = array(
'id' => $id,
'name' => $this->cal->gettext('birthdays'),
'listname' => $this->cal->gettext('birthdays'),
'color' => $prefs['color'],
'showalarms' => $prefs['showalarms'],
'active' => !in_array($id, $hidden),
'class_name' => 'birthdays',
'readonly' => true,
'default' => false,
'children' => false,
);
}
}
return $calendars;
}
@ -163,6 +185,17 @@ class database_driver extends calendar_driver
*/
public function edit_calendar($prop)
{
// birthday calendar properties are saved in user prefs
if ($prop['id'] == self::BIRTHDAY_CALENDAR_ID) {
$prefs = $this->rc->config->get('birthday_calendar', array('color' => '87CEFA'));
if (isset($prop['color']))
$prefs['color'] = $prop['color'];
if (isset($prop['showalarms']))
$prefs['showalarms'] = $prop['showalarms'] ? true : false;
$this->rc->user->save_prefs(array('birthday_calendar' => $prefs));
return true;
}
$query = $this->rc->db->query(
"UPDATE " . $this->db_calendars . "
SET name=?, color=?, showalarms=?
@ -777,7 +810,12 @@ class database_driver extends calendar_driver
$events[] = $this->_read_postprocess($event);
}
}
// add events from the address books birthday calendar
if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars)) {
$events = array_merge($events, $this->load_birthday_events($start, $end, $search));
}
return $events;
}

View file

@ -7,7 +7,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -145,6 +145,26 @@ class kolab_driver extends calendar_driver
}
}
// append the virtual birthdays calendar
if ($this->rc->config->get('calendar_contact_birthdays', false)) {
$id = self::BIRTHDAY_CALENDAR_ID;
$prefs = $this->rc->config->get('kolab_calendars', array()); // read local prefs
if (!$active || $prefs[$id]['active']) {
$calendars[$id] = array(
'id' => $id,
'name' => $this->cal->gettext('birthdays'),
'listname' => $this->cal->gettext('birthdays'),
'color' => $prefs[$id]['color'],
'showalarms' => $prefs[$id]['showalarms'],
'active' => $prefs[$id]['active'],
'class_name' => 'birthdays',
'readonly' => true,
'default' => false,
'children' => false,
);
}
}
return $calendars;
}
@ -245,23 +265,24 @@ class kolab_driver extends calendar_driver
// create ID
$id = kolab_storage::folder_id($newfolder);
// fallback to local prefs
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
unset($prefs['kolab_calendars'][$prop['id']]);
if (isset($prop['color']))
$prefs['kolab_calendars'][$id]['color'] = $prop['color'];
if (isset($prop['showalarms']))
$prefs['kolab_calendars'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
if ($prefs['kolab_calendars'][$id])
$this->rc->user->save_prefs($prefs);
return true;
}
else {
$id = $prop['id'];
}
return false;
// fallback to local prefs
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
unset($prefs['kolab_calendars'][$prop['id']]['color'], $prefs['kolab_calendars'][$prop['id']]['showalarms']);
if (isset($prop['color']))
$prefs['kolab_calendars'][$id]['color'] = $prop['color'];
if (isset($prop['showalarms']))
$prefs['kolab_calendars'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
if (!empty($prefs['kolab_calendars'][$id]))
$this->rc->user->save_prefs($prefs);
return true;
}
@ -275,6 +296,13 @@ class kolab_driver extends calendar_driver
if ($prop['id'] && ($cal = $this->calendars[$prop['id']])) {
return $cal->storage->activate($prop['active']);
}
else {
// save state in local prefs
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
$prefs['kolab_calendars'][$prop['id']]['active'] = (bool)$prop['active'];
$this->rc->user->save_prefs($prefs);
return true;
}
return false;
}
@ -724,7 +752,12 @@ class kolab_driver extends calendar_driver
$events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual, $query));
$categories += $this->calendars[$cid]->categories;
}
// add events from the address books birthday calendar
if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars)) {
$events = array_merge($events, $this->load_birthday_events($start, $end, $search));
}
// add new categories to user prefs
$old_categories = $this->rc->config->get('calendar_categories', $this->default_categories);
if ($newcats = array_diff(array_map('strtolower', array_keys($categories)), array_map('strtolower', array_keys($old_categories)))) {

View file

@ -238,4 +238,9 @@ $labels['futurevents'] = 'Future';
$labels['allevents'] = 'All';
$labels['saveasnew'] = 'Save as new';
// birthdays calendar
$labels['birthdays'] = 'Birthdays';
$labels['birthdayeventtitle'] = '$name\'s Birthday';
$labels['birthdayage'] = 'Age $age';
?>