summaryrefslogtreecommitdiff
path: root/plugins/kolab_zpush
diff options
context:
space:
mode:
authorThomas <thomas@brotherli.ch>2011-09-26 21:36:22 (GMT)
committerThomas <thomas@brotherli.ch>2011-09-26 21:36:22 (GMT)
commit342320f4483aa587693842904ffae4751e479709 (patch)
treed2202066880498e069b418e9e23e23d8c51329a0 /plugins/kolab_zpush
parent4d5a7f43076bd2dcc5dcc4a7b279d25e5e65cff9 (diff)
downloadroundcubemail-plugins-kolab-342320f4483aa587693842904ffae4751e479709.tar.gz
Initial import of the Z-Push configuration plugin for Roundcube
Diffstat (limited to 'plugins/kolab_zpush')
-rw-r--r--plugins/kolab_zpush/README.txt5
-rw-r--r--plugins/kolab_zpush/kolab_zpush.js142
-rw-r--r--plugins/kolab_zpush/kolab_zpush.php282
-rw-r--r--plugins/kolab_zpush/kolab_zpush_ui.php150
-rw-r--r--plugins/kolab_zpush/localization/de_CH.inc22
-rw-r--r--plugins/kolab_zpush/localization/en_US.inc23
-rwxr-xr-xplugins/kolab_zpush/skins/default/alarm-clock.pngbin0 -> 841 bytes
-rw-r--r--plugins/kolab_zpush/skins/default/config.css100
-rw-r--r--plugins/kolab_zpush/skins/default/foldertypes.pngbin0 -> 2568 bytes
-rw-r--r--plugins/kolab_zpush/skins/default/pointer-left.gifbin0 -> 173 bytes
-rwxr-xr-xplugins/kolab_zpush/skins/default/synchronize.pngbin0 -> 836 bytes
-rw-r--r--plugins/kolab_zpush/skins/default/templates/config.html59
12 files changed, 783 insertions, 0 deletions
diff --git a/plugins/kolab_zpush/README.txt b/plugins/kolab_zpush/README.txt
new file mode 100644
index 0000000..ccd513a
--- /dev/null
+++ b/plugins/kolab_zpush/README.txt
@@ -0,0 +1,5 @@
+Icons by Fugue Icons <http://p.yusukekamiyamane.com/>
+
+Copyright (C) 2010 Yusuke Kamiyamane. All rights reserved.
+The icons are licensed under a Creative Commons Attribution
+3.0 license. <http://creativecommons.org/licenses/by/3.0/>
diff --git a/plugins/kolab_zpush/kolab_zpush.js b/plugins/kolab_zpush/kolab_zpush.js
new file mode 100644
index 0000000..b66b2eb
--- /dev/null
+++ b/plugins/kolab_zpush/kolab_zpush.js
@@ -0,0 +1,142 @@
+/**
+ * Client scripts for the Kolab Z-Push configuration utitlity
+ *
+ * @version 0.1
+ * @author Thomas Bruederli <roundcube@gmail.com>
+ *
+ * Copyright (C) 2011, Kolab Systems AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ */
+
+function kolab_zpush_config()
+{
+ /* private members */
+ var me = this;
+ var http_lock = null;
+ var active_device = null;
+
+
+ /* constructor */
+
+ var devicelist = new rcube_list_widget(rcmail.gui_objects.devicelist,
+ { multiselect:true, draggable:false, keyboard:true });
+ devicelist.addEventListener('select', select_device);
+ devicelist.init();
+
+ rcmail.register_command('plugin.save-config', save_config, true);
+ rcmail.addEventListener('plugin.zpush_data_ready', device_data_ready);
+ rcmail.addEventListener('plugin.zpush_save_complete', save_complete);
+
+ $('input.subscription').change(function(e){ $('#'+this.id+'_alarm').prop('disabled', !this.checked); });
+ $(window).bind('resize', resize_ui);
+
+
+ /* private methods */
+ function select_device(list)
+ {
+ active_device = list.get_single_selection();
+ rcmail.enable_command('plugin.save-config');
+
+ if (active_device) {
+ http_lock = rcmail.set_busy(true, 'loading');
+ rcmail.http_request('plugin.zpushjson', { cmd:'load', id:active_device }, http_lock);
+ }
+
+ $('#introtext').hide();
+ }
+
+ // callback from server after loading device data
+ function device_data_ready(data)
+ {
+ if (data.id && data.id == active_device) {
+ $('#config-device-alias').val(data.devicealias);
+ $('#config-device-mode').val(data.syncmode);
+
+ $('input.subscription').each(function(i, elem){
+ var key = elem.value;
+ elem.checked = data.subscribed[key] ? true : false;
+ }).change();
+ $('input.alarm').each(function(i, elem){
+ var key = elem.value;
+ elem.checked = data.subscribed[key] == 2;
+ });
+
+ $('#configform, #prefs-box .boxtitle').show();
+ resize_ui();
+ }
+ else {
+ $('#configform, #prefs-box .boxtitle').hide();
+ }
+ }
+
+ // submit current configuration form to server
+ function save_config()
+ {
+ // TODO: validate device info
+ var data = {
+ cmd: 'save',
+ id: active_device,
+ devicealias: $('#config-device-alias').val(),
+ syncmode: $('#config-device-mode option:selected').val()
+ };
+
+ data.subscribed = {};
+ $('input.subscription:checked').each(function(i, elem){
+ data.subscribed[elem.value] = 1;
+ });
+ $('input.alarm:checked').each(function(i, elem){
+ if (data.subscribed[elem.value])
+ data.subscribed[elem.value] = 2;
+ });
+
+ http_lock = rcmail.set_busy(true, 'kolab_zpush.savingdata');
+ rcmail.http_post('plugin.zpushjson', data, http_lock);
+ }
+
+ // callback function when saving has completed
+ function save_complete(p)
+ {
+ if (p.success && p.devicename) {
+ $('#devices-table tr.selected span.devicealias').html(p.devicename);
+ }
+ }
+
+ // handler for window resize events: sets max-height of folders list scroll container
+ function resize_ui()
+ {
+ if (active_device) {
+ var h = $(window).height();
+ var pos = $('#folderscrollist').offset();
+ $('#folderscrollist').css('max-height', (h - pos.top - 90) + 'px');
+ }
+ }
+}
+
+
+window.rcmail && rcmail.addEventListener('init', function(evt) {
+ var ACTION_CONFIG = 'plugin.zpushconfig';
+
+ // add button to tabs list
+ var tab = $('<span>').attr('id', 'settingstabpluginzpushconfig').addClass('tablink');
+ var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.zpushconfig').html(rcmail.gettext('tabtitle', 'kolab_zpush')).appendTo(tab);
+ rcmail.add_element(tab, 'tabs');
+
+ if (rcmail.env.action == ACTION_CONFIG)
+ new kolab_zpush_config();
+});
+
+
+// extend jQuery
+(function($){
+ $.fn.serializeJSON = function(){
+ var json = {};
+ jQuery.map($(this).serializeArray(), function(n, i) {
+ json[n['name']] = n['value'];
+ });
+ return json;
+ };
+})(jQuery);
+
diff --git a/plugins/kolab_zpush/kolab_zpush.php b/plugins/kolab_zpush/kolab_zpush.php
new file mode 100644
index 0000000..ef87c0c
--- /dev/null
+++ b/plugins/kolab_zpush/kolab_zpush.php
@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * Z-Push configuration utility for Kolab accounts
+ *
+ * @version 0.1
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ *
+ * Copyright (C) 2011, Kolab Systems AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+class kolab_zpush extends rcube_plugin
+{
+ public $task = 'settings';
+ public $urlbase;
+
+ private $rc;
+ private $ui;
+ private $cache;
+ private $devices;
+ private $folders;
+ private $folders_meta;
+ private $root_meta;
+
+ const ROOT_MAILBOX = 'INBOX';
+ const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
+ const ACTIVESYNC_KEY = '/private/vendor/kolab/activesync';
+
+ /**
+ * Plugin initialization.
+ */
+ public function init()
+ {
+ $this->rc = rcmail::get_instance();
+
+ $this->require_plugin('jqueryui');
+ $this->add_texts('localization/', true);
+
+ $this->include_script('kolab_zpush.js');
+
+ $this->register_action('plugin.zpushconfig', array($this, 'config_view'));
+ $this->register_action('plugin.zpushjson', array($this, 'json_command'));
+ }
+
+
+ /**
+ * Establish IMAP connection
+ */
+ public function init_imap()
+ {
+ $this->rc->imap_connect();
+ $this->cache = $this->rc->get_cache('zpush', 'db', 900);
+ $this->cache->expunge();
+
+ if ($meta = $this->rc->imap->get_metadata(self::ROOT_MAILBOX, self::ACTIVESYNC_KEY)) {
+ // clear cache if device config changed
+ if (($oldmeta = $this->cache->read('devicemeta')) && $oldmeta != $meta)
+ $this->cache->remove();
+
+ $this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ACTIVESYNC_KEY]);
+ $this->cache->remove('devicemeta');
+ $this->cache->write('devicemeta', $meta);
+ }
+ }
+
+
+ /**
+ * Handle JSON requests
+ */
+ public function json_command()
+ {
+ $cmd = get_input_value('cmd', RCUBE_INPUT_GPC);
+ $device_id = get_input_value('id', RCUBE_INPUT_GPC);
+
+ switch ($cmd) {
+ case 'load':
+ $result = array();
+ $this->init_imap();
+ $devices = $this->list_devices();
+ if ($device = $devices[$device_id]) {
+ $result['id'] = $device_id;
+ $result['devicealias'] = $device['ALIAS'];
+ $result['syncmode'] = intval($device['MODE']);
+ $result['laxpic'] = intval($device['LAXPIC']);
+ $result['subscribed'] = array();
+
+ foreach ($this->folders_meta() as $folder => $meta) {
+ if ($meta[$device_id]['S'])
+ $result['subscribed'][$folder] = intval($meta[$device_id]['S']);
+ }
+
+ $this->rc->output->command('plugin.zpush_data_ready', $result);
+ }
+ else {
+ $this->rc->output->show_message($this->gettext('devicenotfound'), 'error');
+ }
+ break;
+
+ case 'save':
+ console($_POST);
+ $this->init_imap();
+ $devices = $this->list_devices();
+ $syncmode = get_input_value('syncmode', RCUBE_INPUT_POST);
+ $devicealias = get_input_value('devicealias', RCUBE_INPUT_POST);
+ $subsciptions = get_input_value('subscribed', RCUBE_INPUT_POST);
+ $err = false;
+
+ if ($device = $devices[$device_id]) {
+ // update device config if changed
+ if ($devicealias != $this->root_meta['DEVICE'][$device_id]['ALIAS'] ||
+ $syncmode != $this->root_meta['DEVICE'][$device_id]['MODE'] ||
+ $subsciptions[self::ROOT_MAILBOX] != $this->root_meta['FOLDER'][$device_id]['S']) {
+ $this->root_meta['DEVICE'][$device_id]['MODE'] = $syncmode;
+ $this->root_meta['DEVICE'][$device_id]['ALIAS'] = $devicealias;
+ $this->root_meta['FOLDER'][$device_id]['S'] = intval($subsciptions[self::ROOT_MAILBOX]);
+
+# $err = !$this->rc->imap->set_metadata(self::ROOT_MAILBOX,
+# array(self::ACTIVESYNC_KEY => $this->serialize_metadata($this->root_meta)));
+ }
+ // iterate over folders list and update metadata if necessary
+ foreach ($this->folders_meta() as $folder => $meta) {
+ if ($subsciptions[$folder] != $meta[$device_id]['S']) {
+ $meta[$device_id]['S'] = intval($subsciptions[$folder]);
+ $this->folders_meta[$folder] = $meta;
+ unset($meta['TYPE']);
+
+ // read metadata first
+ $folderdata = $this->rc->imap->get_metadata($folder, array(self::ACTIVESYNC_KEY));
+ if ($asyncdata = $folderdata[$folder][self::ACTIVESYNC_KEY])
+ $metadata = $this->unserialize_metadata($asyncdata);
+ $metadata['FOLDER'] = $meta;
+
+# $err |= !$this->rc->imap->set_metadata($folder, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($metadata)));
+ }
+ }
+
+ // update cache
+ $this->cache->remove('folders');
+ $this->cache->write('folders', $this->folders_meta);
+
+ $this->rc->output->command('plugin.zpush_save_complete', array('success' => !$err, 'devicename' => Q($devicealias)));
+ }
+
+ if ($err)
+ $this->rc->output->show_message($this->gettext('savingerror'), 'error');
+ else
+ $this->rc->output->show_message($this->gettext('successfullysaved'), 'confirmation');
+
+ break;
+ }
+
+ $this->rc->output->send();
+ }
+
+
+ /**
+ * Render main UI for device configuration
+ */
+ public function config_view()
+ {
+ require_once($this->home . '/kolab_zpush_ui.php');
+
+ $this->init_imap();
+
+ // checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
+ if ($this->rc->imap->get_capability('METADATA') || $this->rc->imap->get_capability('ANNOTATEMORE') || $this->rc->imap->get_capability('ANNOTATEMORE2')) {
+ $this->list_devices();
+ }
+ else {
+ $this->rc->output->show_message($this->gettext('notsupported'), 'error');
+ }
+
+ $this->ui = new kolab_zpush_ui($this);
+
+ $this->register_handler('plugin.devicelist', array($this->ui, 'device_list'));
+ $this->register_handler('plugin.deviceconfigform', array($this->ui, 'device_config_form'));
+ $this->register_handler('plugin.foldersubscriptions', array($this->ui, 'folder_subscriptions'));
+
+ $this->rc->output->send('kolab_zpush.config');
+ }
+
+
+ /**
+ * List known devices
+ *
+ * @return array Device list as hash array
+ */
+ public function list_devices()
+ {
+ if (!isset($this->devices)) {
+ $this->devices = (array)$this->root_meta['DEVICE'];
+ }
+
+ return $this->devices;
+ }
+
+
+ /**
+ * Get list of all folders available for sync
+ *
+ * @return array List of mailbox folders
+ */
+ public function list_folders()
+ {
+ if (!isset($this->folders)) {
+ // read cached folder meta data
+ if ($cached_folders = $this->cache->read('folders')) {
+ $this->folders_meta = $cached_folders;
+ $this->folders = array_keys($this->folders_meta);
+ }
+ // fetch folder data from server
+ else {
+ $this->folders = $this->rc->imap->list_unsubscribed();
+ foreach ($this->folders as $folder) {
+ $folderdata = $this->rc->imap->get_metadata($folder, array(self::ACTIVESYNC_KEY, self::CTYPE_KEY));
+ $foldertype = explode('.', $folderdata[$folder][self::CTYPE_KEY]);
+
+ if ($asyncdata = $folderdata[$folder][self::ACTIVESYNC_KEY]) {
+ $metadata = $this->unserialize_metadata($asyncdata);
+ $this->folders_meta[$folder] = $metadata['FOLDER'];
+ }
+ $this->folders_meta[$folder]['TYPE'] = !empty($foldertype[0]) ? $foldertype[0] : 'mail';
+ }
+
+ // cache it!
+ $this->cache->write('folders', $this->folders_meta);
+ }
+ }
+
+ return $this->folders;
+ }
+
+ /**
+ * Getter for folder metadata
+ *
+ * @return array Hash array with meta data for each folder
+ */
+ public function folders_meta()
+ {
+ if (!isset($this->folders_meta))
+ $this->list_folders();
+
+ return $this->folders_meta;
+ }
+
+ /**
+ * Helper method to decode saved IMAP metadata
+ */
+ private function unserialize_metadata($str)
+ {
+ if (!empty($str))
+ return @json_decode(base64_decode($str), true);
+
+ return null;
+ }
+
+ /**
+ * Helper method to encode IMAP metadata for saving
+ */
+ private function serialize_metadata($data)
+ {
+ if (is_array($data))
+ return base64_encode(json_encode($data));
+
+ return '';
+ }
+
+} \ No newline at end of file
diff --git a/plugins/kolab_zpush/kolab_zpush_ui.php b/plugins/kolab_zpush/kolab_zpush_ui.php
new file mode 100644
index 0000000..cd167e0
--- /dev/null
+++ b/plugins/kolab_zpush/kolab_zpush_ui.php
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * Z-Push configuration user interface builder
+ *
+ * @version 0.1
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ *
+ * Copyright (C) 2011, Kolab Systems AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+class kolab_zpush_ui
+{
+ private $rc;
+ private $config;
+
+ public function __construct($config)
+ {
+ $this->config = $config;
+ $this->rc = rcmail::get_instance();
+
+ $skin = $this->rc->config->get('skin');
+ $this->config->include_stylesheet('skins/' . $skin . '/config.css');
+ $this->rc->output->include_script('list.js');
+ $this->skin_path = $this->config->urlbase . 'skins/' . $skin . '/';
+ }
+
+
+ public function device_list($attrib = array())
+ {
+ $attrib += array('id' => 'devices-list');
+
+ $devices = $this->config->list_devices();
+ $table = new html_table();
+
+ foreach ($devices as $id => $device) {
+ $name = $device['ALIAS'] ? $device['ALIAS'] : $id;
+ $table->add_row(array('id' => 'rcmrow' . $id));
+ $table->add(null, html::span('devicealias', Q($name)) . html::span('devicetype', Q($device['TYPE'])));
+ }
+
+ $this->rc->output->add_gui_object('devicelist', $attrib['id']);
+ $this->rc->output->set_env('devices', $devices);
+
+ return $table->show($attrib);
+ }
+
+
+ public function device_config_form($attrib = array())
+ {
+ $table = new html_table(array('cols' => 2));
+
+ $field_id = 'config-device-alias';
+ $input = new html_inputfield(array('name' => 'devicealias', 'id' => $field_id, 'size' => 40));
+ $table->add('title', html::label($field_id, $this->config->gettext('devicealias')));
+ $table->add(null, $input->show());
+
+ $field_id = 'config-device-mode';
+ $select = new html_select(array('name' => 'syncmode', 'id' => $field_id));
+ $select->add(array($this->config->gettext('modeauto'), $this->config->gettext('modeflat'), $this->config->gettext('modefolder')), array('-1', '0', '1'));
+ $table->add('title', html::label($field_id, $this->config->gettext('syncmode')));
+ $table->add(null, $select->show('-1'));
+
+ return $table->show($attrib);
+ }
+
+
+ public function folder_subscriptions($attrib = array())
+ {
+ if (!$attrib['id'])
+ $attrib['id'] = 'foldersubscriptions';
+
+ $table = new html_table();
+ $table->add_header('foldername', $this->config->gettext('folder'));
+ $table->add_header('subscription', $attrib['syncicon'] ? html::img(array('src' => $this->skin_path . $attrib['syncicon'], 'title' => $this->config->gettext('synchronize'))) : '');
+ $table->add_header('alarm', $attrib['alarmicon'] ? html::img(array('src' => $this->skin_path . $attrib['alarmicon'], 'title' => $this->config->gettext('withalarms'))) : '');
+
+ $folders_tree = array();
+ $delimiter = $this->rc->imap->get_hierarchy_delimiter();
+ foreach ($this->config->list_folders() as $folder)
+ rcmail_build_folder_tree($folders_tree, $folder, $delimiter);
+
+ $this->render_folders($folders_tree, $table, 0);
+
+ $this->rc->output->add_gui_object('subscriptionslist', $attrib['id']);
+
+ return $table->show($attrib);
+ }
+
+ /**
+ * Recursively compose folders table
+ */
+ private function render_folders($a_folders, $table, $level = 0)
+ {
+ $idx = 0;
+ $checkbox_sync = new html_checkbox(array('name' => 'subscribed[]', 'class' => 'subscription'));
+ $checkbox_alarm = new html_checkbox(array('name' => 'alarm[]', 'class' => 'alarm', 'disabled' => true));
+ $folders_meta = $this->config->folders_meta();
+
+ foreach ($a_folders as $key => $folder) {
+ $classes = array('mailbox');
+
+ if ($folder_class = rcmail_folder_classname($folder['id'])) {
+ $foldername = rcube_label($folder_class);
+ $classes[] = $folder_class;
+ }
+ else
+ $foldername = $folder['name'];
+
+ // visualize folder type
+ if ($type = $folders_meta[$folder['id']]['TYPE'])
+ $classes[] = $type;
+
+ if ($folder['virtual'])
+ $classes[] = 'virtual';
+
+ $folder_id = 'rcmf' . html_identifier($folder['id']);
+ $padding = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $level);
+
+ $table->add_row(array('class' => (($level+1) * $idx++) % 2 == 0 ? 'even' : 'odd'));
+ $table->add(join(' ', $classes), html::label($folder_id, $padding . Q($foldername)));
+ $table->add('subscription', $folder['virtual'] ? '' : $checkbox_sync->show('', array('value' => $folder['id'], 'id' => $folder_id)));
+
+ if (($type == 'event' || $type == 'task') && !$folder['virtual'])
+ $table->add('alarm', $checkbox_alarm->show('', array('value' => $folder['id'], 'id' => $folder_id.'_alarm')));
+ else
+ $table->add('alarm', '');
+
+ if (!empty($folder['folders']))
+ $this->render_folders($folder['folders'], $table, $level+1);
+ }
+
+ }
+
+}
+
diff --git a/plugins/kolab_zpush/localization/de_CH.inc b/plugins/kolab_zpush/localization/de_CH.inc
new file mode 100644
index 0000000..00d752b
--- /dev/null
+++ b/plugins/kolab_zpush/localization/de_CH.inc
@@ -0,0 +1,22 @@
+<?php
+
+$labels = array();
+$labels['tabtitle'] = 'Activesync';
+$labels['devices'] = 'Geräte';
+$labels['devicealias'] = 'Gerätename';
+$labels['syncmode'] = 'Modus';
+$labels['modeauto'] = 'Automatisch';
+$labels['modeflat'] = 'Forciere flache Struktur';
+$labels['modefolder'] = 'Forciere Ordnerstruktur';
+$labels['synchronize'] = 'Synchronisieren';
+$labels['withalarms'] = 'Mit Erinnerungen';
+$labels['syncsettings'] = 'Synchronisationseinstellungen';
+$labels['deviceconfig'] = 'Gerätekonfiguration';
+$labels['folderstosync'] = 'Order zum Symchronisieren';
+$labels['deletedevice'] = 'Gerät löschen';
+$labels['introtext'] = 'Wählen Sie das zu konfigurierende Gerät aus';
+$labels['savingdata'] = 'Daten werden gespeichert...';
+$labels['savingerror'] = 'Fehler beim Speichern';
+$labels['notsupported'] = 'Ihr Server unterstützt keine Activesync-Konfiguration';
+
+?> \ No newline at end of file
diff --git a/plugins/kolab_zpush/localization/en_US.inc b/plugins/kolab_zpush/localization/en_US.inc
new file mode 100644
index 0000000..74d360c
--- /dev/null
+++ b/plugins/kolab_zpush/localization/en_US.inc
@@ -0,0 +1,23 @@
+<?php
+
+$labels = array();
+$labels['tabtitle'] = 'Activesync';
+$labels['devices'] = 'Devices';
+$labels['devicealias'] = 'Device Name';
+$labels['syncmode'] = 'Sync Mode';
+$labels['modeauto'] = 'Determine automatically';
+$labels['modeflat'] = 'Force flat mode';
+$labels['modefolder'] = 'Force folder mode';
+$labels['synchronize'] = 'Synchronize';
+$labels['withalarms'] = 'With alarms';
+$labels['syncsettings'] = 'Synchronization settings';
+$labels['deviceconfig'] = 'Device configration';
+$labels['folderstosync'] = 'Folders to synchronize';
+$labels['deletedevice'] = 'Delete Device';
+$labels['introtext'] = 'Select a device to configure';
+$labels['nodevices'] = 'There are currently no devices registered.<br/><br/>In order to register a device, please connect it to the server first, using <a href="http://wiki.kolab.org/Z_push#Clients">the instructions in the Wiki</a>. Afterwards the device should become available for configuration here.';
+$labels['savingdata'] = 'Saving data...';
+$labels['savingerror'] = 'Failed to save configuration';
+$labels['notsupported'] = 'Your server does not support metadata/annotations';
+
+?> \ No newline at end of file
diff --git a/plugins/kolab_zpush/skins/default/alarm-clock.png b/plugins/kolab_zpush/skins/default/alarm-clock.png
new file mode 100755
index 0000000..518bdc1
--- /dev/null
+++ b/plugins/kolab_zpush/skins/default/alarm-clock.png
Binary files differ
diff --git a/plugins/kolab_zpush/skins/default/config.css b/plugins/kolab_zpush/skins/default/config.css
new file mode 100644
index 0000000..299734b
--- /dev/null
+++ b/plugins/kolab_zpush/skins/default/config.css
@@ -0,0 +1,100 @@
+/* Stylesheets for the Kolab Z-Push configuration UI */
+
+#devices-table {
+ width: 100%;
+ table-layout: fixed;
+}
+
+#devices-table td {
+ cursor: pointer;
+}
+
+#devices-table td span.devicetype {
+ padding-left: 1em;
+ font-style: italic;
+ color: #666;
+}
+
+#devices-table tr.selected td span.devicetype {
+ color: #ccc;
+}
+
+#foldersubscriptions thead td {
+ color: #999;
+ font-weight: bold;
+ padding: 4px;
+}
+
+
+#foldersubscriptions tbody td {
+ padding: 2px 4px;
+ border-bottom: 1px solid #ddd;
+}
+
+#foldersubscriptions td label {
+ display: block;
+}
+
+#foldersubscriptions td.mailbox {
+ padding-right: 3em;
+ padding-left: 25px;
+ min-width: 12em;
+ background: url(foldertypes.png) 2px 2px no-repeat;
+}
+
+#foldersubscriptions td.virtual {
+ color: #999;
+}
+
+#foldersubscriptions td.mail {
+ background-position: 2px -17px;
+}
+
+#foldersubscriptions td.event {
+ background-position: 2px -37px;
+}
+
+#foldersubscriptions td.contact {
+ background-position: 2px -57px;
+}
+
+#foldersubscriptions td.note {
+ background-position: 2px -77px;
+}
+
+#foldersubscriptions td.task {
+ background-position: 2px -97px;
+}
+
+#folderscrollist {
+ overflow: auto;
+ max-height: 400px;
+ margin-top: 0.5em;
+}
+
+#introtext {
+ position: absolute;
+ top: 12px;
+ left: 5px;
+ padding-left: 10px;
+}
+
+#introtext .pointer-left {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 14px;
+ height: 100%;
+ background: url(pointer-left.gif) right center no-repeat;
+}
+
+#introtext .inner {
+ border: 3px solid #e7e7e7;
+ background: #fff;
+}
+
+#introtext .bordered {
+ padding: 12px;
+ border: 1px solid #ccc;
+ background: #fff;
+}
diff --git a/plugins/kolab_zpush/skins/default/foldertypes.png b/plugins/kolab_zpush/skins/default/foldertypes.png
new file mode 100644
index 0000000..abc16d8
--- /dev/null
+++ b/plugins/kolab_zpush/skins/default/foldertypes.png
Binary files differ
diff --git a/plugins/kolab_zpush/skins/default/pointer-left.gif b/plugins/kolab_zpush/skins/default/pointer-left.gif
new file mode 100644
index 0000000..b2de585
--- /dev/null
+++ b/plugins/kolab_zpush/skins/default/pointer-left.gif
Binary files differ
diff --git a/plugins/kolab_zpush/skins/default/synchronize.png b/plugins/kolab_zpush/skins/default/synchronize.png
new file mode 100755
index 0000000..ba5ebd1
--- /dev/null
+++ b/plugins/kolab_zpush/skins/default/synchronize.png
Binary files differ
diff --git a/plugins/kolab_zpush/skins/default/templates/config.html b/plugins/kolab_zpush/skins/default/templates/config.html
new file mode 100644
index 0000000..927684e
--- /dev/null
+++ b/plugins/kolab_zpush/skins/default/templates/config.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<script type="text/javascript" src="/functions.js"></script>
+<script type="text/javascript" src="/splitter.js"></script>
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+<div id="mainscreen">
+
+<div id="sectionslist">
+<div id="directorylist-title" class="boxtitle"><roundcube:label name="kolab_zpush.devices" /></div>
+<div class="boxlistcontent">
+ <roundcube:object name="plugin.devicelist" id="devices-table" class="records-table" cellspacing="0" />
+</div>
+<div class="boxfooter">
+ <roundcube:button command="device-create" type="link" title="newcontactgroup" class="buttonPas adddevice" classAct="button adddevice" content=" " />
+</div>
+</div>
+
+<div id="prefs-box">
+ <div class="boxtitle" style="display:none"><roundcube:label name="kolab_zpush.syncsettings"></div>
+ <form action="#" method="post" id="configform" class="boxcontent" style="display:none">
+ <fieldset>
+ <legend><roundcube:label name="kolab_zpush.deviceconfig" /></legend>
+ <roundcube:object name="plugin.deviceconfigform" form="configform" />
+ </fieldset>
+ <fieldset>
+ <legend><roundcube:label name="kolab_zpush.folderstosync" /></legend>
+ <div id="folderscrollist">
+ <roundcube:object name="plugin.foldersubscriptions" form="configform" id="foldersubscriptions" cellspacing="0" summary="Folder subscription table" syncicon="synchronize.png" alarmicon="alarm-clock.png" />
+ </div>
+ </fieldset>
+ <p class="formbuttons">
+ <roundcube:button type="input" class="button mainaction" command="plugin.save-config" label="save" />
+ <roundcube:button type="input" class="button" command="plugin.delete-device" label="kolab_zpush.deletedevice" />
+ </p>
+ </form>
+ <div id="introtext">
+ <div class="inner"><div class="bordered"><roundcube:label name="kolab_zpush.introtext" /></div></div>
+ <div class="pointer-left"></div>
+ </div>
+</div>
+
+</div>
+
+<script type="text/javascript">
+ var viewsplit = new rcube_splitter({ id:'devicelistsplitter', p1: 'sectionslist', p2: 'prefs-box', orientation: 'v', relative: true, start: 195 });
+ rcmail.add_onload('viewsplit.init()');
+</script>
+
+</body>
+</html>