plugins/calendar

Thomas Brüderli bruederli at kolabsys.com
Mon Jan 27 19:13:23 CET 2014


 plugins/calendar/config.inc.php.dist                  |    3 
 plugins/calendar/drivers/calendar_driver.php          |  119 +++++++++++++++++-
 plugins/calendar/drivers/database/database_driver.php |   42 ++++++
 plugins/calendar/drivers/kolab/kolab_driver.php       |   63 +++++++--
 plugins/calendar/localization/en_US.inc               |    5 
 5 files changed, 213 insertions(+), 19 deletions(-)

New commits:
commit 4112437fe983c51b5640a4a15beed00599b50089
Author: Thomas Bruederli <thomas at roundcube.net>
Date:   Mon Jan 27 19:12:29 2014 +0100

    First shot at the birthdays calendar feature

diff --git a/plugins/calendar/config.inc.php.dist b/plugins/calendar/config.inc.php.dist
index 34b1009..9a472a7 100644
--- a/plugins/calendar/config.inc.php.dist
+++ b/plugins/calendar/config.inc.php.dist
@@ -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(
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index c09d8b9..0eedf7f 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -8,7 +8,7 @@
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
  * Copyright (C) 2010, Lazlo Westerhof <hello at lazlo.me>
- * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2012-2014, Kolab Systems AG <contact at 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;
   }
 
 }
diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php
index 43c2e1b..18fed04 100644
--- a/plugins/calendar/drivers/database/database_driver.php
+++ b/plugins/calendar/drivers/database/database_driver.php
@@ -8,7 +8,7 @@
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
  * Copyright (C) 2010, Lazlo Westerhof <hello at lazlo.me>
- * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2012-2014, Kolab Systems AG <contact at 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;
   }
 
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index f673f6c..87a5f02 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -7,7 +7,7 @@
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @author Aleksander Machniak <machniak at kolabsys.com>
  *
- * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2012-2014, Kolab Systems AG <contact at 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);
+    }
+    else {
+      $id = $prop['id'];
+    }
 
-      // 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;
+    // 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 ($prefs['kolab_calendars'][$id])
-        $this->rc->user->save_prefs($prefs);
+    if (isset($prop['color']))
+      $prefs['kolab_calendars'][$id]['color'] = $prop['color'];
+    if (isset($prop['showalarms']))
+      $prefs['kolab_calendars'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
 
-      return true;
-    }
+    if (!empty($prefs['kolab_calendars'][$id]))
+      $this->rc->user->save_prefs($prefs);
 
-    return false;
+    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)))) {
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index 5c41ed1..c582db9 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -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';
+
 ?>




More information about the commits mailing list