diff options
author | Gunnar Wrobel <wrobel@gentoo.org> | 2008-08-08 13:20:39 +0000 |
---|---|---|
committer | Gunnar Wrobel <wrobel@gentoo.org> | 2008-08-08 13:20:39 +0000 |
commit | eb785693bfff428c30f77672b1436a1e37980fc3 (patch) | |
tree | d6da09e39fc66b886098be73d58e39e889052bea /www-apps/horde-webmail/files | |
parent | Add longdescription to metadata.xml. (diff) | |
download | gentoo-2-eb785693bfff428c30f77672b1436a1e37980fc3.tar.gz gentoo-2-eb785693bfff428c30f77672b1436a1e37980fc3.tar.bz2 gentoo-2-eb785693bfff428c30f77672b1436a1e37980fc3.zip |
Added a kolabified version of the horde-webmail package.
(Portage version: 2.1.4.4)
Diffstat (limited to 'www-apps/horde-webmail/files')
-rw-r--r-- | www-apps/horde-webmail/files/horde-webmail-1.1.1_kolab.patch | 3411 | ||||
-rw-r--r-- | www-apps/horde-webmail/files/postinstall-en.txt.kolab | 15 | ||||
-rw-r--r-- | www-apps/horde-webmail/files/reconfig.kolab | 15 |
3 files changed, 3441 insertions, 0 deletions
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 new file mode 100644 index 000000000000..8c579bee51ea --- /dev/null +++ b/www-apps/horde-webmail/files/horde-webmail-1.1.1_kolab.patch @@ -0,0 +1,3411 @@ +diff -r e16c159ea616 config/conf.php +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/config/conf.php Fri Aug 08 14:19:56 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 e16c159ea616 config/kolab.php +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/config/kolab.php Fri Aug 08 14:19:56 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 e16c159ea616 config/prefs.php +--- a/config/prefs.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/config/prefs.php Fri Aug 08 14:19:56 2008 +0200 +@@ -357,7 +357,7 @@ + + // UI theme + $_prefs['theme'] = array( +- 'value' => 'bluewhite', ++ 'value' => 'silver', + 'locked' => false, + 'shared' => true, + 'type' => 'select', +diff -r e16c159ea616 imp/config/conf.php +--- a/imp/config/conf.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/imp/config/conf.php Fri Aug 08 14:19:56 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 e16c159ea616 imp/config/hooks.php +--- a/imp/config/hooks.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/imp/config/hooks.php Fri Aug 08 14:19:56 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 e16c159ea616 imp/config/servers.php +--- a/imp/config/servers.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/imp/config/servers.php Fri Aug 08 14:19:56 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 e16c159ea616 kronolith/config/conf.php +--- a/kronolith/config/conf.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/kronolith/config/conf.php Fri Aug 08 14:19:56 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 e16c159ea616 kronolith/config/kolab.php +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/kronolith/config/kolab.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/Horde/Prefs/kolab.php +--- a/lib/Horde/Prefs/kolab.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/Horde/Prefs/kolab.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/Horde/iCalendar.php +--- a/lib/Horde/iCalendar.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/Horde/iCalendar.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/SyncML/Backend.php +--- a/lib/SyncML/Backend.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/SyncML/Backend.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/SyncML/Backend/Horde.php +--- a/lib/SyncML/Backend/Horde.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/SyncML/Backend/Horde.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/SyncML/Constants.php +--- a/lib/SyncML/Constants.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/SyncML/Constants.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/SyncML/Device.php +--- a/lib/SyncML/Device.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/SyncML/Device.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/SyncML/Sync.php +--- a/lib/SyncML/Sync.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/SyncML/Sync.php Fri Aug 08 14:19:56 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 e16c159ea616 lib/core.php +--- a/lib/core.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/lib/core.php Fri Aug 08 14:19:56 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 e16c159ea616 log/.htaccess +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/log/.htaccess Fri Aug 08 14:19:56 2008 +0200 +@@ -0,0 +1,1 @@ ++Deny from All +\ No newline at end of file +diff -r e16c159ea616 mnemo/config/conf.php +--- a/mnemo/config/conf.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/mnemo/config/conf.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/config/conf.php +--- a/nag/config/conf.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/config/conf.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/lib/Block/tree_menu.php +--- a/nag/lib/Block/tree_menu.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/lib/Block/tree_menu.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/lib/Driver.php +--- a/nag/lib/Driver.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/lib/Driver.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/lib/Driver/kolab.php +--- a/nag/lib/Driver/kolab.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/lib/Driver/kolab.php Fri Aug 08 14:19:56 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); + } + + /** +@@ -379,13 +384,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 +439,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 +461,8 @@ + + return $this->_setObject($name, $desc, $start, $due, $priority, + $completed, $estimate, $category, $alarm, +- $parent, $private, $owner, $assignee); ++ $parent, $private, $owner, $assignee, ++ $recurrence); + } + + /** +@@ -474,6 +483,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 +491,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 +502,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; + } +@@ -780,6 +790,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 +@@ -788,7 +799,7 @@ + $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) + { + if (empty($uid)) { + $task_uid = $this->_store->generateUID(); +@@ -808,6 +819,10 @@ + $sensitivity = 'public'; + } + ++ if ($recurrence) { ++ $recurrence = $recurrence->toHash(); ++ } ++ + $result = $this->_store->save(array( + 'uid' => $task_uid, + 'name' => $name, +@@ -817,6 +832,7 @@ + 'priority' => $priority, + 'completed' => $completed, + 'categories' => $category, ++ 'recurrence' => $recurrence, + 'alarm' => $alarm, + 'parent' => $parent, + 'sensitivity' => $sensitivity, +@@ -854,6 +870,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 +878,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); ++ null, $parent, $private, $owner, $assignee, ++ null, $recurrence); + } + + /** +@@ -886,6 +904,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 +912,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; + } +@@ -1033,6 +1053,13 @@ + } + 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'); + +diff -r e16c159ea616 nag/lib/Driver/sql.php +--- a/nag/lib/Driver/sql.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/lib/Driver/sql.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/lib/Forms/task.php +--- a/nag/lib/Forms/task.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/lib/Forms/task.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/lib/Recurrence.php +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/nag/lib/Recurrence.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/lib/UI/VarRenderer/nag.php +--- a/nag/lib/UI/VarRenderer/nag.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/lib/UI/VarRenderer/nag.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/list.php +--- a/nag/list.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/list.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/task.php +--- a/nag/task.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/task.php Fri Aug 08 14:19:56 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 e16c159ea616 nag/templates/list/task_summaries.inc +--- a/nag/templates/list/task_summaries.inc Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/templates/list/task_summaries.inc Fri Aug 08 14:19:56 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 e16c159ea616 nag/templates/view/task.inc +--- a/nag/templates/view/task.inc Fri Aug 08 13:11:07 2008 +0200 ++++ b/nag/templates/view/task.inc Fri Aug 08 14:19:56 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 e16c159ea616 storage/.htaccess +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/storage/.htaccess Fri Aug 08 14:19:56 2008 +0200 +@@ -0,0 +1,1 @@ ++Deny from All +\ No newline at end of file +diff -r e16c159ea616 tmp/.htaccess +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/tmp/.htaccess Fri Aug 08 14:19:56 2008 +0200 +@@ -0,0 +1,1 @@ ++Deny from All +\ No newline at end of file +diff -r e16c159ea616 turba/config/conf.php +--- a/turba/config/conf.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/turba/config/conf.php Fri Aug 08 14:19:56 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 e16c159ea616 turba/config/sources.php +--- a/turba/config/sources.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/turba/config/sources.php Fri Aug 08 14:19:56 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 e16c159ea616 turba/lib/Driver.php +--- a/turba/lib/Driver.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/turba/lib/Driver.php Fri Aug 08 14:19:56 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 e16c159ea616 turba/lib/Driver/share.php +--- a/turba/lib/Driver/share.php Fri Aug 08 13:11:07 2008 +0200 ++++ b/turba/lib/Driver/share.php Fri Aug 08 14:19:56 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/files/postinstall-en.txt.kolab b/www-apps/horde-webmail/files/postinstall-en.txt.kolab new file mode 100644 index 000000000000..95bff6b4b650 --- /dev/null +++ b/www-apps/horde-webmail/files/postinstall-en.txt.kolab @@ -0,0 +1,15 @@ +Congratulations, you nearly finished installing the Kolab web client. + +All you need to do in order to finish the setup of the groupware web +client is to enter the details of your Kolab server in the two files + + ${MY_INSTALLDIR}/config/kolab.php + ${MY_INSTALLDIR}/kronolith/config/kolab.php + +Just follow the comments in these files. + +Once you are finished you should be able to log into the new site at + + http://${VHOST_HOSTNAME}/${VHOST_APPDIR}/ + +Enjoy! diff --git a/www-apps/horde-webmail/files/reconfig.kolab b/www-apps/horde-webmail/files/reconfig.kolab new file mode 100644 index 000000000000..dbf053428f61 --- /dev/null +++ b/www-apps/horde-webmail/files/reconfig.kolab @@ -0,0 +1,15 @@ +#!/bin/bash + +die() { + echo "#####" + echo $1 + echo "#####" + exit 1 +} + +if [ $1 = "install" ]; then + sqlite ${MY_INSTALLDIR}/storage/horde.db < ${MY_INSTALLDIR}/scripts/sql/groupware.sql + chown ${VHOST_SERVER_UID}\:${VHOST_SERVER_GID} ${MY_INSTALLDIR}/storage/horde.db ${MY_INSTALLDIR}/{log,tmp,storage} +elif [ $1 = "clean" ]; then + echo $1 +fi |