diff options
author | Gunnar Wrobel <wrobel@gentoo.org> | 2008-08-19 12:50:26 +0000 |
---|---|---|
committer | Gunnar Wrobel <wrobel@gentoo.org> | 2008-08-19 12:50:26 +0000 |
commit | 1fc46d5f5a4493b568972bbe65ed6b185d6efced (patch) | |
tree | fdb4e84c0b29347cba47a633fd89afa6c3affe33 /www-apps/horde-webmail | |
parent | Version bump. (diff) | |
download | gentoo-2-1fc46d5f5a4493b568972bbe65ed6b185d6efced.tar.gz gentoo-2-1fc46d5f5a4493b568972bbe65ed6b185d6efced.tar.bz2 gentoo-2-1fc46d5f5a4493b568972bbe65ed6b185d6efced.zip |
Added horde-webmail-1.1.2.
(Portage version: 2.1.4.4)
Diffstat (limited to 'www-apps/horde-webmail')
-rw-r--r-- | www-apps/horde-webmail/ChangeLog | 9 | ||||
-rw-r--r-- | www-apps/horde-webmail/files/horde-webmail-1.1.1_kolab.patch | 4154 | ||||
-rw-r--r-- | www-apps/horde-webmail/horde-webmail-1.1.1.ebuild | 33 | ||||
-rw-r--r-- | www-apps/horde-webmail/horde-webmail-1.1.2.ebuild (renamed from www-apps/horde-webmail/horde-webmail-1.1.1-r3.ebuild) | 9 |
4 files changed, 15 insertions, 4190 deletions
diff --git a/www-apps/horde-webmail/ChangeLog b/www-apps/horde-webmail/ChangeLog index 871cc1f72544..c30374b7673e 100644 --- a/www-apps/horde-webmail/ChangeLog +++ b/www-apps/horde-webmail/ChangeLog @@ -1,6 +1,13 @@ # ChangeLog for www-apps/horde-webmail # Copyright 1999-2008 Gentoo Foundation; Distributed under the GPL v2 -# $Header: /var/cvsroot/gentoo-x86/www-apps/horde-webmail/ChangeLog,v 1.14 2008/08/14 07:33:33 wrobel Exp $ +# $Header: /var/cvsroot/gentoo-x86/www-apps/horde-webmail/ChangeLog,v 1.15 2008/08/19 12:50:25 wrobel Exp $ + +*horde-webmail-1.1.2 (19 Aug 2008) + + 19 Aug 2008; wrobel@gentoo.org -files/horde-webmail-1.1.1_kolab.patch, + -horde-webmail-1.1.1.ebuild, -horde-webmail-1.1.1-r3.ebuild, + +horde-webmail-1.1.2.ebuild: + Added horde-webmail-1.1.2. *horde-webmail-1.1.1-r3 (14 Aug 2008) diff --git a/www-apps/horde-webmail/files/horde-webmail-1.1.1_kolab.patch b/www-apps/horde-webmail/files/horde-webmail-1.1.1_kolab.patch deleted file mode 100644 index eef1e76b23a1..000000000000 --- a/www-apps/horde-webmail/files/horde-webmail-1.1.1_kolab.patch +++ /dev/null @@ -1,4154 +0,0 @@ -diff -r f7b1e151bdb5 config/conf.php ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/config/conf.php Wed Aug 13 21:37:22 2008 +0200 -@@ -0,0 +1,113 @@ -+<?php -+/* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */ -+// $Horde: horde/config/conf.xml,v 1.230 2008/05/26 11:51:43 jan Exp $ -+$conf['vhosts'] = false; -+$conf['debug_level'] = E_ALL; -+$conf['max_exec_time'] = 0; -+$conf['compress_pages'] = true; -+$conf['umask'] = 077; -+$conf['use_ssl'] = 2; -+$conf['server']['name'] = $_SERVER['SERVER_NAME']; -+$conf['server']['port'] = $_SERVER['SERVER_PORT']; -+$conf['urls']['pretty'] = false; -+$conf['safe_ips'] = array(); -+$conf['session']['name'] = 'Horde'; -+$conf['session']['use_only_cookies'] = false; -+$conf['session']['cache_limiter'] = 'nocache'; -+$conf['session']['timeout'] = 0; -+$conf['cookie']['domain'] = $_SERVER['SERVER_NAME']; -+$conf['cookie']['path'] = '/'; -+$conf['sql']['database'] = dirname(__FILE__) . '/../storage/horde.db'; -+$conf['sql']['mode'] = '0640'; -+$conf['sql']['charset'] = 'utf-8'; -+$conf['sql']['phptype'] = 'sqlite'; -+$conf['auth']['admins'] = array('manager'); -+$conf['auth']['checkip'] = true; -+$conf['auth']['checkbrowser'] = true; -+$conf['auth']['alternate_login'] = false; -+$conf['auth']['redirect_on_logout'] = false; -+$conf['auth']['params']['login_block'] = true; -+$conf['auth']['params']['login_block_count'] = 3; -+$conf['auth']['params']['login_block_time'] = 5; -+$conf['auth']['driver'] = 'kolab'; -+$conf['signup']['allow'] = false; -+$conf['log']['priority'] = PEAR_LOG_DEBUG; -+$conf['log']['ident'] = 'HORDE'; -+$conf['log']['params'] = array(); -+$conf['log']['name'] = dirname(__FILE__) . '/../log/horde.log'; -+$conf['log']['params']['append'] = true; -+$conf['log']['type'] = 'file'; -+$conf['log']['enabled'] = true; -+$conf['log_accesskeys'] = false; -+$conf['prefs']['driver'] = 'kolab_imap'; -+$conf['alarms']['params']['driverconfig'] = 'horde'; -+$conf['alarms']['params']['ttl'] = 300; -+$conf['alarms']['driver'] = 'sql'; -+$conf['datatree']['params']['driverconfig'] = 'horde'; -+$conf['datatree']['driver'] = 'sql'; -+$conf['group']['driver'] = 'kolab'; -+$conf['group']['cache'] = false; -+$conf['perms']['driverconfig'] = 'horde'; -+$conf['perms']['driver'] = 'sql'; -+$conf['share']['no_sharing'] = false; -+$conf['share']['any_group'] = false; -+$conf['share']['cache'] = true; -+$conf['share']['driver'] = 'kolab'; -+$conf['cache']['default_lifetime'] = 1800; -+$conf['cache']['params']['dir'] = Horde::getTempDir(); -+$conf['cache']['params']['sub'] = 0; -+$conf['cache']['driver'] = 'file'; -+$conf['lock']['driver'] = 'none'; -+$conf['token']['driver'] = 'none'; -+$conf['mailer']['params']['auth'] = false; -+$conf['mailer']['type'] = 'smtp'; -+$conf['mailformat']['brokenrfc2231'] = false; -+$conf['tmpdir'] = dirname(__FILE__) . '/../tmp/'; -+$conf['vfs']['params']['vfsroot'] = dirname(__FILE__) . '/../storage'; -+$conf['vfs']['type'] = 'file'; -+$conf['sessionhandler']['type'] = 'none'; -+$conf['sessionhandler']['memcache'] = false; -+$conf['image']['convert'] = '/usr/bin/convert'; -+$conf['mime']['magic_db'] = '/etc/mime.types'; -+$conf['problems']['email'] = 'webmaster@example.com'; -+$conf['problems']['maildomain'] = 'example.com'; -+$conf['problems']['tickets'] = false; -+$conf['problems']['attachments'] = true; -+$conf['menu']['apps'] = array(); -+$conf['menu']['always'] = false; -+$conf['menu']['links']['help'] = 'all'; -+$conf['menu']['links']['help_about'] = true; -+$conf['menu']['links']['options'] = 'authenticated'; -+$conf['menu']['links']['problem'] = 'all'; -+$conf['menu']['links']['login'] = 'all'; -+$conf['menu']['links']['logout'] = 'authenticated'; -+$conf['hooks']['permsdenied'] = false; -+$conf['hooks']['username'] = false; -+$conf['hooks']['preauthenticate'] = false; -+$conf['hooks']['postauthenticate'] = false; -+$conf['hooks']['authldap'] = false; -+$conf['hooks']['groupldap'] = false; -+$conf['portal']['fixed_blocks'] = array(); -+$conf['accounts']['driver'] = 'null'; -+$conf['user']['verify_from_addr'] = false; -+$conf['imsp']['enabled'] = false; -+$conf['kolab']['ldap']['server'] = 'localhost'; -+$conf['kolab']['ldap']['port'] = 389; -+$conf['kolab']['ldap']['basedn'] = 'dc=example,dc=com'; -+$conf['kolab']['ldap']['phpdn'] = 'cn=nobody,cn=internal,dc=example,dc=com'; -+$conf['kolab']['ldap']['phppw'] = 'dummy'; -+$conf['kolab']['imap']['server'] = 'localhost'; -+$conf['kolab']['imap']['port'] = 143; -+$conf['kolab']['imap']['sieveport'] = 2000; -+$conf['kolab']['imap']['maildomain'] = 'example.com'; -+$conf['kolab']['imap']['virtdomains'] = true; -+$conf['kolab']['smtp']['server'] = 'localhost'; -+$conf['kolab']['smtp']['port'] = 25; -+$conf['kolab']['misc']['multidomain'] = false; -+$conf['kolab']['cache_folders'] = true; -+$conf['kolab']['enabled'] = true; -+$conf['memcache']['enabled'] = false; -+/* CONFIG END. DO NOT CHANGE ANYTHING IN OR BEFORE THIS LINE. */ -+if (file_exists(dirname(__FILE__) . '/kolab.php')) { -+ require_once(dirname(__FILE__) . '/kolab.php'); -+} -diff -r f7b1e151bdb5 config/kolab.php ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/config/kolab.php Wed Aug 13 21:37:22 2008 +0200 -@@ -0,0 +1,34 @@ -+<?php -+/* Add additional admins here */ -+$conf['auth']['admins'] = array('manager'); -+ -+/* Add your horde vhost hostname here */ -+$conf['cookie']['domain'] = 'webmail.example.com'; -+ -+/* Add the path to the vhost install here (the value of the -d switch when you used webapp-config) */ -+$conf['cookie']['path'] = '/'; -+ -+/* The email address of your sys admin */ -+$conf['problems']['email'] = 'hostmaster@example.com'; -+ -+/* Primary mail domain of your Kolab server */ -+$conf['problems']['maildomain'] = 'pardus.de'; -+ -+/* The hostname of your LDAP server (usually the same as the hostname of the Kolab server) */ -+$conf['kolab']['ldap']['server'] = 'localhost'; -+ -+/* Base DN of your LDAP server */ -+$conf['kolab']['ldap']['basedn'] = 'dc=example,dc=com'; -+ -+/* php_dn from your /kolab/etc/kolab/kolab.conf */ -+$conf['kolab']['ldap']['phpdn'] = 'cn=nobody,cn=internal,dc=example,dc=com'; -+ -+/* php_pw from your /kolab/etc/kolab/kolab.conf */ -+$conf['kolab']['ldap']['phppw'] = 'MY_SECRET_PASSWORD'; -+ -+/* Hostname of your IMAP server (usually the same as the hostname of the Kolab server) */ -+$conf['kolab']['imap']['server'] = 'localhost'; -+ -+/* Primary mail domain of your Kolab server */ -+$conf['kolab']['imap']['maildomain'] = 'example.com'; -+?> -diff -r f7b1e151bdb5 config/prefs.php ---- a/config/prefs.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/config/prefs.php Wed Aug 13 21:37:22 2008 +0200 -@@ -357,7 +357,7 @@ - - // UI theme - $_prefs['theme'] = array( -- 'value' => 'bluewhite', -+ 'value' => 'silver', - 'locked' => false, - 'shared' => true, - 'type' => 'select', -diff -r f7b1e151bdb5 imp/config/conf.php ---- a/imp/config/conf.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/config/conf.php Wed Aug 13 21:37:22 2008 +0200 -@@ -1,7 +1,8 @@ - <?php - /* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */ - // $Horde: imp/config/conf.xml,v 1.53.2.33 2008/05/06 17:54:04 slusarz Exp $ --$conf['spell']['driver'] = ''; -+$conf['spell']['driver'] = 'aspell'; -+$conf['utils']['gnupg'] = '/usr/bin/gpg'; - $conf['utils']['gnupg_keyserver'] = array('pgp.mit.edu'); - $conf['utils']['gnupg_timeout'] = 10; - $conf['menu']['apps'] = array(); -@@ -52,8 +53,8 @@ - $conf['hooks']['signature'] = false; - $conf['hooks']['trailer'] = false; - $conf['hooks']['fetchmail_filter'] = false; --$conf['hooks']['mbox_redirect'] = false; --$conf['hooks']['mbox_icon'] = false; -+$conf['hooks']['mbox_redirect'] = true; -+$conf['hooks']['mbox_icon'] = true; - $conf['hooks']['spam_bounce'] = false; - $conf['hooks']['msglist_format'] = false; - $conf['maillog']['use_maillog'] = true; -diff -r f7b1e151bdb5 imp/config/hooks.php ---- a/imp/config/hooks.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/config/hooks.php Wed Aug 13 21:37:22 2008 +0200 -@@ -426,6 +426,9 @@ - case 'contact': - return $GLOBALS['registry']->get('webroot', 'turba'); - -+ case 'prefs': -+ return $GLOBALS['registry']->get('webroot', 'horde') . '/services/prefs.php?app=horde'; -+ - default: - return ''; - } -@@ -476,6 +479,14 @@ - 'alt' => _("Contacts") - ); - break; -+ -+ case 'prefs': -+ $icons[$name] = array( -+ 'icon' => 'prefs.png', -+ 'icondir' => $GLOBALS['registry']->getImageDir('horde'), -+ 'alt' => _("Preferences") -+ ); -+ break; - } - } - -diff -r f7b1e151bdb5 imp/config/prefs.php ---- a/imp/config/prefs.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/config/prefs.php Wed Aug 13 21:37:22 2008 +0200 -@@ -24,7 +24,7 @@ - 'column' => _("General Options"), - 'label' => _("Server and Folder Information"), - 'desc' => _("Change mail server and folder settings."), -- 'members' => array('use_vinbox', 'subscribe', 'folderselect', -+ 'members' => array('use_vinbox', 'use_vtask', 'subscribe', 'folderselect', - 'trashselect', 'spamselect') - ); - } -@@ -267,6 +267,14 @@ - 'shared' => false, - 'type' => 'checkbox', - 'desc' => _("Display Virtual Inbox?")); -+ -+// display Virtual Tasks? -+$_prefs['use_vtask'] = array( -+ 'value' => 1, -+ 'locked' => false, -+ 'shared' => false, -+ 'type' => 'checkbox', -+ 'desc' => _("Display Virtual Tasks?")); - - // use IMAP subscribe? - $_prefs['subscribe'] = array( -@@ -1463,3 +1471,10 @@ - 'locked' => false, - 'shared' => false, - 'type' => 'implicit'); -+ -+// virtual task identifier -+$_prefs['vtask_id'] = array( -+ 'value' => '', -+ 'locked' => false, -+ 'shared' => false, -+ 'type' => 'implicit'); -diff -r f7b1e151bdb5 imp/config/servers.php ---- a/imp/config/servers.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/config/servers.php Wed Aug 13 21:37:22 2008 +0200 -@@ -215,10 +215,30 @@ - - /* Example configurations: */ - --$servers['imap'] = array( -- 'name' => 'IMAP Server', -- 'server' => 'localhost', -- 'hordeauth' => false, -- 'protocol' => 'imap/notls', -- 'port' => 143, --); -+if ($GLOBALS['conf']['kolab']['enabled']) { -+ require_once 'Horde/Kolab.php'; -+ -+ if (!is_callable('Kolab', 'getServer')) { -+ $server = $GLOBALS['conf']['kolab']['imap']['server']; -+ } else { -+ $server = Kolab::getServer('imap'); -+ } -+ -+ $servers['kolab'] = array( -+ 'name' => 'Kolab Cyrus IMAP Server', -+ 'server' => $server, -+ 'hordeauth' => 'full', -+ 'protocol' => 'imap/notls/novalidate-cert', -+ 'port' => $GLOBALS['conf']['kolab']['imap']['port'], -+ 'maildomain' => $GLOBALS['conf']['kolab']['imap']['maildomain'], -+ 'realm' => '', -+ 'preferred' => '', -+ 'quota' => array( -+ 'driver' => 'imap', -+ 'params' => array('hide_quota_when_unlimited' => true), -+ ), -+ 'acl' => array( -+ 'driver' => 'rfc2086', -+ ), -+ ); -+} -diff -r f7b1e151bdb5 imp/folders.php ---- a/imp/folders.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/folders.php Wed Aug 13 21:37:22 2008 +0200 -@@ -212,6 +212,7 @@ - if (!empty($folder_list)) { - ($actionID == 'poll_folder') ? $imptree->addPollList($folder_list) : $imptree->removePollList($folder_list); - $imp_search->createVINBOXFolder(); -+ $imp_search->createVTaskFolder(); - } - break; - -diff -r f7b1e151bdb5 imp/lib/IMAP/Tree.php ---- a/imp/lib/IMAP/Tree.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/lib/IMAP/Tree.php Wed Aug 13 21:37:22 2008 +0200 -@@ -2367,6 +2367,10 @@ - $row['specialvfolder'] = true; - $row['icon'] = 'folders/inbox.png'; - $row['alt'] = _("Virtual INBOX Folder"); -+ } elseif ($GLOBALS['imp_search']->isVTaskFolder($mailbox['v'])) { -+ $row['specialvfolder'] = true; -+ $row['icon'] = 'folders/inbox.png'; -+ $row['alt'] = _("Virtual Task Folder"); - } - } - } else { -diff -r f7b1e151bdb5 imp/lib/Search.php ---- a/imp/lib/Search.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/lib/Search.php Wed Aug 13 21:37:22 2008 +0200 -@@ -96,6 +96,7 @@ - foreach ($this->_getVFolderList() as $key => $val) { - if (!empty($val['vfolder']) && - !$this->isVTrashFolder($key) && -+ !$this->isVTaskFolder($key) && - !$this->isVINBOXFolder($key)) { - $this->_updateIMPTree('add', $key, $val['label']); - $_SESSION['imp']['search']['q'][$key] = $val; -@@ -103,6 +104,7 @@ - } - } - $this->createVINBOXFolder(); -+ $this->createVTaskFolder(); - $this->createVTrashFolder(); - } - -@@ -428,6 +430,71 @@ - } - - /** -+ * Add a virtual task folder for the current user. -+ */ -+ function createVTaskFolder() -+ { -+ /* Initialize IMP_Tree. */ -+ require_once IMP_BASE . '/lib/IMAP/Tree.php'; -+ $imptree = &IMP_Tree::singleton(); -+ -+ /* Delete the current Virtual task folder, if it exists. */ -+ $vtask_id = $GLOBALS['prefs']->getValue('vtask_id'); -+ if (!empty($vtask_id)) { -+ $this->deleteSearchQuery($vtask_id); -+ } -+ -+ if (!$GLOBALS['prefs']->getValue('use_vtask')) { -+ return; -+ } -+ -+ /* Create Virtual TASK with nav_poll list. Filter out any nav_poll -+ * entries that don't exist. Sort the list also. */ -+ $flist = $imptree->getPollList(true, true); -+ -+ require_once IMP_BASE . '/lib/IMAP/Search.php'; -+ $query1 = new IMP_IMAP_Search_Query(); -+ $query1->seen(false); -+ $query1->deleted(false); -+ -+ $query2 = new IMP_IMAP_Search_Query(); -+ $query2->flagged(true); -+ $query2->deleted(false); -+ -+ $query = new IMP_IMAP_Search_Query(); -+ $query->imapOr($query1); -+ $query->imapOr($query2); -+ -+ $label = _("Virtual TASKS"); -+ -+ $this->_saveVFolder = false; -+ if (empty($vtask_id)) { -+ $vtask_id = $this->addVFolder($query, $flist, array(), $label); -+ $GLOBALS['prefs']->setValue('vtask_id', $vtask_id); -+ } else { -+ $this->addVFolder($query, $flist, array(), $label, $vtask_id); -+ } -+ $this->_saveVFolder = true; -+ $_SESSION['imp']['search']['vtask_id'] = $vtask_id; -+ } -+ -+ /** -+ * Determines whether a virtual folder ID is the Virtual TASK Folder. -+ * -+ * @param string $id The search query id to use (by default, will use -+ * the current ID set in the object). -+ * -+ * @return boolean True if the virutal folder ID is the Virtual task -+ * folder. -+ */ -+ function isVTaskFolder($id = null) -+ { -+ $id = $this->_strip($id); -+ $vtask_id = $GLOBALS['prefs']->getValue('vtask_id'); -+ return (!empty($vtask_id) && ($id == $vtask_id)); -+ } -+ -+ /** - * Is the current active folder an editable Virtual Folder? - * - * @param string $id The search query id to use (by default, will use -@@ -439,7 +506,7 @@ - function isEditableVFolder($id = null) - { - $id = $this->_strip($id); -- return ($this->isVFolder($id) && !$this->isVTrashFolder($id) && !$this->isVINBOXFolder($id)); -+ return ($this->isVFolder($id) && !$this->isVTrashFolder($id) && !$this->isVINBOXFolder($id) && !$this->isVTaskFolder($id)); - } - - /** -@@ -518,7 +585,7 @@ - $id = $this->_strip($id); - if (empty($_SESSION['imp']['search']['q'][$id])) { - return ''; -- } elseif ($this->isVINBOXFolder($id) || $this->isVTrashFolder($id)) { -+ } elseif ($this->isVINBOXFolder($id) || $this->isVTrashFolder($id) || $this->isVTaskFolder($id)) { - return $_SESSION['imp']['search']['q'][$id]['label']; - } elseif (empty($_SESSION['imp']['search']['q'][$id]['uiinfo'])) { - unset($_SESSION['imp']['search']['q'][$id]); -diff -r f7b1e151bdb5 imp/lib/prefs.php ---- a/imp/lib/prefs.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/lib/prefs.php Wed Aug 13 21:37:22 2008 +0200 -@@ -165,7 +165,7 @@ - } - - if (($prefs->isDirty('use_vtrash') && $prefs->getValue('use_vtrash')) || -- $prefs->isDirty('use_vinbox')) { -+ $prefs->isDirty('use_vinbox') || $prefs->isDirty('use_vtask')) { - require_once IMP_BASE . '/lib/Search.php'; - $imp_search = new IMP_Search(); - $imp_search->sessionSetup(true); -diff -r f7b1e151bdb5 imp/mailbox.php ---- a/imp/mailbox.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/imp/mailbox.php Wed Aug 13 21:37:23 2008 +0200 -@@ -248,7 +248,8 @@ - /* Determine if we are going to show the Hide/Purge Deleted Message links. */ - if (!$prefs->getValue('use_trash') && - !$prefs->getValue('use_vtrash') && -- !$GLOBALS['imp_search']->isVINBOXFolder()) { -+ !$GLOBALS['imp_search']->isVINBOXFolder() && -+ !$GLOBALS['imp_search']->isVTaskFolder()) { - $showdelete = array('hide' => ($sortpref['by'] != SORTTHREAD), 'purge' => true); - } else { - $showdelete = array('hide' => false, 'purge' => false); -@@ -335,7 +336,7 @@ - $vtrash = null; - if ($search_mbox) { - $unread = 0; -- if ($imp_search->isVINBOXFolder()) { -+ if ($imp_search->isVINBOXFolder() || $imp_search->isVTaskFolder()) { - $unread = $imp_mailbox->getMessageCount(); - } elseif ($imp_search->isVTrashFolder()) { - $vtrash = $imp_search->createSearchID($search_mbox); -diff -r f7b1e151bdb5 ingo/config/backends.php ---- a/ingo/config/backends.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/ingo/config/backends.php Wed Aug 13 21:37:23 2008 +0200 -@@ -40,6 +40,8 @@ - * give them permissions to do so. If you want to enable this - * feature, you need to set this parameter to true. - */ -+ -+if (!$GLOBALS['conf']['kolab']['enabled']) { - - /* IMAP Example */ - $backends['imap'] = array( -@@ -297,6 +299,8 @@ - 'scriptparams' => array() - ); - -+} -+ - /* Kolab Example (using Sieve) */ - if ($GLOBALS['conf']['kolab']['enabled']) { - require_once 'Horde/Kolab.php'; -diff -r f7b1e151bdb5 kronolith/config/conf.php ---- a/kronolith/config/conf.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/kronolith/config/conf.php Wed Aug 13 21:37:23 2008 +0200 -@@ -1,12 +1,11 @@ - <?php - /* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */ - // $Horde: kronolith/config/conf.xml,v 1.14.10.5 2007/12/20 14:12:23 jan Exp $ --$conf['calendar']['params']['table'] = 'kronolith_events'; --$conf['calendar']['params']['driverconfig'] = 'horde'; --$conf['calendar']['driver'] = 'sql'; --$conf['storage']['params']['table'] = 'kronolith_storage'; --$conf['storage']['params']['driverconfig'] = 'horde'; --$conf['storage']['driver'] = 'sql'; -+$conf['calendar']['driver'] = 'kolab'; -+$conf['storage']['driver'] = 'kolab'; -+$conf['storage']['default_domain'] = ''; -+$conf['storage']['freebusy']['protocol'] = 'https'; -+$conf['storage']['freebusy']['port'] = 443; - $conf['metadata']['keywords'] = false; - $conf['autoshare']['shareperms'] = 'none'; - $conf['holidays']['enable'] = true; -@@ -14,3 +13,6 @@ - $conf['menu']['import_export'] = true; - $conf['menu']['apps'] = array(); - /* CONFIG END. DO NOT CHANGE ANYTHING IN OR BEFORE THIS LINE. */ -+if (file_exists(dirname(__FILE__) . '/kolab.php')) { -+ require_once(dirname(__FILE__) . '/kolab.php'); -+} -diff -r f7b1e151bdb5 kronolith/config/kolab.php ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/kronolith/config/kolab.php Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,10 @@ -+<?php -+/* Primary mail domain of your Kolab server */ -+$conf['storage']['default_domain'] = 'example.com'; -+ -+/* Hostname of your IMAP server (usually the same as the hostname of the Kolab server) */ -+$conf['reminder']['server_name'] = 'localhost'; -+ -+/* The email address of your sys admin */ -+$conf['reminder']['from_addr'] = 'hostmaster@example.com'; -+?> -diff -r f7b1e151bdb5 lib/Horde/Kolab/IMAP.php ---- a/lib/Horde/Kolab/IMAP.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/Kolab/IMAP.php Wed Aug 13 21:37:23 2008 +0200 -@@ -1021,7 +1021,9 @@ - function generateUID() - { - do { -- $key = md5(uniqid(mt_rand(), true)); -+ $key = date('YmdHis') . '.' -+ . substr(str_pad(base_convert(microtime(), 10, 36), 16, uniqid(mt_rand()), STR_PAD_LEFT), -16) -+ . '@' . $GLOBALS['conf']['server']['name']; - } while($this->objectUidExists($key)); - - return $key; -diff -r f7b1e151bdb5 lib/Horde/Kolab/XML/contact.php ---- a/lib/Horde/Kolab/XML/contact.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/Kolab/XML/contact.php Wed Aug 13 21:37:23 2008 +0200 -@@ -294,6 +294,9 @@ - function _load(&$children) - { - $object = $this->loadArray($children, $this->_fields_specific); -+ if (is_a($object, 'PEAR_Error')) { -+ return $object; -+ } - - // Handle name fields - if (isset($object['name'])) { -diff -r f7b1e151bdb5 lib/Horde/Kolab/XML/distributionlist.php ---- a/lib/Horde/Kolab/XML/distributionlist.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/Kolab/XML/distributionlist.php Wed Aug 13 21:37:23 2008 +0200 -@@ -60,6 +60,9 @@ - function _load(&$children) - { - $object = $this->loadArray($children, $this->_fields_specific); -+ if (is_a($object, 'PEAR_Error')) { -+ return $object; -+ } - - // Map the display-name of a kolab dist list to horde's lastname attribute - if (isset($object['display-name'])) { -diff -r f7b1e151bdb5 lib/Horde/Kolab/XML/jobtype.php ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/lib/Horde/Kolab/XML/jobtype.php Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,56 @@ -+<?php -+/** -+ * @package Horde_Kolab -+ * -+ * $Horde: framework/Kolab/Kolab/Format/XML/hprefs.php,v 1.1 2008/06/16 11:46:09 wrobel Exp $ -+ */ -+ -+/** -+ * Kolab XML handler for timetracking jobtypes -+ * -+ * $Horde: framework/Kolab/Kolab/Format/XML/hprefs.php,v 1.1 2008/06/16 11:46:09 wrobel Exp $ -+ * -+ * Copyright 2008 The Horde Project (http://www.horde.org/) -+ * -+ * @author Gunnar Wrobel <wrobel@pardus.de> -+ * @package Horde_Kolab -+ */ -+class Kolab_Format_XML_jobtype extends Kolab_Format_XML { -+ /** -+ * Specific data fields for the jobtype object -+ * -+ * @var Kolab -+ */ -+ var $_fields_specific; -+ -+ /** -+ * Constructor -+ */ -+ function Kolab_Format_XML_jobtype() -+ { -+ $this->_root_name = 'jobtype'; -+ -+ /** Specific preferences fields, in kolab format specification order -+ */ -+ $this->_fields_specific = array( -+ 'billable' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_INTEGER, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ 'enabled' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_INTEGER, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ 'name' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, -+ ), -+ 'rate' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ ); -+ -+ parent::Kolab_Format_XML(); -+ } -+} -diff -r f7b1e151bdb5 lib/Horde/Kolab/XML/note.php ---- a/lib/Horde/Kolab/XML/note.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/Kolab/XML/note.php Wed Aug 13 21:37:23 2008 +0200 -@@ -64,6 +64,9 @@ - function _load(&$children) - { - $object = $this->loadArray($children, $this->_fields_specific); -+ if (is_a($object, 'PEAR_Error')) { -+ return $object; -+ } - - $object['desc'] = $object['summary']; - unset($object['summary']); -diff -r f7b1e151bdb5 lib/Horde/Kolab/XML/task.php ---- a/lib/Horde/Kolab/XML/task.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/Kolab/XML/task.php Wed Aug 13 21:37:23 2008 +0200 -@@ -109,6 +109,9 @@ - function _load(&$children) - { - $object = $this->loadArray($children, $this->_fields_specific); -+ if (is_a($object, 'PEAR_Error')) { -+ return $object; -+ } - - $object['name'] = $object['summary']; - unset($object['summary']); -diff -r f7b1e151bdb5 lib/Horde/Kolab/XML/timetrack.php ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/lib/Horde/Kolab/XML/timetrack.php Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,90 @@ -+<?php -+/** -+ * @package Horde_Kolab -+ * -+ * $Horde: framework/Kolab/Kolab/Format/XML/hprefs.php,v 1.1 2008/06/16 11:46:09 wrobel Exp $ -+ */ -+ -+/** -+ * Kolab XML handler for time tracking entries -+ * -+ * $Horde: framework/Kolab/Kolab/Format/XML/hprefs.php,v 1.1 2008/06/16 11:46:09 wrobel Exp $ -+ * -+ * Copyright 2008 The Horde Project (http://www.horde.org/) -+ * -+ * @author Gunnar Wrobel <wrobel@pardus.de> -+ * @package Horde_Kolab -+ */ -+class Kolab_Format_XML_timetrack extends Kolab_Format_XML { -+ /** -+ * Specific data fields for the jobtype object -+ * -+ * @var Kolab -+ */ -+ var $_fields_specific; -+ -+ /** -+ * Constructor -+ */ -+ function Kolab_Format_XML_timetrack() -+ { -+ $this->_root_name = 'timetrack'; -+ -+ /** Specific preferences fields, in kolab format specification order -+ */ -+ $this->_fields_specific = array( -+ 'client' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, -+ ), -+ 'employee' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, -+ ), -+ 'type' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, -+ ), -+ 'hours' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, -+ ), -+ 'billable' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_INTEGER, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ 'submitted' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_INTEGER, -+ 'value' => HORDE_KOLAB_XML_VALUE_DEFAULT, -+ 'default' => 0, -+ ), -+ 'exported' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_INTEGER, -+ 'value' => HORDE_KOLAB_XML_VALUE_DEFAULT, -+ 'default' => 0, -+ ), -+ 'date' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_DATE, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ 'description' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ 'note' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ 'rate' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, -+ ), -+ 'costobject' => array( -+ 'type' => HORDE_KOLAB_XML_TYPE_STRING, -+ 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, -+ ), -+ ); -+ -+ parent::Kolab_Format_XML(); -+ } -+} -diff -r f7b1e151bdb5 lib/Horde/Prefs/kolab.php ---- a/lib/Horde/Prefs/kolab.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/Prefs/kolab.php Wed Aug 13 21:37:23 2008 +0200 -@@ -1,5 +1,6 @@ - <?php - -+require_once 'Horde/Auth.php'; - require_once 'Horde/Prefs/ldap.php'; - require_once 'Horde/Kolab.php'; - -@@ -42,7 +43,8 @@ - 'searchpw' => $GLOBALS['conf']['kolab']['ldap']['phppw'], - 'uid' => 'mail'); - -- parent::Prefs_ldap($user, $password, $scope, $params, $caching); -+ parent::Prefs_ldap(Auth::getAuth(), Auth::getCredential('password'), -+ $scope, $params, $caching); - } - - } -diff -r f7b1e151bdb5 lib/Horde/iCalendar.php ---- a/lib/Horde/iCalendar.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/Horde/iCalendar.php Wed Aug 13 21:37:23 2008 +0200 -@@ -152,6 +152,41 @@ - 'value' => $value, - 'values' => $values - ); -+ } -+ } -+ -+ /** -+ * Sets an attribute empty. -+ * -+ * @param string $name The name of the attribute. -+ * @param array $params Array containing any addition parameters for -+ * this attribute. -+ * @param boolean $append True to append the attribute, False to replace -+ * the first matching attribute found. -+ */ -+ function setAttributeEmpty($name, $params = array(), $append = false) -+ { -+ switch ($name) { -+ case 'EXDATE': -+ case 'RDATE': -+ $this->setAttribute($name, array(), $params, $append, true); -+ break; -+ case 'COMPLETED': -+ case 'CREATED': -+ case 'DCREATED': -+ case 'LAST-MODIFIED': -+ case 'DTEND': -+ case 'DTSTART': -+ case 'DTSTAMP': -+ case 'DUE': -+ case 'AALARM': -+ case 'RECURRENCE-ID': -+ /* These values expect a date and there is no sensible -+ default so we better leave them alone for now. */ -+ break; -+ default: -+ $this->setAttribute($name, '', $params, $append); -+ break; - } - } - -diff -r f7b1e151bdb5 lib/SyncML/Backend.php ---- a/lib/SyncML/Backend.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/SyncML/Backend.php Wed Aug 13 21:37:23 2008 +0200 -@@ -574,6 +574,24 @@ - } - - /** -+ * Checks if the entry specified by $cuid has been modified on the server. -+ * -+ * @param string $databaseURI URI of Database to sync. Like -+ * calendar, tasks, contacts or notes. -+ * May include optional parameters: -+ * tasks?options=ignorecompleted. -+ * @param string $cuid Client ID of this entry -+ * @param integer $from_ts Start timestamp. -+ * -+ * @return boolean True if the client entry has been modified on -+ * the server. False otherwise. -+ */ -+ function unchanged($databaseURI, $cuid, $from_ts) -+ { -+ die ("Not implemented!"); -+ } -+ -+ /** - * Authenticates the user at the backend. - * - * For some types of authentications (notably auth:basic) the username -diff -r f7b1e151bdb5 lib/SyncML/Backend/Horde.php ---- a/lib/SyncML/Backend/Horde.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/SyncML/Backend/Horde.php Wed Aug 13 21:37:23 2008 +0200 -@@ -202,6 +202,11 @@ - __FILE__, __LINE__, PEAR_LOG_DEBUG); - continue; - } -+ if ($suid_ts > $to_ts) { -+ // Add delayed to the next sync operation -+ $this->logMessage("Add ignored, lies in sync future: $suid", __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ continue; -+ } - $this->logMessage( - "Adding to client from db $database, server id $suid", - __FILE__, __LINE__, PEAR_LOG_DEBUG); -@@ -250,6 +255,11 @@ - // mirror that back to the client. - $this->logMessage("Changed on server after sent from client: $suid ignored", - __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ continue; -+ } -+ if ($suid_ts > $to_ts) { -+ // Change delayed to the next sync operation -+ $this->logMessage("Change ignored, lies in sync future: $suid", __FILE__, __LINE__, PEAR_LOG_DEBUG); - continue; - } - $cuid = $this->_getCuid($database, $suid); -@@ -311,6 +321,11 @@ - $this->logMessage("Deleted on server after request from client: $suid ignored", - __FILE__, __LINE__, PEAR_LOG_DEBUG); - continue; -+ } -+ if ($suid_ts > $to_ts) { -+ // Delete delayed to the next sync operation -+ $this->logMessage("Delete ignored, lies in sync future: $suid", __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ continue; - } - $cuid = $this->_getCuid($database, $suid); - if (!$cuid) { -@@ -509,6 +524,71 @@ - $this->createUidMap($database, $cuid, $suid, $ts); - } - -+ return true; -+ } -+ -+ /** -+ * Checks if the entry specified by $cuid has been modified on the server. -+ * -+ * @param string $databaseURI URI of Database to sync. Like -+ * calendar, tasks, contacts or notes. -+ * May include optional parameters: -+ * tasks?options=ignorecompleted. -+ * @param string $cuid Client ID of this entry -+ * @param integer $from_ts Last server sync time. -+ * -+ * @return boolean True if the client entry has been modified on -+ * the server. False otherwise. -+ */ -+ function getConflict($databaseURI, $cuid, $server_ts) -+ { -+ global $registry; -+ -+ $database = $this->_normalize($databaseURI); -+ -+ // Only server needs to do a cuid<->suid map -+ if ($this->_backendMode == SYNCML_BACKENDMODE_SERVER) { -+ $suid = $this->_getSuid($database, $cuid); -+ } else { -+ $suid = $cuid; -+ } -+ $this->logMessage("checking modification date of entry suid $suid in $database", __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ -+ if ($suid) { -+ $mod_ts = $registry->call($database . '/getActionTimestamp', array($suid, 'modify', SyncML_Backend::getParameter($databaseURI,'source'))); -+ $client_ts = $this->_getChangeTS($database, $suid); -+ // Check that the last server anchor is smaller than the -+ // last modification stamp (this means that a change -+ // occurred after the last sync started). This might still -+ // be a client change so check that the last client update -+ // also happened before the last modification) -+ if ($server_ts < $mod_ts && $client_ts < $mod_ts) { -+ // Duplicate the server entry -+ $this->logMessage("Conflict detected. Returning conflicting server entry $suid.", __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ $device = &$_SESSION['SyncML.state']->getDevice(); -+ $contentType = $device->getPreferredContentType($databaseURI); -+ return $this->retrieveEntry($databaseURI, $suid, $contentType); -+ } -+ } -+ return false; -+ } -+ -+ function duplicateConflict($databaseURI, $content) -+ { -+ global $registry; -+ -+ $database = $this->_normalize($databaseURI); -+ $device = &$_SESSION['SyncML.state']->getDevice(); -+ $contentType = $device->getPreferredContentType($databaseURI); -+ $content = preg_replace('/(\r\n|\r|\n)UID:.*?(\r\n|\r|\n)/', '\1', $content, 1); -+ $duid = $registry->call($database. '/import', -+ array($content, $contentType, -+ SyncML_Backend::getParameter($databaseURI,'source'))); -+ if (is_a($duid, 'PEAR_Error')) { -+ $this->logMessage("duplicating entry failed.", __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ return false; -+ } -+ $this->logMessage("duplicated server entry to $duid.", __FILE__, __LINE__, PEAR_LOG_DEBUG); - return true; - } - -diff -r f7b1e151bdb5 lib/SyncML/Constants.php ---- a/lib/SyncML/Constants.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/SyncML/Constants.php Wed Aug 13 21:37:23 2008 +0200 -@@ -64,7 +64,7 @@ - define('RESPONSE_PARTIAL_CONTENT', 206); - define('RESPONSE_CONFLICT_RESOLVED_WITH_MERGE', 207); - define('RESPONSE_CONFLICT_RESOLVED_WITH_CLIENT_WINNING', 208); --define('RESPONSE_CONFILCT_RESOLVED_WITH_DUPLICATE', 209); -+define('RESPONSE_CONFLICT_RESOLVED_WITH_DUPLICATE', 209); - define('RESPONSE_DELETE_WITHOUT_ARCHIVE', 210); - define('RESPONSE_ITEM_NO_DELETED', 211); - define('RESPONSE_AUTHENTICATION_ACCEPTED', 212); -diff -r f7b1e151bdb5 lib/SyncML/Device.php ---- a/lib/SyncML/Device.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/SyncML/Device.php Wed Aug 13 21:37:23 2008 +0200 -@@ -161,12 +161,81 @@ - SYNCML_LOGFILE_DATA, - "\nInput received from client ($contentType):\n$content\n"); - -+ $content = $this->_completeEmptyAttributes($content, $contentType); -+ - // Always remove client UID. UID will be seperately passed in XML. - $content = preg_replace('/(\r\n|\r|\n)UID:.*?(\r\n|\r|\n)/', - '\1', $content, 1); - - return array($content, $contentType); - } -+ -+ /** -+ * Complete any attributes that the client supports but did not -+ * provide. In case the user did delete an attribute on the client -+ * this action is required to indicate to the server that the -+ * attribute needs to be deleted. A completely missing attribute -+ * would be considered as "no change" by the server. -+ * -+ * @param string $content The content to convert -+ * @param string $contentType The contentType of the content -+ * -+ * @return string The converted content -+ */ -+ function _completeEmptyAttributes($content, $contentType) -+ { -+ -+ $di = $_SESSION['SyncML.state']->deviceInfo; -+ -+ if (empty($di) || !isset($di->_CTCap) || !isset($di->_CTCap[$contentType])) { -+ return $content; -+ } -+ -+ require_once 'Horde/iCalendar.php'; -+ $iCal = new Horde_iCalendar(); -+ if (!$iCal->parsevCalendar($content)) { -+ // We cant parse the content, return it unchanged -+ return $content; -+ } -+ $components = $iCal->getComponents(); -+ $attributes = $di->_CTCap[$contentType]; -+ -+ foreach ($components as $component) { -+ foreach ($attributes as $name => $properties) { -+ if ($name == 'BEGIN' || $name == 'END') { -+ continue; -+ } -+ $values = $component->getAllAttributes($name); -+ if (!isset($properties->_params)) { -+ if (empty($values)) { -+ // Undefined attribute -> replace it with -+ // the correct empty value -+ $component->setAttributeEmpty($name); -+ } -+ } else { -+ foreach ($properties->_params as $key => $property) { -+ if (!empty($values)) { -+ $present = true; -+ } else { -+ $present = false; -+ foreach ($values as $value) { -+ if (in_array($key, array_keys($value['params']))) { -+ $present = true; -+ break; -+ } -+ } -+ } -+ if (!$present) { -+ // Undefined attribute -> replace it with -+ // the correct empty value -+ $component->setAttributeEmpty($name, array($key => null), true); -+ } -+ } -+ } -+ } -+ } -+ return $iCal->exportvCalendar(); -+ } - - /** - * Converts the content from the backend to a format suitable for the -diff -r f7b1e151bdb5 lib/SyncML/Sync.php ---- a/lib/SyncML/Sync.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/SyncML/Sync.php Wed Aug 13 21:37:23 2008 +0200 -@@ -214,7 +214,13 @@ - } - } elseif ($item->elementType =='Delete') { - /* Handle client delete requests. */ -+ $duplicate = $backend->getConflict($hordedatabase, $cuid, $this->_serverAnchorLast); - $ok = $backend->deleteEntry($database, $cuid); -+ $duplicated = false; -+ if ($duplicate) { -+ $duplicated = $backend->duplicateConflict($hordedatabase, $duplicate); -+ } -+ - if (!$ok && $tasksincalendar) { - $backend->logMessage( - 'Task ' . $cuid . ' deletion sent with calendar request', -@@ -224,8 +230,14 @@ - - if ($ok) { - $this->_client_delete_count++; -- $item->responseCode = RESPONSE_OK; - $backend->logMessage('Deleted entry ' . $suid . ' due to client request', __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ if (!$duplicated) { -+ $item->responseCode = RESPONSE_OK; -+ } else { -+ $this->_client_add_count++; -+ $backend->logMessage('Duplicated entry ' . $suid . ' due to client/server conflict', __FILE__, __LINE__, PEAR_LOG_INFO); -+ $item->responseCode = RESPONSE_CONFLICT_RESOLVED_WITH_DUPLICATE; -+ } - } else { - $this->_errors++; - $item->responseCode = RESPONSE_ITEM_NO_DELETED; -@@ -234,13 +246,24 @@ - - } elseif ($item->elementType == 'Replace') { - /* Handle client replace requests. */ -+ $duplicate = $backend->getConflict($hordedatabase, $cuid, $this->_serverAnchorLast); - $suid = $backend->replaceEntry($hordedatabase, $content, - $contentType, $cuid); -+ $duplicated = false; -+ if ($duplicate) { -+ $duplicated = $backend->duplicateConflict($hordedatabase, $duplicate); -+ } - - if (!is_a($suid, 'PEAR_Error')) { - $this->_client_replace_count++; -- $item->responseCode = RESPONSE_OK; - $backend->logMessage('Replaced entry ' . $suid . ' due to client request', __FILE__, __LINE__, PEAR_LOG_DEBUG); -+ if (!$duplicated) { -+ $item->responseCode = RESPONSE_OK; -+ } else { -+ $this->_client_add_count++; -+ $backend->logMessage('Duplicated entry ' . $suid . ' due to client/server conflict', __FILE__, __LINE__, PEAR_LOG_INFO); -+ $item->responseCode = RESPONSE_CONFLICT_RESOLVED_WITH_DUPLICATE; -+ } - } else { - $backend->logMessage($suid->message, __FILE__, __LINE__, PEAR_LOG_DEBUG); - -diff -r f7b1e151bdb5 lib/core.php ---- a/lib/core.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/lib/core.php Wed Aug 13 21:37:23 2008 +0200 -@@ -31,6 +31,12 @@ - * include_path, you must add an ini_set() call here to add their location to - * the include_path. */ - ini_set('include_path', dirname(__FILE__) . PATH_SEPARATOR . dirname(__FILE__) . '/../pear'); -+ini_set('log_errors', true); -+ini_set('display_errors', '0'); -+ini_set('error_log', dirname(__FILE__) . '/../log/php-errors.log'); -+session_save_path(dirname(__FILE__) . '/../tmp/'); -+ini_set('upload_tmp_dir', dirname(__FILE__) . '/../tmp/'); -+putenv('TMPDIR=' . dirname(__FILE__) . '/../tmp/'); - - /* PEAR base class. */ - include_once 'PEAR.php'; -diff -r f7b1e151bdb5 log/.htaccess ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/log/.htaccess Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,1 @@ -+Deny from All -\ No newline at end of file -diff -r f7b1e151bdb5 mnemo/config/conf.php ---- a/mnemo/config/conf.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/mnemo/config/conf.php Wed Aug 13 21:37:23 2008 +0200 -@@ -1,9 +1,8 @@ - <?php - /* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */ - // $Horde: mnemo/config/conf.xml,v 1.17.10.1 2007/12/20 14:17:38 jan Exp $ --$conf['storage']['params']['table'] = 'mnemo_memos'; --$conf['storage']['params']['driverconfig'] = 'horde'; --$conf['storage']['driver'] = 'sql'; -+$conf['storage']['driver'] = 'kolab'; -+$conf['utils']['gnupg'] = '/usr/bin/gpg'; - $conf['menu']['print'] = true; - $conf['menu']['import_export'] = true; - $conf['menu']['apps'] = array(); -diff -r f7b1e151bdb5 nag/config/conf.php ---- a/nag/config/conf.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/config/conf.php Wed Aug 13 21:37:23 2008 +0200 -@@ -1,9 +1,7 @@ - <?php - /* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */ - // $Horde: nag/config/conf.xml,v 1.25.10.2 2007/12/20 14:23:06 jan Exp $ --$conf['storage']['params']['table'] = 'nag_tasks'; --$conf['storage']['params']['driverconfig'] = 'horde'; --$conf['storage']['driver'] = 'sql'; -+$conf['storage']['driver'] = 'kolab'; - $conf['menu']['print'] = true; - $conf['menu']['import_export'] = true; - $conf['menu']['apps'] = array(); -diff -r f7b1e151bdb5 nag/lib/Block/tree_menu.php ---- a/nag/lib/Block/tree_menu.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/Block/tree_menu.php Wed Aug 13 21:37:23 2008 +0200 -@@ -39,6 +39,15 @@ - array('icon' => 'add.png', - 'icondir' => $icondir, - 'url' => Util::addParameter($add, array('tasklist_id' => $name)))); -+ -+ $tree->addNode($parent . $name, -+ $parent, -+ $tasklist->get('name'), -+ $indent + 1, -+ false, -+ array('icon' => 'nag.png', -+ 'icondir' => $icondir, -+ 'url' => Util::addParameter(Horde::applicationUrl('list.php'), array('tasklist_id' => $name)))); - } - - $tree->addNode($parent . '__search', -diff -r f7b1e151bdb5 nag/lib/Driver.php ---- a/nag/lib/Driver.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/Driver.php Wed Aug 13 21:37:23 2008 +0200 -@@ -208,13 +208,14 @@ - * @param boolean $private Whether the task is private. - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return array array(ID,UID) of new task - */ - function add($name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', $alarm = 0, - $uid = null, $parent = '', $private = false, $owner = null, -- $assignee = null) -+ $assignee = null, $recurrence = null) - { - if (is_null($uid)) { - $uid = $this->generateUID(); -@@ -225,7 +226,7 @@ - - $taskId = $this->_add($name, $desc, $start, $due, $priority, $estimate, - $completed, $category, $alarm, $uid, $parent, -- $private, $owner, $assignee); -+ $private, $owner, $assignee, $recurrence); - if (is_a($taskId, 'PEAR_Error')) { - return $taskId; - } -@@ -273,11 +274,13 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. - */ - function modify($taskId, $name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', - $alarm = 0, $parent = '', $private = false, -- $owner = null, $assignee = null, $completed_date = null) -+ $owner = null, $assignee = null, $completed_date = null, -+ $recurrence = null) - { - /* Retrieve unmodified task. */ - $task = $this->get($taskId); -@@ -293,7 +296,7 @@ - $modify = $this->_modify($taskId, $name, $desc, $start, $due, - $priority, $estimate, $completed, $category, - $alarm, $parent, $private, $owner, $assignee, -- $completed_date); -+ $completed_date, $recurrence); - if (is_a($modify, 'PEAR_Error')) { - return $modify; - } -@@ -546,6 +549,13 @@ - var $private; - - /** -+ * Recurrence information for a periodic task -+ * -+ * @var Nag_Recurrence -+ */ -+ var $recurrence; -+ -+ /** - * URL to view the task. - * - * @var string -@@ -659,6 +669,11 @@ - $key = 'id'; - } elseif ($key == 'parent') { - $key = 'parent_id'; -+ } elseif ($key == 'recurrence' && is_array($val)) { -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ $recurrence = new Nag_Recurrence($task['due']); -+ $recurrence->fromHash($val); -+ $val = $recurrence; - } - $this->$key = $val; - } -@@ -684,7 +699,8 @@ - $this->private, - $this->owner, - $this->assignee, -- $this->completed_date); -+ $this->completed_date, -+ $this->recurrence); - } - - /** -@@ -1012,6 +1028,12 @@ - */ - function toHash() - { -+ if ($this->recurs()) { -+ $recurrence = $this->recurrence->toHash(); -+ } else { -+ $recurrence = null; -+ } -+ - return array('tasklist_id' => $this->tasklist, - 'task_id' => $this->id, - 'uid' => $this->uid, -@@ -1028,7 +1050,8 @@ - 'completed' => $this->completed, - 'completed_date' => $this->completed_date, - 'alarm' => $this->alarm, -- 'private' => $this->private); -+ 'private' => $this->private, -+ 'recurrence' => $recurrence); - } - - /** -@@ -1299,4 +1322,54 @@ - } - } - -+ /** -+ * Returns whether this task is a recurring task. -+ * -+ * @return boolean True if this is a recurring task. -+ */ -+ function recurs() -+ { -+ return isset($this->recurrence) && -+ !$this->recurrence->hasRecurType(NAG_RECUR_NONE); -+ } -+ -+ /** -+ * Toggles completion status of this task. Moves a recurring task -+ * to the next occurence on completion. -+ */ -+ function toggleComplete() -+ { -+ if ($this->completed) { -+ $this->completed_date = null; -+ $this->completed = false; -+ return; -+ } -+ -+ if ($this->recurs()) { -+ /* Get current occurrence (task due date) */ -+ $current = new Horde_Date($this->due); -+ /* Advance this occurence by a day to indicate that we want the -+ * following occurence (Recurrence uses days as minimal time -+ * duration between occurrences). */ -+ $current->mday++; -+ /* Get the next occurence. */ -+ $next = $this->recurrence->nextActiveRecurrence($current); -+ if ($next) { -+ if (!empty($this->start)) { -+ /* The task has a delayed start that should be moved forward -+ * by the same timespan. -+ */ -+ $this->start += $next->timestamp() - $this->due; -+ } -+ $this->due = $next->timestamp(); -+ $this->completed = false; -+ $this->recurrence->addCompletion($next->year, $next->month, -+ $next->mday); -+ return; -+ } -+ } -+ -+ $this->completed_date = time(); -+ $this->completed = true; -+ } - } -diff -r f7b1e151bdb5 nag/lib/Driver/kolab.php ---- a/nag/lib/Driver/kolab.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/Driver/kolab.php Wed Aug 13 21:37:23 2008 +0200 -@@ -114,17 +114,19 @@ - * @param boolean $private Whether the task is private. - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return string The Nag ID of the new task. - */ - function _add($name, $desc, $start = 0, $due = 0, $priority = 0, - $completed = 0, $estimate = 0.0, $category = '', $alarm = 0, - $uid = null, $parent = null, $private = false, $owner = null, -- $assignee = null) -+ $assignee = null, $recurrence = null) - { - return $this->_wrapper->add($name, $desc, $start, $due, $priority, - $completed, $estimate, $category, $alarm, -- $uid, $parent, $private, $owner, $assignee); -+ $uid, $parent, $private, $owner, $assignee, -+ $recurrence); - } - - /** -@@ -145,18 +147,21 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return boolean Indicates if the modification was successfull. - */ - function _modify($taskId, $name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', - $alarm = 0, $parent = null, $private = false, -- $owner = null, $assignee = null, $completed_date = null) -+ $owner = null, $assignee = null, $completed_date = null, -+ $recurrence = null) - { - return $this->_wrapper->modify($taskId, $name, $desc, $start, $due, - $priority, $estimate, $completed, - $category, $alarm, $parent, $private, -- $owner, $assignee, $completed_date); -+ $owner, $assignee, $completed_date, -+ $recurrence); - } - - /** -@@ -271,6 +276,9 @@ - */ - function Nag_Driver_kolab_wrapper($tasklist, &$kolab) - { -+ global $kolab_current_tasklist; -+ $kolab_current_tasklist = null; -+ - $this->_tasklist = $tasklist; - $this->_kolab = &$kolab; - } -@@ -285,7 +293,9 @@ - */ - function connect($loader = 0) - { -- if ($this->_connected) { -+ global $kolab_current_tasklist; -+ -+ if ($kolab_current_tasklist == $this->_tasklist) { - return true; - } - -@@ -294,7 +304,7 @@ - return $result; - } - -- $this->_connected = true; -+ $kolab_current_tasklist = $this->_tasklist; - - return true; - } -@@ -379,13 +389,15 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return string The ID of the task. - */ - function _setObject($name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', - $alarm = 0, $parent = null, $private = false, -- $owner = null, $assignee = null, $completed_date = null) -+ $owner = null, $assignee = null, $completed_date = null, -+ $recurrence = null) - { - if ($due == 0) { - $alarm = 0; -@@ -432,13 +444,14 @@ - * @param boolean $private Whether the task is private. - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return string The Nag ID of the new task. - */ - function add($name, $desc, $start = 0, $due = 0, $priority = 0, - $completed = 0, $estimate = 0.0, $category = '', $alarm = 0, - $uid = null, $parent = null, $private = false, $owner = null, -- $assignee = null) -+ $assignee = null, $recurrence = null) - { - // Usually provided by the generic Driver class - if ($uid !== null) { -@@ -453,7 +466,8 @@ - - return $this->_setObject($name, $desc, $start, $due, $priority, - $completed, $estimate, $category, $alarm, -- $parent, $private, $owner, $assignee); -+ $parent, $private, $owner, $assignee, -+ $recurrence); - } - - /** -@@ -474,6 +488,7 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return boolean Indicates if the modification was successfull. - */ -@@ -481,7 +496,7 @@ - $priority = 0, $estimate = 0.0, $completed = 0, - $category = '', $alarm = 0, $parent = null, - $private = false, $owner = null, $assignee = null, -- $completed_date = null) -+ $completed_date = null, $recurrence = null) - { - // Load the object into the kolab driver - $result = $this->_kolab->loadObject($taskId); -@@ -492,7 +507,7 @@ - $result = $this->_setObject($name, $desc, $start, $due, $priority, - $estimate, $completed, $category, $alarm, - $parent, $private, $owner, $assignee, -- $completed_date); -+ $completed_date, $recurrence); - if (is_a($result, 'PEAR_Error')) { - return $result; - } -@@ -672,7 +687,9 @@ - */ - function connect() - { -- if ($this->_connected) { -+ global $kolab_current_tasklist; -+ -+ if (isset($this->_store) && $kolab_current_tasklist == $this->_tasklist) { - return true; - } - -@@ -687,43 +704,6 @@ - } - - /** -- * Split the tasklist name of the id. We use this to make ids -- * unique across folders. -- * -- * @param string $id The ID of the task appended with the tasklist -- * name. -- * -- * @return array The task id and tasklist name -- */ -- function _splitId($id) -- { -- $split = split('@', $id, 2); -- if (count($split) == 2) { -- list($id, $tasklist) = $split; -- } else if (count($split) == 1) { -- $tasklist = Auth::getAuth(); -- } -- return array($id, $tasklist); -- } -- -- /** -- * Append the tasklist name to the id. We use this to make ids -- * unique across folders. -- * -- * @param string $id The ID of the task -- * -- * @return string The task id appended with the tasklist -- * name. -- */ -- function _uniqueId($id) -- { -- if ($this->_tasklist == Auth::getAuth()) { -- return $id; -- } -- return $id . '@' . $this->_tasklist; -- } -- -- /** - * Retrieves one task from the store. - * - * @param string $taskId The id of the task to retrieve. -@@ -732,7 +712,10 @@ - */ - function get($taskId) - { -- list($taskId, $tasklist) = $this->_splitId($taskId); -+ $result = $this->connect(); -+ if (is_a($result, 'PEAR_Error')) { -+ return $result; -+ } - - if ($this->_store->objectUidExists($taskId)) { - $task = $this->_store->getObject($taskId); -@@ -751,15 +734,24 @@ - */ - function getByUID($uid) - { -- list($taskId, $tasklist) = $this->_splitId($uid); -+ $tasklists = array_keys(Nag::listTasklists(true, PERMS_READ)); - -- if ($this->_tasklist != $tasklist) { -+ foreach ($tasklists as $tasklist) { - $this->_tasklist = $tasklist; -- $this->_connected = false; -- $this->connect(); -+ $result = $this->connect(); -+ if (is_a($result, 'PEAR_Error')) { -+ return $result; -+ } -+ -+ if (!$this->_store->objectUidExists($uid)) { -+ continue; -+ } -+ -+ // Ok, found task -+ return $this->get($uid); - } - -- return $this->get($taskId); -+ return PEAR::raiseError(sprintf(_("Task not found: %s"), $uid)); - } - - /** -@@ -780,6 +772,8 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. -+ * @param boolean $add Are we adding a new task? - * - * @return mixed The id of the task if successful, a PEAR error - * otherwise -@@ -788,18 +782,13 @@ - $estimate = 0.0, $completed = 0, $category = '', - $alarm = 0, $uid = null, $parent = null, - $private = false, $owner = null, $assignee = null, -- $completed_date = null) -+ $completed_date = null, $recurrence = null, -+ $add = false) - { -- if (empty($uid)) { -- $task_uid = $this->_store->generateUID(); -+ if ($add) { - $old_uid = null; - } else { -- list($task_uid, $tasklist) = $this->_splitId($uid); -- $old_uid = $task_uid; -- } -- -- if ($parent) { -- list($parent, $dummy) = $this->_splitId($parent); -+ $old_uid = $uid; - } - - if ($private) { -@@ -808,8 +797,12 @@ - $sensitivity = 'public'; - } - -+ if ($recurrence) { -+ $recurrence = $recurrence->toHash(); -+ } -+ - $result = $this->_store->save(array( -- 'uid' => $task_uid, -+ 'uid' => $uid, - 'name' => $name, - 'body' => $desc, - 'start' => $start, -@@ -817,6 +810,7 @@ - 'priority' => $priority, - 'completed' => $completed, - 'categories' => $category, -+ 'recurrence' => $recurrence, - 'alarm' => $alarm, - 'parent' => $parent, - 'sensitivity' => $sensitivity, -@@ -834,7 +828,7 @@ - return $result; - } - -- return $task_uid; -+ return $uid; - } - - /** -@@ -854,6 +848,7 @@ - * @param boolean $private Whether the task is private. - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return mixed The id of the task if successful, a PEAR error - * otherwise -@@ -861,11 +856,12 @@ - function add($name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', $alarm = 0, - $uid = null, $parent = null, $private = false, $owner = null, -- $assignee = null) -+ $assignee = null, $recurrence = null) - { - return $this->_setObject($name, $desc, $start, $due, $priority, - $estimate, $completed, $category, $alarm, -- null, $parent, $private, $owner, $assignee); -+ $uid, $parent, $private, $owner, $assignee, -+ null, $recurrence, true); - } - - /** -@@ -886,6 +882,7 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return mixed The id of the task if successful, a PEAR error - * otherwise -@@ -893,12 +890,13 @@ - function modify($taskId, $name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', - $alarm = 0, $parent = null, $private = false, -- $owner = null, $assignee = null, $completed_date = null) -+ $owner = null, $assignee = null, $completed_date = null, -+ $recurrence = null) - { - $result = $this->_setObject($name, $desc, $start, $due, $priority, - $estimate, $completed, $category, $alarm, - $taskId, $parent, $private, $owner, $assignee, -- $completed_date); -+ $completed_date, $recurrence); - if (is_a($result, 'PEAR_Error')) { - return $result; - } -@@ -916,8 +914,6 @@ - */ - function move($taskId, $newTasklist) - { -- list($taskId, $tasklist) = $this->_splitId($taskId); -- - return $this->_store->move($taskId, $newTasklist); - } - -@@ -928,8 +924,6 @@ - */ - function delete($taskId) - { -- list($taskId, $tasklist) = $this->_splitId($taskId); -- - return $this->_store->delete($taskId); - } - -@@ -953,6 +947,11 @@ - */ - function retrieve($completed = 1) - { -+ $result = $this->connect(); -+ if (is_a($result, 'PEAR_Error')) { -+ return $result; -+ } -+ - $dict = array(); - $tasks = new Nag_Task(); - -@@ -960,13 +959,14 @@ - if (is_a($task_list, 'PEAR_Error')) { - return $task_list; - } -+ - - if (empty($task_list)) { - return $tasks; - } - - foreach ($task_list as $task) { -- $tuid = $this->_uniqueId($task['uid']); -+ $tuid = $task['uid']; - $t = &new Nag_Task($this->_buildTask($task)); - $complete = $t->completed; - if (empty($t->start)) { -@@ -1014,10 +1014,10 @@ - function _buildTask($task) - { - $task['tasklist_id'] = $this->_tasklist; -- $task['task_id'] = $this->_uniqueId($task['uid']); -+ $task['task_id'] = $task['uid']; - - if (!empty($task['parent'])) { -- $task['parent'] = $this->_uniqueId($task['parent']); -+ $task['parent'] = $task['parent']; - } - - $task['category'] = $task['categories']; -@@ -1032,6 +1032,13 @@ - $task['private'] = true; - } - unset($task['sensitivity']); -+ -+ if (isset($task['recurrence']) && isset($task['due'])) { -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ $recurrence = new Nag_Recurrence($task['due']); -+ $recurrence->fromHash($task['recurrence']); -+ $task['recurrence'] = $recurrence; -+ } - - $share = &$GLOBALS['nag_shares']->getShare($this->_tasklist); - $task['owner'] = $share->get('owner'); -@@ -1048,6 +1055,11 @@ - */ - function listAlarms($date) - { -+ $result = $this->connect(); -+ if (is_a($result, 'PEAR_Error')) { -+ return $result; -+ } -+ - $task_list = $this->_store->getObjects(); - if (is_a($task_list, 'PEAR_Error')) { - return $task_list; -@@ -1059,7 +1071,7 @@ - - $tasks = array(); - foreach ($task_list as $task) { -- $tuid = $this->_uniqueId($task['uid']); -+ $tuid = $task['uid']; - $t = new Nag_Task($this->_buildTask($task)); - if ($t->alarm && $t->due && - $t->due - $t->alarm * 60 < $date) { -@@ -1079,7 +1091,10 @@ - */ - function getChildren($parentId) - { -- list($parentId, $tasklist) = $this->_splitId($parentId); -+ $result = $this->connect(); -+ if (is_a($result, 'PEAR_Error')) { -+ return $result; -+ } - - $task_list = $this->_store->getObjects(); - if (is_a($task_list, 'PEAR_Error')) { -diff -r f7b1e151bdb5 nag/lib/Driver/sql.php ---- a/nag/lib/Driver/sql.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/Driver/sql.php Wed Aug 13 21:37:23 2008 +0200 -@@ -158,13 +158,14 @@ - * @param boolean $private Whether the task is private. - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. -+ * @param Nag_Recurrence $recurrence Task recurrence. - * - * @return string The Nag ID of the new task. - */ - function _add($name, $desc, $start = 0, $due = 0, $priority = 0, - $estimate = 0.0, $completed = 0, $category = '', $alarm = 0, - $uid = null, $parent = '', $private = false, $owner = null, -- $assignee = null) -+ $assignee = null, $recurrence = null) - { - $taskId = md5(uniqid(mt_rand(), true)); - if ($uid === null) { -@@ -229,12 +230,13 @@ - * @param string $owner The owner of the event. - * @param string $assignee The assignee of the event. - * @param integer $completed_date The task's completion date. -+ * @param Nag_Recurrence $recurrence Task recurrence. - */ - function _modify($taskId, $name, $desc, $start = 0, $due = 0, - $priority = 0, $estimate = 0.0, $completed = 0, - $category = '', $alarm = 0, $parent = '', - $private = false, $owner = null, $assignee = null, -- $completed_date = null) -+ $completed_date = null, $recurrence = null) - { - $query = sprintf('UPDATE %s SET' . - ' task_creator = ?, ' . -diff -r f7b1e151bdb5 nag/lib/Forms/task.php ---- a/nag/lib/Forms/task.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/Forms/task.php Wed Aug 13 21:37:23 2008 +0200 -@@ -87,6 +87,7 @@ - - $this->addVariable(_("Private?"), 'private', 'boolean', false); - $this->addVariable(_("Due By"), 'due', 'nag_due', false); -+ - $this->addVariable(_("Delay Start Until"), 'start', 'nag_start', false); - $this->addVariable(_("Alarm"), 'alarm', 'nag_alarm', false); - -@@ -96,6 +97,9 @@ - $this->addVariable(_("Estimated Time"), 'estimate', 'number', false); - $this->addVariable(_("Completed?"), 'completed', 'boolean', false); - $this->addVariable(_("Description"), 'desc', 'longtext', false); -+ -+ $this->addVariable(_("Recurrence"), 'recurrence', 'nag_recurrence', false, false, false); -+ $this->addVariable(_("Recur Until"), 'recur_end', 'nag_recur_end', false, false, false); - - $buttons = array(_("Save")); - if ($delete) { -@@ -214,6 +218,150 @@ - } - - /** -+ * The Horde_Form_Type_nag_recurrence class provides a form field for editing -+ * task recurrences. -+ * -+ * @author Gunnar Wrobel <p@rdus.de> -+ * @since Nag 2.2 -+ * @package Nag -+ */ -+class Horde_Form_Type_nag_recurrence extends Horde_Form_Type { -+ -+ function getInfo(&$vars, &$var, &$info) -+ { -+ $recurrence = $vars->get('recurrence'); -+ if ($recurrence) { -+ $info = $recurrence; -+ return; -+ } -+ -+ // Recurrence. -+ $recur = $vars->get('recur'); -+ if ($recur !== null && $recur !== '') { -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ $recurrence = new Nag_Recurrence(time()); -+ -+ $recurrence->setRecurType($recur); -+ switch ($recur) { -+ case NAG_RECUR_DAILY: -+ $recurrence->setRecurInterval($vars->get('recur_daily_interval', 1)); -+ break; -+ -+ case NAG_RECUR_WEEKLY: -+ $weekly = $vars->get('weekly'); -+ $weekdays = 0; -+ if (is_array($weekly)) { -+ foreach ($weekly as $day) { -+ $weekdays |= $day; -+ } -+ } -+ -+ if ($weekdays == 0) { -+ // Sunday starts at 0. -+ switch ($this->start->dayOfWeek()) { -+ case 0: $weekdays |= HORDE_DATE_MASK_SUNDAY; break; -+ case 1: $weekdays |= HORDE_DATE_MASK_MONDAY; break; -+ case 2: $weekdays |= HORDE_DATE_MASK_TUESDAY; break; -+ case 3: $weekdays |= HORDE_DATE_MASK_WEDNESDAY; break; -+ case 4: $weekdays |= HORDE_DATE_MASK_THURSDAY; break; -+ case 5: $weekdays |= HORDE_DATE_MASK_FRIDAY; break; -+ case 6: $weekdays |= HORDE_DATE_MASK_SATURDAY; break; -+ } -+ } -+ -+ $recurrence->setRecurInterval($vars->get('recur_weekly_interval', 1)); -+ $recurrence->setRecurOnDay($weekdays); -+ break; -+ -+ case NAG_RECUR_MONTHLY_DATE: -+ $recurrence->setRecurInterval($vars->get('recur_day_of_month_interval', 1)); -+ break; -+ -+ case NAG_RECUR_MONTHLY_WEEKDAY: -+ $recurrence->setRecurInterval($vars->get('recur_week_of_month_interval', 1)); -+ break; -+ -+ case NAG_RECUR_YEARLY_DATE: -+ $recurrence->setRecurInterval($vars->get('recur_yearly_interval', 1)); -+ break; -+ -+ case NAG_RECUR_YEARLY_DAY: -+ $recurrence->setRecurInterval($vars->get('recur_yearly_day_interval', 1)); -+ break; -+ -+ case NAG_RECUR_YEARLY_WEEKDAY: -+ $recurrence->setRecurInterval($vars->get('recur_yearly_weekday_interval', 1)); -+ break; -+ } -+ $info = $recurrence->toHash(); -+ } else { -+ $info = array(); -+ } -+ } -+ -+ function isValid(&$var, &$vars, $value, &$message) -+ { -+ return true; -+ } -+ -+} -+ -+/** -+ * The Horde_Form_Type_nag_recur_end class provides a form field for editing -+ * task recurrence ends. -+ * -+ * @author Gunnar Wrobel <p@rdus.de> -+ * @since Nag 2.2 -+ * @package Nag -+ */ -+class Horde_Form_Type_nag_recur_end extends Horde_Form_Type { -+ -+ function getInfo(&$vars, &$var, &$info) -+ { -+ $r = $vars->get('recurrence'); -+ if ($r) { -+ $info = array('range' => $r['range'], -+ 'range-type' => $r['range-type']); -+ return; -+ } -+ -+ // Recurrence. -+ $recur = $vars->get('recur'); -+ if ($recur !== null && $recur !== '') { -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ $recurrence = new Nag_Recurrence(time()); -+ $recurrence->setRecurType(NAG_RECUR_DAILY); -+ if ($vars->get('recur_enddate_type') == 'date') { -+ $recur_enddate = $vars->get('recur_enddate'); -+ $recurrence->setRecurEnd(new Horde_Date( -+ array('hour' => 1, -+ 'min' => 1, -+ 'sec' => 1, -+ 'month' => $recur_enddate['month'], -+ 'mday' => $recur_enddate['day'], -+ 'year' => $recur_enddate['year']))); -+ } elseif ($vars->get('recur_enddate_type') == 'count') { -+ $recurrence->setRecurCount($vars->get('recur_count')); -+ } elseif ($vars->get('recur_enddate_type') == 'none') { -+ $recurrence->setRecurCount(0); -+ $recurrence->setRecurEnd(null); -+ } -+ $r = $recurrence->toHash(); -+ $info = array('range' => $r['range'], -+ 'range-type' => $r['range-type']); -+ } else { -+ $info = array(); -+ } -+ } -+ -+ function isValid(&$var, &$vars, $value, &$message) -+ { -+ return true; -+ } -+ -+} -+ -+/** - * The Horde_Form_Type_nag_start class provides a form field for editing - * task delayed start dates. - * -diff -r f7b1e151bdb5 nag/lib/Recurrence.php ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/nag/lib/Recurrence.php Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,1469 @@ -+<?php -+/** -+ * This file contains the Nag_Recurrence class and according constants. -+ * -+ * $Horde: nag/lib/Recurrence.php,v 1.1 2008/07/13 12:36:03 jan Exp $ -+ * -+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/) -+ * -+ * See the enclosed file COPYING for license information (LGPL). If you -+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. -+ * -+ * @since Horde 3.2 -+ * @package Horde_Date -+ */ -+ -+/** Horde_Date */ -+require_once 'Horde/Date.php'; -+ -+/** Date_Calc */ -+require_once 'Date/Calc.php'; -+ -+/** No recurrence. */ -+define('NAG_RECUR_NONE', 0); -+/** Recurs daily. */ -+define('NAG_RECUR_DAILY', 1); -+/** Recurs weekly. */ -+define('NAG_RECUR_WEEKLY', 2); -+/** Recurs monthly on the same date. */ -+define('NAG_RECUR_MONTHLY_DATE', 3); -+/** Recurs monthly on the same week day. */ -+define('NAG_RECUR_MONTHLY_WEEKDAY', 4); -+/** Recurs yearly on the same date. */ -+define('NAG_RECUR_YEARLY_DATE', 5); -+/** Recurs yearly on the same day of the year. */ -+define('NAG_RECUR_YEARLY_DAY', 6); -+/** Recurs yearly on the same week day. */ -+define('NAG_RECUR_YEARLY_WEEKDAY', 7); -+ -+/** -+ * The Nag_Recurrence class implements algorithms for calculating -+ * recurrences of events, including several recurrence types, intervals, -+ * exceptions, and conversion from and to vCalendar and iCalendar recurrence -+ * rules. -+ * -+ * All methods expecting dates as parameters accept all values that the -+ * Horde_Date constructor accepts, i.e. a timestamp, another Horde_Date -+ * object, an ISO time string or a hash. -+ * -+ * @author Jan Schneider <jan@horde.org> -+ * @since Horde 3.2 -+ * @package Horde_Date -+ */ -+class Nag_Recurrence { -+ -+ /** -+ * The start time of the event. -+ * -+ * @var Horde_Date -+ */ -+ var $start; -+ -+ /** -+ * The end date of the recurrence interval. -+ * -+ * @var Horde_Date -+ */ -+ var $recurEnd = null; -+ -+ /** -+ * The number of recurrences. -+ * -+ * @var integer -+ */ -+ var $recurCount = null; -+ -+ /** -+ * The type of recurrence this event follows. NAG_RECUR_* constant. -+ * -+ * @var integer -+ */ -+ var $recurType = NAG_RECUR_NONE; -+ -+ /** -+ * The length of time between recurrences. The time unit depends on the -+ * recurrence type. -+ * -+ * @var integer -+ */ -+ var $recurInterval = 1; -+ -+ /** -+ * Any additional recurrence data. -+ * -+ * @var integer -+ */ -+ var $recurData = null; -+ -+ /** -+ * All the exceptions from recurrence for this event. -+ * -+ * @var array -+ */ -+ var $exceptions = array(); -+ -+ /** -+ * All the dates this recurrence has been marked as completed. -+ * -+ * @var array -+ */ -+ var $completions = array(); -+ -+ /** -+ * Constructor. -+ * -+ * @param Horde_Date $start Start of the recurring event. -+ */ -+ function Nag_Recurrence($start) -+ { -+ $this->start = new Horde_Date($start); -+ } -+ -+ /** -+ * Checks if this event recurs on a given day of the week. -+ * -+ * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* -+ * constants specifying the day(s) to check. -+ * -+ * @return boolean True if this event recurs on the given day(s). -+ */ -+ function recurOnDay($dayMask) -+ { -+ return ($this->recurData & $dayMask); -+ } -+ -+ /** -+ * Specifies the days this event recurs on. -+ * -+ * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* -+ * constants specifying the day(s) to recur on. -+ */ -+ function setRecurOnDay($dayMask) -+ { -+ $this->recurData = $dayMask; -+ } -+ -+ /** -+ * Returns the days this event recurs on. -+ * -+ * @return integer A mask consisting of HORDE_DATE_MASK_* constants -+ * specifying the day(s) this event recurs on. -+ */ -+ function getRecurOnDays() -+ { -+ return $this->recurData; -+ } -+ -+ /** -+ * Returns whether this event has a specific recurrence type. -+ * -+ * @param integer $recurrence NAG_RECUR_* constant of the -+ * recurrence type to check for. -+ * -+ * @return boolean True if the event has the specified recurrence type. -+ */ -+ function hasRecurType($recurrence) -+ { -+ return ($recurrence == $this->recurType); -+ } -+ -+ /** -+ * Sets a recurrence type for this event. -+ * -+ * @param integer $recurrence A NAG_RECUR_* constant. -+ */ -+ function setRecurType($recurrence) -+ { -+ $this->recurType = $recurrence; -+ } -+ -+ /** -+ * Returns recurrence type of this event. -+ * -+ * @return integer A NAG_RECUR_* constant. -+ */ -+ function getRecurType() -+ { -+ return $this->recurType; -+ } -+ -+ /** -+ * Returns a description of this event's recurring type. -+ * -+ * @return string Human readable recurring type. -+ */ -+ function getRecurName() -+ { -+ switch ($this->getRecurType()) { -+ case NAG_RECUR_NONE: return _("No recurrence"); -+ case NAG_RECUR_DAILY: return _("Daily"); -+ case NAG_RECUR_WEEKLY: return _("Weekly"); -+ case NAG_RECUR_MONTHLY_DATE: -+ case NAG_RECUR_MONTHLY_WEEKDAY: return _("Monthly"); -+ case NAG_RECUR_YEARLY_DATE: -+ case NAG_RECUR_YEARLY_DAY: -+ case NAG_RECUR_YEARLY_WEEKDAY: return _("Yearly"); -+ } -+ } -+ -+ /** -+ * Sets the length of time between recurrences of this event. -+ * -+ * @param integer $interval The time between recurrences. -+ */ -+ function setRecurInterval($interval) -+ { -+ if ($interval > 0) { -+ $this->recurInterval = $interval; -+ } -+ } -+ -+ /** -+ * Retrieves the length of time between recurrences of this event. -+ * -+ * @return integer The number of seconds between recurrences. -+ */ -+ function getRecurInterval() -+ { -+ return $this->recurInterval; -+ } -+ -+ /** -+ * Sets the number of recurrences of this event. -+ * -+ * @param integer $count The number of recurrences. -+ */ -+ function setRecurCount($count) -+ { -+ if ($count > 0) { -+ $this->recurCount = (int)$count; -+ // Recurrence counts and end dates are mutually exclusive. -+ $this->recurEnd = null; -+ } else { -+ $this->recurCount = null; -+ } -+ } -+ -+ /** -+ * Retrieves the number of recurrences of this event. -+ * -+ * @return integer The number recurrences. -+ */ -+ function getRecurCount() -+ { -+ return $this->recurCount; -+ } -+ -+ /** -+ * Returns whether this event has a recurrence with a fixed count. -+ * -+ * @return boolean True if this recurrence has a fixed count. -+ */ -+ function hasRecurCount() -+ { -+ return isset($this->recurCount); -+ } -+ -+ /** -+ * Sets the start date of the recurrence interval. -+ * -+ * @param Horde_Date $start The recurrence start. -+ */ -+ function setRecurStart($start) -+ { -+ $this->start = new Horde_Date($start); -+ } -+ -+ /** -+ * Retrieves the start date of the recurrence interval. -+ * -+ * @return Horde_Date The recurrence start. -+ */ -+ function getRecurStart() -+ { -+ return $this->start; -+ } -+ -+ /** -+ * Sets the end date of the recurrence interval. -+ * -+ * @param Horde_Date $end The recurrence end. -+ */ -+ function setRecurEnd($end) -+ { -+ if (!empty($end)) { -+ // Recurrence counts and end dates are mutually exclusive. -+ $this->recurCount = null; -+ } -+ $this->recurEnd = new Horde_Date($end); -+ } -+ -+ /** -+ * Retrieves the end date of the recurrence interval. -+ * -+ * @return Horde_Date The recurrence end. -+ */ -+ function getRecurEnd() -+ { -+ return $this->recurEnd; -+ } -+ -+ /** -+ * Returns whether this event has a recurrence end. -+ * -+ * @return boolean True if this recurrence ends. -+ */ -+ function hasRecurEnd() -+ { -+ return isset($this->recurEnd) && isset($this->recurEnd->year) && -+ $this->recurEnd->year != 9999; -+ } -+ -+ /** -+ * Finds the next recurrence of this event that's after $afterDate. -+ * -+ * @param Horde_Date $afterDate Return events after this date. -+ * -+ * @return Horde_Date|boolean The date of the next recurrence or false -+ * if the event does not recur after -+ * $afterDate. -+ */ -+ function nextRecurrence($afterDate) -+ { -+ $after = new Horde_Date($afterDate); -+ $after->correct(); -+ -+ if ($this->start->compareDateTime($after) >= 0) { -+ return new Horde_Date($this->start); -+ } -+ -+ if ($this->recurInterval == 0) { -+ return false; -+ } -+ -+ switch ($this->getRecurType()) { -+ case NAG_RECUR_DAILY: -+ $diff = Date_Calc::dateDiff($this->start->mday, $this->start->month, $this->start->year, $after->mday, $after->month, $after->year); -+ $recur = ceil($diff / $this->recurInterval); -+ if ($this->recurCount && $recur >= $this->recurCount) { -+ return false; -+ } -+ $recur *= $this->recurInterval; -+ $next = new Horde_Date($this->start); -+ list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); -+ if ((!$this->hasRecurEnd() || -+ $next->compareDateTime($this->recurEnd) <= 0) && -+ $next->compareDateTime($after) >= 0) { -+ return new Horde_Date($next); -+ } -+ break; -+ -+ case NAG_RECUR_WEEKLY: -+ if (empty($this->recurData)) { -+ return false; -+ } -+ -+ list($start_week->mday, $start_week->month, $start_week->year) = explode('/', Date_Calc::beginOfWeek($this->start->mday, $this->start->month, $this->start->year, '%e/%m/%Y')); -+ $start_week->hour = $this->start->hour; -+ $start_week->min = $this->start->min; -+ $start_week->sec = $this->start->sec; -+ list($after_week->mday, $after_week->month, $after_week->year) = explode('/', Date_Calc::beginOfWeek($after->mday, $after->month, $after->year, '%e/%m/%Y')); -+ $after_week_end = new Horde_Date($after_week); -+ $after_week_end->mday += 7; -+ $after_week_end->correct(); -+ -+ $diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year, -+ $after_week->mday, $after_week->month, $after_week->year); -+ $recur = $diff + ($diff % ($this->recurInterval * 7)); -+ if ($this->recurCount && -+ ceil($recur / 7) / $this->recurInterval >= $this->recurCount) { -+ return false; -+ } -+ $next = $start_week; -+ list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); -+ $next = new Horde_Date($next); -+ while ($next->compareDateTime($after) < 0 && -+ $next->compareDateTime($after_week_end) < 0) { -+ ++$next->mday; -+ $next->correct(); -+ } -+ if (!$this->hasRecurEnd() || -+ $next->compareDateTime($this->recurEnd) <= 0) { -+ if ($next->compareDateTime($after_week_end) >= 0) { -+ return $this->nextRecurrence($after_week_end); -+ } -+ while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) && -+ $next->compareDateTime($after_week_end) < 0) { -+ ++$next->mday; -+ $next->correct(); -+ } -+ if (!$this->hasRecurEnd() || -+ $next->compareDateTime($this->recurEnd) <= 0) { -+ if ($next->compareDateTime($after_week_end) >= 0) { -+ return $this->nextRecurrence($after_week_end); -+ } else { -+ return $next; -+ } -+ } -+ } -+ break; -+ -+ case NAG_RECUR_MONTHLY_DATE: -+ $start = new Horde_Date($this->start); -+ if ($after->compareDateTime($start) < 0) { -+ $after = $start; -+ } -+ -+ // If we're starting past this month's recurrence of the event, -+ // look in the next month on the day the event recurs. -+ if ($after->mday > $start->mday) { -+ ++$after->month; -+ $after->mday = $start->mday; -+ $after->correct(); -+ } -+ -+ // Adjust $start to be the first match. -+ $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12; -+ $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; -+ -+ if ($this->recurCount && -+ ($offset / $this->recurInterval) >= $this->recurCount) { -+ return false; -+ } -+ $start->month += $offset; -+ $count = $offset / $this->recurInterval; -+ -+ do { -+ if ($this->recurCount && -+ $count++ >= $this->recurCount) { -+ return false; -+ } -+ -+ // Don't correct for day overflow; we just skip February 30th, -+ // for example. -+ $start->correct(HORDE_DATE_MASK_MONTH); -+ -+ // Bail if we've gone past the end of recurrence. -+ if ($this->hasRecurEnd() && -+ $this->recurEnd->compareDateTime($start) < 0) { -+ return false; -+ } -+ if ($start->isValid()) { -+ return $start; -+ } -+ -+ // If the interval is 12, and the date isn't valid, then we -+ // need to see if February 29th is an option. If not, then the -+ // event will _never_ recur, and we need to stop checking to -+ // avoid an infinite loop. -+ if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) { -+ return false; -+ } -+ -+ // Add the recurrence interval. -+ $start->month += $this->recurInterval; -+ } while (true); -+ -+ break; -+ -+ case NAG_RECUR_MONTHLY_WEEKDAY: -+ // Start with the start date of the event. -+ $estart = new Horde_Date($this->start); -+ -+ // What day of the week, and week of the month, do we recur on? -+ $nth = ceil($this->start->mday / 7); -+ $weekday = $estart->dayOfWeek(); -+ -+ // Adjust $estart to be the first candidate. -+ $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; -+ $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; -+ -+ // Adjust our working date until it's after $after. -+ $estart->month += $offset - $this->recurInterval; -+ -+ $count = $offset / $this->recurInterval; -+ do { -+ if ($this->recurCount && -+ $count++ >= $this->recurCount) { -+ return false; -+ } -+ -+ $estart->month += $this->recurInterval; -+ $estart->correct(); -+ -+ $next = new Horde_Date($estart); -+ $next->setNthWeekday($weekday, $nth); -+ -+ if ($next->compareDateTime($after) < 0) { -+ // We haven't made it past $after yet, try again. -+ continue; -+ } -+ if ($this->hasRecurEnd() && -+ $next->compareDateTime($this->recurEnd) > 0) { -+ // We've gone past the end of recurrence; we can give up -+ // now. -+ return false; -+ } -+ -+ // We have a candidate to return. -+ break; -+ } while (true); -+ -+ return $next; -+ -+ case NAG_RECUR_YEARLY_DATE: -+ // Start with the start date of the event. -+ $estart = new Horde_Date($this->start); -+ -+ if ($after->month > $estart->month || -+ ($after->month == $estart->month && $after->mday > $estart->mday)) { -+ ++$after->year; -+ $after->month = $estart->month; -+ $after->mday = $estart->mday; -+ } -+ -+ // Seperate case here for February 29th -+ if ($estart->month == 2 && $estart->mday == 29) { -+ while (!Horde_Date::isLeapYear($after->year)) { -+ ++$after->year; -+ } -+ } -+ -+ // Adjust $estart to be the first candidate. -+ $offset = $after->year - $estart->year; -+ if ($offset > 0) { -+ $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; -+ $estart->year += $offset; -+ } -+ -+ // We've gone past the end of recurrence; give up. -+ if ($this->recurCount && -+ $offset >= $this->recurCount) { -+ return false; -+ } -+ if ($this->hasRecurEnd() && -+ $this->recurEnd->compareDateTime($estart) < 0) { -+ return false; -+ } -+ -+ return $estart; -+ -+ case NAG_RECUR_YEARLY_DAY: -+ // Check count first. -+ $dayofyear = $this->start->dayOfYear(); -+ $count = ($after->year - $this->start->year) / $this->recurInterval + 1; -+ if ($this->recurCount && -+ ($count > $this->recurCount || -+ ($count == $this->recurCount && -+ $after->dayOfYear() > $dayofyear))) { -+ return false; -+ } -+ -+ // Start with a rough interval. -+ $estart = new Horde_Date($this->start); -+ $estart->year += floor($count - 1) * $this->recurInterval; -+ -+ // Now add the difference to the required day of year. -+ $estart->mday += $dayofyear - $estart->dayOfYear(); -+ $estart->correct(); -+ -+ // Add an interval if the estimation was wrong. -+ if ($estart->compareDate($after) < 0) { -+ $estart->year += $this->recurInterval; -+ $estart->mday += $dayofyear - $estart->dayOfYear(); -+ $estart->correct(); -+ } -+ -+ // We've gone past the end of recurrence; give up. -+ if ($this->hasRecurEnd() && -+ $this->recurEnd->compareDateTime($estart) < 0) { -+ return false; -+ } -+ -+ return $estart; -+ -+ case NAG_RECUR_YEARLY_WEEKDAY: -+ // Start with the start date of the event. -+ $estart = new Horde_Date($this->start); -+ -+ // What day of the week, and week of the month, do we recur on? -+ $nth = ceil($this->start->mday / 7); -+ $weekday = $estart->dayOfWeek(); -+ -+ // Adjust $estart to be the first candidate. -+ $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; -+ -+ // Adjust our working date until it's after $after. -+ $estart->year += $offset - $this->recurInterval; -+ -+ $count = $offset / $this->recurInterval; -+ do { -+ if ($this->recurCount && -+ $count++ >= $this->recurCount) { -+ return false; -+ } -+ -+ $estart->year += $this->recurInterval; -+ $estart->correct(); -+ -+ $next = new Horde_Date($estart); -+ $next->setNthWeekday($weekday, $nth); -+ -+ if ($next->compareDateTime($after) < 0) { -+ // We haven't made it past $after yet, try again. -+ continue; -+ } -+ if ($this->hasRecurEnd() && -+ $next->compareDateTime($this->recurEnd) > 0) { -+ // We've gone past the end of recurrence; we can give up -+ // now. -+ return false; -+ } -+ -+ // We have a candidate to return. -+ break; -+ } while (true); -+ -+ return $next; -+ } -+ -+ // We didn't find anything, the recurType was bad, or something else -+ // went wrong - return false. -+ return false; -+ } -+ -+ /** -+ * Returns whether this event has any date that matches the recurrence -+ * rules and is not an exception. -+ * -+ * @return boolean True if an active recurrence exists. -+ */ -+ function hasActiveRecurrence() -+ { -+ if (!$this->hasRecurEnd()) { -+ return true; -+ } -+ -+ $next = $this->nextRecurrence(new Horde_Date($this->start)); -+ while (is_object($next)) { -+ if (!$this->hasException($next->year, $next->month, $next->mday) && -+ !$this->hasCompletion($next->year, $next->month, $next->mday)) { -+ return true; -+ } -+ -+ $next = $this->nextRecurrence(array('year' => $next->year, -+ 'month' => $next->month, -+ 'mday' => $next->mday + 1, -+ 'hour' => $next->hour, -+ 'min' => $next->min, -+ 'sec' => $next->sec)); -+ } -+ -+ return false; -+ } -+ -+ /** -+ * Returns the next active recurrence. -+ * -+ * @param Horde_Date $afterDate Return events after this date. -+ * -+ * @return Horde_Date|boolean The date of the next active -+ * recurrence or false if the event -+ * has no active recurrence after -+ * $afterDate. -+ */ -+ function nextActiveRecurrence($afterDate) -+ { -+ $next = $this->nextRecurrence($afterDate); -+ while (is_object($next)) { -+ if (!$this->hasException($next->year, $next->month, $next->mday) && -+ !$this->hasCompletion($next->year, $next->month, $next->mday)) { -+ return $next; -+ } -+ $next->mday++; -+ $next = $this->nextRecurrence($next); -+ } -+ -+ return false; -+ } -+ -+ /** -+ * Adds an exception to a recurring event. -+ * -+ * @param integer $year The year of the execption. -+ * @param integer $month The month of the execption. -+ * @param integer $mday The day of the month of the exception. -+ */ -+ function addException($year, $month, $mday) -+ { -+ $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday); -+ } -+ -+ /** -+ * Deletes an exception from a recurring event. -+ * -+ * @param integer $year The year of the execption. -+ * @param integer $month The month of the execption. -+ * @param integer $mday The day of the month of the exception. -+ */ -+ function deleteException($year, $month, $mday) -+ { -+ $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions); -+ if ($key !== false) { -+ unset($this->exceptions[$key]); -+ } -+ } -+ -+ /** -+ * Checks if an exception exists for a given reccurence of an event. -+ * -+ * @param integer $year The year of the reucrance. -+ * @param integer $month The month of the reucrance. -+ * @param integer $mday The day of the month of the reucrance. -+ * -+ * @return boolean True if an exception exists for the given date. -+ */ -+ function hasException($year, $month, $mday) -+ { -+ return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), -+ $this->getExceptions()); -+ } -+ -+ /** -+ * Retrieves all the exceptions for this event. -+ * -+ * @return array Array containing the dates of all the exceptions in -+ * YYYYMMDD form. -+ */ -+ function getExceptions() -+ { -+ return $this->exceptions; -+ } -+ -+ /** -+ * Adds an completion to a recurring event. -+ * -+ * @param integer $year The year of the execption. -+ * @param integer $month The month of the execption. -+ * @param integer $mday The day of the month of the completion. -+ */ -+ function addCompletion($year, $month, $mday) -+ { -+ $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday); -+ } -+ -+ /** -+ * Deletes an completion from a recurring event. -+ * -+ * @param integer $year The year of the execption. -+ * @param integer $month The month of the execption. -+ * @param integer $mday The day of the month of the completion. -+ */ -+ function deleteCompletion($year, $month, $mday) -+ { -+ $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions); -+ if ($key !== false) { -+ unset($this->completions[$key]); -+ } -+ } -+ -+ /** -+ * Checks if a completion exists for a given reccurence of an event. -+ * -+ * @param integer $year The year of the reucrance. -+ * @param integer $month The month of the recurrance. -+ * @param integer $mday The day of the month of the recurrance. -+ * -+ * @return boolean True if an completion exists for the given date. -+ */ -+ function hasCompletion($year, $month, $mday) -+ { -+ return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), -+ $this->getCompletions()); -+ } -+ -+ /** -+ * Retrieves all the completions for this event. -+ * -+ * @return array Array containing the dates of all the completions in -+ * YYYYMMDD form. -+ */ -+ function getCompletions() -+ { -+ return $this->completions; -+ } -+ -+ /** -+ * Parses a vCalendar 1.0 recurrence rule. -+ * -+ * @link http://www.imc.org/pdi/vcal-10.txt -+ * @link http://www.shuchow.com/vCalAddendum.html -+ * -+ * @param string $rrule A vCalendar 1.0 conform RRULE value. -+ */ -+ function fromRRule10($rrule) -+ { -+ if (!$rrule) { -+ return; -+ } -+ -+ if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) { -+ // No recurrence data - event does not recur. -+ $this->setRecurType(NAG_RECUR_NONE); -+ } -+ -+ // Always default the recurInterval to 1. -+ $this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1); -+ -+ $remainder = trim($matches[3]); -+ -+ switch ($matches[1]) { -+ case 'D': -+ $this->setRecurType(NAG_RECUR_DAILY); -+ break; -+ -+ case 'W': -+ $this->setRecurType(NAG_RECUR_WEEKLY); -+ if (!empty($remainder)) { -+ $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, -+ 'MO' => HORDE_DATE_MASK_MONDAY, -+ 'TU' => HORDE_DATE_MASK_TUESDAY, -+ 'WE' => HORDE_DATE_MASK_WEDNESDAY, -+ 'TH' => HORDE_DATE_MASK_THURSDAY, -+ 'FR' => HORDE_DATE_MASK_FRIDAY, -+ 'SA' => HORDE_DATE_MASK_SATURDAY); -+ $mask = 0; -+ while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) { -+ $day = trim($matches[0]); -+ $remainder = substr($remainder, strlen($matches[0])); -+ $mask |= $maskdays[$day]; -+ } -+ $this->setRecurOnDay($mask); -+ } else { -+ // Recur on the day of the week of the original recurrence. -+ $maskdays = array(HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, -+ HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, -+ HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, -+ HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, -+ HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, -+ HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, -+ HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); -+ $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); -+ } -+ break; -+ -+ case 'MP': -+ $this->setRecurType(NAG_RECUR_MONTHLY_WEEKDAY); -+ break; -+ -+ case 'MD': -+ $this->setRecurType(NAG_RECUR_MONTHLY_DATE); -+ break; -+ -+ case 'YM': -+ $this->setRecurType(NAG_RECUR_YEARLY_DATE); -+ break; -+ -+ case 'YD': -+ $this->setRecurType(NAG_RECUR_YEARLY_DAY); -+ break; -+ } -+ -+ // We don't support modifiers at the moment, strip them. -+ while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) { -+ $remainder = substr($remainder, 1); -+ } -+ if (!empty($remainder)) { -+ if (strpos($remainder, '#') !== false) { -+ $this->setRecurCount(substr($remainder, 1)); -+ } else { -+ list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d'); -+ $this->setRecurEnd(new Horde_Date(array('year' => $year, -+ 'month' => $month, -+ 'mday' => $mday))); -+ } -+ } -+ } -+ -+ /** -+ * Creates a vCalendar 1.0 recurrence rule. -+ * -+ * @link http://www.imc.org/pdi/vcal-10.txt -+ * @link http://www.shuchow.com/vCalAddendum.html -+ * -+ * @param Horde_iCalendar $calendar A Horde_iCalendar object instance. -+ * -+ * @return string A vCalendar 1.0 conform RRULE value. -+ */ -+ function toRRule10($calendar) -+ { -+ switch ($this->recurType) { -+ case NAG_RECUR_NONE: -+ return ''; -+ -+ case NAG_RECUR_DAILY: -+ $rrule = 'D' . $this->recurInterval; -+ break; -+ -+ case NAG_RECUR_WEEKLY: -+ $rrule = 'W' . $this->recurInterval; -+ $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); -+ -+ for ($i = 0; $i <= 7 ; ++$i) { -+ if ($this->recurOnDay(pow(2, $i))) { -+ $rrule .= ' ' . $vcaldays[$i]; -+ } -+ } -+ break; -+ -+ case NAG_RECUR_MONTHLY_DATE: -+ $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday); -+ break; -+ -+ case NAG_RECUR_MONTHLY_WEEKDAY: -+ $next_week = new Horde_Date($this->start); -+ $next_week->mday += 7; -+ $next_week->correct(); -+ -+ if ($this->start->month != $next_week->month) { -+ $p = 5; -+ } else { -+ $p = (int)($this->start->mday / 7); -+ if (($this->start->mday % 7) > 0) { -+ $p++; -+ } -+ } -+ -+ $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); -+ $rrule = 'MP' . $this->recurInterval . ' ' . $p . '+ ' . $vcaldays[$this->start->dayOfWeek()]; -+ break; -+ -+ case NAG_RECUR_YEARLY_DATE: -+ $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month); -+ break; -+ -+ case NAG_RECUR_YEARLY_DAY: -+ $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear(); -+ break; -+ -+ default: -+ return ''; -+ } -+ -+ return $this->hasRecurEnd() ? -+ $rrule . ' ' . $calendar->_exportDate($this->recurEnd) : -+ $rrule . ' #' . (int)$this->getRecurCount(); -+ } -+ -+ /** -+ * Parses an iCalendar 2.0 recurrence rule. -+ * -+ * @link http://rfc.net/rfc2445.html#s4.8.5 -+ * @link http://www.shuchow.com/vCalAddendum.html -+ * -+ * @param string $rrule An iCalendar 2.0 conform RRULE value. -+ */ -+ function fromRRule20($rrule) -+ { -+ // Parse the recurrence rule into keys and values. -+ $rdata = array(); -+ $parts = explode(';', $rrule); -+ foreach ($parts as $part) { -+ list($key, $value) = explode('=', $part, 2); -+ $rdata[String::upper($key)] = $value; -+ } -+ -+ if (isset($rdata['FREQ'])) { -+ // Always default the recurInterval to 1. -+ $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); -+ -+ switch (String::upper($rdata['FREQ'])) { -+ case 'DAILY': -+ $this->setRecurType(NAG_RECUR_DAILY); -+ break; -+ -+ case 'WEEKLY': -+ $this->setRecurType(NAG_RECUR_WEEKLY); -+ if (isset($rdata['BYDAY'])) { -+ $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, -+ 'MO' => HORDE_DATE_MASK_MONDAY, -+ 'TU' => HORDE_DATE_MASK_TUESDAY, -+ 'WE' => HORDE_DATE_MASK_WEDNESDAY, -+ 'TH' => HORDE_DATE_MASK_THURSDAY, -+ 'FR' => HORDE_DATE_MASK_FRIDAY, -+ 'SA' => HORDE_DATE_MASK_SATURDAY); -+ $days = explode(',', $rdata['BYDAY']); -+ $mask = 0; -+ foreach ($days as $day) { -+ $mask |= $maskdays[$day]; -+ } -+ $this->setRecurOnDay($mask); -+ } else { -+ // Recur on the day of the week of the original -+ // recurrence. -+ $maskdays = array( -+ HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, -+ HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, -+ HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, -+ HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, -+ HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, -+ HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, -+ HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); -+ $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); -+ } -+ break; -+ -+ case 'MONTHLY': -+ if (isset($rdata['BYDAY'])) { -+ $this->setRecurType(NAG_RECUR_MONTHLY_WEEKDAY); -+ } else { -+ $this->setRecurType(NAG_RECUR_MONTHLY_DATE); -+ } -+ break; -+ -+ case 'YEARLY': -+ if (isset($rdata['BYYEARDAY'])) { -+ $this->setRecurType(NAG_RECUR_YEARLY_DAY); -+ } elseif (isset($rdata['BYDAY'])) { -+ $this->setRecurType(NAG_RECUR_YEARLY_WEEKDAY); -+ } else { -+ $this->setRecurType(NAG_RECUR_YEARLY_DATE); -+ } -+ break; -+ } -+ -+ if (isset($rdata['UNTIL'])) { -+ list($year, $month, $mday) = sscanf($rdata['UNTIL'], -+ '%04d%02d%02d'); -+ $this->setRecurEnd(new Horde_Date(array('year' => $year, -+ 'month' => $month, -+ 'mday' => $mday))); -+ } -+ if (isset($rdata['COUNT'])) { -+ $this->setRecurCount($rdata['COUNT']); -+ } -+ } else { -+ // No recurrence data - event does not recur. -+ $this->setRecurType(NAG_RECUR_NONE); -+ } -+ } -+ -+ /** -+ * Creates an iCalendar 2.0 recurrence rule. -+ * -+ * @link http://rfc.net/rfc2445.html#s4.8.5 -+ * @link http://www.shuchow.com/vCalAddendum.html -+ * -+ * @param Horde_iCalendar $calendar A Horde_iCalendar object instance. -+ * -+ * @return string An iCalendar 2.0 conform RRULE value. -+ */ -+ function toRRule20($calendar) -+ { -+ switch ($this->recurType) { -+ case NAG_RECUR_NONE: -+ return ''; -+ -+ case NAG_RECUR_DAILY: -+ $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; -+ break; -+ -+ case NAG_RECUR_WEEKLY: -+ $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY='; -+ $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); -+ -+ for ($i = $flag = 0; $i <= 7 ; ++$i) { -+ if ($this->recurOnDay(pow(2, $i))) { -+ if ($flag) { -+ $rrule .= ','; -+ } -+ $rrule .= $vcaldays[$i]; -+ $flag = true; -+ } -+ } -+ break; -+ -+ case NAG_RECUR_MONTHLY_DATE: -+ $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval; -+ break; -+ -+ case NAG_RECUR_MONTHLY_WEEKDAY: -+ $next_week = new Horde_Date($this->start); -+ $next_week->mday += 7; -+ $next_week->correct(); -+ if ($this->start->month != $next_week->month) { -+ $p = 5; -+ } else { -+ $p = (int)($this->start->mday / 7); -+ if (($this->start->mday % 7) > 0) { -+ $p++; -+ } -+ } -+ $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); -+ $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval -+ . ';BYDAY=' . $p . $vcaldays[$this->start->dayOfWeek()]; -+ break; -+ -+ case NAG_RECUR_YEARLY_DATE: -+ $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; -+ break; -+ -+ case NAG_RECUR_YEARLY_DAY: -+ $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval -+ . ';BYYEARDAY=' . $this->start->dayOfYear(); -+ break; -+ -+ case NAG_RECUR_YEARLY_WEEKDAY: -+ $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); -+ $weekday = date('W', mktime(0, -+ 0, -+ 0, -+ $this->start->month, -+ 1, -+ $this->start->year)); -+ $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval -+ . ';BYDAY=' . ($this->start->weekOfYear() - $weekday + 1) -+ . $vcaldays[$this->start->dayOfWeek()] -+ . ';BYMONTH=' . $this->start->month; -+ break; -+ } -+ -+ if ($this->hasRecurEnd()) { -+ $rrule .= ';UNTIL=' . $calendar->_exportDate($this->recurEnd); -+ } -+ if ($count = $this->getRecurCount()) { -+ $rrule .= ';COUNT=' . $count; -+ } -+ return $rrule; -+ } -+ -+ /** -+ * Parses the recurrence data from a hash. -+ * -+ * @param array $hash The hash to convert. -+ * -+ * @return boolean True if the hash seemed valid, false otherwise. -+ */ -+ function fromHash($hash) -+ { -+ if (!isset($hash['interval']) || !isset($hash['interval']) || -+ !isset($hash['range-type'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ $this->setRecurInterval((int) $hash['interval']); -+ -+ $parse_day = false; -+ $set_daymask = false; -+ $update_month = false; -+ $update_daynumber = false; -+ $update_weekday = false; -+ $nth_weekday = -1; -+ -+ switch ($hash['cycle']) { -+ case 'daily': -+ $this->setRecurType(NAG_RECUR_DAILY); -+ break; -+ -+ case 'weekly': -+ $this->setRecurType(NAG_RECUR_WEEKLY); -+ $parse_day = true; -+ $set_daymask = true; -+ break; -+ -+ case 'monthly': -+ if (!isset($hash['daynumber'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ switch ($hash['type']) { -+ case 'daynumber': -+ $this->setRecurType(NAG_RECUR_MONTHLY_DATE); -+ $update_daynumber = true; -+ break; -+ -+ case 'weekday': -+ $this->setRecurType(NAG_RECUR_MONTHLY_WEEKDAY); -+ $nth_weekday = (int) $hash['daynumber']; -+ $hash['daynumber'] = 1; -+ $parse_day = true; -+ $update_daynumber = true; -+ $update_weekday = true; -+ break; -+ } -+ break; -+ -+ case 'yearly': -+ if (!isset($hash['type'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ switch ($hash['type']) { -+ case 'monthday': -+ $this->setRecurType(NAG_RECUR_YEARLY_DATE); -+ $update_month = true; -+ $update_daynumber = true; -+ break; -+ -+ case 'yearday': -+ if (!isset($hash['month'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ $this->setRecurType(NAG_RECUR_YEARLY_DAY); -+ // Start counting days in January. -+ $hash['month'] = 'january'; -+ $update_month = true; -+ $update_daynumber = true; -+ break; -+ -+ case 'weekday': -+ if (!isset($hash['daynumber'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ $this->setRecurType(NAG_RECUR_YEARLY_WEEKDAY); -+ $nth_weekday = (int) $hash['daynumber']; -+ $hash['daynumber'] = 1; -+ $parse_day = true; -+ $update_month = true; -+ $update_daynumber = true; -+ $update_weekday = true; -+ break; -+ } -+ } -+ -+ switch ($hash['range-type']) { -+ case 'number': -+ if (!isset($hash['range'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ $this->setRecurCount((int) $hash['range']); -+ break; -+ -+ case 'date': -+ $recur_end = new Horde_Date($hash['range']); -+ $recur_end->hour = 23; -+ $recur_end->min = 59; -+ $recur_end->sec = 59; -+ $this->setRecurEnd($recur_end); -+ break; -+ } -+ -+ // Need to parse <day>? -+ $last_found_day = -1; -+ if ($parse_day) { -+ if (!isset($hash['day'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ $mask = 0; -+ $bits = array( -+ 'monday' => HORDE_DATE_MASK_MONDAY, -+ 'tuesday' => HORDE_DATE_MASK_TUESDAY, -+ 'wednesday' => HORDE_DATE_MASK_WEDNESDAY, -+ 'thursday' => HORDE_DATE_MASK_THURSDAY, -+ 'friday' => HORDE_DATE_MASK_FRIDAY, -+ 'saturday' => HORDE_DATE_MASK_SATURDAY, -+ 'sunday' => HORDE_DATE_MASK_SUNDAY, -+ ); -+ $days = array( -+ 'monday' => HORDE_DATE_MONDAY, -+ 'tuesday' => HORDE_DATE_TUESDAY, -+ 'wednesday' => HORDE_DATE_WEDNESDAY, -+ 'thursday' => HORDE_DATE_THURSDAY, -+ 'friday' => HORDE_DATE_FRIDAY, -+ 'saturday' => HORDE_DATE_SATURDAY, -+ 'sunday' => HORDE_DATE_SUNDAY, -+ ); -+ -+ foreach ($hash['day'] as $day) { -+ // Validity check. -+ if (empty($day) || !isset($bits[$day])) { -+ continue; -+ } -+ -+ $mask |= $bits[$day]; -+ $last_found_day = $days[$day]; -+ } -+ -+ if ($set_daymask) { -+ $this->setRecurOnDay($mask); -+ } -+ } -+ -+ if ($update_month || $update_daynumber || $update_weekday) { -+ if ($update_month) { -+ $month2number = array( -+ 'january' => 1, -+ 'february' => 2, -+ 'march' => 3, -+ 'april' => 4, -+ 'may' => 5, -+ 'june' => 6, -+ 'july' => 7, -+ 'august' => 8, -+ 'september' => 9, -+ 'october' => 10, -+ 'november' => 11, -+ 'december' => 12, -+ ); -+ -+ if (isset($month2number[$hash['month']])) { -+ $this->start->month = $month2number[$hash['month']]; -+ } -+ } -+ -+ if ($update_daynumber) { -+ if (!isset($hash['daynumber'])) { -+ $this->setRecurType(NAG_RECUR_NONE); -+ return false; -+ } -+ -+ $this->start->mday = $hash['daynumber']; -+ } -+ -+ if ($update_weekday) { -+ $this->start->setNthWeekday($last_found_day, $nth_weekday); -+ } -+ -+ $this->start->correct(); -+ } -+ -+ // Exceptions. -+ if (isset($hash['exceptions'])) { -+ $this->exceptions = $hash['exceptions']; -+ } -+ -+ if (isset($hash['completions'])) { -+ $this->completions = $hash['completions']; -+ } -+ -+ return true; -+ } -+ -+ /** -+ * Export this object into a hash. -+ * -+ * @return array The recurrence hash. -+ */ -+ function toHash() -+ { -+ if ($this->getRecurType() == NAG_RECUR_NONE) { -+ return array(); -+ } -+ -+ $day2number = array( -+ 0 => 'sunday', -+ 1 => 'monday', -+ 2 => 'tuesday', -+ 3 => 'wednesday', -+ 4 => 'thursday', -+ 5 => 'friday', -+ 6 => 'saturday' -+ ); -+ $month2number = array( -+ 1 => 'january', -+ 2 => 'february', -+ 3 => 'march', -+ 4 => 'april', -+ 5 => 'may', -+ 6 => 'june', -+ 7 => 'july', -+ 8 => 'august', -+ 9 => 'september', -+ 10 => 'october', -+ 11 => 'november', -+ 12 => 'december' -+ ); -+ -+ $hash = array('interval' => $this->getRecurInterval()); -+ $start = $this->getRecurStart(); -+ -+ switch ($this->getRecurType()) { -+ case NAG_RECUR_DAILY: -+ $hash['cycle'] = 'daily'; -+ break; -+ -+ case NAG_RECUR_WEEKLY: -+ $hash['cycle'] = 'weekly'; -+ $bits = array( -+ 'monday' => HORDE_DATE_MASK_MONDAY, -+ 'tuesday' => HORDE_DATE_MASK_TUESDAY, -+ 'wednesday' => HORDE_DATE_MASK_WEDNESDAY, -+ 'thursday' => HORDE_DATE_MASK_THURSDAY, -+ 'friday' => HORDE_DATE_MASK_FRIDAY, -+ 'saturday' => HORDE_DATE_MASK_SATURDAY, -+ 'sunday' => HORDE_DATE_MASK_SUNDAY, -+ ); -+ $days = array(); -+ foreach($bits as $name => $bit) { -+ if ($this->recurOnDay($bit)) { -+ $days[] = $name; -+ } -+ } -+ $hash['day'] = $days; -+ break; -+ -+ case NAG_RECUR_MONTHLY_DATE: -+ $hash['cycle'] = 'monthly'; -+ $hash['type'] = 'daynumber'; -+ $hash['daynumber'] = $start->mday; -+ break; -+ -+ case NAG_RECUR_MONTHLY_WEEKDAY: -+ $hash['cycle'] = 'monthly'; -+ $hash['type'] = 'weekday'; -+ $hash['daynumber'] = $start->weekOfMonth(); -+ $hash['day'] = array ($day2number[$start->dayOfWeek()]); -+ break; -+ -+ case NAG_RECUR_YEARLY_DATE: -+ $hash['cycle'] = 'yearly'; -+ $hash['type'] = 'monthday'; -+ $hash['daynumber'] = $start->mday; -+ $hash['month'] = $month2number[$start->month]; -+ break; -+ -+ case NAG_RECUR_YEARLY_DAY: -+ $hash['cycle'] = 'yearly'; -+ $hash['type'] = 'yearday'; -+ $hash['daynumber'] = $start->dayOfYear(); -+ break; -+ -+ case NAG_RECUR_YEARLY_WEEKDAY: -+ $hash['cycle'] = 'yearly'; -+ $hash['type'] = 'weekday'; -+ $hash['daynumber'] = $start->weekOfMonth(); -+ $hash['day'] = array ($day2number[$start->dayOfWeek()]); -+ $hash['month'] = $month2number[$start->month]; -+ } -+ -+ if ($this->hasRecurCount()) { -+ $hash['range-type'] = 'number'; -+ $hash['range'] = $this->getRecurCount(); -+ } elseif ($this->hasRecurEnd()) { -+ $date = $this->getRecurEnd(); -+ $hash['range-type'] = 'date'; -+ $hash['range'] = $date->datestamp(); -+ } else { -+ $hash['range-type'] = 'none'; -+ $hash['range'] = ''; -+ } -+ -+ // Recurrence exceptions -+ $hash['exceptions'] = $this->exceptions; -+ $hash['completions'] = $this->completions; -+ -+ return $hash; -+ } -+ -+} -diff -r f7b1e151bdb5 nag/lib/UI/VarRenderer/nag.php ---- a/nag/lib/UI/VarRenderer/nag.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/UI/VarRenderer/nag.php Wed Aug 13 21:37:23 2008 +0200 -@@ -26,6 +26,147 @@ - * @package Nag - */ - class Horde_UI_VarRenderer_nag extends Horde_UI_VarRenderer_html { -+ -+ function _renderVarInput_nag_recurrence($form, &$var, &$vars) -+ { -+ $var->type->getInfo($vars, $var, $info); -+ -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ -+ $r = &new Nag_Recurrence(time()); -+ if (!empty($info)) { -+ $r->fromHash($info); -+ } -+?> -+<table cellspacing="0"> -+<tr> -+ <td valign="top" colspan="4"> -+ <table cellspacing="0" width="100%"> -+ <tr> -+ <td class="nowrap"> -+ <input id="recurnone" type="radio" class="checkbox" name="recur" onclick="clearFields(0);" value="<?php echo NAG_RECUR_NONE ?>"<?php if ($r->hasRecurType(NAG_RECUR_NONE)) echo ' checked="checked"' ?> /><label for="recurnone"> <?php echo _("No recurrence") ?></label> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recurdaily" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_daily_interval');" value="<?php echo NAG_RECUR_DAILY ?>"<?php if ($r->hasRecurType(NAG_RECUR_DAILY)) echo ' checked="checked"' ?> /><label for="recurdaily"> <?php echo _("Daily: Recurs every") ?> </label> -+ <input type="text" id="recur_daily_interval" name="recur_daily_interval" size="2" onkeypress="setRecur(1);" onchange="setRecur(1);" value="<?php echo $r->hasRecurType(NAG_RECUR_DAILY) ? $r->getRecurInterval() : '' ?>" /> <?php echo Horde::label('recur_daily_interval', _("day(s)")) ?> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recurweekly" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_weekly_interval');" value="<?php echo NAG_RECUR_WEEKLY ?>"<?php if ($r->hasRecurType(NAG_RECUR_WEEKLY)) echo ' checked="checked"' ?> /><label for="recurweekly"> <?php echo _("Weekly: Recurs every") ?> </label> -+ <input type="text" id="recur_weekly_interval" name="recur_weekly_interval" size="2" onkeypress="setRecur(2);" onchange="setRecur(2);" value="<?php echo $r->hasRecurType(NAG_RECUR_WEEKLY) ? $r->getRecurInterval() : '' ?>" /> -+ <?php echo Horde::label('recur_weekly_interval', _("week(s) on:")) ?><br /> -+ <label for="mo"><?php echo _("Mo") ?></label><input id="mo" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_MONDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_MONDAY)) echo ' checked="checked"' ?> /> -+ <label for="tu"><?php echo _("Tu") ?></label><input id="tu" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_TUESDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_TUESDAY)) echo ' checked="checked"' ?> /> -+ <label for="we"><?php echo _("We") ?></label><input id="we" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_WEDNESDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_WEDNESDAY)) echo ' checked="checked"' ?> /> -+ <label for="th"><?php echo _("Th") ?></label><input id="th" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_THURSDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_THURSDAY)) echo ' checked="checked"' ?> /> -+ <label for="fr"><?php echo _("Fr") ?></label><input id="fr" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_FRIDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_FRIDAY)) echo ' checked="checked"' ?> /> -+ <label for="sa"><?php echo _("Sa") ?></label><input id="sa" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_SATURDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_SATURDAY)) echo ' checked="checked"' ?> /> -+ <label for="su"><?php echo _("Su") ?></label><input id="su" type="checkbox" class="checkbox" name="weekly[]" onclick="setInterval('recur_weekly_interval');setRecur(2);" value="<?php echo HORDE_DATE_MASK_SUNDAY ?>"<?php if ($r->recurOnDay(HORDE_DATE_MASK_SUNDAY)) echo ' checked="checked"' ?> /> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recurmonthday" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_day_of_month_interval');" value="<?php echo NAG_RECUR_MONTHLY_DATE ?>"<?php if ($r->hasRecurType(NAG_RECUR_MONTHLY_DATE)) echo ' checked="checked"' ?> /><label for="recurmonthday"> <?php echo _("Monthly: Recurs every") ?> </label> -+ <input type="text" id="recur_day_of_month_interval" name="recur_day_of_month_interval" size="2" onkeypress="setRecur(3);" onchange="setRecur(3);" value="<?php echo $r->hasRecurType(NAG_RECUR_MONTHLY_DATE) ? $r->getRecurInterval() : '' ?>" /> <?php echo Horde::label('recur_day_of_month_interval', _("month(s)") . ' ' . _("on the same date")) ?> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recurmonthweek" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_week_of_month_interval');" value="<?php echo NAG_RECUR_MONTHLY_WEEKDAY ?>"<?php if ($r->hasRecurType(NAG_RECUR_MONTHLY_WEEKDAY)) echo ' checked="checked"' ?> /><label for="recurmonthweek"> <?php echo _("Monthly: Recurs every") ?> </label> -+ <input type="text" id="recur_week_of_month_interval" name="recur_week_of_month_interval" size="2" onkeypress="setRecur(4);" onchange="setRecur(4);" value="<?php echo $r->hasRecurType(NAG_RECUR_MONTHLY_WEEKDAY) ? $r->getRecurInterval() : '' ?>" /> <?php echo Horde::label('recur_week_of_month_interval', _("month(s)") . ' ' . _("on the same weekday")) ?> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recuryear" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_yearly_interval');" value="<?php echo NAG_RECUR_YEARLY_DATE ?>"<?php if ($r->hasRecurType(NAG_RECUR_YEARLY_DATE)) echo ' checked="checked"' ?> /><label for="recuryear"> <?php echo _("Yearly: Recurs every") ?> </label> -+ <input type="text" id="recur_yearly_interval" name="recur_yearly_interval" size="2" onkeypress="setRecur(5);" onchange="setRecur(5);" value="<?php echo $r->hasRecurType(NAG_RECUR_YEARLY_DATE) ? $r->getRecurInterval() : '' ?>" /> <?php echo Horde::label('recur_yearly_interval', _("year(s) on the same date")) ?> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recuryearday" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_yearly_day_interval');" value="<?php echo NAG_RECUR_YEARLY_DAY ?>"<?php if ($r->hasRecurType(NAG_RECUR_YEARLY_DAY)) echo ' checked="checked"' ?> /><label for="recuryearday"> <?php echo _("Yearly: Recurs every") ?> </label> -+ <input type="text" id="recur_yearly_day_interval" name="recur_yearly_day_interval" size="2" onkeypress="setRecur(6);" onchange="setRecur(6);" value="<?php echo $r->hasRecurType(NAG_RECUR_YEARLY_DAY) ? $r->getRecurInterval() : '' ?>" /> <?php echo Horde::label('recur_yearly_day_interval', _("year(s) on the same day of the year")) ?> -+ </td> -+ </tr> -+ <tr> -+ <td class="nowrap"> -+ <input id="recuryearweekday" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_yearly_weekday_interval');" value="<?php echo NAG_RECUR_YEARLY_WEEKDAY ?>"<?php if ($r->hasRecurType(NAG_RECUR_YEARLY_WEEKDAY)) echo ' checked="checked"' ?> /><label for="recuryearweekday"> <?php echo _("Yearly: Recurs every") ?> </label> -+ <input type="text" id="recur_yearly_weekday_interval" name="recur_yearly_weekday_interval" size="2" onkeypress="setRecur(7);" onchange="setRecur(7);" value="<?php echo $r->hasRecurType(NAG_RECUR_YEARLY_WEEKDAY) ? $r->getRecurInterval() : '' ?>" /> <?php echo Horde::label('recur_yearly_weekday_interval', _("year(s) on the same weekday and month of the year")) ?> -+ </td> -+ </tr> -+ </table> -+ </td> -+</tr> -+</table> -+<?php -+ } -+ -+ function _renderVarInput_nag_recur_end($form, &$var, &$vars) -+ { -+ $var->type->getInfo($vars, $var, $recur_end); -+ -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ -+ $r = &new Nag_Recurrence(time()); -+ $recurrence = $vars->get('recurrence'); -+ if ($recurrence && $recur_end) { -+ $recurrence = array_merge($recurrence, $recur_end); -+ } -+ $r->fromHash($recurrence); -+ if ($r->hasRecurEnd()) { -+ $end = $r->recurEnd; -+ } else { -+ if ($vars->exists('task_due')) { -+ $end = new Horde_Date($vars->get('task_due')); -+ } else { -+ $end = new Horde_Date(time()); -+ } -+ } -+?> -+<table cellspacing="0"> -+<tr> -+ <td colspan="3"> -+ <input id="recurnoend" type="radio" class="checkbox" name="recur_enddate_type" value="none"<?php echo (($r->hasRecurEnd() || $r->hasRecurCount()) ? '' : ' checked="checked"') ?> /><label for="recurnoend"> <?php echo _("No end date") ?></label> -+ </td> -+<?php if ($GLOBALS['browser']->hasFeature('dom')): ?> -+ <td> </td> -+<?php endif; ?> -+</tr> -+<tr> -+ <td colspan="3"> -+ <input type="radio" class="checkbox" id="recur_enddate_type" name="recur_enddate_type" value="date"<?php echo ($r->hasRecurEnd() ? ' checked="checked"' : '') ?> /> -+ <label for="recur_enddate_year" class="hidden"><?php echo _("Recurrence End Year") ?></label> -+ <input name="recur_enddate[year]" value="<?php echo $end->year ?>" type="text" id="recur_enddate_year" size="4" maxlength="4" onkeypress="document.<?php echo $form->getName() ?>.recur_enddate_type[1].checked = true;" onchange="document.<?php echo $form->getName() ?>.recur_enddate_type[1].checked = true;" /> - -+ <label for="recur_enddate_month" class="hidden"><?php echo _("Recurrence End Month") ?></label> -+ <input name="recur_enddate[month]" value="<?php echo $end->month ?>" type="text" id="recur_enddate_month" size="4" maxlength="4" onkeypress="document.<?php echo $form->getName() ?>.recur_enddate_type[1].checked = true;" onchange="document.<?php echo $form->getName() ?>.recur_enddate_type[1].checked = true;" /> - -+ <label for="recur_enddate_day" class="hidden"><?php echo _("Recurrence End Day") ?></label> -+ <input name="recur_enddate[day]" value="<?php echo $end->mday ?>" type="text" id="recur_enddate_day" size="4" maxlength="4" onkeypress="document.<?php echo $form->getName() ?>.recur_enddate_type[1].checked = true;" onchange="document.<?php echo $form->getName() ?>.recur_enddate_type[1].checked = true;" /> -+<?php if ($GLOBALS['browser']->hasFeature('dom')): ?> -+ </td> -+ <td> -+<?php -+ Horde::addScriptFile('open_calendar.js', 'horde'); -+ echo Horde::link('#', _("Select a date"), '', '', 'openCalendar(\'recur_enddateimg\', \'recur_enddate\'); return false;') . Horde::img('calendar.png', _("Set recurrence end date"), 'id="recur_enddateimg"', $GLOBALS['registry']->getImageDir('horde')) . '</a>'; -+endif; -+?> -+ </td> -+</tr> -+<tr> -+ <td colspan="3"> -+ <input type="radio" class="checkbox" name="recur_enddate_type" value="count"<?php echo ($r->getRecurCount() ? ' checked="checked"' : '') ?> /> -+ <input type="text" id="recur_count" name="recur_count" size="2" onkeypress="document.<?php echo $form->getName() ?>.recur_enddate_type[2].checked = true;" onchange="document.<?php echo $form->getName() ?>.recur_enddate_type[2].checked = true;" value="<?php if ($r->getRecurCount()) echo $r->getRecurCount() ?>" /> -+ <label for="recur_count"><?php echo _("recurrences") ?></label> -+ </td> -+<?php if ($GLOBALS['browser']->hasFeature('dom')): ?> -+ <td> </td> -+<?php endif; ?> -+</tr> -+</table> -+<?php -+ } - - function _renderVarInput_nag_start($form, &$var, &$vars) - { -diff -r f7b1e151bdb5 nag/lib/api.php ---- a/nag/lib/api.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/lib/api.php Wed Aug 13 21:37:23 2008 +0200 -@@ -96,6 +96,11 @@ - - $_services['replace'] = array( - 'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'), -+ 'type' => 'boolean', -+); -+ -+$_services['complete'] = array( -+ 'args' => array('uid' => '{urn:horde}stringArray'), - 'type' => 'boolean', - ); - -@@ -1121,6 +1126,34 @@ - } - - /** -+ * Completes a task identified by UID. -+ * -+ * @param string|array $uid Identify the task to complete, either a single UID -+ * or an array. -+ * -+ * @return boolean Success or failure. -+ */ -+function _nag_complete($uid) -+{ -+ require_once dirname(__FILE__) . '/base.php'; -+ -+ $storage = &Nag_Driver::singleton(); -+ $task = $storage->getByUID($uid); -+ if (is_a($task, 'PEAR_Error')) { -+ return $task; -+ } -+ -+ if (!Auth::isAdmin() && -+ !array_key_exists($task->tasklist, -+ Nag::listTasklists(false, PERMS_EDIT))) { -+ return PEAR::raiseError(_("Permission Denied")); -+ } -+ -+ $task->toggleComplete(); -+ return $task->save(); -+} -+ -+/** - * Replaces the task identified by UID with the content represented in the - * specified content type. - * -diff -r f7b1e151bdb5 nag/list.php ---- a/nag/list.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/list.php Wed Aug 13 21:37:23 2008 +0200 -@@ -75,10 +75,24 @@ - break; - - default: -+ $tasklist = Util::getFormData('tasklist_id'); -+ if ($tasklist) { -+ $share = $GLOBALS['nag_shares']->getShare($tasklist); -+ if (!is_a($share, 'PEAR_Error')) { -+ $notification->push(sprintf(_("Displaying all tasks from tasklist %s."), $share->get('name'))); -+ $completed = 1; -+ } else { -+ $tasklist = null; -+ } -+ } else { -+ $completed = null; -+ } - /* Get the full, sorted task list. */ - $tasks = Nag::listTasks($prefs->getValue('sortby'), - $prefs->getValue('sortdir'), -- $prefs->getValue('altsortby')); -+ $prefs->getValue('altsortby'), -+ $tasklist, -+ $completed); - if (is_a($tasks, 'PEAR_Error')) { - $notification->push($tasks, 'horde.error'); - $tasks = new Nag_Task(); -diff -r f7b1e151bdb5 nag/task.php ---- a/nag/task.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/task.php Wed Aug 13 21:37:23 2008 +0200 -@@ -141,6 +141,19 @@ - $cManager->add($info['category']['value']); - } - -+ /* Handle recurrence. */ -+ if ($info['recurrence'] && $info['due']) { -+ require_once NAG_BASE . '/lib/Recurrence.php'; -+ $recurrence = &new Nag_Recurrence($info['due']); -+ $r = $info['recurrence']; -+ if ($info['recur_end']) { -+ $r = array_merge($r, $info['recur_end']); -+ } -+ $recurrence->fromHash($r); -+ } else { -+ $recurrence = null; -+ } -+ - /* If a task id is set, we're modifying an existing task. - * Otherwise, we're adding a new task with the provided - * attributes. */ -@@ -154,7 +167,8 @@ - $info['category']['value'], - $info['alarm'], $info['parent'], - (int)$info['private'], -- Auth::getAuth()); -+ Auth::getAuth(), null, null, -+ $recurrence); - - if (!is_a($result, 'PEAR_Error') && - $info['old_tasklist'] != $info['tasklist_id']) { -@@ -191,7 +205,8 @@ - $info['category']['value'], - $info['alarm'], null, $info['parent'], - (int)$info['private'], -- Auth::getAuth()); -+ Auth::getAuth(), null, -+ $recurrence); - } - - /* Check our results. */ -@@ -221,12 +236,7 @@ - if (is_a($share, 'PEAR_Error') || !$share->hasPermission(Auth::getAuth(), PERMS_EDIT)) { - $notification->push(sprintf(_("Access denied completing task %s."), $task->name), 'horde.error'); - } else { -- $task->completed = !$task->completed; -- if ($task->completed) { -- $task->completed_date = time(); -- } else { -- $task->completed_date = null; -- } -+ $task->toggleComplete(); - $result = $task->save(); - if (is_a($result, 'PEAR_Error')) { - $notification->push(sprintf(_("There was a problem completing %s: %s"), -@@ -234,6 +244,13 @@ - } else { - if ($task->completed) { - $notification->push(sprintf(_("Completed %s."), $task->name), 'horde.success'); -+ } else if ($task->recurs()) { -+ if (!empty($task->start)) { -+ $delay = sprintf(_(" (Delayed until %s)"), Nag::formatDate($task->start, false)); -+ } else { -+ $delay = ''; -+ } -+ $notification->push(sprintf(_("Next task recurrence due at %s" . $delay . "."), Nag::formatDate($task->due)), 'horde.success'); - } else { - $notification->push(sprintf(_("%s is now incomplete."), $task->name), 'horde.success'); - } -diff -r f7b1e151bdb5 nag/templates/list/task_summaries.inc ---- a/nag/templates/list/task_summaries.inc Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/templates/list/task_summaries.inc Wed Aug 13 21:37:23 2008 +0200 -@@ -4,7 +4,7 @@ - if (!is_a($share, 'PEAR_Error') && - $share->hasPermission(Auth::getAuth(), PERMS_EDIT)) { - if (!$task->completed) { -- if (!$task->childrenCompleted()) { -+ if (!$task->childrenCompleted() && !$task->recurs()) { - $label = _("Incomplete sub tasks, complete them first"); - echo Horde::img('unchecked.png', $label, array('title' => $label)); - } else { -diff -r f7b1e151bdb5 nag/templates/view/task.inc ---- a/nag/templates/view/task.inc Wed Aug 13 17:47:52 2008 +0200 -+++ b/nag/templates/view/task.inc Wed Aug 13 21:37:23 2008 +0200 -@@ -66,6 +66,54 @@ - </tr> - <?php endif; ?> - -+<?php if ($task->recurs()): ?> -+<!-- recurrence --> -+<tr> -+ <td colspan="2" class="control"><strong><?php echo _("Recurrence") ?></strong></td> -+</tr> -+<tr> -+ <td class="rightAlign" valign="top"><strong><?php echo _("Pattern") ?> </strong></td> -+ <td valign="top"> -+<?php if ($task->recurrence->hasRecurType(NAG_RECUR_DAILY)): ?> -+ <?php echo _("Daily: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("day(s)") ?> -+<?php elseif ($task->recurrence->hasRecurType(NAG_RECUR_WEEKLY)): -+ $weekdays = array(); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_MONDAY)) $weekdays[] = _("Monday"); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_TUESDAY)) $weekdays[] = _("Tuesday"); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_WEDNESDAY)) $weekdays[] = _("Wednesday"); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_THURSDAY)) $weekdays[] = _("Thursday"); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_FRIDAY)) $weekdays[] = _("Friday"); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_SATURDAY)) $weekdays[] = _("Saturday"); -+ if ($task->recurrence->recurOnDay(HORDE_DATE_MASK_SUNDAY)) $weekdays[] = _("Sunday"); -+ echo _("Weekly: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("week(s) on:") . ' ' . implode(', ', $weekdays) ?> -+<?php elseif ($task->recurrence->hasRecurType(NAG_RECUR_MONTHLY_DATE)): ?> -+ <?php echo _("Monthly: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("month(s)") . ' ' . _("on the same date") ?> -+<?php elseif ($task->recurrence->hasRecurType(NAG_RECUR_MONTHLY_WEEKDAY)): ?> -+ <?php echo _("Monthly: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("month(s)") . ' ' . _("on the same weekday") ?> -+<?php elseif ($task->recurrence->hasRecurType(NAG_RECUR_YEARLY_DATE)): ?> -+ <?php echo _("Yearly: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("year(s) on the same date") ?> -+<?php elseif ($task->recurrence->hasRecurType(NAG_RECUR_YEARLY_DAY)): ?> -+ <?php echo _("Yearly: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("year(s) on the same day of the year") ?> -+<?php elseif ($task->recurrence->hasRecurType(NAG_RECUR_YEARLY_WEEKDAY)): ?> -+ <?php echo _("Yearly: Recurs every") . ' ' . $task->recurrence->getRecurInterval() . ' ' . _("year(s) on the same weekday and month of the year") ?> -+<?php endif; ?> -+ </td> -+</tr> -+ -+<!-- recur end date --> -+<tr> -+ <td class="rightAlign"><strong><?php echo _("Recur Until") ?> </strong></td> -+ <td><?php echo $task->recurrence->hasRecurEnd() ? strftime($prefs->getValue('date_format'), $task->recurrence->recurEnd->timestamp()) . date($prefs->getValue('twentyFour') ? ' G:i' : ' g:i a', $task->recurrence->recurEnd->timestamp()) : ($task->recurrence->getRecurCount() ? sprintf(_("%d times"), $task->recurrence->getRecurCount()) : _("No end date")) ?></td> -+</tr> -+ -+<?php if ($task->recurrence->getExceptions()): ?> -+<!-- exceptions --> -+<tr> -+ <td class="rightAlign"><strong><?php echo _("Exceptions") ?> </strong></td> -+ <td><?php echo $task->exceptionsList(); ?></td> -+</tr> -+<?php endif; endif; ?> -+ - <?php if (strlen($task->desc)): ?> - <tr> - <td colspan="2" class="taskBody"> -diff -r f7b1e151bdb5 storage/.htaccess ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/storage/.htaccess Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,1 @@ -+Deny from All -\ No newline at end of file -diff -r f7b1e151bdb5 tmp/.htaccess ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/tmp/.htaccess Wed Aug 13 21:37:23 2008 +0200 -@@ -0,0 +1,1 @@ -+Deny from All -\ No newline at end of file -diff -r f7b1e151bdb5 turba/config/conf.php ---- a/turba/config/conf.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/turba/config/conf.php Wed Aug 13 21:37:23 2008 +0200 -@@ -3,8 +3,8 @@ - // $Horde: turba/config/conf.xml,v 1.6.2.5 2008/05/06 21:26:59 bklang Exp $ - $conf['menu']['import_export'] = true; - $conf['menu']['apps'] = array(); --$conf['client']['addressbook'] = 'localsql'; --$conf['shares']['source'] = 'localsql'; -+$conf['client']['addressbook'] = 'INBOX%2FClients'; -+$conf['shares']['source'] = 'kolab'; - $conf['comments']['allow'] = true; --$conf['documents']['type'] = 'none'; -+$conf['documents']['type'] = 'horde'; - /* CONFIG END. DO NOT CHANGE ANYTHING IN OR BEFORE THIS LINE. */ -diff -r f7b1e151bdb5 turba/config/sources.php ---- a/turba/config/sources.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/turba/config/sources.php Wed Aug 13 21:37:23 2008 +0200 -@@ -149,6 +149,8 @@ - * Here are some example configurations: - */ - -+if (empty($GLOBALS['conf']['kolab']['enabled'])) { -+ - /** - * A local address book in an SQL database. This implements a private - * per-user address book. Sharing of this source with other users may be -@@ -260,3 +262,233 @@ - 'use_shares' => true, - 'list_name_field' => 'lastname', - ); -+ -+} -+ -+/* Begin Kolab sources. */ -+if (!empty($GLOBALS['conf']['kolab']['enabled'])) { -+ -+ /* Only use LDAP if we have that extension in PHP */ -+ if (function_exists('ldap_connect')) { -+ require_once 'Horde/Kolab.php'; -+ -+ if (!is_callable('Kolab', 'getServer')) { -+ $_kolab_server = $GLOBALS['conf']['kolab']['ldap']['server']; -+ } else { -+ $_kolab_server = Kolab::getServer('ldap'); -+ } -+ -+ /* A global address book for a Kolab Server. This is typically a -+ * read-only public directory, stored in the default Kolab LDAP server. -+ * The user accessing this should have read permissions to the shared -+ * directory in LDAP. */ -+ $cfgSources['kolab_global'] = array( -+ 'title' => _("Global Address Book"), -+ 'type' => 'ldap', -+ 'params' => array( -+ 'server' => $_kolab_server, -+ 'port' => $GLOBALS['conf']['kolab']['ldap']['port'], -+ 'tls' => false, -+ 'root' => $GLOBALS['conf']['kolab']['ldap']['basedn'], -+ 'sizelimit' => 200, -+ 'dn' => array('cn'), -+ 'objectclass' => array( -+ 'inetOrgPerson' -+ ), -+ 'scope' => 'sub', -+ 'charset' => 'utf-8', -+ 'version' => 3, -+ 'bind_dn' => '', -+ 'bind_password' => '', -+ ), -+ 'map' => array( -+ '__key' => 'dn', -+ 'name' => 'cn', -+ 'firstname' => 'givenName', -+ 'lastname' => 'sn', -+ 'email' => 'mail', -+ 'alias' => 'alias', -+ 'title' => 'title', -+ 'company' => 'o', -+ 'workStreet' => 'street', -+ 'workCity' => 'l', -+ 'workProvince' => 'st', -+ 'workPostalCode' => 'postalCode', -+ 'workCountry' => 'c', -+ 'homePhone' => 'homePhone', -+ 'workPhone' => 'telephoneNumber', -+ 'cellPhone' => 'mobile', -+ 'fax' => 'fax', -+ 'notes' => 'description', -+ 'freebusyUrl' => 'kolabHomeServer', -+ ), -+ 'search' => array( -+ 'name', -+ 'firstname', -+ 'lastname', -+ 'email', -+ 'title', -+ 'company', -+ 'workAddress', -+ 'workCity', -+ 'workProvince', -+ 'workPostalCode', -+ 'workCountry', -+ 'homePhone', -+ 'workPhone', -+ 'cellPhone', -+ 'fax', -+ 'notes', -+ ), -+ 'strict' => array( -+ 'dn', -+ ), -+ 'export' => true, -+ 'browse' => true, -+ ); -+ } -+ -+ /** -+ * The local address books for a Kolab user. These are stored in specially -+ * flagged contact folder within the users Cyrus IMAP mailbox. -+ * -+ * Still missing attributes are: -+ * -+ * picture, sensitivity -+ */ -+ -+ $cfgSources['kolab'] = array( -+ 'title' => _("Contacts"), -+ 'type' => 'kolab', -+ 'params' => array( -+ 'charset' => 'utf-8', -+ ), -+ 'list_name_field' => 'lastname', -+ 'map' => array( -+ '__key' => 'uid', -+ '__uid' => 'uid', -+ '__type' => '__type', -+ '__members' => '__members', -+ /* Personal */ -+ 'name' => array('fields' => array('firstname', 'middlenames', 'lastname'), -+ 'format' => '%s %s %s', -+ 'attribute' => 'full-name'), -+ 'firstname' => 'given-name', -+ 'lastname' => 'last-name', -+ 'middlenames' => 'middle-names', -+ 'namePrefix' => 'prefix', -+ 'nameSuffix' => 'suffix', -+ 'initials' => 'initials', -+ 'nickname' => 'nick-name', -+ 'gender' => 'gender', -+ 'birthday' => 'birthday', -+ 'spouse' => 'spouse-name', -+ 'anniversary' => 'anniversary', -+ 'children' => 'children', -+ /* Location */ -+ 'workStreet' => 'addr-business-street', -+ 'workCity' => 'addr-business-locality', -+ 'workProvince' => 'addr-business-region', -+ 'workPostalCode' => 'addr-business-postal-code', -+ 'workCountry' => 'addr-business-country', -+ 'homeStreet' => 'addr-home-street', -+ 'homeCity' => 'addr-home-locality', -+ 'homeProvince' => 'addr-home-region', -+ 'homePostalCode' => 'addr-home-postal-code', -+ 'homeCountry' => 'addr-home-country', -+ /* Communications */ -+ 'emails' => 'emails', -+ 'homePhone' => 'phone-home1', -+ 'workPhone' => 'phone-business1', -+ 'cellPhone' => 'phone-mobile', -+ 'fax' => 'phone-businessfax', -+ 'instantMessenger' => 'im-address', -+ /* Organization */ -+ 'title' => 'job-title', -+ 'role' => 'profession', -+ 'company' => 'organization', -+ 'department' => 'department', -+ 'office' => 'office-location', -+ 'manager' => 'manager-name', -+ 'assistant' => 'assistant', -+ /* Other */ -+ 'category' => 'categories', -+ 'notes' => 'body', -+ 'website' => 'web-page', -+ 'freebusyUrl' => 'free-busy-url', -+ 'language' => 'language', -+ 'latitude' => 'latitude', -+ 'longitude' => 'longitude', -+ ), -+ 'tabs' => array( -+ _("Personal") => array('name', 'firstname', 'lastname', 'middlenames', -+ 'namePrefix', 'nameSuffix', 'initials', 'nickname', -+ 'gender', 'birthday', 'spouse', 'anniversary', -+ 'children'), -+ _("Location") => array('homeStreet', 'homeCity', 'homeProvince', -+ 'homePostalCode', 'homeCountry', 'workStreet', -+ 'workCity', 'workProvince', 'workPostalCode', -+ 'workCountry'), -+ _("Communications") => array('emails', 'homePhone', 'workPhone', -+ 'cellPhone', 'fax', 'instantMessenger'), -+ _("Organization") => array('title', 'role', 'company', 'department', -+ 'office', 'manager', 'assistant'), -+ _("Other") => array('category', 'notes', 'website', 'freebusyUrl', -+ 'language', 'latitude', 'longitude'), -+ ), -+ 'search' => array( -+ /* Personal */ -+ 'firstname', -+ 'lastname', -+ 'middlenames', -+ 'namePrefix', -+ 'nameSuffix', -+ 'initials', -+ 'nickname', -+ 'gender', -+ 'birthday', -+ 'spouse', -+ 'anniversary', -+ 'children', -+ /* Location */ -+ 'workStreet', -+ 'workCity', -+ 'workProvince', -+ 'workPostalCode', -+ 'workCountry', -+ 'homeStreet', -+ 'homeCity', -+ 'homeProvince', -+ 'homePostalCode', -+ 'homeCountry', -+ /* Communications */ -+ 'emails', -+ 'homePhone', -+ 'workPhone', -+ 'cellPhone', -+ 'fax', -+ 'instantMessenger', -+ /* Organization */ -+ 'title', -+ 'role', -+ 'company', -+ 'department', -+ 'office', -+ 'manager', -+ 'assistant', -+ /* Other */ -+ 'category', -+ 'notes', -+ 'website', -+ 'language', -+ ), -+ 'strict' => array( -+ 'uid', -+ ), -+ 'export' => true, -+ 'browse' => true, -+ 'use_shares' => true, -+ 'shares_only' => true, -+ ); -+} -+/* End Kolab sources. */ -diff -r f7b1e151bdb5 turba/lib/Driver.php ---- a/turba/lib/Driver.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/turba/lib/Driver.php Wed Aug 13 21:37:23 2008 +0200 -@@ -1368,9 +1368,6 @@ - $hash = array(); - $attr = $vcard->getAllAttributes(); - foreach ($attr as $item) { -- if (empty($item['value'])) { -- continue; -- } - - switch ($item['name']) { - case 'FN': -diff -r f7b1e151bdb5 turba/lib/Driver/share.php ---- a/turba/lib/Driver/share.php Wed Aug 13 17:47:52 2008 +0200 -+++ b/turba/lib/Driver/share.php Wed Aug 13 21:37:23 2008 +0200 -@@ -145,7 +145,7 @@ - function _deleteAll($sourceName = null) - { - if (is_null($sourceName)) { -- $sourceName = $this->getContactOwner(); -+ $sourceName = $this->getName(); - } - return $this->_driver->_deleteAll($sourceName); - } diff --git a/www-apps/horde-webmail/horde-webmail-1.1.1.ebuild b/www-apps/horde-webmail/horde-webmail-1.1.1.ebuild deleted file mode 100644 index f1186d29800f..000000000000 --- a/www-apps/horde-webmail/horde-webmail-1.1.1.ebuild +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 1999-2008 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/www-apps/horde-webmail/horde-webmail-1.1.1.ebuild,v 1.2 2008/08/07 18:41:48 wrobel Exp $ - -HORDE_PN=${PN} - -HORDE_APPLICATIONS="dimp imp ingo kronolith mimp mnemo nag turba" - -inherit horde - -DESCRIPTION="browser based communication suite" - -KEYWORDS="~alpha ~amd64 ~hppa ~ppc ~sparc ~x86" -IUSE="crypt mysql postgres ldap oracle" - -DEPEND="" -RDEPEND="!www-apps/horde - crypt? ( app-crypt/gnupg ) - virtual/php - >=www-apps/horde-pear-1.3 - dev-php/PEAR-Log - dev-php/PEAR-Mail_Mime - dev-php/PEAR-DB" - -pkg_setup() { - HORDE_PHP_FEATURES=" - imap ssl session xml nls iconv gd ftp - $(use ldap && echo ldap) $(use oracle && echo oci8) - $(use crypt && echo crypt) $(use mysql && echo mysql mysqli) - $(use postgres && echo postgres) - " - horde_pkg_setup -} diff --git a/www-apps/horde-webmail/horde-webmail-1.1.1-r3.ebuild b/www-apps/horde-webmail/horde-webmail-1.1.2.ebuild index 1af9d90bb90a..c4fa9be7e623 100644 --- a/www-apps/horde-webmail/horde-webmail-1.1.1-r3.ebuild +++ b/www-apps/horde-webmail/horde-webmail-1.1.2.ebuild @@ -1,6 +1,6 @@ # Copyright 1999-2008 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 -# $Header: /var/cvsroot/gentoo-x86/www-apps/horde-webmail/horde-webmail-1.1.1-r3.ebuild,v 1.1 2008/08/14 07:33:33 wrobel Exp $ +# $Header: /var/cvsroot/gentoo-x86/www-apps/horde-webmail/horde-webmail-1.1.2.ebuild,v 1.1 2008/08/19 12:50:25 wrobel Exp $ HORDE_PN=${PN} @@ -10,6 +10,11 @@ inherit horde DESCRIPTION="browser based communication suite" +HORDE_PATCHSET_REV=1 + +SRC_URI="${SRC_URI} + http://files.pardus.de/horde-webmail-patches-${PV}-r${HORDE_PATCHSET_REV}.tar.bz2" + KEYWORDS="~alpha ~amd64 ~hppa ~ppc ~sparc ~x86" IUSE="crypt mysql postgres ldap oracle kolab" @@ -22,7 +27,7 @@ RDEPEND="!www-apps/horde dev-php/PEAR-Mail_Mime dev-php/PEAR-DB" -EHORDE_PATCHES="$(use kolab && echo ${FILESDIR}/${P}_kolab.patch)" +EHORDE_PATCHES="$(use kolab && echo ${WORKDIR}/horde-webmail-kolab.patch)" HORDE_RECONFIG="$(use kolab && echo ${FILESDIR}/reconfig.kolab)" HORDE_POSTINST="$(use kolab && echo ${FILESDIR}/postinstall-en.txt.kolab)" |