[packaging] Branch 'ucs/2.4/kolab-2.3' - 2 commits - plugins/calendar plugins/compose_addressbook plugins/tinymce_config

Christoph Wickert wickert at kolabsys.com
Mon Nov 5 17:08:08 CET 2012


 plugins/calendar/calendar.php                                                                        |   90 -
 plugins/calendar/calendar_base.js                                                                    |    2 
 plugins/calendar/calendar_ui.js                                                                      |   32 
 plugins/calendar/drivers/calendar_driver.php                                                         |    8 
 plugins/calendar/drivers/database/database_driver.php                                                |  112 +
 plugins/calendar/drivers/kolab/kolab_calendar.php                                                    |   89 -
 plugins/calendar/drivers/kolab/kolab_driver.php                                                      |   30 
 plugins/calendar/lib/calendar_ical.php                                                               |   75 -
 plugins/calendar/lib/calendar_itip.php                                                               |    2 
 plugins/calendar/lib/calendar_recurrence.php                                                         |   26 
 plugins/calendar/lib/calendar_ui.php                                                                 |    8 
 plugins/compose_addressbook/CHANGES                                                                  |   49 
 plugins/compose_addressbook/LICENSE                                                                  |  339 +++++
 plugins/compose_addressbook/README                                                                   |   42 
 plugins/compose_addressbook/compose_addressbook.js                                                   |  224 +++
 plugins/compose_addressbook/compose_addressbook.php                                                  |  179 ++
 plugins/compose_addressbook/config.inc.php.dist                                                      |   21 
 plugins/compose_addressbook/localization/de_DE.inc                                                   |   11 
 plugins/compose_addressbook/localization/en_GB.inc                                                   |   10 
 plugins/compose_addressbook/localization/en_US.inc                                                   |   10 
 plugins/compose_addressbook/localization/es_ES.inc                                                   |   10 
 plugins/compose_addressbook/localization/fr_FR.inc                                                   |   10 
 plugins/compose_addressbook/localization/it_IT.inc                                                   |   10 
 plugins/compose_addressbook/localization/nl_NL.inc                                                   |   10 
 plugins/compose_addressbook/localization/pl_PL.inc                                                   |   10 
 plugins/compose_addressbook/localization/sv_SE.inc                                                   |   10 
 plugins/compose_addressbook/localization/zh_TW.inc                                                   |   10 
 plugins/compose_addressbook/skins/default/compose_addressbook.css                                    |   83 +
 plugins/compose_addressbook/skins/default/compose_addressbook.png                                    |binary
 plugins/compose_addressbook/skins/default/searchfield.gif                                            |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-anim_basic_16x16.gif                  |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png           |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_75_ffffff_40x100.png          |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png          |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_65_ffffff_1x400.png          |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_dadada_1x400.png          |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png          |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png          |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_222222_256x240.png              |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_2e83ff_256x240.png              |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_454545_256x240.png              |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_888888_256x240.png              |binary
 plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_cd0a0a_256x240.png              |binary
 plugins/compose_addressbook/skins/default/smoothness/jquery-ui-1.8.2.custom.css                      |  345 +++++
 plugins/tinymce_config/LICENSE                                                                       |  661 ++++++++++
 plugins/tinymce_config/package.xml                                                                   |   53 
 plugins/tinymce_config/tinymce_config.php                                                            |   47 
 48 files changed, 2451 insertions(+), 167 deletions(-)

New commits:
commit 76d616ff07e6d60940d07c1b53096f1ef730c41e
Merge: c6ae5e3 8f93f85
Author: Christoph Wickert (Kolab Systems) <wickert at kolabsys.com>
Date:   Mon Nov 5 17:05:58 2012 +0100

    Merge commit 'upstream/0.7.4' into ucs/2.4/kolab-2.3



commit 8f93f85d10f0a2702bcf226161e6620bf714988e
Author: Christoph Wickert (Kolab Systems) <wickert at kolabsys.com>
Date:   Mon Nov 5 17:05:57 2012 +0100

    Imported Upstream version 0.7.4

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index ccf6461..ec1d995 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -3,7 +3,7 @@
 /**
  * Calendar plugin for Roundcube webmail
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
@@ -45,6 +45,7 @@ class calendar extends rcube_plugin
   public $urlbase;
   public $timezone;
   public $gmt_offset;
+  public $user_timezone;
 
   public $ical;
   public $ui;
@@ -105,7 +106,14 @@ class calendar extends rcube_plugin
     $this->timezone = $this->rc->config->get('timezone');
     $this->dst_active = $this->rc->config->get('dst_active');
     $this->gmt_offset = ($this->timezone + $this->dst_active) * 3600;
-    $this->user_timezone = new DateTimeZone($this->timezone ? timezone_name_from_abbr("", $this->gmt_offset, $this->dst_active) : 'GMT');
+
+    try {
+        $this->user_timezone = new DateTimeZone($this->timezone ? timezone_name_from_abbr("", $this->gmt_offset, $this->dst_active) : 'GMT');
+    }
+    catch (Exception $e) {
+        $this->timezone = 0;
+        $this->user_timezone = new DateTimeZone('UTC');
+    }
 
     require($this->home . '/lib/calendar_ui.php');
     $this->ui = new calendar_ui($this);
@@ -852,7 +860,7 @@ class calendar extends rcube_plugin
       $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name']);
 
       $count = $errors = 0;
-      $rangestart = $_REQUEST['_range'] ? strtotime("now -" . intval($_REQUEST['_range']) . " months") : 0;
+      $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0;
       foreach ($events as $event) {
         // TODO: correctly handle recurring events which start before $rangestart
         if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart)))
@@ -1023,12 +1031,22 @@ class calendar extends rcube_plugin
   }
 
   /**
-   * Convert the given date string into a GMT-based time stamp
+   * Convert the given date value into a DateTime object in user's timezone
    */
