From 95f0d84cfebaf234130f02c08bfc9d0108a7ad18 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 30 Sep 2011 14:01:08 +0200 Subject: [PATCH] Finish z-push config: group folders by type; implement delete-device command --- plugins/kolab_zpush/kolab_zpush.js | 28 ++++-- plugins/kolab_zpush/kolab_zpush.php | 53 +++++++++++ plugins/kolab_zpush/kolab_zpush_ui.php | 90 +++++++++++++++--- plugins/kolab_zpush/localization/de_CH.inc | 7 ++ plugins/kolab_zpush/localization/en_US.inc | 7 ++ plugins/kolab_zpush/skins/default/config.css | 72 ++++++++------ .../kolab_zpush/skins/default/foldertypes.png | Bin 2568 -> 2291 bytes .../skins/default/templates/config.html | 6 +- 8 files changed, 212 insertions(+), 51 deletions(-) diff --git a/plugins/kolab_zpush/kolab_zpush.js b/plugins/kolab_zpush/kolab_zpush.js index f820c70d..ced5a0d3 100644 --- a/plugins/kolab_zpush/kolab_zpush.js +++ b/plugins/kolab_zpush/kolab_zpush.js @@ -1,7 +1,7 @@ /** * Client scripts for the Kolab Z-Push configuration utitlity * - * @version 0.1 + * @version 0.2 * @author Thomas Bruederli * * Copyright (C) 2011, Kolab Systems AG @@ -25,19 +25,26 @@ function kolab_zpush_config() devicelist.addEventListener('select', select_device); devicelist.init(); - rcmail.register_command('plugin.save-config', save_config, true); + rcmail.register_command('plugin.save-config', save_config); + rcmail.register_command('plugin.delete-device', delete_device_config); 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); - + + // select the one and only device from list + if (rcmail.env.devicecount == 1) { + for (var imei in rcmail.env.devices) + break; + devicelist.select(imei); + } /* private methods */ function select_device(list) { active_device = list.get_single_selection(); - rcmail.enable_command('plugin.save-config'); + rcmail.enable_command('plugin.save-config', 'plugin.delete-device', true); if (active_device) { http_lock = rcmail.set_busy(true, 'loading'); @@ -109,14 +116,23 @@ function kolab_zpush_config() rcmail.env.devices[p.id].ALIAS = p.devicealias; } } + + // handler for delete commands + function delete_device_config() + { + if (active_device && confirm(rcmail.gettext('devicedeleteconfirm', 'kolab_zpush'))) { + http_lock = rcmail.set_busy(true, 'kolab_zpush.savingdata'); + rcmail.http_post('plugin.zpushjson', { cmd:'delete', id:active_device }, http_lock); + } + } // 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'); + var pos = $('#foldersubscriptions').offset(); + $('#foldersubscriptions').css('max-height', (h - pos.top - 90) + 'px'); } } } diff --git a/plugins/kolab_zpush/kolab_zpush.php b/plugins/kolab_zpush/kolab_zpush.php index 619158ef..9ecd1b8c 100644 --- a/plugins/kolab_zpush/kolab_zpush.php +++ b/plugins/kolab_zpush/kolab_zpush.php @@ -54,6 +54,9 @@ class kolab_zpush extends rcube_plugin $this->register_action('plugin.zpushconfig', array($this, 'config_view')); $this->register_action('plugin.zpushjson', array($this, 'json_command')); + + if ($this->rc->action == 'plugin.zpushconfig') + $this->require_plugin('kolab_core'); } @@ -173,6 +176,56 @@ class kolab_zpush extends rcube_plugin $this->rc->output->show_message($this->gettext('successfullysaved'), 'confirmation'); break; + + case 'delete': + $this->init_imap(); + $devices = $this->list_devices(); + + if ($device = $devices[$imei]) { + unset($this->root_meta['DEVICE'][$imei], $this->root_meta['FOLDER'][$imei]); + + // update annotation and cached meta data + if ($success = $this->rc->imap->set_metadata(self::ROOT_MAILBOX, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($this->root_meta)))) { + $this->cache->remove('devicemeta'); + $this->cache->write('devicemeta', $this->rc->imap->get_metadata(self::ROOT_MAILBOX, self::ACTIVESYNC_KEY)); + + // remove device annotation in every folder + foreach ($this->folders_meta() as $folder => $meta) { + // skip root folder (already handled above) + if ($folder == self::ROOT_MAILBOX) + continue; + + if (isset($meta[$imei])) { + $type = $meta['TYPE']; // remember folder type + unset($meta[$imei], $meta['TYPE']); + + // read metadata first and update FOLDER property + $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; + + if ($this->rc->imap->set_metadata($folder, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($metadata)))) { + $this->folders_meta[$folder] = $metadata; + $this->folders_meta[$folder]['TYPE'] = $type; + } + } + } + + // update cache + $this->cache->remove('folders'); + $this->cache->write('folders', $this->folders_meta); + } + } + + if ($success) { + $this->rc->output->show_message($this->gettext('successfullydeleted'), 'confirmation'); + $this->rc->output->redirect(array('action' => 'plugin.zpushconfig')); // reload UI + } + else + $this->rc->output->show_message($this->gettext('savingerror'), 'error'); + + break; } $this->rc->output->send(); diff --git a/plugins/kolab_zpush/kolab_zpush_ui.php b/plugins/kolab_zpush/kolab_zpush_ui.php index 75c712f8..02fcfda9 100644 --- a/plugins/kolab_zpush/kolab_zpush_ui.php +++ b/plugins/kolab_zpush/kolab_zpush_ui.php @@ -79,6 +79,9 @@ class kolab_zpush_ui $checkbox = new html_checkbox(array('name' => 'laxpic', 'value' => '1', 'id' => $field_id)); $table->add('title', $this->config->gettext('imageformat')); $table->add(null, html::label($field_id, $checkbox->show() . ' ' . $this->config->gettext('laxpiclabel'))); + + if ($attrib['form']) + $this->rc->output->add_gui_object('editform', $attrib['form']); return $table->show($attrib); } @@ -88,22 +91,81 @@ class kolab_zpush_ui { 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); - + + // group folders by type + $folder_groups = array('mail' => array(), 'contact' => array(), 'event' => array()); + $folder_meta = $this->config->folders_meta(); + foreach ($this->config->list_folders() as $folder) { + $type = $folder_meta[$folder]['TYPE'] ? $folder_meta[$folder]['TYPE'] : 'mail'; + $folder_groups[$type][] = $folder; + } + + // build block for every folder type + foreach ($folder_groups as $type => $group) { + if (empty($group)) + continue; + $attrib['type'] = $type; + $html .= html::div('subscriptionblock', + html::tag('h3', $type, $this->config->gettext($type)) . + $this->folder_subscriptions_block($group, $attrib)); + } + $this->rc->output->add_gui_object('subscriptionslist', $attrib['id']); + + return html::div($attrib, $html); + } - return $table->show($attrib); + public function folder_subscriptions_block($a_folders, $attrib) + { + $alarms = ($attrib['type'] == 'event' || $attrib['type'] == 'task'); + + $table = new html_table(array('cellspacing' => 0)); + $table->add_header('subscription', $attrib['syncicon'] ? html::img(array('src' => $this->skin_path . $attrib['syncicon'], 'title' => $this->config->gettext('synchronize'))) : ''); + $table->add_header('alarm', $alarms && $attrib['alarmicon'] ? html::img(array('src' => $this->skin_path . $attrib['alarmicon'], 'title' => $this->config->gettext('withalarms'))) : ''); + $table->add_header('foldername', $this->config->gettext('folder')); + + $checkbox_sync = new html_checkbox(array('name' => 'subscribed[]', 'class' => 'subscription')); + $checkbox_alarm = new html_checkbox(array('name' => 'alarm[]', 'class' => 'alarm', 'disabled' => true)); + + $names = array(); + foreach ($a_folders as $folder) { + $foldername = $origname = preg_replace('/^INBOX »\s+/', '', rcube_kolab::object_name($folder)); + + // find folder prefix to truncate (the same code as in kolab_addressbook plugin) + for ($i = count($names)-1; $i >= 0; $i--) { + if (strpos($foldername, $names[$i].' » ') === 0) { + $length = strlen($names[$i].' » '); + $prefix = substr($foldername, 0, $length); + $count = count(explode(' » ', $prefix)); + $foldername = str_repeat('  ', $count-1) . '» ' . substr($foldername, $length); + break; + } + } + + $names[] = $origname; + + $classes = array('mailbox'); + + if ($folder_class = rcmail_folder_classname($folder)) { + $foldername = rcube_label($folder_class); + $classes[] = $folder_class; + } + + $folder_id = 'rcmf' . html_identifier($folder); + $padding = str_repeat('    ', $level); + + $table->add_row(array('class' => (($level+1) * $idx++) % 2 == 0 ? 'even' : 'odd')); + $table->add('subscription', $checkbox_sync->show('', array('value' => $folder, 'id' => $folder_id))); + + if ($alarms) + $table->add('alarm', $checkbox_alarm->show('', array('value' => $folder, 'id' => $folder_id.'_alarm'))); + else + $table->add('alarm', ''); + + $table->add(join(' ', $classes), html::label($folder_id, $padding . Q($foldername))); + } + + return $table->show(); } /** diff --git a/plugins/kolab_zpush/localization/de_CH.inc b/plugins/kolab_zpush/localization/de_CH.inc index 195f2a08..2f92044d 100644 --- a/plugins/kolab_zpush/localization/de_CH.inc +++ b/plugins/kolab_zpush/localization/de_CH.inc @@ -13,6 +13,11 @@ $labels['withalarms'] = 'Mit Erinnerungen'; $labels['syncsettings'] = 'Synchronisationseinstellungen'; $labels['deviceconfig'] = 'Gerätekonfiguration'; $labels['folderstosync'] = 'Order zum Synchronisieren'; +$labels['mail'] = 'E-Mail'; +$labels['contact'] = 'Adressbücher'; +$labels['event'] = 'Kalendar'; +$labels['task'] = 'Aufgaben'; +$labels['note'] = 'Notizen'; $labels['deletedevice'] = 'Gerät löschen'; $labels['imageformat'] = 'Bildformat'; $labels['laxpiclabel'] = 'Erlaube PNG- und GIF-Bilder'; @@ -21,5 +26,7 @@ $labels['nodevices'] = 'Es sind noch keine Geräte registriert.

