Page MenuHomePhorge

No OneTemporary

Size
59 KB
Referenced Files
None
Subscribers
None
diff --git a/plugins/kolab_addressbook/kolab_addressbook.php b/plugins/kolab_addressbook/kolab_addressbook.php
index 13cff3de..bb1a11a5 100644
--- a/plugins/kolab_addressbook/kolab_addressbook.php
+++ b/plugins/kolab_addressbook/kolab_addressbook.php
@@ -1,591 +1,592 @@
<?php
/**
- * Kolab address book 0.6
+ * Kolab address book
*
* Sample plugin to add a new address book source with data from Kolab storage
* It provides also a possibilities to manage contact folders
* (create/rename/delete/acl) directly in Addressbook UI.
*
+ * @version @package_version@
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* 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/>.
*/
class kolab_addressbook extends rcube_plugin
{
public $task = 'mail|settings|addressbook|calendar';
private $folders;
private $sources;
private $rc;
private $ui;
const GLOBAL_FIRST = 0;
const PERSONAL_FIRST = 1;
const GLOBAL_ONLY = 2;
const PERSONAL_ONLY = 3;
/**
* Startup method of a Roundcube plugin
*/
public function init()
{
require_once(dirname(__FILE__) . '/lib/rcube_kolab_contacts.php');
$this->rc = rcmail::get_instance();
// load required plugin
$this->require_plugin('kolab_core');
// register hooks
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
$this->add_hook('addressbook_get', array($this, 'get_address_book'));
$this->add_hook('config_get', array($this, 'config_get'));
if ($this->rc->task == 'addressbook') {
$this->add_texts('localization');
$this->add_hook('contact_form', array($this, 'contact_form'));
// Plugin actions
$this->register_action('plugin.book', array($this, 'book_actions'));
$this->register_action('plugin.book-save', array($this, 'book_save'));
// Load UI elements
if ($this->api->output->type == 'html') {
require_once($this->home . '/lib/kolab_addressbook_ui.php');
$this->ui = new kolab_addressbook_ui($this);
}
}
else if ($this->rc->task == 'settings') {
$this->add_texts('localization');
$this->add_hook('preferences_list', array($this, 'prefs_list'));
$this->add_hook('preferences_save', array($this, 'prefs_save'));
}
}
/**
* Handler for the addressbooks_list hook.
*
* This will add all instances of available Kolab-based address books
* to the list of address sources of Roundcube.
* This will also hide some addressbooks according to kolab_addressbook_prio setting.
*
* @param array $p Hash array with hook parameters
*
* @return array Hash array with modified hook parameters
*/
public function address_sources($p)
{
// Load configuration
$this->load_config();
$abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio');
$undelete = $this->rc->config->get('undo_timeout');
// Disable all global address books
// Assumes that all non-kolab_addressbook sources are global
if ($abook_prio == self::PERSONAL_ONLY) {
$p['sources'] = array();
}
$sources = array();
$names = array();
foreach ($this->_list_sources() as $abook_id => $abook) {
$name = $origname = $abook->get_name();
// find folder prefix to truncate
for ($i = count($names)-1; $i >= 0; $i--) {
if (strpos($name, $names[$i].' &raquo; ') === 0) {
$length = strlen($names[$i].' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . substr($name, $length);
break;
}
}
$names[] = $origname;
// register this address source
$sources[$abook_id] = array(
'id' => $abook_id,
'name' => $name,
'readonly' => $abook->readonly,
'editable' => $abook->editable,
'groups' => $abook->groups,
'undelete' => $abook->undelete && $undelete,
'realname' => rcube_charset_convert($abook->get_realname(), 'UTF7-IMAP'), // IMAP folder name
'class_name' => $abook->get_namespace(),
'kolab' => true,
);
}
// Add personal address sources to the list
if ($abook_prio == self::PERSONAL_FIRST) {
// $p['sources'] = array_merge($sources, $p['sources']);
// Don't use array_merge(), because if you have folders name
// that resolve to numeric identifier it will break output array keys
foreach ($p['sources'] as $idx => $value)
$sources[$idx] = $value;
$p['sources'] = $sources;
}
else {
// $p['sources'] = array_merge($p['sources'], $sources);
foreach ($sources as $idx => $value)
$p['sources'][$idx] = $value;
}
return $p;
}
/**
* Sets autocomplete_addressbooks option according to
* kolab_addressbook_prio setting extending list of address sources
* to be used for autocompletion.
*/
public function config_get($args)
{
if ($args['name'] != 'autocomplete_addressbooks') {
return $args;
}
// Load configuration
$this->load_config();
$abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio');
// here we cannot use rc->config->get()
$sources = $GLOBALS['CONFIG']['autocomplete_addressbooks'];
// Disable all global address books
// Assumes that all non-kolab_addressbook sources are global
if ($abook_prio == self::PERSONAL_ONLY) {
$sources = array();
}
if (!is_array($sources)) {
$sources = array();
}
$kolab_sources = array();
foreach ($this->_list_sources() as $abook_id => $abook) {
if (!in_array($abook_id, $sources))
$kolab_sources[] = $abook_id;
}
// Add personal address sources to the list
if (!empty($kolab_sources)) {
if ($abook_prio == self::PERSONAL_FIRST) {
$sources = array_merge($kolab_sources, $sources);
}
else {
$sources = array_merge($sources, $kolab_sources);
}
}
$args['result'] = $sources;
return $args;
}
/**
* Getter for the rcube_addressbook instance
*
* @param array $p Hash array with hook parameters
*
* @return array Hash array with modified hook parameters
*/
public function get_address_book($p)
{
if ($p['id']) {
$this->_list_sources();
if ($this->sources[$p['id']]) {
$p['instance'] = $this->sources[$p['id']];
}
}
return $p;
}
private function _list_sources()
{
// already read sources
if (isset($this->sources))
return $this->sources;
$this->sources = array();
// Load configuration
$this->load_config();
$abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio');
// Personal address source(s) disabled?
if ($abook_prio == self::GLOBAL_ONLY) {
return $this->sources;
}
// get all folders that have "contact" type
$this->folders = rcube_kolab::get_folders('contact');
if (PEAR::isError($this->folders)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to list contact folders from Kolab server:" . $this->folders->getMessage()),
true, false);
}
else {
// convert to UTF8 and sort
$names = array();
foreach ($this->folders as $c_folder)
$names[$c_folder->name] = rcube_charset_convert($c_folder->name, 'UTF7-IMAP');
asort($names, SORT_LOCALE_STRING);
foreach ($names as $utf7name => $name) {
// create instance of rcube_contacts
$abook_id = rcube_kolab::folder_id($utf7name);
$abook = new rcube_kolab_contacts($utf7name);
$this->sources[$abook_id] = $abook;
}
}
return $this->sources;
}
/**
* Plugin hook called before rendering the contact form or detail view
*
* @param array $p Hash array with hook parameters
*
* @return array Hash array with modified hook parameters
*/
public function contact_form($p)
{
// none of our business
if (!is_object($GLOBALS['CONTACTS']) || !is_a($GLOBALS['CONTACTS'], 'rcube_kolab_contacts'))
return $p;
// extend the list of contact fields to be displayed in the 'personal' section
if (is_array($p['form']['personal'])) {
$p['form']['contact']['content']['officelocation'] = array('size' => 40);
$p['form']['personal']['content']['initials'] = array('size' => 6);
$p['form']['personal']['content']['profession'] = array('size' => 40);
$p['form']['personal']['content']['children'] = array('size' => 40);
$p['form']['personal']['content']['pgppublickey'] = array('size' => 40);
$p['form']['personal']['content']['freebusyurl'] = array('size' => 40);
// re-order fields according to the coltypes list
$p['form']['contact']['content'] = $this->_sort_form_fields($p['form']['contact']['content']);
$p['form']['personal']['content'] = $this->_sort_form_fields($p['form']['personal']['content']);
/* define a separate section 'settings'
$p['form']['settings'] = array(
'name' => $this->gettext('settings'),
'content' => array(
'pgppublickey' => array('size' => 40, 'visible' => true),
'freebusyurl' => array('size' => 40, 'visible' => true),
)
);
*/
}
return $p;
}
private function _sort_form_fields($contents)
{
$block = array();
$contacts = reset($this->sources);
foreach ($contacts->coltypes as $col => $prop) {
if (isset($contents[$col]))
$block[$col] = $contents[$col];
}
return $block;
}
/**
* Handler for user preferences form (preferences_list hook)
*
* @param array $args Hash array with hook parameters
*
* @return array Hash array with modified hook parameters
*/
public function prefs_list($args)
{
if ($args['section'] != 'addressbook') {
return $args;
}
// Load configuration
$this->load_config();
// Load localization
$this->add_texts('localization');
// Check that configuration is not disabled
$dont_override = (array) $this->rc->config->get('dont_override', array());
if (!in_array('kolab_addressbook_prio', $dont_override)) {
$field_id = '_kolab_addressbook_prio';
$select = new html_select(array('name' => $field_id, 'id' => $field_id));
$select->add($this->gettext('globalfirst'), self::GLOBAL_FIRST);
$select->add($this->gettext('personalfirst'), self::PERSONAL_FIRST);
$select->add($this->gettext('globalonly'), self::GLOBAL_ONLY);
$select->add($this->gettext('personalonly'), self::PERSONAL_ONLY);
$args['blocks']['main']['options']['kolab_addressbook_prio'] = array(
'title' => html::label($field_id, Q($this->gettext('addressbookprio'))),
'content' => $select->show((int)$this->rc->config->get('kolab_addressbook_prio')),
);
}
return $args;
}
/**
* Handler for user preferences save (preferences_save hook)
*
* @param array $args Hash array with hook parameters
*
* @return array Hash array with modified hook parameters
*/
public function prefs_save($args)
{
if ($args['section'] != 'addressbook') {
return $args;
}
// Load configuration
$this->load_config();
// Check that configuration is not disabled
$dont_override = (array) $this->rc->config->get('dont_override', array());
if (!in_array('kolab_addressbook_prio', $dont_override)) {
$key = 'kolab_addressbook_prio';
$args['prefs'][$key] = (int) get_input_value('_'.$key, RCUBE_INPUT_POST);
}
return $args;
}
/**
* Handler for plugin actions
*/
public function book_actions()
{
$action = trim(get_input_value('_act', RCUBE_INPUT_GPC));
if ($action == 'create') {
$this->ui->book_edit();
}
else if ($action == 'edit') {
$this->ui->book_edit();
}
else if ($action == 'delete') {
$this->book_delete();
}
}
/**
* Handler for address book create/edit form submit
*/
public function book_save()
{
$folder = trim(get_input_value('_name', RCUBE_INPUT_POST, true, 'UTF7-IMAP'));
$oldfolder = trim(get_input_value('_oldname', RCUBE_INPUT_POST, true)); // UTF7-IMAP
$path = trim(get_input_value('_parent', RCUBE_INPUT_POST, true)); // UTF7-IMAP
$delimiter = $_SESSION['imap_delimiter'];
if (strlen($oldfolder)) {
$this->rc->imap_connect();
$options = $this->rc->imap->mailbox_info($oldfolder);
}
if (!empty($options) && ($options['norename'] || $options['protected'])) {
}
// sanity checks (from steps/settings/save_folder.inc)
else if (!strlen($folder)) {
$error = rcube_label('cannotbeempty');
}
else if (strlen($folder) > 128) {
$error = rcube_label('nametoolong');
}
else {
// these characters are problematic e.g. when used in LIST/LSUB
foreach (array($delimiter, '%', '*') as $char) {
if (strpos($folder, $delimiter) !== false) {
$error = rcube_label('forbiddencharacter') . " ($char)";
break;
}
}
}
if (!$error) {
if (!empty($options) && ($options['protected'] || $options['norename'])) {
$folder = $oldfolder;
}
else if (strlen($path)) {
$folder = $path . $delimiter . $folder;
}
else {
// add namespace prefix (when needed)
$this->rc->imap_init();
$folder = $this->rc->imap->mod_mailbox($folder, 'in');
}
// Check access rights to the parent folder
if (strlen($path) && (!strlen($oldfolder) || $oldfolder != $folder)) {
$this->rc->imap_connect();
$parent_opts = $this->rc->imap->mailbox_info($path);
if ($parent_opts['namespace'] != 'personal'
&& (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts)))
) {
$error = rcube_label('parentnotwritable');
}
}
}
if (!$error) {
// update the folder name
if (strlen($oldfolder)) {
$type = 'update';
$plugin = $this->rc->plugins->exec_hook('addressbook_update', array(
'name' => $folder, 'oldname' => $oldfolder));
if (!$plugin['abort']) {
if ($oldfolder != $folder)
$result = rcube_kolab::folder_rename($oldfolder, $folder);
else
$result = true;
}
else {
$result = $plugin['result'];
}
}
// create new folder
else {
$type = 'create';
$plugin = $this->rc->plugins->exec_hook('addressbook_create', array('name' => $folder));
$folder = $plugin['name'];
if (!$plugin['abort']) {
$result = rcube_kolab::folder_create($folder, 'contact', false);
}
else {
$result = $plugin['result'];
}
}
}
if ($result) {
$kolab_folder = new rcube_kolab_contacts($folder);
// create display name for the folder (see self::address_sources())
if (strpos($folder, $delimiter)) {
$names = array();
foreach ($this->_list_sources() as $abook_id => $abook) {
$realname = $abook->get_realname();
// The list can be not updated yet, handle old folder name
if ($type == 'update' && $realname == $oldfolder) {
$abook = $kolab_folder;
$realname = $folder;
}
$name = $origname = $abook->get_name();
// find folder prefix to truncate
for ($i = count($names)-1; $i >= 0; $i--) {
if (strpos($name, $names[$i].' &raquo; ') === 0) {
$length = strlen($names[$i].' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . substr($name, $length);
break;
}
}
$names[] = $origname;
if ($realname == $folder) {
break;
}
}
}
else {
$name = $kolab_folder->get_name();
}
$this->rc->output->show_message('kolab_addressbook.book'.$type.'d', 'confirmation');
$this->rc->output->command('set_env', 'delimiter', $delimiter);
$this->rc->output->command('book_update', array(
'id' => rcube_kolab::folder_id($folder),
'name' => $name,
'readonly' => false,
'editable' => true,
'groups' => true,
'realname' => rcube_charset_convert($folder, 'UTF7-IMAP'), // IMAP folder name
'class_name' => $kolab_folder->get_namespace(),
'kolab' => true,
), rcube_kolab::folder_id($oldfolder));
$this->rc->output->send('iframe');
}
if (!$error)
$error = $plugin['message'] ? $plugin['message'] : 'kolab_addressbook.book'.$type.'error';
$this->rc->output->show_message($error, 'error');
// display the form again
$this->ui->book_edit();
}
/**
* Handler for address book delete action (AJAX)
*/
private function book_delete()
{
$folder = trim(get_input_value('_source', RCUBE_INPUT_GPC, true, 'UTF7-IMAP'));
if (rcube_kolab::folder_delete($folder)) {
$this->rc->output->show_message('kolab_addressbook.bookdeleted', 'confirmation');
$this->rc->output->set_env('pagecount', 0);
$this->rc->output->command('set_rowcount', rcmail_get_rowcount_text(new rcube_result_set()));
$this->rc->output->command('list_contacts_clear');
$this->rc->output->command('book_delete_done', rcube_kolab::folder_id($folder));
}
else {
$this->rc->output->show_message('kolab_addressbook.bookdeleteerror', 'error');
}
$this->rc->output->send();
}
}
diff --git a/plugins/kolab_addressbook/package.xml b/plugins/kolab_addressbook/package.xml
new file mode 100644
index 00000000..7e7b84c7
--- /dev/null
+++ b/plugins/kolab_addressbook/package.xml
@@ -0,0 +1,74 @@
+<?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>kolab_addressbook</name>
+ <uri>http://kolabsys.com</uri>
+ <summary>Kolab addressbook</summary>
+ <description>
+ Sample plugin to add a new address book source with data from Kolab storage.
+ It provides also a possibilities to manage contact folders
+ (create/rename/delete/acl) directly in Addressbook UI.
+ </description>
+ <lead>
+ <name>Aleksander Machniak</name>
+ <user>machniak</user>
+ <email>machniak@kolabsys.com</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Thomas Bruederli</name>
+ <user>bruederli</user>
+ <email>bruederli@kolabsys.com</email>
+ <active>yes</active>
+ </lead>
+ <date>2011-11-01</date>
+ <version>
+ <release>0.6</release>
+ <api>0.6</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="kolab_addressbook.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="lib/kolab_addressbook_ui.php" role="php"></file>
+ <file name="lib/rcube_kolab_contacts.php" role="php"></file>
+ <file name="config.inc.php.dist" role="data"></file>
+ <file name="LICENSE" role="data"></file>
+ <file name="skins/default/kolab_addressbook.css" role="data"></file>
+ <file name="skins/default/kolab_folders.gif" role="data"></file>
+ <file name="skins/default/kolab_folders.png" role="data"></file>
+ <file name="skins/default/templates/bookedit.html" role="data"></file>
+
+ <file name="localization/en_US.inc" role="data"></file>
+ <file name="localization/ja_JP.inc" role="data"></file>
+ <file name="localization/pl_PL.inc" role="data"></file>
+ <file name="localization/ru_RU.inc" role="data"></file>
+ </dir>
+ <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.1</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ <package>
+ <name>kolab_core</name>
+ <uri>http://kolabsys.com</uri>
+ </package>
+ </required>
+ </dependencies>
+ <phprelease/>
+</package>
diff --git a/plugins/kolab_config/kolab_config.php b/plugins/kolab_config/kolab_config.php
index 7b0947b0..24d569e3 100644
--- a/plugins/kolab_config/kolab_config.php
+++ b/plugins/kolab_config/kolab_config.php
@@ -1,144 +1,145 @@
<?php
/**
* Kolab configuration storage.
*
* Plugin to use Kolab server as a configuration storage. Provides an API to handle
* configuration according to http://wiki.kolab.org/KEP:9.
*
+ * @version @package_version@
* @author Machniak Aleksander <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* 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/>.
*/
class kolab_config extends rcube_plugin
{
public $task = 'utils';
private $config;
private $enabled;
/**
* Required startup method of a Roundcube plugin
*/
public function init()
{
$rcmail = rcmail::get_instance();
// Register spellchecker dictionary handlers
if (strtolower($rcmail->config->get('spellcheck_dictionary')) != 'shared') {
$this->add_hook('spell_dictionary_save', array($this, 'dictionary_save'));
$this->add_hook('spell_dictionary_get', array($this, 'dictionary_get'));
}
/*
// Register addressbook saved searches handlers
$this->add_hook('saved_search_create', array($this, 'saved_search_create'));
$this->add_hook('saved_search_delete', array($this, 'saved_search_delete'));
$this->add_hook('saved_search_list', array($this, 'saved_search_list'));
$this->add_hook('saved_search_get', array($this, 'saved_search_get'));
*/
}
/**
* Initializes config object and dependencies
*/
private function load()
{
if ($this->config)
return;
$this->require_plugin('kolab_folders');
// load dependencies
require_once 'Horde/Util.php';
require_once 'Horde/Kolab/Format.php';
require_once 'Horde/Kolab/Format/XML.php';
require_once $this->home . '/lib/configuration.php';
require_once $this->home . '/lib/kolab_configuration.php';
String::setDefaultCharset('UTF-8');
$this->config = new kolab_configuration();
// check if configuration folder exist
if (strlen($this->config->dir)) {
$this->enabled = true;
}
}
/**
* Saves spellcheck dictionary.
*
* @param array $args Hook arguments
*
* @return array Hook arguments
*/
public function dictionary_save($args)
{
$this->load();
if (!$this->enabled) {
return $args;
}
$lang = $args['language'];
$dict = $this->dict;
$dict['type'] = 'dictionary';
$dict['language'] = $args['language'];
$dict['e'] = $args['dictionary'];
if (empty($dict['e'])) {
// Delete the object
$this->config->del($dict);
}
else {
// Update the object
$this->config->set($dict);
}
$args['abort'] = true;
return $args;
}
/**
* Returns spellcheck dictionary.
*
* @param array $args Hook arguments
*
* @return array Hook arguments
*/
public function dictionary_get($args)
{
$this->load();
if (!$this->enabled) {
return $args;
}
$lang = $args['language'];
$this->dict = $this->config->get('dictionary.'.$lang);
if (!empty($this->dict)) {
$args['dictionary'] = $this->dict['e'];
}
$args['abort'] = true;
return $args;
}
}
diff --git a/plugins/kolab_config/package.xml b/plugins/kolab_config/package.xml
new file mode 100644
index 00000000..73291c8d
--- /dev/null
+++ b/plugins/kolab_config/package.xml
@@ -0,0 +1,57 @@
+<?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>kolab_config</name>
+ <uri>http://kolabsys.com</uri>
+ <summary>Kolab configuration storage</summary>
+ <description>
+ Plugin to use Kolab server as a configuration storage. Provides an API to handle
+ configuration according to http://wiki.kolab.org/KEP:9.
+ </description>
+ <lead>
+ <name>Aleksander Machniak</name>
+ <user>machniak</user>
+ <email>machniak@kolabsys.com</email>
+ <active>yes</active>
+ </lead>
+ <date>2011-11-01</date>
+ <version>
+ <release>1.0</release>
+ <api>1.0</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="kolab_config.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="LICENSE" role="data"></file>
+ <file name="lib/configuration.php" role="php"></file>
+ <file name="lib/kolab_configuration.php" role="php"></file>
+ </dir>
+ <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.1</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ <package>
+ <name>kolab_folders</name>
+ <uri>http://kolabsys.com</uri>
+ </package>
+ </required>
+ </dependencies>
+ <phprelease/>
+</package>
diff --git a/plugins/kolab_core/README.txt b/plugins/kolab_core/README
similarity index 100%
rename from plugins/kolab_core/README.txt
rename to plugins/kolab_core/README
diff --git a/plugins/kolab_core/kolab_core.php b/plugins/kolab_core/kolab_core.php
index b027a485..9a2cc1d2 100644
--- a/plugins/kolab_core/kolab_core.php
+++ b/plugins/kolab_core/kolab_core.php
@@ -1,54 +1,55 @@
<?php
/**
* Kolab core library
*
* Plugin to setup a basic environment for interaction with a Kolab server.
* Other Kolab-related plugins will depend on it and can use the static API rcube_core
*
+ * @version @package_version@
* @author Thomas Bruederli <roundcube@gmail.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* 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/>.
*/
class kolab_core extends rcube_plugin
{
/**
* Required startup method of a Roundcube plugin
*/
public function init()
{
// load local config
$this->load_config();
// extend include path to load bundled Horde classes
$include_path = $this->home . PATH_SEPARATOR . ini_get('include_path');
set_include_path($include_path);
// Register password reset hook
$this->add_hook('password_change', array($this, 'password_change'));
}
/**
* Resets auth session data after password change
*/
public function password_change($args)
{
rcmail::get_instance()->session->remove('__auth');
return $args;
}
}
diff --git a/plugins/kolab_core/package.xml b/plugins/kolab_core/package.xml
new file mode 100644
index 00000000..46aa29fe
--- /dev/null
+++ b/plugins/kolab_core/package.xml
@@ -0,0 +1,63 @@
+<?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>kolab_core</name>
+ <uri>http://kolabsys.com</uri>
+ <summary>Kolab API</summary>
+ <description>
+ Plugin to setup a basic environment for interaction with a Kolab server.
+ Other Kolab-related plugins will depend on it and can use the static API rcube_kolab.
+ </description>
+ <lead>
+ <name>Thomas Bruederli</name>
+ <user>bruederli</user>
+ <email>bruederli@kolabsys.com</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Aleksander Machniak</name>
+ <user>machniak</user>
+ <email>machniak@kolabsys.com</email>
+ <active>yes</active>
+ </lead>
+ <date>2011-11-01</date>
+ <version>
+ <release>1.0</release>
+ <api>1.0</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="kolab_core.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="rcube_kolab.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="config.inc.php.dist" role="data"></file>
+ <file name="README" role="data"></file>
+ <file name="LICENSE" role="data"></file>
+ </dir>
+ <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.1</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <phprelease/>
+</package>
diff --git a/plugins/kolab_folders/README b/plugins/kolab_folders/README
deleted file mode 100644
index eddd5aa5..00000000
--- a/plugins/kolab_folders/README
+++ /dev/null
@@ -1,25 +0,0 @@
-
-Type-aware folder management/listing for Kolab
-
-Author: Aleksander Machniak <machniak@kolabsys.com>
-
-1. About.
----------
-
-The plugin extends folders handling with features of the Kolab Suite.
-With this plugin enabled it is possible to:
- - set/get/change folder's type
- - filter folders list by folder type.
- - style folders list rows (in folder manager)
- - create default folders with specified type
-
-http://www.kolab.org/doc/kolabformat-2.0-html
-
-2. Examples.
-------------
-
-To get list of all folders of type 'event' use code:
-
-$RCMAIL->imap->list_mailboxes('', '*', 'event');
-
-Plugin provides also set_folder_type/get_folder_type methods.
diff --git a/plugins/kolab_folders/kolab_folders.php b/plugins/kolab_folders/kolab_folders.php
index 7622d652..446a091d 100644
--- a/plugins/kolab_folders/kolab_folders.php
+++ b/plugins/kolab_folders/kolab_folders.php
@@ -1,643 +1,643 @@
<?php
/**
* Type-aware folder management/listing for Kolab
*
- * @version 0.3
+ * @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* 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/>.
*/
class kolab_folders extends rcube_plugin
{
public $task = '?(?!login).*';
public $types = array('mail', 'event', 'journal', 'task', 'note', 'contact', 'configuration');
public $mail_types = array('inbox', 'drafts', 'sentitems', 'outbox', 'wastebasket', 'junkemail');
private $rc;
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
/**
* Plugin initialization.
*/
function init()
{
$this->rc = rcmail::get_instance();
// Folder listing hooks
$this->add_hook('mailboxes_list', array($this, 'mailboxes_list'));
// Folder manager hooks
$this->add_hook('folder_form', array($this, 'folder_form'));
$this->add_hook('folder_update', array($this, 'folder_save'));
$this->add_hook('folder_create', array($this, 'folder_save'));
$this->add_hook('folder_delete', array($this, 'folder_save'));
$this->add_hook('folder_rename', array($this, 'folder_save'));
$this->add_hook('folders_list', array($this, 'folders_list'));
}
/**
* Handler for mailboxes_list hook. Enables type-aware lists filtering.
*/
function mailboxes_list($args)
{
if (!$this->metadata_support()) {
return $args;
}
$filter = $args['filter'];
// all-folders request, use core method
if (!$filter) {
return $args;
}
// get folders types
$folderdata = $this->get_folder_type_list($args['root'].$args['name'], true);
if (!is_array($folderdata)) {
$args['folders'] = false;
return $args;
}
$regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
// In some conditions we can skip LIST command (?)
if ($args['mode'] == 'LIST' && $filter != 'mail'
&& $args['root'] == '' && $args['name'] == '*'
) {
foreach ($folderdata as $folder => $type) {
if (!preg_match($regexp, $type)) {
unset($folderdata[$folder]);
}
}
$args['folders'] = array_keys($folderdata);
return $args;
}
// Get folders list
if ($args['mode'] == 'LIST') {
$args['folders'] = $this->rc->imap->conn->listMailboxes($args['root'], $args['name']);
}
else {
$args['folders'] = $this->list_subscribed($args['root'], $args['name']);
}
// In case of an error, return empty list
if (!is_array($args['folders'])) {
$args['folders'] = array();
return $args;
}
// Filter folders list
foreach ($args['folders'] as $idx => $folder) {
$type = $folderdata[$folder];
if ($filter == 'mail' && empty($type)) {
continue;
}
if (empty($type) || !preg_match($regexp, $type)) {
unset($args['folders'][$idx]);
}
}
return $args;
}
/**
* Handler for folders_list hook. Add css classes to folder rows.
*/
function folders_list($args)
{
if (!$this->metadata_support()) {
return $args;
}
$table = $args['table'];
// get folders types
$folderdata = $this->get_folder_type_list('*');
if (!is_array($folderdata)) {
return $args;
}
// Add type-based style for table rows
// See kolab_folders::folder_class_name()
for ($i=1, $cnt=$table->size(); $i<=$cnt; $i++) {
$attrib = $table->get_row_attribs($i);
$folder = $attrib['foldername']; // UTF7-IMAP
$type = $folderdata[$folder];
if (!$type)
$type = 'mail';
$class_name = self::folder_class_name($type);
$attrib['class'] = trim($attrib['class'] . ' ' . $class_name);
$table->set_row_attribs($attrib, $i);
}
return $args;
}
/**
* Handler for folder info/edit form (folder_form hook).
* Adds folder type selector.
*/
function folder_form($args)
{
if (!$this->metadata_support()) {
return $args;
}
// load translations
$this->add_texts('localization/', false);
// INBOX folder is of type mail.inbox and this cannot be changed
if ($args['name'] == 'INBOX') {
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => sprintf('%s (%s)', $this->gettext('foldertypemail'), $this->gettext('inbox')),
);
return $args;
}
if ($args['options']['is_root']) {
return $args;
}
$mbox = strlen($args['name']) ? $args['name'] : $args['parent_name'];
if (isset($_POST['_ctype'])) {
$new_ctype = trim(get_input_value('_ctype', RCUBE_INPUT_POST));
$new_subtype = trim(get_input_value('_subtype', RCUBE_INPUT_POST));
}
// Get type of the folder or the parent
if (strlen($mbox)) {
list($ctype, $subtype) = $this->get_folder_type($mbox);
if (strlen($args['parent_name']) && $subtype == 'default')
$subtype = ''; // there can be only one
}
if (!$ctype) {
$ctype = 'mail';
}
// Don't allow changing type of shared folder, according to ACL
if (strlen($mbox)) {
$options = $this->rc->imap->mailbox_info($mbox);
if ($options['namespace'] != 'personal' && !in_array('a', $options['rights'])) {
if (in_array($ctype, $this->types))
$value = $this->gettext('foldertype'.$ctype);
else
$value = $ctype;
if ($subtype)
$value .= ' ('. ($subtype == 'default' ? $this->gettext('default') : $subtype) .')';
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => $value,
);
return $args;
}
}
// Add javascript script to the client
$this->include_script('kolab_folders.js');
// build type SELECT fields
$type_select = new html_select(array('name' => '_ctype', 'id' => '_ctype'));
$sub_select = new html_select(array('name' => '_subtype', 'id' => '_subtype'));
foreach ($this->types as $type) {
$type_select->add($this->gettext('foldertype'.$type), $type);
}
// add non-supported type
if (!in_array($ctype, $this->types)) {
$type_select->add($ctype, $ctype);
}
$sub_select->add('', '');
$sub_select->add($this->gettext('default'), 'default');
foreach ($this->mail_types as $type) {
$sub_select->add($this->gettext($type), $type);
}
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => $type_select->show(isset($new_ctype) ? $new_ctype : $ctype)
. $sub_select->show(isset($new_subtype) ? $new_subtype : $subtype),
);
return $args;
}
/**
* Handler for folder update/create action (folder_update/folder_create hook).
*/
function folder_save($args)
{
// Folder actions from folders list
if (empty($args['record'])) {
// Just clear Horde folders cache and return
$this->clear_folders_cache();
return $args;
}
// Folder create/update with form
$ctype = trim(get_input_value('_ctype', RCUBE_INPUT_POST));
$subtype = trim(get_input_value('_subtype', RCUBE_INPUT_POST));
$mbox = $args['record']['name'];
$old_mbox = $args['record']['oldname'];
$subscribe = $args['record']['subscribe'];
if (empty($ctype)) {
return $args;
}
// load translations
$this->add_texts('localization/', false);
// Skip folder creation/rename in core
// @TODO: Maybe we should provide folder_create_after and folder_update_after hooks?
// Using create_mailbox/rename_mailbox here looks bad
$args['abort'] = true;
// There can be only one default folder of specified type
if ($subtype == 'default') {
$default = $this->get_default_folder($ctype);
if ($default !== null && $old_mbox != $default) {
$args['result'] = false;
$args['message'] = $this->gettext('defaultfolderexists');
return $args;
}
}
// Subtype sanity-checks
else if ($subtype && ($ctype != 'mail' || !in_array($subtype, $this->mail_types))) {
$subtype = '';
}
$ctype .= $subtype ? '.'.$subtype : '';
// Create folder
if (!strlen($old_mbox)) {
// By default don't subscribe to non-mail folders
if ($subscribe)
$subscribe = (bool) preg_match('/^mail/', $ctype);
$result = $this->rc->imap->create_mailbox($mbox, $subscribe);
// Set folder type
if ($result) {
$this->set_folder_type($mbox, $ctype);
}
}
// Rename folder
else {
if ($old_mbox != $mbox) {
$result = $this->rc->imap->rename_mailbox($old_mbox, $mbox);
}
else {
$result = true;
}
if ($result) {
list($oldtype, $oldsubtype) = $this->get_folder_type($mbox);
$oldtype .= $oldsubtype ? '.'.$oldsubtype : '';
if ($ctype != $oldtype) {
$this->set_folder_type($mbox, $ctype);
}
}
}
// Clear Horde folders cache
if ($result) {
$this->clear_folders_cache();
}
$args['record']['class'] = self::folder_class_name($ctype);
$args['record']['subscribe'] = $subscribe;
$args['result'] = $result;
return $args;
}
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @return boolean
*/
function metadata_support()
{
return $this->rc->imap->get_capability('METADATA') ||
$this->rc->imap->get_capability('ANNOTATEMORE') ||
$this->rc->imap->get_capability('ANNOTATEMORE2');
}
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @param string $folder Folder name
*
* @return array Folder content-type
*/
function get_folder_type($folder)
{
$folderdata = $this->rc->imap->get_metadata($folder, array(kolab_folders::CTYPE_KEY));
return explode('.', $folderdata[$folder][kolab_folders::CTYPE_KEY]);
}
/**
* Sets folder content-type.
*
* @param string $folder Folder name
* @param string $type Content type
*
* @return boolean True on success
*/
function set_folder_type($folder, $type='mail')
{
return $this->rc->imap->set_metadata($folder, array(kolab_folders::CTYPE_KEY => $type));
}
/**
* Returns list of subscribed folders (directly from IMAP server)
*
* @param string $root Optional root folder
* @param string $name Optional name pattern
*
* @return array List of mailboxes/folders
*/
private function list_subscribed($root='', $name='*')
{
$imap = $this->rc->imap;
// Code copied from rcube_imap::_list_mailboxes()
// Server supports LIST-EXTENDED, we can use selection options
// #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
if (!$this->rc->config->get('imap_force_lsub') && $imap->get_capability('LIST-EXTENDED')) {
// This will also set mailbox options, LSUB doesn't do that
$a_folders = $imap->conn->listMailboxes($root, $name,
NULL, array('SUBSCRIBED'));
// remove non-existent folders
if (is_array($a_folders) && $name = '*') {
foreach ($a_folders as $idx => $folder) {
if ($imap->conn->data['LIST'] && ($opts = $imap->conn->data['LIST'][$folder])
&& in_array('\\NonExistent', $opts)
) {
$imap->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
// retrieve list of folders from IMAP server using LSUB
else {
$a_folders = $imap->conn->listSubscribed($root, $name);
// unsubscribe non-existent folders, remove from the list
if (is_array($a_folders) && $name == '*') {
foreach ($a_folders as $idx => $folder) {
if ($imap->conn->data['LIST'] && ($opts = $imap->conn->data['LIST'][$folder])
&& in_array('\\Noselect', $opts)
) {
// Some servers returns \Noselect for existing folders
if (!$imap->mailbox_exists($folder)) {
$imap->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
}
return $a_folders;
}
/**
* Returns list of folder(s) type(s)
*
* @param string $mbox Folder name or pattern
* @param bool $defaults Enables creation of configured default folders
*
* @return array List of folders data, indexed by folder name
*/
function get_folder_type_list($mbox, $create_defaults = false)
{
// Use mailboxes. prefix so the cache will be cleared by core
// together with other mailboxes-related cache data
$cache_key = 'mailboxes.folder-type.'.$mbox;
// get cached metadata
$metadata = $this->rc->imap->get_cache($cache_key);
if (!is_array($metadata)) {
$metadata = $this->rc->imap->get_metadata($mbox, kolab_folders::CTYPE_KEY);
$need_update = true;
}
if (!is_array($metadata)) {
return false;
}
// make the result more flat
if ($need_update) {
$metadata = array_map('implode', $metadata);
}
// create default folders if needed
if ($create_defaults) {
$this->create_default_folders($metadata, $cache_key);
}
// write mailboxlist to cache
if ($need_update) {
$this->rc->imap->update_cache($cache_key, $metadata);
}
return $metadata;
}
/**
* Returns the name of default folder
*
* @param string $type Folder type
*
* @return string Folder name
*/
function get_default_folder($type)
{
$folderdata = $this->get_folder_type_list('*');
if (!is_array($folderdata)) {
return null;
}
$type .= '.default';
$namespace = $this->rc->imap->get_namespace();
// get all folders of specified type
$folderdata = array_intersect($folderdata, array($type));
unset($folders[0]);
foreach ($folderdata as $folder => $data) {
// check if folder is in personal namespace
foreach (array('shared', 'other') as $nskey) {
if (!empty($namespace[$nskey])) {
foreach ($namespace[$nskey] as $ns) {
if ($ns[0] && substr($folder, 0, strlen($ns[0])) == $ns[0]) {
continue 3;
}
}
}
}
// There can be only one default folder of specified type
return $folder;
}
return null;
}
/**
* Returns CSS class name for specified folder type
*
* @param string $type Folder type
*
* @return string Class name
*/
static function folder_class_name($type)
{
list($ctype, $subtype) = explode('.', $type);
$class[] = 'type-' . ($ctype ? $ctype : 'mail');
if ($subtype)
$class[] = 'subtype-' . $subtype;
return implode(' ', $class);
}
/**
* Clear Horde's folder cache. See Kolab_List::singleton().
*/
private function clear_folders_cache()
{
unset($_SESSION['horde_session_objects']['kolab_folderlist']);
}
/**
* Creates default folders if they doesn't exist
*/
private function create_default_folders(&$folderdata, $cache_key = null)
{
$namespace = $this->rc->imap->get_namespace();
$defaults = array();
$need_update = false;
// Find personal namespace prefix
if (is_array($namespace['personal']) && count($namespace['personal']) == 1) {
$prefix = $namespace['personal'][0][0];
}
else {
$prefix = '';
}
$this->load_config();
// get configured defaults
foreach ($this->types as $type) {
$subtypes = $type == 'mail' ? $this->mail_types : array('default');
foreach ($subtypes as $subtype) {
$opt_name = 'kolab_folders_' . $type . '_' . $subtype;
if ($folder = $this->rc->config->get($opt_name)) {
// convert configuration value to UTF7-IMAP charset
$folder = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
// and namespace prefix if needed
if ($prefix && strpos($folder, $prefix) === false && $folder != 'INBOX') {
$folder = $prefix . $folder;
}
$defaults[$type . '.' . $subtype] = $folder;
}
}
}
// find default folders
foreach ($defaults as $type => $foldername) {
// folder exists, do nothing
if (!empty($folderdata[$foldername])) {
continue;
}
// special case, need to set type only
if ($foldername == 'INBOX' || $type == 'mail.inbox') {
$this->set_folder_type($foldername, 'mail.inbox');
continue;
}
// get all folders of specified type
$folders = array_intersect($folderdata, array($type));
unset($folders[0]);
// find folders in personal namespace
foreach ($folders as $folder) {
if ($folder) {
foreach (array('shared', 'other') as $nskey) {
if (!empty($namespace[$nskey])) {
foreach ($namespace[$nskey] as $ns) {
if ($ns[0] && substr($folder, 0, strlen($ns[0])) == $ns[0]) {
continue 3;
}
}
}
}
}
// got folder in personal namespace
continue 2;
}
list($type1, $type2) = explode('.', $type);
// create folder
if ($type1 != 'mail' || !$this->rc->imap->mailbox_exists($foldername)) {
$this->rc->imap->create_mailbox($foldername, $type1 == 'mail');
}
// set type
$result = $this->set_folder_type($foldername, $type);
// add new folder to the result
if ($result) {
$folderdata[$foldername] = $type;
$need_update = true;
}
}
// update cache
if ($need_update && $cache_key) {
$this->rc->imap->update_cache($cache_key, $folderdata);
}
}
}
diff --git a/plugins/kolab_folders/package.xml b/plugins/kolab_folders/package.xml
new file mode 100644
index 00000000..54ff20b5
--- /dev/null
+++ b/plugins/kolab_folders/package.xml
@@ -0,0 +1,63 @@
+<?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>kolab_folders</name>
+ <uri>http://kolabsys.com</uri>
+ <summary>Type-aware folder management/listing for Kolab</summary>
+ <description>
+ The plugin extends folders handling with features of the Kolab Suite
+ according to specified format (http://www.kolab.org/doc/kolabformat-2.0-html).
+ With this plugin enabled it is possible to:
+ - set/get/change folder's type,
+ - filter folders list by folder type,
+ - style folders list rows (in folder manager),
+ - create default folders with specified type.
+ </description>
+ <lead>
+ <name>Aleksander Machniak</name>
+ <user>machniak</user>
+ <email>machniak@kolabsys.com</email>
+ <active>yes</active>
+ </lead>
+ <date>2011-11-01</date>
+ <version>
+ <release>1.0</release>
+ <api>1.0</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="kolab_folders.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="kolab_folders.js" role="data">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="config.inc.php.dist" role="data"></file>
+ <file name="localization/en_US.inc" role="data"></file>
+ <file name="localization/pl_PL.inc" role="data"></file>
+ <file name="LICENSE" role="data"></file>
+ </dir>
+ <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.1</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <phprelease />
+</package>

File Metadata

Mime Type
text/x-diff
Expires
Tue, Aug 18, 1:05 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1258512
Default Alt Text
(59 KB)

Event Timeline