-  function fromGMT($datetime)
+  function toUserDateTime($datetime)
   {
-    $ts = is_numeric($datetime) ? $datetime : strtotime($datetime);
-    return $ts + $this->gmt_offset;
+    if (is_a($datetime, 'DateTime')) {
+      $datetime->setTimezone($this->user_timezone);
+      return $datetime;
+    }
+    try {
+        $dt = new DateTime(is_numeric($datetime) ? '@'.$datetime : $datetime, new DateTimeZone('UTC'));
+        $dt->setTimezone($this->user_timezone);
+    }
+    catch (Exception $e) {
+        $dt = new DateTime('1970-01-01 00:00:00', $this->user_timezone);
+    }
+    return $dt;
   }
 
   /**
@@ -1057,11 +1075,13 @@ class calendar extends rcube_plugin
       $event['alarms_text'] = $this->_alarms_text($event['alarms']);
     if ($event['recurrence'])
       $event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
+    if ($event['recurrence']['UNTIL'])
+      $event['recurrence']['UNTIL'] = $this->toUserDateTime($event['recurrence']['UNTIL'])->format('c');
 
     return array(
       '_id'   => $event['calendar'] . ':' . $event['id'],  // unique identifier for fullcalendar
-      'start' => gmdate('c', $this->fromGMT($event['start'])), // client treats date strings as they were in users's timezone
-      'end'   => gmdate('c', $this->fromGMT($event['end'])),   // so shift timestamps to users's timezone and render a date string
+      'start' => $this->toUserDateTime($event['start'])->format('c'), // client treats date strings as they were in users's timezone
+      'end'   => $this->toUserDateTime($event['end'])->format('c'),   // so shift timestamps to users's timezone and render a date string
       'description' => strval($event['description']),
       'location'    => strval($event['location']),
       'className'   => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower($event['categories']), true),
@@ -1079,8 +1099,8 @@ class calendar extends rcube_plugin
     foreach ($alarms as $alarm) {
       $out[] = array(
         'id'       => $alarm['id'],
-        'start'    => gmdate('c', $this->fromGMT($alarm['start'])),
-        'end'      => gmdate('c', $this->fromGMT($alarm['end'])),
+        'start'    => $this->toUserDateTime($alarm['start'])->format('c'),
+        'end'      => $this->toUserDateTime($alarm['end'])->format('c'),
         'allDay'   => ($alarm['allday'] == 1)?true:false,
         'title'    => $alarm['title'],
         'location' => $alarm['location'],
@@ -1109,7 +1129,7 @@ class calendar extends rcube_plugin
     }
     
     if (preg_match('/@(\d+)/', $trigger, $m)) {
-      $text .= ' ' . $this->gettext(array('name' => 'alarmat', 'vars' => array('datetime' => format_date($m[1]))));
+      $text .= ' ' . $this->gettext(array('name' => 'alarmat', 'vars' => array('datetime' => self::format_date($m[1]))));
     }
     else if ($val = self::parse_alaram_value($trigger)) {
       $text .= ' ' . intval($val[0]) . ' ' . $this->gettext('trigger' . $val[1]);
@@ -1149,7 +1169,7 @@ class calendar extends rcube_plugin
     if ($rrule['COUNT'])
       $until =  $this->gettext(array('name' => 'forntimes', 'vars' => array('nr' => $rrule['COUNT'])));
     else if ($rrule['UNTIL'])
-      $until = $this->gettext('recurrencend') . ' ' . format_date($rrule['UNTIL'], self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])));
+      $until = $this->gettext('recurrencend') . ' ' . self::format_date($rrule['UNTIL'], self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])));
     else
       $until = $this->gettext('forever');
     
@@ -1191,11 +1211,11 @@ class calendar extends rcube_plugin
       $k = strtoupper($k);
       switch ($k) {
         case 'UNTIL':
-          $val = gmdate('Ymd\THis', $val);
+          $val = $val->format('Ymd\THis');
           break;
         case 'EXDATE':
           foreach ((array)$val as $i => $ex)
-            $val[$i] = gmdate('Ymd\THis', $ex);
+            $val[$i] = $ex->format('Ymd\THis');
           $val = join(',', $val);
           break;
       }
@@ -1291,7 +1311,7 @@ class calendar extends rcube_plugin
         $start -= 3600;
       
       if ($allday) {
-        $start = strtotime(date('Y-m-d 00:00:00', $start));
+        $start = date_create(date('Y-m-d 00:00:00', $start));
         $duration = 86399;
       }
       
@@ -1304,8 +1324,8 @@ class calendar extends rcube_plugin
       
       $this->driver->new_event(array(
         'uid' => $this->generate_uid(),
-        'start' => $start,
-        'end' => $start + $duration,
+        'start' => new DateTime('@'.$start),
+        'end' => new DateTime('@'.($start + $duration)),
         'allday' => $allday,
         'title' => rtrim($title),
         'free_busy' => $fb == 2 ? 'outofoffice' : ($fb ? 'busy' : 'free'),
@@ -1545,6 +1565,13 @@ class calendar extends rcube_plugin
    */
   private function prepare_event(&$event, $action)
   {
+    // convert dates into DateTime objects in user's current timezone
+    $event['start'] = new DateTime($event['start'], $this->user_timezone);
+    $event['end'] = new DateTime($event['end'], $this->user_timezone);
+
+    if ($event['recurrence']['UNTIL'])
+      $event['recurrence']['UNTIL'] = new DateTime($event['recurrence']['UNTIL'], $this->user_timezone);
+
     $attachments = array();
     $eventid = 'cal:'.$event['id'];
     if (is_array($_SESSION['event_session']) && $_SESSION['event_session']['id'] == $eventid) {
@@ -1647,24 +1674,26 @@ class calendar extends rcube_plugin
   public function event_date_text($event, $tzinfo = false)
   {
     $fromto = '';
-    $duration = $event['end'] - $event['start'];
+    $event['start'] = $this->toUserDateTime($event['start']);
+    $event['end'] = $this->toUserDateTime($event['end']);
+    $duration = $event['end']->format('U') - $event['start']->format('U');
     
     $this->date_format_defaults();
     $date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']));
     $time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']));
     
     if ($event['allday']) {
-      $fromto = format_date($event['start'], $date_format);
-      if (($todate = format_date($event['end'], $date_format)) != $fromto)
+      $fromto = self::format_date($event['start'], $date_format);
+      if (($todate = self::format_date($event['end'], $date_format)) != $fromto)
         $fromto .= ' - ' . $todate;
     }
-    else if ($duration < 86400 && gmdate('d', $event['start']) == gmdate('d', $event['end'])) {
-      $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
-        ' - ' . format_date($event['end'], $time_format);
+    else if ($duration < 86400 && $event['start']->format('d') == $event['end']->format('d')) {
+      $fromto = self::format_date($event['start'], $date_format) . ' ' . self::format_date($event['start'], $time_format) .
+        ' - ' . self::format_date($event['end'], $time_format);
     }
     else {
-      $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
-        ' - ' . format_date($event['end'], $date_format) . ' ' . format_date($event['end'], $time_format);
+      $fromto = self::format_date($event['start'], $date_format) . ' ' . self::format_date($event['start'], $time_format) .
+        ' - ' . self::format_date($event['end'], $date_format) . ' ' . self::format_date($event['end'], $time_format);
     }
     
     // add timezone information
@@ -1676,6 +1705,15 @@ class calendar extends rcube_plugin
   }
 
   /**
+   * Wrapper for Roundcube's internal format_date() function
+   * accepting DateTime objects as argument
+   */
+  public static function format_date($date, $format=null)
+  {
+    return format_date(is_a($date, 'DateTime') ? $date->format('c') : $date, $format, false);
+  }
+
+  /**
    * Echo simple free/busy status text for the given user and time range
    */
   public function freebusy_status()
diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js
index 23bc60a..4766503 100644
--- a/plugins/calendar/calendar_base.js
+++ b/plugins/calendar/calendar_base.js
@@ -1,7 +1,7 @@
 /**
  * Base Javascript class for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index a8eb2b8..0d4cd5a 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -1,7 +1,7 @@
 /**
  * Client UI Javascript for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
@@ -208,7 +208,19 @@ function rcube_calendar_ui(settings)
         date.setTime((ts + 3600) * 1000);
       return date;
     };
-    
+
+    // turn the given date into an ISO 8601 date string understandable by PHPs strtotime()
+    var date2servertime = function(date)
+    {
+      return date.getFullYear()+'-'+zeropad(date.getMonth()+1)+'-'+zeropad(date.getDate())
+          + 'T'+zeropad(date.getHours())+':'+zeropad(date.getMinutes())+':'+zeropad(date.getSeconds());
+    }
+
+    var zeropad = function(num)
+    {
+        return (num < 10 ? '0' : '') + num;
+    }
+
     // determine whether the given date is on a weekend
     var is_weekend = function(date)
     {
@@ -545,7 +557,7 @@ function rcube_calendar_ui(settings)
         recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
         interval = $('select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1);
         rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1);
-        rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(new Date(event.recurrence.UNTIL*1000), settings['date_format']) : '');
+        rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate($.fullCalendar.parseISO8601(event.recurrence.UNTIL,true), settings['date_format']) : '');
         $('input.edit-recurrence-until:checked').prop('checked', false);
       
         var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
@@ -642,8 +654,8 @@ function rcube_calendar_ui(settings)
         // post data to server
         var data = {
           calendar: event.calendar,
-          start: date2unixtime(start),
-          end: date2unixtime(end),
+          start: date2servertime(start),
+          end: date2servertime(end),
           allday: allday.checked?1:0,
           title: title.val(),
           description: description.val(),
@@ -702,7 +714,7 @@ function rcube_calendar_ui(settings)
           if (until == 'count')
             data.recurrence.COUNT = rrtimes.val();
           else if (until == 'until')
-            data.recurrence.UNTIL = date2unixtime(parse_datetime(endtime.val(), rrenddate.val()));
+            data.recurrence.UNTIL = date2servertime(parse_datetime(endtime.val(), rrenddate.val()));
           
           if (freq == 'WEEKLY') {
             var byday = [];
@@ -2355,8 +2367,8 @@ function rcube_calendar_ui(settings)
         var data = {
           id: event.id,
           calendar: event.calendar,
-          start: date2unixtime(event.start),
-          end: date2unixtime(event.end),
+          start: date2servertime(event.start),
+          end: date2servertime(event.end),
           allday: allDay?1:0
         };
         update_event_confirm('move', event, data);
@@ -2373,8 +2385,8 @@ function rcube_calendar_ui(settings)
         var data = {
           id: event.id,
           calendar: event.calendar,
-          start: date2unixtime(event.start),
-          end: date2unixtime(event.end)
+          start: date2servertime(event.start),
+          end: date2servertime(event.end)
         };
         update_event_confirm('resize', event, data);
       },
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index babecd7..70066ea 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -3,7 +3,7 @@
 /**
  * Driver interface for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
@@ -37,8 +37,8 @@
  *            'id' => 'Event ID used for editing',
  *           'uid' => 'Unique identifier of this event',
  *      'calendar' => 'Calendar identifier to add event to or where the event is stored',
- *         'start' => <unixtime>,  // Event start date/time as unix timestamp
- *           'end' => <unixtime>,  // Event end date/time as unix timestamp
+ *         'start' => DateTime,  // Event start date/time as unix timestamp
+ *           'end' => DateTime,  // Event end date/time as unix timestamp
  *        'allday' => true|false,  // Boolean flag if this is an all-day event
  *       'changed' => <unixtime>, // Last modification date of event
  *         'title' => 'Event title/summary',
@@ -47,7 +47,7 @@
  *    'recurrence' => array(   // Recurrence definition according to iCalendar (RFC 2445) specification as list of key-value pairs
  *            'FREQ' => 'DAILY|WEEKLY|MONTHLY|YEARLY',
  *        'INTERVAL' => 1...n,
- *           'UNTIL' => <unixtime>,
+ *           'UNTIL' => DateTime,
  *           'COUNT' => 1..n,   // number of times
  *                      // + more properties (see http://www.kanzaki.com/docs/ical/recur.html)
  *          'EXDATE' => array(),  // list of <unixtime>s of exception Dates/Times
diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php
index 0dd2b42..653f35d 100644
--- a/plugins/calendar/drivers/database/database_driver.php
+++ b/plugins/calendar/drivers/database/database_driver.php
@@ -3,12 +3,12 @@
 /**
  * Database driver for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
- * Copyright (C) 2010, Lazlo Westerhof - Netherlands
- * Copyright (C) 2011, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2010, Lazlo Westerhof <hello at lazlo.me>
+ * Copyright (C) 2012, 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
@@ -26,12 +26,14 @@
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 
 class database_driver extends calendar_driver
 {
+  const DB_DATE_FORMAT = 'Y-m-d H:i:s';
+
   // features this backend supports
   public $alarms = true;
   public $attendees = true;
@@ -45,6 +47,7 @@ class database_driver extends calendar_driver
   private $calendars = array();
   private $calendar_ids = '';
   private $free_busy_map = array('free' => 0, 'busy' => 1, 'out-of-office' => 2, 'outofoffice' => 2, 'tentative' => 3);
+  private $server_timezone;
   
   private $db_events = 'events';
   private $db_calendars = 'calendars';
@@ -61,6 +64,7 @@ class database_driver extends calendar_driver
   {
     $this->cal = $cal;
     $this->rc = $cal->rc;
+    $this->server_timezone = new DateTimeZone(date_default_timezone_get());
     
     // load library classes
     require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php');
@@ -93,7 +97,8 @@ class database_driver extends calendar_driver
       );
       while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
         $arr['showalarms'] = intval($arr['showalarms']);
-        $arr['active'] = !in_array($arr['id'], $hidden);
+        $arr['active']     = !in_array($arr['id'], $hidden);
+        $arr['name']       = Q($arr['name']);
         $this->calendars[$arr['calendar_id']] = $arr;
         $calendar_ids[] = $this->rc->db->quote($arr['calendar_id']);
       }
@@ -220,14 +225,14 @@ class database_driver extends calendar_driver
       $query = $this->rc->db->query(sprintf(
         "INSERT INTO " . $this->db_events . "
          (calendar_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, attendees, alarms, notifyat)
-         VALUES (?, %s, %s, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-          $this->rc->db->now(),
+         VALUES (?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
           $this->rc->db->now(),
-          $this->rc->db->fromunixtime($event['start']),
-          $this->rc->db->fromunixtime($event['end'])
+          $this->rc->db->now()
         ),
         $event['calendar'],
         strval($event['uid']),
+        $event['start']->format(self::DB_DATE_FORMAT),
+        $event['end']->format(self::DB_DATE_FORMAT),
         intval($event['all_day']),
         $event['_recurrence'],
         strval($event['title']),
@@ -304,20 +309,23 @@ class database_driver extends calendar_driver
           case 'future':
             if ($master['id'] != $event['id']) {
               // set until-date on master event, then save this instance as new recurring event
-              $master['recurrence']['UNTIL'] = $event['start'] - 86400;
+              $master['recurrence']['UNTIL'] = clone $event['start'];
+              $master['recurrence']['UNTIL']->modify('-1 day');
               unset($master['recurrence']['COUNT']);
               $update_master = true;
-            
+              
               // if recurrence COUNT, update value to the correct number of future occurences
               if ($event['recurrence']['COUNT']) {
+                $fromdate = clone $event['start'];
+                $fromdate->setTimezone($this->server_timezone);
                 $sqlresult = $this->rc->db->query(sprintf(
                   "SELECT event_id FROM " . $this->db_events . "
                    WHERE calendar_id IN (%s)
-                   AND start >= %s
+                   AND start >= ?
                    AND recurrence_id=?",
-                  $this->calendar_ids,
-                  $this->rc->db->fromunixtime($event['start'])
+                  $this->calendar_ids
                   ),
+                  $fromdate->format(self::DB_DATE_FORMAT),
                   $master['id']);
                 if ($count = $this->rc->db->num_rows($sqlresult))
                   $event['recurrence']['COUNT'] = $count;
@@ -334,20 +342,21 @@ class database_driver extends calendar_driver
             $event['recurrence_id'] = 0;
             
             // use start date from master but try to be smart on time or duration changes
-            $old_start_date = date('Y-m-d', $old['start']);
-            $old_start_time = date('H:i', $old['start']);
-            $old_duration = $old['end'] - $old['start'];
+            $old_start_date = $old['start']->format('Y-m-d');
+            $old_start_time = $old['start']->format('H:i');
+            $old_duration = $old['end']->format('U') - $old['start']->format('U');
             
-            $new_start_date = date('Y-m-d', $event['start']);
-            $new_start_time = date('H:i', $event['start']);
-            $new_duration = $event['end'] - $event['start'];
+            $new_start_date = $event['start']->format('Y-m-d');
+            $new_start_time = $event['start']->format('H:i');
+            $new_duration = $event['end']->format('U') - $event['start']->format('U');
             
             $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
             
             // shifted or resized
             if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
-              $event['start'] = $master['start'] + ($event['start'] - $old['start']);
-              $event['end'] = $event['start'] + $new_duration;
+              $event['start'] = $master['start']->add($old['start']->diff($event['start']));
+              $event['end'] = clone $event['start'];
+              $event['end']->modify('+'.$new_duration.' seconds');
             }
             break;
         }
@@ -368,6 +377,12 @@ class database_driver extends calendar_driver
    */
   private function _save_preprocess($event)
   {
+    // shift dates to server's timezone
+    $event['start'] = clone $event['start'];
+    $event['start']->setTimezone($this->server_timezone);
+    $event['end'] = clone $event['end'];
+    $event['end']->setTimezone($this->server_timezone);
+    
     // compose vcalendar-style recurrencue rule from structured data
     $rrule = $event['recurrence'] ? calendar::to_rrule($event['recurrence']) : '';
     $event['_recurrence'] = rtrim($rrule, ';');
@@ -439,9 +454,11 @@ class database_driver extends calendar_driver
   {
     $event = $this->_save_preprocess($event);
     $sql_set = array();
-    $set_cols = array('all_day', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat');
+    $set_cols = array('start', 'end', 'all_day', 'recurrence_id', 'sequence', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat');
     foreach ($set_cols as $col) {
-      if (isset($event[$col]))
+      if (is_object($event[$col]) && is_a($event[$col], 'DateTime'))
+        $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]->format(self::DB_DATE_FORMAT));
+      else if (isset($event[$col]))
         $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]);
     }
     
@@ -453,12 +470,10 @@ class database_driver extends calendar_driver
     
     $query = $this->rc->db->query(sprintf(
       "UPDATE " . $this->db_events . "
-       SET   changed=%s, start=%s, end=%s %s
+       SET   changed=%s %s
        WHERE event_id=?
        AND   calendar_id IN (" . $this->calendar_ids . ")",
         $this->rc->db->now(),
-        $this->rc->db->fromunixtime($event['start']),
-        $this->rc->db->fromunixtime($event['end']),
         ($sql_set ? ', ' . join(', ', $sql_set) : '')
       ),
       $event['id']
@@ -512,21 +527,24 @@ class database_driver extends calendar_driver
       require_once($this->cal->home . '/lib/calendar_recurrence.php');
       
       $recurrence = new calendar_recurrence($this->cal, $event);
-      
-      $duration = $event['end'] - $event['start'];
-      while ($next_ts = $recurrence->next_start()) {
-        $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_ts, 'end' => $next_ts + $duration));
+
+      $duration = $event['end']->format('U') - $event['start']->format('U');
+      while ($next_start = $recurrence->next_start()) {
+        $next_start->setTimezone($this->server_timezone);
+        $next_end = clone $next_start;
+        $next_end->modify('+'.$duration.' seconds');
+        $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_start, 'end' => $next_end));
         $query = $this->rc->db->query(sprintf(
           "INSERT INTO " . $this->db_events . "
            (calendar_id, recurrence_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, notifyat)
-            SELECT calendar_id, ?, %s, %s, uid, %s, %s, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, ?
+            SELECT calendar_id, ?, %s, %s, uid, ?, ?, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, ?
             FROM  " . $this->db_events . " WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")",
             $this->rc->db->now(),
-            $this->rc->db->now(),
-            $this->rc->db->fromunixtime($next_ts),
-            $this->rc->db->fromunixtime($next_ts + $duration)
+            $this->rc->db->now()
           ),
           $event['id'],
+          $next_start->format(self::DB_DATE_FORMAT),
+          $next_end->format(self::DB_DATE_FORMAT),
           $notify_at,
           $event['id']
         );
@@ -583,7 +601,7 @@ class database_driver extends calendar_driver
 
       // read master if deleting a recurring event
       if ($event['recurrence'] || $event['recurrence_id']) {
-        $master = $event['recurrence_id'] ? $this->get_event(array('id' => $old['recurrence_id'])) : $event;
+        $master = $event['recurrence_id'] ? $this->get_event(array('id' => $event['recurrence_id'])) : $event;
         $savemode = $event['_savemode'];
       }
 
@@ -605,16 +623,20 @@ class database_driver extends calendar_driver
         case 'future':
           if ($master['id'] != $event['id']) {
             // set until-date on master event
-            $master['recurrence']['UNTIL'] = $event['start'] - 86400;
+            $master['recurrence']['UNTIL'] = clone $event['start'];
+            $master['recurrence']['UNTIL']->modify('-1 day');
             unset($master['recurrence']['COUNT']);
             $update_master = true;
             
             // delete this and all future instances
+            $fromdate = clone $old['start'];
+            $fromdate->setTimezone($this->server_timezone);
             $query = $this->rc->db->query(
               "DELETE FROM " . $this->db_events . "
                WHERE calendar_id IN (" . $this->calendar_ids . ")
-               AND start >= " . $this->rc->db->fromunixtime($old['start']) . "
+               AND start >= ?
                AND recurrence_id=?",
+              $fromdate->format(self::DB_DATE_FORMAT),
               $master['id']
             );
             break;
@@ -651,8 +673,8 @@ class database_driver extends calendar_driver
   public function get_event($event, $writeable = null)
   {
     $id = is_array($event) ? ($event['id'] ? $event['id'] : $event['uid']) : $event;
-    $col = $event['id'] && is_numeric($event['id']) ? 'event_id' : 'uid';
-    
+    $col = is_array($event) && is_numeric($id) ? 'event_id' : 'uid';
+
     if ($this->cache[$id])
       return $this->cache[$id];
     
@@ -727,8 +749,8 @@ class database_driver extends calendar_driver
     $free_busy_map = array_flip($this->free_busy_map);
     
     $event['id'] = $event['event_id'];
-    $event['start'] = strtotime($event['start']);
-    $event['end'] = strtotime($event['end']);
+    $event['start'] = new DateTime($event['start']);
+    $event['end'] = new DateTime($event['end']);
     $event['allday'] = intval($event['all_day']);
     $event['changed'] = strtotime($event['changed']);
     $event['free_busy'] = $free_busy_map[$event['free_busy']];
@@ -742,9 +764,9 @@ class database_driver extends calendar_driver
         if (is_numeric($rr[2]))
           $rr[2] = intval($rr[2]);
         else if ($rr[1] == 'UNTIL')
-          $rr[2] = strtotime($rr[2]);
+          $rr[2] = date_create($rr[2]);
         else if ($rr[1] == 'EXDATE')
-          $rr[2] = array_map('strtotime', explode(',', $rr[2]));
+          $rr[2] = array_map('date_create', explode(',', $rr[2]));
         $event['recurrence'][$rr[1]] = $rr[2];
       }
     }
@@ -816,7 +838,7 @@ class database_driver extends calendar_driver
   public function dismiss_alarm($event_id, $snooze = 0)
   {
     // set new notifyat time or unset if not snoozed
-    $notify_at = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
+    $notify_at = $snooze > 0 ? date(self::DB_DATE_FORMAT, time() + $snooze) : null;
     
     $query = $this->rc->db->query(sprintf(
       "UPDATE " . $this->db_events . "
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 0475ba1..b7a1bc8 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -1,9 +1,9 @@
 <?php
 
 /**
- * Kolab calendar storage class 
+ * Kolab calendar storage class
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @author Aleksander Machniak <machniak at kolabsys.com>
  *
@@ -218,6 +218,10 @@ class kolab_calendar
    */
   public function list_events($start, $end, $search = null, $virtual = 1)
   {
+    // convert to DateTime for comparisons
+    $start = new DateTime('@'.$start);
+    $end = new DateTime('@'.$end);
+    
     $this->_fetch_events();
     
     if (!empty($search))
@@ -443,14 +447,15 @@ class kolab_calendar
     require_once($this->cal->home . '/lib/calendar_recurrence.php');
     
     $recurrence = new calendar_recurrence($this->cal, $event);
-    
+
     $events = array();
-    $duration = $event['end'] - $event['start'];
+    $duration = $event['end']->format('U') - $event['start']->format('U');
     $i = 0;
     while ($rec_start = $recurrence->next_start()) {
-      $rec_end = $rec_start + $duration;
+      $rec_end = clone $rec_start;
+      $rec_end->modify('+'.$duration.' seconds');
       $rec_id = $event['id'] . '-' . ++$i;
-      
+
       // add to output if in range
       if (($rec_start <= $end && $rec_end >= $start) || ($event_id && $rec_id == $event_id)) {
         $rec_event = $event;
@@ -481,15 +486,27 @@ class kolab_calendar
     $start_time = date('H:i:s', $rec['start-date']);
     $allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date']));
     if ($allday) {  // in Roundcube all-day events only go from 12:00 to 13:00
-      $rec['start-date'] += 12 * 3600;
-      $rec['end-date']   -= 11 * 3600;
-      $rec['end-date']   -= $this->cal->gmt_offset - date('Z', $rec['end-date']);    // shift times from server's timezone to user's timezone
-      $rec['start-date'] -= $this->cal->gmt_offset - date('Z', $rec['start-date']);  // because generated with mktime() in Horde_Kolab_Format_Date::decodeDate()
+      $dtstart = new DateTime('@'.$rec['start-date']);
+      $dtstart->modify('+12 hours');
+      $dtstart->setTimezone($this->cal->user_timezone);
+      $dtstart->setTime(12, 0, 0);
+
+      $dtend = new DateTime('@'.$rec['end-date']);
+      $dtend->setTimezone($this->cal->user_timezone);
+      $dtend->modify('-11 hours');
+      $dtend->setTime(13, 0, 0);
+
       // sanity check
-      if ($rec['end-date'] <= $rec['start-date'])
-        $rec['end-date'] += 86400;
+      if ($dtend <= $dtstart) {
+        $dtend = clone $dtstart;
+        $dtend->modify('+25 hours');
+      }
     }
-    
+    else {
+      $dtstart = new DateTime('@'.$rec['start-date']);
+      $dtend = new DateTime('@'.$rec['end-date']);
+    }
+
     // convert alarm time into internal format
     if ($rec['alarm']) {
       $alarm_value = $rec['alarm'];
@@ -514,8 +531,10 @@ class kolab_calendar
       
       if ($recurrence['range-type'] == 'number')
         $rrule['COUNT'] = intval($recurrence['range']);
-      else if ($recurrence['range-type'] == 'date')
-        $rrule['UNTIL'] = $recurrence['range'];
+      else if ($recurrence['range-type'] == 'date') {
+        $rrule['UNTIL'] = new DateTime('@'.$recurrence['range']);
+        $rrule['UNTIL']->setTimezone($this->cal->user_timezone);
+      }
       
       if ($recurrence['day']) {
         $byday = array();
@@ -537,7 +556,7 @@ class kolab_calendar
       
       if ($recurrence['exclusion']) {
         foreach ((array)$recurrence['exclusion'] as $excl)
-          $rrule['EXDATE'][] = strtotime($excl . date(' H:i:s', $rec['start-date']));  // use time of event start
+          $rrule['EXDATE'][] = new DateTime($excl . $dtstart->format(' H:i:s'), $this->cal->user_timezone);  // use time of event start
       }
     }
 
@@ -586,8 +605,8 @@ class kolab_calendar
       'title' => $rec['summary'],
       'location' => $rec['location'],
       'description' => $rec['body'],
-      'start' => $rec['start-date'],
-      'end' => $rec['end-date'],
+      'start' => $dtstart,
+      'end' => $dtend,
       'allday' => $allday,
       'recurrence' => $rrule,
       'alarms' => $alarm_value . $alarm_unit,
@@ -611,7 +630,6 @@ class kolab_calendar
   private function _from_rcube_event($event)
   {
     $priority_map = $this->priority_map;
-    $tz_offset = $this->cal->gmt_offset;
 
     $object = array(
     // kolab         => roundcube
@@ -620,8 +638,8 @@ class kolab_calendar
       'location'     => $event['location'],
       'body'         => $event['description'],
       'categories'   => $event['categories'],
-      'start-date'   => $event['start'],
-      'end-date'     => $event['end'],
+      'start-date'   => $event['start']->format('U'),
+      'end-date'     => $event['end']->format('U'),
       'sensitivity'  =>$this->sensitivity_map[$event['sensitivity']],
       'show-time-as' => $event['free_busy'],
       'priority'     => $event['priority'],
@@ -655,7 +673,7 @@ class kolab_calendar
       //Range Type
       if ($ra['UNTIL']) {
         $object['recurrence']['range-type'] = 'date';
-        $object['recurrence']['range'] = $ra['UNTIL'];
+        $object['recurrence']['range'] = $ra['UNTIL']->format('U');
       }
       if ($ra['COUNT']) {
         $object['recurrence']['range-type'] = 'number';
@@ -670,7 +688,7 @@ class kolab_calendar
         }
         else {
           // use weekday of start date if empty
-          $object['recurrence']['day'][] = strtolower(gmdate('l', $event['start'] + $tz_offset));
+          $object['recurrence']['day'][] = strtolower($event['start']->format('l'));
         }
       }
       
@@ -683,7 +701,7 @@ class kolab_calendar
           $object['recurrence']['type']  = 'weekday';
         }
         else {
-          $object['recurrence']['daynumber'] = date('j', $event['start']);
+          $object['recurrence']['daynumber'] = $event['start']->format('j');
           $object['recurrence']['cycle'] = 'monthly';
           $object['recurrence']['type']  = 'daynumber';
         }
@@ -692,7 +710,7 @@ class kolab_calendar
       //yearly
       if ($ra['FREQ'] == 'YEARLY') {
         if (!$ra['BYMONTH'])
-          $ra['BYMONTH'] = gmdate('n', $event['start'] + $tz_offset);
+          $ra['BYMONTH'] = $event['start']->format('n');
         
         $object['recurrence']['cycle'] = 'yearly';
         $object['recurrence']['month'] = $this->month_map[intval($ra['BYMONTH'])];
@@ -704,29 +722,34 @@ class kolab_calendar
         }
         else {
           $object['recurrence']['type'] = 'monthday';
-          $object['recurrence']['daynumber'] = gmdate('j', $event['start'] + $tz_offset);
+          $object['recurrence']['daynumber'] = $event['start']->format('j');
         }
       }
       
       //exclusions
       foreach ((array)$ra['EXDATE'] as $excl) {
-        $object['recurrence']['exclusion'][] = gmdate('Y-m-d', $excl + $tz_offset);
+        $object['recurrence']['exclusion'][] = $excl->format('Y-m-d');
       }
     }
     
     // whole day event
     if ($event['allday']) {
-      $object['end-date'] += 12 * 3600;  // end is at 13:00 => jump to the next day
-      $object['end-date'] += $tz_offset - date('Z');   // shift 00 times from user's timezone to server's timezone 
-      $object['start-date'] += $tz_offset - date('Z');  // because Horde_Kolab_Format_Date::encodeDate() uses strftime()
+      // shift times from user's timezone to server's timezone
+      // because Horde_Kolab_Format_Date::encodeDate() uses strftime()
+      $server_tz = new DateTimeZone(date_default_timezone_get());
+      $event['start']->setTimezone($server_tz);
+      $event['end']->setTimezone($server_tz);
+      
+      $event['start']->setTime(0,0,0);
+      $event['end']->setTime(0,0,0);
       
       // create timestamps at exactly 00:00. This is also needed for proper re-interpretation in _to_rcube_event() after updating an event
-      $object['start-date'] = mktime(0,0,0, date('n', $object['start-date']), date('j', $object['start-date']), date('Y', $object['start-date']));
-      $object['end-date']   = mktime(0,0,0, date('n', $object['end-date']),   date('j', $object['end-date']),   date('Y', $object['end-date']));
+      $object['start-date'] = mktime(0,0,0, $event['start']->format('n'), $event['start']->format('j'), $event['start']->format('Y'));
+      $object['end-date']   = mktime(0,0,0, $event['end']->format('n'),   $event['end']->format('j'),   $event['end']->format('Y')) + 86400;
       
       // sanity check: end date is same or smaller than start
       if (date('Y-m-d', $object['end-date']) <= date('Y-m-d', $object['start-date']))
-        $object['end-date'] = mktime(13,0,0, date('n', $object['start-date']), date('j', $object['start-date']), date('Y', $object['start-date'])) + 86400;
+        $object['end-date'] = mktime(13,0,0, $event['start']->format('n'), $event['start']->format('j'), $event['start']->format('Y')) + 86400;
       
       $object['_is_all_day'] = 1;
     }
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 83fd69d..e470fe2 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -3,7 +3,7 @@
 /**
  * Kolab driver for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @author Aleksander Machniak <machniak at kolabsys.com>
  *
@@ -659,7 +659,8 @@ class kolab_driver extends calendar_driver
       case 'future':
         if ($master['id'] != $event['id']) {
           // set until-date on master event
-          $master['recurrence']['UNTIL'] = $old['start'] - 86400;
+          $master['recurrence']['UNTIL'] = clone $old['start'];
+          $master['recurrence']['UNTIL']->modify('-1 day');
           unset($master['recurrence']['COUNT']);
           $storage->update_event($master);
           
@@ -675,7 +676,7 @@ class kolab_driver extends calendar_driver
           // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::insert_event()
           if (strlen($event['recurrence']['BYDAY']) == 2)
             unset($event['recurrence']['BYDAY']);
-          if ($master['recurrence']['BYMONTH'] == gmdate('n', $master['start']))
+          if ($master['recurrence']['BYMONTH'] == $master['start']->format('n'))
             unset($event['recurrence']['BYMONTH']);
           
           $success = $storage->insert_event($event);
@@ -687,26 +688,29 @@ class kolab_driver extends calendar_driver
         $event['uid'] = $master['uid'];
 
         // use start date from master but try to be smart on time or duration changes
-        $old_start_date = date('Y-m-d', $old['start']);
-        $old_start_time = date('H:i', $old['start']);
-        $old_duration = $old['end'] - $old['start'];
+        $old_start_date = $old['start']->format('Y-m-d');
+        $old_start_time = $old['start']->format('H:i');
+        $old_duration = $old['end']->format('U') - $old['start']->format('U');
         
-        $new_start_date = date('Y-m-d', $event['start']);
-        $new_start_time = date('H:i', $event['start']);
-        $new_duration = $event['end'] - $event['start'];
+        $new_start_date = $event['start']->format('Y-m-d');
+        $new_start_time = $event['start']->format('H:i');
+        $new_duration = $event['end']->format('U') - $event['start']->format('U');
         
         $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
         
         // shifted or resized
         if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
-          $event['start'] = $master['start'] + ($event['start'] - $old['start']);
-          $event['end'] = $event['start'] + $new_duration;
+          $offset = $event['start']->format('U') - $old['start']->format('U');
+          $event['start'] = clone $master['start'];
+          $event['start']->modify(($offset > 0 ? '+' : '') . $offset . ' seconds');
+          $event['end'] = clone $event['start'];
+          $event['end']->modify('+'.$new_duration.' seconds');
           
           // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::update_event()
           if ($old_start_date != $new_start_date) {
             if (strlen($event['recurrence']['BYDAY']) == 2)
               unset($event['recurrence']['BYDAY']);
-            if ($old['recurrence']['BYMONTH'] == gmdate('n', $old['start']))
+            if ($old['recurrence']['BYMONTH'] == $old['start']->format('n'))
               unset($event['recurrence']['BYMONTH']);
           }
         }
@@ -794,7 +798,7 @@ class kolab_driver extends calendar_driver
 
       foreach ($calendar->list_events($time, $time + 86400 * 365) as $e) {
         // add to list if alarm is set
-        if ($e['_alarm'] && ($notifyat = $e['start'] - $e['_alarm'] * 60) <= $time) {
+        if ($e['_alarm'] && ($notifyat = $e['start']->format('U') - $e['_alarm'] * 60) <= $time) {
           $id = $e['id'];
           $events[$id] = $e;
           $events[$id]['notifyat'] = $notifyat;
diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index d79a917..d65a0e9 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -3,7 +3,7 @@
 /**
  * iCalendar functions for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @author Bogomil "Bogo" Shopov <shopov at kolabsys.com>
@@ -153,23 +153,25 @@ class calendar_ical
       'uid' => $ve->getAttributeDefault('UID'),
       'changed' => $ve->getAttributeDefault('DTSTAMP', 0),
       'title' => $ve->getAttributeDefault('SUMMARY'),
-      'start' => $ve->getAttribute('DTSTART'),
-      'end' => $ve->getAttribute('DTEND'),
+      'start' => $this->_date2time($ve->getAttribute('DTSTART')),
+      'end' => $this->_date2time($ve->getAttribute('DTEND')),
       // set defaults
       'free_busy' => 'busy',
       'priority' => 0,
+      'attendees' => array(),
     );
     
     // check for all-day dates
-    if (is_array($event['start'])) {
-      // create timestamp at 12:00 in user's timezone
-      $event['start'] = $this->_date2time($event['start']);
+    if (is_array($ve->getAttribute('DTSTART')))
       $event['allday'] = true;
-    }
-    if (is_array($event['end'])) {
-      $event['end'] = $this->_date2time($event['end']) - 23 * 3600;
-    }
-
+    
+    if ($event['allday'])
+      $event['end'] = new DateTime('@'.($event['end']->format('U') - 23*3600));
+    
+    // assign current timezone to event start/end
+    $event['start']->setTimezone($this->cal->user_timezone);
+    $event['end']->setTimezone($this->cal->user_timezone);
+    
     // map other attributes to internal fields
     $_attendees = array();
     foreach ($ve->getAllAttributes() as $attr) {
@@ -224,9 +226,15 @@ class calendar_ical
             $params[$k] = $v;
           }
           if ($params['UNTIL'])
-            $params['UNTIL'] = $ve->_parseDateTime($params['UNTIL']);
+            $params['UNTIL'] = date_create($params['UNTIL']);
           if (!$params['INTERVAL'])
             $params['INTERVAL'] = 1;
+          if ($params['EXDATE']) {
+            $exdates = array();
+            foreach (explode(',', $params['EXDATE']) as $excl)
+              $exdates[] = new DateTime($params['EXDATE'], $this->cal->user_timezone);
+            $params['EXDATE'] = $exdates;
+          }
           
           $event['recurrence'] = $params;
           break;
@@ -307,7 +315,7 @@ class calendar_ical
 
     // make sure the event has an UID
     if (!$event['uid'])
-      $event['uid'] = $this->cal->$this->generate_uid();
+      $event['uid'] = $this->cal->generate_uid();
     
     return $event;
   }
@@ -318,10 +326,14 @@ class calendar_ical
   private function _date2time($prop)
   {
     // create timestamp at 12:00 in user's timezone
-    return is_array($prop) ? strtotime(sprintf('%04d%02d%02dT120000%s', $prop['year'], $prop['month'], $prop['mday'], $this->timezone)) : $prop;
+    if (is_array($prop))
+      return date_create(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->user_timezone);
+    else if (is_numeric($prop))
+      return date_create('@'.$prop);
+    
+    return $prop;
   }
 
-
   /**
    * Free resources by clearing member vars
    */
@@ -357,15 +369,16 @@ class calendar_ical
       foreach ($events as $event) {
         $vevent = "BEGIN:VEVENT" . self::EOL;
         $vevent .= "UID:" . self::escpape($event['uid']) . self::EOL;
-        $vevent .= "DTSTAMP:" . gmdate('Ymd\THis\Z', $event['changed'] ? $event['changed'] : time()) . self::EOL;
+        $vevent .= $this->format_datetime("DTSTAMP", $event['changed'] ?: new DateTime(), false, true) . self::EOL;
         // correctly set all-day dates
         if ($event['allday']) {
-          $vevent .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL;
-          $vevent .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 86400) . self::EOL;  // ends the next day
+          $event['end'] = new DateTime('@'.($event['end']->format('U') + 86400));  // ends the next day
+          $vevent .= $this->format_datetime("DTSTART", $event['start'], true) . self::EOL;
+          $vevent .= $this->format_datetime("DTEND",   $event['end'], true) . self::EOL;
         }
         else {
-          $vevent .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
-          $vevent .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
+          $vevent .= $this->format_datetime("DTSTART", $event['start'], false) . self::EOL;
+          $vevent .= $this->format_datetime("DTEND",   $event['end'], false) . self::EOL;
         }
         $vevent .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
         $vevent .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;
@@ -428,7 +441,27 @@ class calendar_ical
       // fold lines to 75 chars
       return rcube_vcard::rfc2425_fold($ical);
   }
-  
+
+  private function format_datetime($attr, $dt, $dateonly = false, $utc = false)
+  {
+    if (is_numeric($dt))
+        $dt = new DateTime('@'.$dt);
+
+    if ($utc)
+      $dt->setTimezone(new DateTimeZone('UTC'));
+
+    if ($dateonly) {
+      return $attr . ';VALUE=DATE:' . $dt->format('Ymd');
+    }
+    else {
+      // <ATTR>;TZID=Europe/Zurich:20120706T210000
+      $tz = $dt->getTimezone();
+      $tzname = $tz ? $tz->getName() : 'UTC';
+      $tzid = $tzname != 'UTC' && $tzname != '+00:00' ? ';TZID=' . $tzname : '';
+      return $attr . $tzid . ':' . $dt->format('Ymd\THis' . ($tzid ? '' : '\Z'));
+    }
+  }
+
   private function escpape($str)
   {
     return preg_replace('/(?<!\\\\)([\:\;\,\\n\\r])/', '\\\$1', $str);
diff --git a/plugins/calendar/lib/calendar_itip.php b/plugins/calendar/lib/calendar_itip.php
index 76c4fb4..8c41127 100644
--- a/plugins/calendar/lib/calendar_itip.php
+++ b/plugins/calendar/lib/calendar_itip.php
@@ -5,7 +5,7 @@
  *
  * Class providing functionality to manage iTIP invitations
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @package calendar
  *
diff --git a/plugins/calendar/lib/calendar_recurrence.php b/plugins/calendar/lib/calendar_recurrence.php
index 0354080..ce1738b 100644
--- a/plugins/calendar/lib/calendar_recurrence.php
+++ b/plugins/calendar/lib/calendar_recurrence.php
@@ -5,7 +5,7 @@
  *
  * Uitility class to compute instances of recurring events.
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @package calendar
  *
@@ -35,7 +35,6 @@ class calendar_recurrence
   private $event;
   private $engine;
   private $tz_offset = 0;
-  private $dst_start = false;
   private $hour = 0;
 
   /**
@@ -47,41 +46,48 @@ class calendar_recurrence
   function __construct($cal, $event)
   {
     $this->cal = $cal;
+    $dtstart = clone $event['start'];
+    $dtstart->setTimezone($cal->user_timezone);
 
     // use Horde classes to compute recurring instances
     // TODO: replace with something that has less than 6'000 lines of code
     require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php');
 
+    // shift until date by one day in order to trick the Horde_Date_Recurrence computation
+    if ($event['recurrence']['UNTIL']) {
+      $event['recurrence']['UNTIL'] = clone $event['recurrence']['UNTIL'];
+      $event['recurrence']['UNTIL']->modify('+1 day');
+    }
+
     $this->event = $event;
-    $this->engine = new Horde_Date_Recurrence($event['start']);
+    $this->engine = new Horde_Date_Recurrence($dtstart->format('U'));
     $this->engine->fromRRule20(calendar::to_rrule($event['recurrence']));
 
     if (is_array($event['recurrence']['EXDATE'])) {
       foreach ($event['recurrence']['EXDATE'] as $exdate)
-        $this->engine->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate));
+        $this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
     }
 
     $this->tz_offset = $event['allday'] ? $this->cal->gmt_offset - date('Z') : 0;
-    $this->next = new Horde_Date($event['start'] + $this->tz_offset);  # shift all-day times to server timezone because computation operates in local TZ
-    $this->dst_start = $this->next->format('I');
+    $this->next = new Horde_Date($dtstart->format('U'));
     $this->hour = $this->next->hour;
   }
 
   /**
    * Get timestamp of the next occurence of this event
    *
-   * @return mixed Unix timestamp or False if recurrence ended
+   * @return mixed DateTime or False if recurrence ended
    */
   public function next_start()
   {
     $time = false;
     if ($this->next && ($next = $this->engine->nextActiveRecurrence(array('year' => $this->next->year, 'month' => $this->next->month, 'mday' => $this->next->mday + 1, 'hour' => $this->next->hour, 'min' => $this->next->min, 'sec' => $this->next->sec)))) {
+      # fix time for all-day events
       if ($this->event['allday']) {
-        $next->hour = $this->hour;  # fix time for all-day events
+        $next->hour = $this->hour;
         $next->min = 0;
       }
-      # $dst_diff = ($this->dst_start - $next->format('I')) * 3600;  # consider difference in daylight saving between base event and recurring instance
-      $time = $next->timestamp() - $this->tz_offset;
+      $time = new DateTime($next->iso8601DateTime(), $this->cal->user_timezone);
       $this->next = $next;
     }
 
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 477d1e3..3eb4c23 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -2,7 +2,7 @@
 /**
  * User Interface class for the Calendar plugin
  *
- * @version 0.7-beta
+ * @version 0.7.3
  * @author Lazlo Westerhof <hello at lazlo.me>
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
@@ -337,9 +337,11 @@ class calendar_ui
     $input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
     
     $select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
-    foreach (array('-M','-H','-D','+M','+H','+D','@') as $trigger)
+    foreach (array('-M','-H','-D','+M','+H','+D') as $trigger)
       $select_offset->add($this->cal->gettext('trigger' . $trigger), $trigger);
-     
+    if (!is_a($this->cal->driver, 'kolab_driver'))
+      $select_offset->add($this->cal->gettext('trigger@'), '@');
+    
     // pre-set with default values from user settings
     $preset = calendar::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
     $hidden = array('style' => 'display:none');
diff --git a/plugins/compose_addressbook/CHANGES b/plugins/compose_addressbook/CHANGES
new file mode 100644
index 0000000..89ab48a
--- /dev/null
+++ b/plugins/compose_addressbook/CHANGES
@@ -0,0 +1,49 @@
+5.0-beta1           use with svn/0.6
+
+4.0  0.5            use with 0.5 stable
+
+3.1  0.4.2-stable   only use with 0.4.2 and up
+
+3.0  0.4-stable     release for roundcube 0.4-stable
+
+2.21                added dependancy information to README
+
+2.2  ui changes     minor ui changes
+     dependancy     IMPORTANT: as of 2.2 you need to install jqueryui plugin
+
+2.1  ui change      switched from jqmodal to jquery-ui dialog window due to incompatibility with roundcube
+     bugfix         small bug in search mode fixed
+
+2.0  new            compatible with new address groups
+     new            will use all address sources by default
+
+IMPORTANT:  only use version 2.0+ with roundcube 0.4 or newer. 
+
+1.5.1 new           allow skin override
+
+1.5.0 new           improve LDAP compatibility, create 'mode' option
+
+1.4.0 bugfix        make compatible with jquery 1.4
+
+1.3.2 translation   added spanish and polish translations
+      bugfix        typo in english translation
+
+1.3.1 translation   added french translation by bagu
+
+1.3 translation     added taiwanese translatopn by siulee88
+    ui change       changed some ui elements, patch by epinter
+
+1.2 translation     added swedish translation by Joans Nasholm 
+
+1.1 bugfix          LDAP addressbooks didnt work
+
+1.0 no changes
+
+0.3 ui change       changed toolbar icon
+
+0.2 bugfix	        config option needs to be an array
+    bugfix         	changed some variable names to not cause JS name
+		                conflict with other plugins
+    translation     german translation added (thanks to Rosali) 
+
+0.1 initial version
diff --git a/plugins/compose_addressbook/LICENSE b/plugins/compose_addressbook/LICENSE
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/plugins/compose_addressbook/LICENSE
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugins/compose_addressbook/README b/plugins/compose_addressbook/README
new file mode 100644
index 0000000..625cc98
--- /dev/null
+++ b/plugins/compose_addressbook/README
@@ -0,0 +1,42 @@
++-------------------------------------------------------------------------+
+|
+|  Author:  Cor Bosman (roundcube at wa.ter.net)
+|  Plugin:  compose_addressbook
+|  Version: 5.0-beta1
+|  Purpose: Add addressbook window to compose screen
+| Required: jqueryui plugin
+|
++-------------------------------------------------------------------------+
+
+IMPORTANT: as of version 2.2 you need to install the jqueryui plugin.
+           http://underwa.ter.net/roundcube-plugins/
+
+
+compose_addressbook plugin adds a button to the compose screen allowing you
+to select contacts with the mouse. These contacts can then be added to your
+recipients (To/CC/BCC) with a mouse click. 
+
+Some features:
+
+- doubleclick to quickly add a contact to your To field
+- select multiple contacts and click one of the buttons to add to the
+  specific field
+- config option lets you select which addressbooks to use
+- set mode in config file to switch between full or search mod
+- you can drag the window using the top name bar
+- use the search field to search in your addressbook
+
+Installation
+------------
+
+- install jqueryui plugin (http://underwa.ter.net/roundcube-plugins/)
+
+- Add 'jqueryui' and 'compose_addressbook' to your plugin config in main.inc.php
+
+- if necessary edit config.php.inc in the compose_addressbook plugin
+ 
+
+Licensing:
+----------
+This plugin is distributed under the GNU General Public License Version 2.
+Please read through the file LICENSE for more information about this license.
diff --git a/plugins/compose_addressbook/compose_addressbook.js b/plugins/compose_addressbook/compose_addressbook.js
new file mode 100644
index 0000000..de5d952
--- /dev/null
+++ b/plugins/compose_addressbook/compose_addressbook.js
@@ -0,0 +1,224 @@
+var compose_addressbook_fetched = false;
+
+if(window.rcmail) {
+  rcmail.addEventListener('init', function(evt) {
+
+    // mode of operation. configure this in config.php
+    var mode = rcmail.env.compose_addressbook_mode;
+    
+    // to be able to have translated buttons, we need to predefine the buttons array
+    var cab_to  = rcmail.gettext('to');
+    var cab_cc  = rcmail.gettext('cc');
+    var cab_bcc = rcmail.gettext('bcc');
+    
+    var buttons = {};
+    buttons[cab_bcc] = function() {
+      compose_addressbook_add_recipients('_bcc');
+      $('.ui-dialog-buttonpane button').removeClass('ui-state-focus');
+    }
+    buttons[cab_cc] = function() {
+      compose_addressbook_add_recipients('_cc');
+      $('.ui-dialog-buttonpane button').removeClass('ui-state-focus');    
+    }
+    buttons[cab_to] = function() {
+      compose_addressbook_add_recipients('_to');
+      $('.ui-dialog-buttonpane button').removeClass('ui-state-focus');
+    }
+    
+    // bind the dialog functionality to the dialog div
+    $("#compose_addressbook_dialog").dialog({
+      autoOpen: false,
+      modal: false,
+      resizable: false,
+      width: 285,
+      height: 500,
+      minHeight: 400,
+      buttons: buttons,
+      position: [$(window).width()-400,50]
+    });
+    
+    // register the command associated with the toolbar button
+    rcmail.register_command('plugin.compose_addressbook', compose_addressbook_start , true);
+    
+    // add the command to the list of compose commands
+    rcmail.env.compose_commands.push('plugin.compose_addressbook');
+
+    // register the callback function 
+    rcmail.addEventListener('plugin.compose_addressbook_receive', compose_addressbook_receive);
+    
+    // register the callback function for the group expander
+    rcmail.addEventListener('plugin.compose_addressbook_receive_expand', compose_addressbook_receive_expand);
+    
+    // create an rc list object 
+    if(rcmail.gui_objects.compose_addressbook_list) {
+      rcmail.compose_addressbook_list = new rcube_list_widget(rcmail.gui_objects.compose_addressbook_list, {multiselect:true, draggable:false, keyboard:false});
+      
+      // add a listener for double click
+      rcmail.compose_addressbook_list.addEventListener('dblclick', function(o){ compose_address_dblclick(o); });
+      
+      // initialize the list
+      rcmail.compose_addressbook_list.init();  
+    }  
+    
+    // each mode of operation has a different key handler
+    if(mode == 'full') {
+      // bind keyevent handler to the search box
+      $('#compose_addressbook_filter').bind('keyup', function(evt) {
+        var search = $('#compose_addressbook_filter').val();
+        var regexp = new RegExp(search, 'i');
+        $('#compose_addressbook_table').find('td').each(function() {
+          var content = $(this).attr('title');
+          if(regexp.test(content)) {
+            $(this).parent().show();
+          } else {
+            $(this).parent().hide();
+          }
+        });
+      });
+    } else {
+      $('#compose_addressbook_filter').bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), function(evt) {
+        var key = rcube_event.get_keycode(evt);
+        if(key == 13) {
+          var search = $('#compose_addressbook_filter').val();
+          $('#compose_addressbook_filter').val('');
+          compose_addressbook_search(search);
+          return false;
+        } 
+      });
+    }
+    
+    // bind click event to clear function
+    $("#compose_addressbook_searchreset").bind('click', function(e) { 
+      $('#compose_addressbook_filter').val('');
+      $('#compose_addressbook_filter').focus();
+      $('#compose_addressbook_table').find('tr').each(function() {
+        $(this).show();
+      });
+    });
+  });
+}
+
+function compose_addressbook_start() 
+{
+  compose_addressbook_fetch();
+  $('#compose_addressbook_dialog').dialog('open');
+}
+
+function compose_addressbook_fetch() 
+{
+  if(!compose_addressbook_fetched) {
+    lock = rcmail.set_busy(true, 'loading');
+    rcmail.http_post('plugin.get_addressbook', '', lock);
+    compose_addressbook_fetched = true;
+  }
+}
+
+function compose_addressbook_search(search) 
+{
+  rcmail.compose_addressbook_list.clear();
+  lock = rcmail.set_busy(true, 'loading');
+  rcmail.http_post('plugin.get_addressbook', '_search='+urlencode(search), lock);
+}
+
+function compose_addressbook_receive(data) 
+{
+  var addresses = data.addresses;
+  var name;
+  var email;
+  
+  // save the addresses for later use
+  rcmail.compose_addressbook_addresses = addresses;
+
+  for(var j=0; j<addresses.length; j++) {
+    var name = addresses[j].name;
+    
+    if(addresses[j].id) {
+      email = 'address group';
+    } else {
+      email = addresses[j].email;
+    }
+    // add address to the row
+    compose_addressbook_add(name,email,j);    
+  }
+}
+
+function compose_addressbook_add(address,email,id) {
+  var row = document.createElement('tr');
+  row.id = 'rcmrow'+id;
+  td = document.createElement('td');
+  td.innerHTML = address;
+  td.setAttribute('title', email);
+  td.style.cursor='pointer';
+  row.appendChild(td);
+  
+  // add element to the list
+  rcmail.compose_addressbook_list.insert_row(row,0);
+}
+
+function compose_address_dblclick(list) {
+  var group_ids = [];
+  var group_sources = [];
+  
+  var id = list.get_single_selection();
+  if(id == null) return;
+  
+  var uid = list.rows[id].uid;
+  if(rcmail.compose_addressbook_addresses[uid].id) {
+    group_ids[0] = rcmail.compose_addressbook_addresses[uid].id;
+    group_sources[0] = rcmail.compose_addressbook_addresses[uid].source;
+    compose_addressbook_expand(group_ids, group_sources, '_to');
+  } else {
+    $("[name='_to']").attr('value', $("[name='_to']").val() + rcmail.compose_addressbook_addresses[uid].email+", ");
+  }
+  rcmail.compose_addressbook_list.clear_selection();
+}
+
+function compose_addressbook_add_recipients(target) {
+  var group_ids = [];
+  var group_sources = [];
+  
+  if(rcmail.compose_addressbook_list.selection.length == 0) {
+    rcmail.display_message(rcmail.gettext('compose_addressbook_noselect', 'compose_addressbook'), 'error');
+    return;
+  }
+  rcmail.compose_addressbook_list.focused = false;
+  switch(target) {
+    case '_cc':
+      rcmail_ui.show_header_form('cc');
+      break;
+    case '_bcc':
+      rcmail_ui.show_header_form('bcc');
+      break;
+  }
+  
+  for (var n=0; n<rcmail.compose_addressbook_list.selection.length; n++) {
+    var id = rcmail.compose_addressbook_list.selection[n];
+    var uid = rcmail.compose_addressbook_list.rows[id].uid;
+    var form = '[name="'+target+'"]';
+    
+    if(rcmail.compose_addressbook_addresses[uid].id) {
+      group_ids[group_ids.length] = rcmail.compose_addressbook_addresses[uid].id;
+      group_sources[group_sources.length] = rcmail.compose_addressbook_addresses[uid].source;
+    } else {
+      $('#'+target).attr('value', $('#'+target).val() + rcmail.compose_addressbook_addresses[uid].email+", ");
+    }
+  }
+  compose_addressbook_expand(group_ids, group_sources,target);
+  rcmail.compose_addressbook_list.clear_selection();  
+  rcmail.display_message(rcmail.gettext('compose_addressbook_added', 'compose_addressbook'), 'confirmation');
+}
+
+function compose_addressbook_expand(group_ids, group_sources,target) {
+  if(group_ids.length > 0) {
+    lock = rcmail.set_busy(true, 'loading');
+    rcmail.http_request('plugin.expand_groups', '_groupids='+urlencode(group_ids.join(','))+'&_groupsources='+urlencode(group_sources.join(','))+'&_target='+target, lock);
+  }
+}
+
+function compose_addressbook_receive_expand(data) {
+  var form = '[name="'+data.target+'"]';
+  
+  for(var j in data.members) {
+    $(form).attr('value', $(form).val() + data.members[j]+", ");
+  }
+}
diff --git a/plugins/compose_addressbook/compose_addressbook.php b/plugins/compose_addressbook/compose_addressbook.php
new file mode 100644
index 0000000..eec3244
--- /dev/null
+++ b/plugins/compose_addressbook/compose_addressbook.php
@@ -0,0 +1,179 @@
+<?php
+
+/**
+  * This plugin lets you add addressbook entries from the compose window using the mouse
+  * 
+  * @author Cor Bosman (roundcube at wa.ter.net)
+  */
+  
+class compose_addressbook extends rcube_plugin
+{
+  public $task = 'mail';
+
+  public function init()
+  {  
+    $rcmail = rcmail::get_instance();
+    $this->require_plugin('jqueryui');
+
+    $this->register_action('plugin.get_addressbook', array($this, 'get_address'));
+    $this->register_action('plugin.expand_groups', array($this, 'expand_groups'));
+    
+    if($rcmail->action == 'compose') {      
+      $this->compose_addressbook_init();      
+    }
+  }
+
+  public function compose_addressbook_init()
+  {
+    $this->add_texts('localization', true);
+    
+    $rcmail = rcmail::get_instance();
+    
+    $skin_path = $this->local_skin_path();
+  
+    // add javascript and stylesheets
+    $this->include_script('compose_addressbook.js');
+    $this->include_stylesheet("$skin_path/compose_addressbook.css");
+    
+    // html for dialog window
+    $table = new html_table(array('id' => 'compose_addressbook_table', 'class' => 'records-table', 'cols' => 1, 'cellspacing' => 0));
+     
+    // create div for dialog window
+    $rcmail->output->add_footer(html::div(array('id' => "compose_addressbook_dialog", 'title' => Q($this->gettext('compose_addressbook_title'))),
+                                  html::div(array('id' => "compose_addressbook_quicksearchbar"),
+                                    html::img(array('id'=>'compose_addressbook_searchmod','src'=>'/images/icons/glass.png')) .
+                                    html::tag('input', array('type' => "text", 'class' => 'compose_addressbook_filter','id'=>'compose_addressbook_filter')). 
+                                    html::a(array('id' => 'compose_addressbook_searchreset', 'href'=>'#'),
+                                      html::img(array('src'=>'/images/icons/reset.gif')))
+                                  ) . 
+                                  html::div(array('id' => "compose_addressbook_container"),
+                                    $table->show()
+                                  )
+                                ));
+                  
+    // add the addressbook button
+    $this->add_button(array(
+      'command' => 'plugin.compose_addressbook', 
+      'imagepas' => $skin_path.'/compose_addressbook.png', 
+      'imageact' => $skin_path.'/compose_addressbook.png', 
+      'title' => 'compose_addressbook.compose_addressbook_buttontitle', 
+      'id' => 'rcmbtn_compose_addressbook'), 'toolbar');
+    
+    $this->load_config();    
+    $rcmail->output->set_env('compose_addressbook_mode', $rcmail->config->get('compose_addressbook_mode', 'full'));
+    $rcmail->output->add_gui_object('compose_addressbook_list', 'compose_addressbook_table');
+    
+    // add some labels 
+    $rcmail->output->add_label('cc', 'bcc', 'to');
+    
+    // add list functions
+    $rcmail->output->include_script('list.js');
+                               
+  }
+  
+  // get the addressbook entries and return them to the UI.
+  function get_address() {
+    $contacts = array();
+    $this->load_config();
+    $rcmail = rcmail::get_instance();
+
+    $mode   = $rcmail->config->get('compose_addressbook_mode', 'full');
+    $single = $rcmail->config->get('autocomplete_single');
+
+    // get the addressbooks, or default to all address sources
+    $book_types = (array) $rcmail->config->get('compose_addressbooks', array_keys($rcmail->get_address_sources()));
+        
+    foreach ($book_types as $id) {
+      $abook = $rcmail->get_address_book($id);
+      $abook->set_pagesize(50000);
+
+      if($mode == 'full') {
+        $result = $abook->list_records();
+        while ($sql_arr = $result->iterate()) {
+          foreach (array_unique((array)$abook->get_col_values('email', $sql_arr, true)) as $email) {
+            $contact = format_email_recipient($email, $sql_arr['name']);
+            $contacts[] = array('name' => $sql_arr['name'] , 'email' => format_email_recipient($email, $sql_arr['name']));
+            if ($single)
+              break;
+          }
+        }
+        $search = null;
+        if($abook->groups) {
+          foreach($abook->list_groups($search) as $group) {
+            $abook->reset();
+            $abook->set_group($group['ID']);
+            $result = $abook->count();
+            if ($result->count) {
+              $contacts[] = array('name' => $group['name'] . ' (' . intval($result->count) . ')', 'id' => $group['ID'], 'source' => $id);
+            }
+          }
+        }
+      } else {
+        $search=trim(get_input_value('_search', RCUBE_INPUT_POST));
+        
+        if(!empty($search)) {
+          $result = $abook->search(array('name','email'),$search, false, true, true, 'email');
+          while ($sql_arr = $result->iterate()) {
+            foreach (array_unique((array)$abook->get_col_values('email', $sql_arr, true)) as $email) {
+              $contact = format_email_recipient($email, $sql_arr['name']);
+              $contacts[] = array('name' => $sql_arr['name'] , 'email' => format_email_recipient($email, $sql_arr['name']));
+              if ($single)
+                break;
+            }
+          }
+          if($abook->groups) {
+            foreach($abook->list_groups($search) as $group) {
+              $abook->reset();
+              $abook->set_group($group['ID']);
+              $result = $abook->count();
+              if ($result->count) {
+                $contacts[] = array('name' => $group['name'] . ' (' . intval($result->count) . ')', 'id' => $group['ID'], 'source' => $id);
+              }
+            }
+          }
+        } 
+      }
+    }
+    
+    sort($contacts);
+    
+    // send the addressbook back to javascript
+    $rcmail->output->command('plugin.compose_addressbook_receive', array('addresses' => $contacts));    
+  }
+  
+  // expand all the groups that we added
+  function expand_groups() {
+    $rcmail = rcmail::get_instance();
+    
+    $group_ids_input=trim(get_input_value('_groupids', RCUBE_INPUT_GET));
+    $group_sources_input=trim(get_input_value('_groupsources', RCUBE_INPUT_GET));
+    $target = trim(get_input_value('_target', RCUBE_INPUT_GET));
+    
+    if($group_ids_input == '' || $group_sources_input == '') exit;
+    
+    $group_ids = explode(',', $group_ids_input);
+    $group_sources = explode(',', $group_sources_input);
+    
+    // create a list of ids per address source
+    for($i=0; $i<count($group_sources);$i++) {
+      $address_sources[$group_sources[$i]][] = $group_ids[$i];
+    }
+    
+    // iterate over each address source and get the expanded groups
+    $members = array();
+    foreach($address_sources as $source => $groups) {
+      $abook = $rcmail->get_address_book($source);
+      foreach($groups as $group) {
+        $abook->set_group($group);
+        $abook->set_pagesize(1000);
+        $result = $abook->list_records(array('email','name'));
+        while ($result && ($sql_arr = $result->iterate())) {
+          $email = (array)$sql_arr['email'];
+          $members[] = format_email_recipient($email[0], $sql_arr['name']);
+        }
+      }
+    }
+    $rcmail->output->command('plugin.compose_addressbook_receive_expand', array('members' => array_unique($members), 'target' => $target));
+  }
+}
+?>
diff --git a/plugins/compose_addressbook/config.inc.php.dist b/plugins/compose_addressbook/config.inc.php.dist
new file mode 100644
index 0000000..b2ba8f9
--- /dev/null
+++ b/plugins/compose_addressbook/config.inc.php.dist
@@ -0,0 +1,21 @@
+<?php
+
+// which addressbook do we show. this can contain any addressbooks you use, including those set by plugins.
+// by default it's the same as the autocomplete_addressbooks setting.
+
+// $rcmail_config['compose_addressbooks'] = array('ldap');
+// $rcmail_config['compose_addressbooks'] = array('sql', 'static');
+// $rcmail_config['compose_addressbooks'] = array('sql');
+
+// which mode of operation do we use.
+//
+// full   - show the full addressbook in the popup window. this should work
+//          with most users, and is the default
+//
+// search - popup window starts up empty, and you can search for specific
+//          addresses. This is recommended for extremely large addressbooks
+//          containing thousands of addresses, and in most LDAP environments.
+
+$rcmail_config['compose_addressbook_mode'] = 'full';
+
+?>
diff --git a/plugins/compose_addressbook/localization/de_DE.inc b/plugins/compose_addressbook/localization/de_DE.inc
new file mode 100644
index 0000000..929a649
--- /dev/null
+++ b/plugins/compose_addressbook/localization/de_DE.inc
@@ -0,0 +1,11 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Adressbuch anzeigen';
+$labels['compose_addressbook_title'] = 'Adressbuch';
+$labels['compose_addressbook_close'] = 'Schließen';
+$labels['compose_addressbook_noselect'] = 'Sie haben keine Adresse ausgewählt';
+$labels['compose_addressbook_added'] = 'Adresse wurde hinzugefügt';
+
+
+?>
\ No newline at end of file
diff --git a/plugins/compose_addressbook/localization/en_GB.inc b/plugins/compose_addressbook/localization/en_GB.inc
new file mode 100644
index 0000000..a8078f4
--- /dev/null
+++ b/plugins/compose_addressbook/localization/en_GB.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Show Address Book';
+$labels['compose_addressbook_title'] = 'Address Book';
+$labels['compose_addressbook_close'] = 'Close';
+$labels['compose_addressbook_noselect'] = 'You did not select an address';
+$labels['compose_addressbook_added'] = 'Addresses added';
+
+?>
diff --git a/plugins/compose_addressbook/localization/en_US.inc b/plugins/compose_addressbook/localization/en_US.inc
new file mode 100644
index 0000000..a8078f4
--- /dev/null
+++ b/plugins/compose_addressbook/localization/en_US.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Show Address Book';
+$labels['compose_addressbook_title'] = 'Address Book';
+$labels['compose_addressbook_close'] = 'Close';
+$labels['compose_addressbook_noselect'] = 'You did not select an address';
+$labels['compose_addressbook_added'] = 'Addresses added';
+
+?>
diff --git a/plugins/compose_addressbook/localization/es_ES.inc b/plugins/compose_addressbook/localization/es_ES.inc
new file mode 100644
index 0000000..2a000f2
--- /dev/null
+++ b/plugins/compose_addressbook/localization/es_ES.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Mostrar Direcciones';
+$labels['compose_addressbook_title'] = 'Libreta de Direcciones';
+$labels['compose_addressbook_close'] = 'Cerrar';
+$labels['compose_addressbook_noselect'] = 'No has seleccionado ninguna dirección!';
+$labels['compose_addressbook_added'] = 'Dirección añadida';
+
+?>
diff --git a/plugins/compose_addressbook/localization/fr_FR.inc b/plugins/compose_addressbook/localization/fr_FR.inc
new file mode 100644
index 0000000..1f52ffe
--- /dev/null
+++ b/plugins/compose_addressbook/localization/fr_FR.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Afficher le carnet d\'adresse';
+$labels['compose_addressbook_title'] = 'Carnet d\'adresse';
+$labels['compose_addressbook_close'] = 'Fermer';
+$labels['compose_addressbook_noselect'] = 'Vous n\'avez pas sélectionné d\'adresse';
+$labels['compose_addressbook_added'] = 'Adresses ajoutées';
+
+?>
diff --git a/plugins/compose_addressbook/localization/it_IT.inc b/plugins/compose_addressbook/localization/it_IT.inc
new file mode 100644
index 0000000..d1b0c46
--- /dev/null
+++ b/plugins/compose_addressbook/localization/it_IT.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Mostra la rubrica';
+$labels['compose_addressbook_title'] = 'Rubrica';
+$labels['compose_addressbook_close'] = 'Chiudi';
+$labels['compose_addressbook_noselect'] = 'Non hai selezionato nessun indirizzo';
+$labels['compose_addressbook_added'] = 'L\'indirizzo è stato aggiunto';
+
+?>
diff --git a/plugins/compose_addressbook/localization/nl_NL.inc b/plugins/compose_addressbook/localization/nl_NL.inc
new file mode 100644
index 0000000..f5de52a
--- /dev/null
+++ b/plugins/compose_addressbook/localization/nl_NL.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Voeg adres uit adresboek toe';
+$labels['compose_addressbook_title'] = 'Adres boek';
+$labels['compose_addressbook_close'] = 'Sluiten';
+$labels['compose_addressbook_noselect'] = 'Er is geen contact adres geselecteerd';
+$labels['compose_addressbook_added'] = 'Adressen zijn toegevoegd';
+
+?>
diff --git a/plugins/compose_addressbook/localization/pl_PL.inc b/plugins/compose_addressbook/localization/pl_PL.inc
new file mode 100644
index 0000000..eb95e42
--- /dev/null
+++ b/plugins/compose_addressbook/localization/pl_PL.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Zobacz książkę adresową';
+$labels['compose_addressbook_title'] = 'Książka adresowa';
+$labels['compose_addressbook_close'] = 'Zamknij';
+$labels['compose_addressbook_noselect'] = 'Zaznacz jakiÅ› adres';
+$labels['compose_addressbook_added'] = 'Adres został dodany';
+
+?>
diff --git a/plugins/compose_addressbook/localization/sv_SE.inc b/plugins/compose_addressbook/localization/sv_SE.inc
new file mode 100644
index 0000000..6e6d49f
--- /dev/null
+++ b/plugins/compose_addressbook/localization/sv_SE.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = 'Visa adressbok';
+$labels['compose_addressbook_title'] = 'Adressbok';
+$labels['compose_addressbook_close'] = 'Stäng';
+$labels['compose_addressbook_noselect'] = 'Ingen adress valdes';
+$labels['compose_addressbook_added'] = 'Adress tillagd';
+
+?>
diff --git a/plugins/compose_addressbook/localization/zh_TW.inc b/plugins/compose_addressbook/localization/zh_TW.inc
new file mode 100644
index 0000000..1be7b61
--- /dev/null
+++ b/plugins/compose_addressbook/localization/zh_TW.inc
@@ -0,0 +1,10 @@
+<?php
+
+$labels = array();
+$labels['compose_addressbook_buttontitle'] = '顯示地址簿';
+$labels['compose_addressbook_title'] = '地址簿';
+$labels['compose_addressbook_close'] = '關閉';
+$labels['compose_addressbook_noselect'] = '沒有選擇電郵地址';
+$labels['compose_addressbook_added'] = '已加入電郵地址';
+
+?>
\ No newline at end of file
diff --git a/plugins/compose_addressbook/skins/default/compose_addressbook.css b/plugins/compose_addressbook/skins/default/compose_addressbook.css
new file mode 100644
index 0000000..f8ec665
--- /dev/null
+++ b/plugins/compose_addressbook/skins/default/compose_addressbook.css
@@ -0,0 +1,83 @@
+#compose_addressbook_dialog {
+  display: none;
+  background-color: #FAFAFA;
+  color: #333;
+  padding: 5px 0 0 0;
+  overflow: hidden;
+}
+
+#compose_addressbook_dialog .ui-dialog {
+  padding: 0;
+}
+
+#compose_addressbook_container {
+  height: 376px;
+  overflow: auto;
+  overflow-x: hidden;
+  border-top: 1px solid #AAAAAA;
+  position: relative;
+  top: 30px;
+}
+
+#compose_addressbook_dialog table {
+  width: 100%;
+  overflow: hidden;
+}
+
+#compose_addressbook_quicksearchbar
+{
+  position: absolute;
+  left: 60px;
+  width: 182px;
+  height: 20px;
+  text-align: right;
+  background: url('searchfield.gif') top left no-repeat;
+}
+
+#compose_addressbook_searchreset
+{
+  position: absolute;
+  top: 3px;
+  right: 4px;
+  text-decoration: none;
+}
+
+#compose_addressbook_searchmod
+{
+ position: absolute;
+ top: 3px;
+ right: 160px;
+}
+
+#compose_addressbook_quicksearchbar img
+{
+  vertical-align: middle;
+}
+
+#compose_addressbook_filter
+{
+  margin-right: 4px;
+  margin-bottom: 5px;
+  position: absolute;
+  top: 2px;
+  left: 24px;
+  width: 140px;
+  height: 15px;
+  font-size: 11px;
+  padding: 0px;
+  border: none;
+}
+
+#compose_addressbook_dialog input {
+  outline: none;
+  border: none !important;
+}
+
+#compose_addressbook_filter[type=text]:focus {
+  outline: 0 none;
+}
+
+.ui-dialog-buttonpane button {
+  font-size: 10px !important;
+  width: 85px !important;
+}
\ No newline at end of file
diff --git a/plugins/compose_addressbook/skins/default/compose_addressbook.png b/plugins/compose_addressbook/skins/default/compose_addressbook.png
new file mode 100644
index 0000000..e58e4dd
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/compose_addressbook.png differ
diff --git a/plugins/compose_addressbook/skins/default/searchfield.gif b/plugins/compose_addressbook/skins/default/searchfield.gif
new file mode 100644
index 0000000..756a17e
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/searchfield.gif differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-anim_basic_16x16.gif b/plugins/compose_addressbook/skins/default/smoothness/images/ui-anim_basic_16x16.gif
new file mode 100644
index 0000000..085ccae
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-anim_basic_16x16.gif differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644
index 0000000..5b5dab2
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100644
index 0000000..ac8b229
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644
index 0000000..ad3d634
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 0000000..42ccba2
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100644
index 0000000..5a46b47
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 0000000..86c2baa
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 0000000..4443fdc
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644
index 0000000..7c9fa6c
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_222222_256x240.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_222222_256x240.png
new file mode 100644
index 0000000..b273ff1
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_222222_256x240.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_2e83ff_256x240.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_2e83ff_256x240.png
new file mode 100644
index 0000000..09d1cdc
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_2e83ff_256x240.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_454545_256x240.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_454545_256x240.png
new file mode 100644
index 0000000..59bd45b
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_454545_256x240.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_888888_256x240.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_888888_256x240.png
new file mode 100644
index 0000000..6d02426
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_888888_256x240.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_cd0a0a_256x240.png b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 0000000..2ab019b
Binary files /dev/null and b/plugins/compose_addressbook/skins/default/smoothness/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/plugins/compose_addressbook/skins/default/smoothness/jquery-ui-1.8.2.custom.css b/plugins/compose_addressbook/skins/default/smoothness/jquery-ui-1.8.2.custom.css
new file mode 100644
index 0000000..401eb1d
--- /dev/null
+++ b/plugins/compose_addressbook/skins/default/smoothness/jquery-ui-1.8.2.custom.css
@@ -0,0 +1,345 @@
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorH
 ighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+*/
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-right {  -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* Resizable
+----------------------------------*/
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Button
+----------------------------------*/
+
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; } 
+button.ui-button-icons-only { width: 3.7em; } 
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4;  }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+
+
+
+
+
+/* Dialog
+----------------------------------*/
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
diff --git a/plugins/tinymce_config/LICENSE b/plugins/tinymce_config/LICENSE
new file mode 100644
index 0000000..dba13ed
--- /dev/null
+++ b/plugins/tinymce_config/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/plugins/tinymce_config/package.xml b/plugins/tinymce_config/package.xml
new file mode 100644
index 0000000..621d815
--- /dev/null
+++ b/plugins/tinymce_config/package.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+    http://pear.php.net/dtd/tasks-1.0.xsd
+    http://pear.php.net/dtd/package-2.0
+    http://pear.php.net/dtd/package-2.0.xsd">
+	<name>tinymce_config</name>
+	<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
+	<summary>TinyMCE Editor Configurator</summary>
+	<description>
+		Sample plugin to set additional TinyMCE editor configuration.
+	</description>
+	<lead>
+		<name>Aleksander Machniak</name>
+		<user>machniak</user>
+		<email>machniak at kolabsys.com</email>
+		<active>yes</active>
+	</lead>
+	<date>2012-10-12</date>
+	<version>
+		<release>0.1</release>
+		<api>0.1</api>
+	</version>
+	<stability>
+		<release>stable</release>
+		<api>stable</api>
+	</stability>
+	<license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3</license>
+	<notes>-</notes>
+	<contents>
+		<dir baseinstalldir="/" name="/">
+			<file name="tinymce_config.php" role="php">
+				<tasks:replace from="@name@" to="name" type="package-info"/>
+				<tasks:replace from="@package_version@" to="version" type="package-info"/>
+			</file>
+		</dir>
+		<!-- / -->
+	</contents>
+	<dependencies>
+		<required>
+			<php>
+				<min>5.2.1</min>
+			</php>
+			<pearinstaller>
+				<min>1.7.0</min>
+			</pearinstaller>
+			<package>
+				<name>libkolab</name>
+				<uri>http://git.kolab.org/roundcubemail-plugins-kolab/tree/plugins/tinymce_config</uri>
+			</package>
+		</required>
+	</dependencies>
+	<phprelease/>
+</package>
diff --git a/plugins/tinymce_config/tinymce_config.php b/plugins/tinymce_config/tinymce_config.php
new file mode 100644
index 0000000..faa17f3
--- /dev/null
+++ b/plugins/tinymce_config/tinymce_config.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Sample plugin to configure TinyMCE editor
+ *
+ * Copyright (C) 2011-2012, 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Aleksander Machniak <machniak at kolabsys.com>
+ */
+class tinymce_config extends rcube_plugin
+{
+  public $task = 'mail|settings';
+
+  function init()
+  {
+    $this->add_hook('html_editor', array($this, 'config'));
+  }
+
+  function config($args)
+  {
+    $rcmail = rcmail::get_instance();
+
+    $config = array(
+        'forced_root_block' => '',
+        'force_p_newlines' => false,
+        'force_br_newlines' => true,
+    );
+
+    $script = sprintf('$.extend(window.rcmail_editor_settings, %s);', json_encode($config));
+
+    $rcmail->output->add_script($script, 'foot');
+  }
+}
+




More information about the packaging-commits mailing list