Um ein $labels['savingdata'] = 'Daten werden gespeichert...'; $labels['savingerror'] = 'Fehler beim Speichern'; $labels['notsupported'] = 'Ihr Server unterstützt keine Activesync-Konfiguration'; +$labels['devicedeleteconfirm'] = 'Wollen Sie wirklich alle Einstellungen für dieses Gerät löschen?'; +$labels['successfullydeleted'] = 'Die Geräteinstellungen wurden erfolgreich gelöscht'; ?> \ No newline at end of file diff --git a/plugins/kolab_zpush/localization/en_US.inc b/plugins/kolab_zpush/localization/en_US.inc index d8e12dba..5160d91c 100644 --- a/plugins/kolab_zpush/localization/en_US.inc +++ b/plugins/kolab_zpush/localization/en_US.inc @@ -13,6 +13,11 @@ $labels['withalarms'] = 'With alarms'; $labels['syncsettings'] = 'Synchronization settings'; $labels['deviceconfig'] = 'Device configration'; $labels['folderstosync'] = 'Folders to synchronize'; +$labels['mail'] = 'Email'; +$labels['contact'] = 'Address Books'; +$labels['event'] = 'Calendars'; +$labels['task'] = 'Tasks'; +$labels['note'] = 'Notes'; $labels['deletedevice'] = 'Delete device'; $labels['imageformat'] = 'Image format'; $labels['laxpiclabel'] = 'Allow PNG and GIF images'; @@ -21,5 +26,7 @@ $labels['nodevices'] = 'There are currently no devices registered.

In o $labels['savingdata'] = 'Saving data...'; $labels['savingerror'] = 'Failed to save configuration'; $labels['notsupported'] = 'Your server does not support metadata/annotations'; +$labels['devicedeleteconfirm'] = 'Do you really want to delete the configuration for this device?'; +$labels['successfullydeleted'] = 'The device configuration was successfully removed'; ?> \ No newline at end of file diff --git a/plugins/kolab_zpush/skins/default/config.css b/plugins/kolab_zpush/skins/default/config.css index 83857776..a3f0f188 100644 --- a/plugins/kolab_zpush/skins/default/config.css +++ b/plugins/kolab_zpush/skins/default/config.css @@ -1,4 +1,7 @@ /* Stylesheets for the Kolab Z-Push configuration UI */ +#configform { + padding-top: 15px; +} #devices-table { width: 100%; @@ -19,16 +22,52 @@ color: #ccc; } +.boxfooter a.button.delete, +.boxfooter a.buttonPas.delete { + background-image: url(deviceactions.png); +} + +div.subscriptionblock { + float: left; + margin: 0.5em 3em 2em 0; + padding: 0; + border: 1px solid #ddd; +} + +div.subscriptionblock h3 { + font-size: 12px; + color: #333; + margin: 0 0 0.4em 0; + padding: 4px 4px 5px 30px; + background: url(foldertypes.png) 4px 4px no-repeat #fbfbfb; +} + +div.subscriptionblock h3.contact { + background-position: 4px -16px; +} + +div.subscriptionblock h3.event { + background-position: 4px -36px; +} + +div.subscriptionblock h3.task { + background-position: 4px -56x; +} + +div.subscriptionblock h3.note { + background-position: 4px -76px; +} + #foldersubscriptions thead td { color: #999; font-weight: bold; - padding: 4px; + padding: 3px 5px; + min-width: 2em; } - #foldersubscriptions tbody td { - padding: 2px 4px; - border-bottom: 1px solid #ddd; + padding: 2px 5px; + border-top: 1px solid #ddd; } #foldersubscriptions td label { @@ -37,36 +76,15 @@ #foldersubscriptions td.mailbox { padding-right: 3em; - padding-left: 30px; + padding-left: 2px; 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 { +#foldersubscriptions { overflow: auto; max-height: 400px; margin-top: 0.5em; diff --git a/plugins/kolab_zpush/skins/default/foldertypes.png b/plugins/kolab_zpush/skins/default/foldertypes.png index abc16d85fc4c7a45052f9571d268107b7ab4dc89..4950296a906a82b571650afd4974eed21fb9dced 100644 GIT binary patch delta 2278 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 Ae*gdg delta 2557 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
- +
@@ -33,9 +33,7 @@
-
- -
+