Projects
Kolab:3.4
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 8
View file
kolab-syncroton.spec
Changed
@@ -1,12 +1,19 @@ +%{!?php_inidir: %global php_inidir %{_sysconfdir}/php.d} -%if 0%{?suse_version} -%global _ap_sysconfdir %{_sysconfdir}/apache2 +f 0%{?suse_version} +%global httpd_group www +%global httpd_name apache2 +%global httpd_user wwwrun %else -%global _ap_sysconfdir %{_sysconfdir}/httpd +%global httpd_group apache +%global httpd_name httpd +%global httpd_user apache %endif +%global _ap_sysconfdir %{_sysconfdir}/%{httpd_name} + Name: kolab-syncroton -Version: 2.1.0 +Version: 2.2.0 Release: 1%{?dist} Summary: ActiveSync for Kolab Groupware @@ -102,9 +109,9 @@ if [ -f "%{php_inidir}/apc.ini" ]; then if [ ! -z "`grep ^apc.enabled=1 %{php_inidir}/apc.ini`" ]; then %if 0%{?fedora} > 15 - /sbin/systemctl condrestart httpd.service + /sbin/systemctl condrestart %{httpd_name}.service %else - /sbin/service httpd condrestart + /sbin/service %{httpd_name} condrestart %endif fi fi @@ -121,9 +128,12 @@ %config(noreplace) %{_ap_sysconfdir}/conf.d/ %config(noreplace) %{_sysconfdir}/logrotate.d/%{name} %{_datadir}/%{name} -%attr(0770,apache,apache) %{_var}/log/%{name} +%attr(0770,%{httpd_user},%{httpd_group}) %{_var}/log/%{name} %changelog +* Sun Sep 8 2013 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 2.2.0-1 +- Release version 2.2.0 + * Wed Sep 3 2013 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 2.1.0-1 - Bug fixes for #1658, and attachment sending
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +kolab-syncroton (2.2.0-1) unstable; urgency=low + + * Check in version 2.2.0 + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Sun, 8 Sep 2013 21:07:13 +0100 + kolab-syncroton (2.1.0-1) unstable; urgency=low * Imported Upstream version 2.1~rc2
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_shared.inc
Deleted
@@ -1,474 +0,0 @@ -<?php - -/* - +-----------------------------------------------------------------------+ - | program/include/rcube_shared.inc | - | | - | This file is part of the Roundcube PHP suite | - | Copyright (C) 2005-2012, The Roundcube Dev Team | - | | - | Licensed under the GNU General Public License version 3 or | - | any later version with exceptions for skins & plugins. | - | See the README file for a full license statement. | - | | - | CONTENTS: | - | Shared functions used by Roundcube Framework | - | | - +-----------------------------------------------------------------------+ - | Author: Thomas Bruederli <roundcube@gmail.com> | - +-----------------------------------------------------------------------+ -*/ - - -/** - * Roundcube shared functions - * - * @package Core - */ - - -/** - * Similar function as in_array() but case-insensitive - * - * @param string $needle Needle value - * @param array $heystack Array to search in - * - * @return boolean True if found, False if not - */ -function in_array_nocase($needle, $haystack) -{ - $needle = mb_strtolower($needle); - foreach ((array)$haystack as $value) { - if ($needle === mb_strtolower($value)) { - return true; - } - } - - return false; -} - - -/** - * Find out if the string content means true or false - * - * @param string $str Input value - * - * @return boolean Boolean value - */ -function get_boolean($str) -{ - $str = strtolower($str); - - return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true); -} - - -/** - * Parse a human readable string for a number of bytes. - * - * @param string $str Input string - * - * @return float Number of bytes - */ -function parse_bytes($str) -{ - if (is_numeric($str)) { - return floatval($str); - } - - if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) { - $bytes = floatval($regs[1]); - switch (strtolower($regs[2])) { - case 'g': - case 'gb': - $bytes *= 1073741824; - break; - case 'm': - case 'mb': - $bytes *= 1048576; - break; - case 'k': - case 'kb': - $bytes *= 1024; - break; - } - } - - return floatval($bytes); -} - - -/** - * Make sure the string ends with a slash - */ -function slashify($str) -{ - return unslashify($str).'/'; -} - - -/** - * Remove slashes at the end of the string - */ -function unslashify($str) -{ - return preg_replace('/\/+$/', '', $str); -} - - -/** - * Delete all files within a folder - * - * @param string Path to directory - * - * @return boolean True on success, False if directory was not found - */ -function clear_directory($dir_path) -{ - $dir = @opendir($dir_path); - if (!$dir) { - return false; - } - - while ($file = readdir($dir)) { - if (strlen($file) > 2) { - unlink("$dir_path/$file"); - } - } - - closedir($dir); - - return true; -} - - -/** - * Returns number of seconds for a specified offset string. - * - * @param string $str String representation of the offset (e.g. 20min, 5h, 2days, 1week) - * - * @return int Number of seconds - */ -function get_offset_sec($str) -{ - if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) { - $amount = (int) $regs[1]; - $unit = strtolower($regs[2]); - } - else { - $amount = (int) $str; - $unit = 's'; - } - - switch ($unit) { - case 'w': - $amount *= 7; - case 'd': - $amount *= 24; - case 'h': - $amount *= 60; - case 'm': - $amount *= 60; - } - - return $amount; -} - - -/** - * Create a unix timestamp with a specified offset from now. - * - * @param string $offset_str String representation of the offset (e.g. 20min, 5h, 2days) - * @param int $factor Factor to multiply with the offset - * - * @return int Unix timestamp - */ -function get_offset_time($offset_str, $factor=1) -{ - return time() + get_offset_sec($offset_str) * $factor; -} - - -/** - * Truncate string if it is longer than the allowed length. - * Replace the middle or the ending part of a string with a placeholder. - * - * @param string $str Input string - * @param int $maxlength Max. length - * @param string $placeholder Replace removed chars with this - * @param bool $ending Set to True if string should be truncated from the end - * - * @return string Abbreviated string - */ -function abbreviate_string($str, $maxlength, $placeholder='...', $ending=false) -{ - $length = mb_strlen($str); - - if ($length > $maxlength) { - if ($ending) { - return mb_substr($str, 0, $maxlength) . $placeholder; - } - - $placeholder_length = mb_strlen($placeholder); - $first_part_length = floor(($maxlength - $placeholder_length)/2); - $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length; - - $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location); - } - - return $str; -} - - -/** - * Get all keys from array (recursive). - * - * @param array $array Input array - * - * @return array List of array keys - */ -function array_keys_recursive($array) -{ - $keys = array(); - - if (!empty($array) && is_array($array)) { - foreach ($array as $key => $child) { - $keys[] = $key; - foreach (array_keys_recursive($child) as $val) { - $keys[] = $val; - } - } - } - - return $keys; -} - - -/** - * Remove all non-ascii and non-word chars except ., -, _ - */ -function asciiwords($str, $css_id = false, $replace_with = '') -{ - $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : ''); - return preg_replace("/[^$allowed]/i", $replace_with, $str); -} - - -/** - * Check if a string contains only ascii characters - * - * @param string $str String to check - * @param bool $control_chars Includes control characters - * - * @return bool - */ -function is_ascii($str, $control_chars = true) -{ - $regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/'; - return preg_match($regexp, $str) ? false : true; -} - - -/** - * Remove single and double quotes from a given string - * - * @param string Input value - * - * @return string Dequoted string - */ -function strip_quotes($str) -{ - return str_replace(array("'", '"'), '', $str); -} - - -/** - * Remove new lines characters from given string - * - * @param string $str Input value - * - * @return string Stripped string - */ -function strip_newlines($str) -{ - return preg_replace('/[\r\n]/', '', $str); -} - - -/** - * Compose a valid representation of name and e-mail address - * - * @param string $email E-mail address - * @param string $name Person name - * - * @return string Formatted string - */ -function format_email_recipient($email, $name = '') -{ - $email = trim($email); - - if ($name && $name != $email) { - // Special chars as defined by RFC 822 need to in quoted string (or escaped). - if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) { - $name = '"'.addcslashes($name, '"').'"'; - } - - return "$name <$email>"; - } - - return $email; -} - - -/** - * Format e-mail address - * - * @param string $email E-mail address - * - * @return string Formatted e-mail address - */ -function format_email($email) -{ - $email = trim($email); - $parts = explode('@', $email); - $count = count($parts); - - if ($count > 1) { - $parts[$count-1] = mb_strtolower($parts[$count-1]); - - $email = implode('@', $parts); - } - - return $email; -} - - -/** - * mbstring replacement functions - */ -if (!extension_loaded('mbstring')) -{ - function mb_strlen($str) - { - return strlen($str); - } - - function mb_strtolower($str) - { - return strtolower($str); - } - - function mb_strtoupper($str) - { - return strtoupper($str); - } - - function mb_substr($str, $start, $len=null) - { - return substr($str, $start, $len); - } - - function mb_strpos($haystack, $needle, $offset=0) - { - return strpos($haystack, $needle, $offset); - } - - function mb_strrpos($haystack, $needle, $offset=0) - { - return strrpos($haystack, $needle, $offset); - } -} - -/** - * intl replacement functions - */ - -if (!function_exists('idn_to_utf8')) -{ - function idn_to_utf8($domain, $flags=null) - { - static $idn, $loaded; - - if (!$loaded) { - $idn = new Net_IDNA2(); - $loaded = true; - } - - if ($idn && $domain && preg_match('/(^|\.)xn--/i', $domain)) { - try { - $domain = $idn->decode($domain); - } - catch (Exception $e) { - } - } - return $domain; - } -} - -if (!function_exists('idn_to_ascii')) -{ - function idn_to_ascii($domain, $flags=null) - { - static $idn, $loaded; - - if (!$loaded) { - $idn = new Net_IDNA2(); - $loaded = true; - } - - if ($idn && $domain && preg_match('/[^\x20-\x7E]/', $domain)) { - try { - $domain = $idn->encode($domain); - } - catch (Exception $e) { - } - } - return $domain; - } -} - -/** - * Use PHP5 autoload for dynamic class loading - * - * @todo Make Zend, PEAR etc play with this - * @todo Make our classes conform to a more straight forward CS. - */ -function rcube_autoload($classname) -{ - $filename = preg_replace( - array( - '/Mail_(.+)/', - '/Net_(.+)/', - '/Auth_(.+)/', - '/^html_.+/', - '/^utf8$/', - ), - array( - 'Mail/\\1', - 'Net/\\1', - 'Auth/\\1', - 'html', - 'utf8.class', - ), - $classname - ); - - if ($fp = @fopen("$filename.php", 'r', true)) { - fclose($fp); - include_once "$filename.php"; - return true; - } - - return false; -} - -/** - * Local callback function for PEAR errors - */ -function rcube_pear_error($err) -{ - error_log(sprintf("%s (%s): %s", - $err->getMessage(), - $err->getCode(), - $err->getUserinfo()), 0); -}
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/SQL/mysql.sql
Deleted
@@ -1,25 +0,0 @@ -/** - * libkolab database schema - * - * @version @package_version@ - * @author Thomas Bruederli - * @licence GNU AGPL - **/ - -DROP TABLE IF EXISTS `kolab_cache`; - -CREATE TABLE `kolab_cache` ( - `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL, - `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - PRIMARY KEY(`resource`,`type`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/bin/Date_Recurrence_weekday.diff
Deleted
@@ -1,325 +0,0 @@ ---- Date/Recurrence.php.orig 2012-07-10 19:54:48.000000000 +0200 -+++ Date/Recurrence.php 2012-07-10 19:55:38.000000000 +0200 -@@ -95,6 +95,20 @@ - public $recurData = null; - - /** -+ * BYDAY recurrence number -+ * -+ * @var integer -+ */ -+ public $recurNthDay = null; -+ -+ /** -+ * BYMONTH recurrence data -+ * -+ * @var array -+ */ -+ public $recurMonths = array(); -+ -+ /** - * All the exceptions from recurrence for this event. - * - * @var array -@@ -157,6 +171,44 @@ - } - - /** -+ * -+ * @param integer $nthDay The nth weekday of month to repeat events on -+ */ -+ public function setRecurNthWeekday($nth) -+ { -+ $this->recurNthDay = (int)$nth; -+ } -+ -+ /** -+ * -+ * @return integer The nth weekday of month to repeat events. -+ */ -+ public function getRecurNthWeekday() -+ { -+ return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7); -+ } -+ -+ /** -+ * Specifies the months for yearly (weekday) recurrence -+ * -+ * @param array $months List of months (integers) this event recurs on. -+ */ -+ function setRecurByMonth($months) -+ { -+ $this->recurMonths = (array)$months; -+ } -+ -+ /** -+ * Returns a list of months this yearly event recurs on -+ * -+ * @return array List of months (integers) this event recurs on. -+ */ -+ function getRecurByMonth() -+ { -+ return $this->recurMonths; -+ } -+ -+ /** - * Returns the days this event recurs on. - * - * @return integer A mask consisting of Horde_Date::MASK_* constants -@@ -546,8 +598,13 @@ - $estart = clone $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(); -+ if (isset($this->recurNthDay)) { -+ $nth = $this->recurNthDay; -+ $weekday = log($this->recurData, 2); -+ } else { -+ $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; -@@ -660,8 +717,13 @@ - $estart = clone $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(); -+ if (isset($this->recurNthDay)) { -+ $nth = $this->recurNthDay; -+ $weekday = log($this->recurData, 2); -+ } else { -+ $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; -@@ -894,15 +956,6 @@ - case 'W': - $this->setRecurType(self::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]); -@@ -953,7 +1006,10 @@ - list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d'); - $this->setRecurEnd(new Horde_Date(array('year' => $year, - 'month' => $month, -- 'mday' => $mday))); -+ 'mday' => $mday, -+ 'hour' => 23, -+ 'min' => 59, -+ 'sec' => 59))); - } - } - } -@@ -1049,6 +1105,16 @@ - // Always default the recurInterval to 1. - $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); - -+ $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, -+ ); -+ - switch (Horde_String::upper($rdata['FREQ'])) { - case 'DAILY': - $this->setRecurType(self::RECUR_DAILY); -@@ -1057,15 +1123,6 @@ - case 'WEEKLY': - $this->setRecurType(self::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) { -@@ -1090,6 +1147,10 @@ - case 'MONTHLY': - if (isset($rdata['BYDAY'])) { - $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); -+ if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { -+ $this->setRecurOnDay($maskdays[$m[2]]); -+ $this->setRecurNthWeekday($m[1]); -+ } - } else { - $this->setRecurType(self::RECUR_MONTHLY_DATE); - } -@@ -1100,6 +1161,14 @@ - $this->setRecurType(self::RECUR_YEARLY_DAY); - } elseif (isset($rdata['BYDAY'])) { - $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); -+ if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { -+ $this->setRecurOnDay($maskdays[$m[2]]); -+ $this->setRecurNthWeekday($m[1]); -+ } -+ if ($rdata['BYMONTH']) { -+ $months = explode(',', $rdata['BYMONTH']); -+ $this->setRecurByMonth($months); -+ } - } else { - $this->setRecurType(self::RECUR_YEARLY_DATE); - } -@@ -1163,13 +1232,19 @@ - break; - - case self::RECUR_MONTHLY_WEEKDAY: -- $nth_weekday = (int)($this->start->mday / 7); -- if (($this->start->mday % 7) > 0) { -- $nth_weekday++; -+ if (isset($this->recurNthDay)) { -+ $nth_weekday = $this->recurNthDay; -+ $day_of_week = log($this->recurData, 2); -+ } else { -+ $day_of_week = $this->start->dayOfWeek(); -+ $nth_weekday = (int)($this->start->mday / 7); -+ if (($this->start->mday % 7) > 0) { -+ $nth_weekday++; -+ } - } - $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); - $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval -- . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()]; -+ . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; - break; - - case self::RECUR_YEARLY_DATE: -@@ -1182,15 +1257,22 @@ - break; - - case self::RECUR_YEARLY_WEEKDAY: -- $nth_weekday = (int)($this->start->mday / 7); -- if (($this->start->mday % 7) > 0) { -- $nth_weekday++; -- } -+ if (isset($this->recurNthDay)) { -+ $nth_weekday = $this->recurNthDay; -+ $day_of_week = log($this->recurData, 2); -+ } else { -+ $day_of_week = $this->start->dayOfWeek(); -+ $nth_weekday = (int)($this->start->mday / 7); -+ if (($this->start->mday % 7) > 0) { -+ $nth_weekday++; -+ } -+ } -+ $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month; - $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); - $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval - . ';BYDAY=' - . $nth_weekday -- . $vcaldays[$this->start->dayOfWeek()] -+ . $vcaldays[$day_of_week] - . ';BYMONTH=' . $this->start->month; - break; - } -@@ -1223,6 +1305,21 @@ - - $this->setRecurInterval((int)$hash['interval']); - -+ $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, -+ ); -+ - $parse_day = false; - $set_daymask = false; - $update_month = false; -@@ -1255,11 +1352,9 @@ - - case 'weekday': - $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); -- $nth_weekday = (int)$hash['daynumber']; -- $hash['daynumber'] = 1; -+ $this->setRecurNthWeekday($hash['daynumber']); - $parse_day = true; -- $update_daynumber = true; -- $update_weekday = true; -+ $set_daymask = true; - break; - } - break; -@@ -1297,12 +1392,13 @@ - } - - $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); -- $nth_weekday = (int)$hash['daynumber']; -- $hash['daynumber'] = 1; -+ $this->setRecurNthWeekday($hash['daynumber']); - $parse_day = true; -- $update_month = true; -- $update_daynumber = true; -- $update_weekday = true; -+ $set_daymask = true; -+ -+ if ($hash['month'] && isset($month2number[$hash['month']])) { -+ $this->setRecurByMonth($month2number[$hash['month']]); -+ } - break; - } - } -@@ -1368,21 +1464,6 @@ - - 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']]; - } -@@ -1398,7 +1479,7 @@ - } - - if ($update_weekday) { -- $this->start->setNthWeekday($last_found_day, $nth_weekday); -+ $this->setNthWeekday($nth_weekday); - } - } -
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/bin/Date_last_weekday.diff
Deleted
@@ -1,37 +0,0 @@ ---- Date.php.orig 2012-07-10 19:14:26.000000000 +0200 -+++ Date.php 2012-07-10 19:16:22.000000000 +0200 -@@ -627,16 +627,25 @@ - return; - } - -- $this->_mday = 1; -- $first = $this->dayOfWeek(); -- if ($weekday < $first) { -- $this->_mday = 8 + $weekday - $first; -- } else { -- $this->_mday = $weekday - $first + 1; -+ if ($nth < 0) { // last $weekday of month -+ $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year); -+ $last = $this->dayOfWeek(); -+ $this->_mday += ($weekday - $last); -+ if ($this->_mday > $lastday) -+ $this->_mday -= 7; -+ } -+ else { -+ $this->_mday = 1; -+ $first = $this->dayOfWeek(); -+ if ($weekday < $first) { -+ $this->_mday = 8 + $weekday - $first; -+ } else { -+ $this->_mday = $weekday - $first + 1; -+ } -+ $diff = 7 * $nth - 7; -+ $this->_mday += $diff; -+ $this->_correct(self::MASK_DAY, $diff < 0); - } -- $diff = 7 * $nth - 7; -- $this->_mday += $diff; -- $this->_correct(self::MASK_DAY, $diff < 0); - } - - /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/bin/get_horde_date.sh
Deleted
@@ -1,64 +0,0 @@ -#!/bin/sh - -# Copy Horde_Date_Recurrence classes and dependencies to the given target directory. -# This will create a standalone copy of the classes requried for date recurrence computation. - -SRCDIR=$1 -DESTDIR=$2 -BINDIR=`dirname $0` - -if [ ! -d "$SRCDIR" -o ! -d "$DESTDIR" ]; then - echo "Usage: get_horde_date.sh SRCDIR DESTDIR" - echo "Please enter valid source and destination directories for the Horde libs" - exit 1 -fi - - -# concat Date.php and Date/Utils.php -HORDE_DATE="$DESTDIR/Horde_Date.php" - -echo "<?php - -/** - * This is a concatenated copy of the following files: - * Horde/Date.php, Horde/Date/Utils.php - * Pull the latest version of these files from the PEAR channel of the Horde - * project at http://pear.horde.org by installing the Horde_Date package. - */ -" > $HORDE_DATE - -patch $SRCDIR/Date.php $BINDIR/Date_last_weekday.diff --output=$HORDE_DATE.patched -sed 's/<?php//; s/?>//' $HORDE_DATE.patched >> $HORDE_DATE -sed 's/<?php//; s/?>//' $SRCDIR/Date/Utils.php >> $HORDE_DATE - -# copy and patch Date/Recurrence.php -HORDE_DATE_RECURRENCE="$DESTDIR/Horde_Date_Recurrence.php" - -echo "<?php - -/** - * This is a modified copy of Horde/Date/Recurrence.php - * Pull the latest version of this file from the PEAR channel of the Horde - * project at http://pear.horde.org by installing the Horde_Date package. - */ - -if (!class_exists('Horde_Date')) - require_once(dirname(__FILE__) . '/Horde_Date.php'); - -// minimal required implementation of Horde_Date_Translation to avoid a huge dependency nightmare -class Horde_Date_Translation -{ - function t(\$arg) { return \$arg; } - function ngettext(\$sing, \$plur, \$num) { return (\$num > 1 ? \$plur : \$sing); } -} -" > $HORDE_DATE_RECURRENCE - -patch $SRCDIR/Date/Recurrence.php $BINDIR/Date_Recurrence_weekday.diff --output=$HORDE_DATE_RECURRENCE.patched -sed 's/<?php//; s/?>//' $HORDE_DATE_RECURRENCE.patched >> $HORDE_DATE_RECURRENCE - -# remove dependency to Horde_String -sed -i '' "s/Horde_String::/strto/" $HORDE_DATE_RECURRENCE - -rm $DESTDIR/Horde_Date*.patched - -
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/Horde_Date.php
Deleted
@@ -1,1304 +0,0 @@ -<?php - -/** - * This is a concatenated copy of the following files: - * Horde/Date/Utils.php, Horde/Date/Recurrence.php - * Pull the latest version of these files from the PEAR channel of the Horde - * project at http://pear.horde.org by installing the Horde_Date package. - */ - - -/** - * Horde Date wrapper/logic class, including some calculation - * functions. - * - * @category Horde - * @package Date - * - * @TODO in format(): - * http://php.net/intldateformatter - * - * @TODO on timezones: - * http://trac.agavi.org/ticket/1008 - * http://trac.agavi.org/changeset/3659 - * - * @TODO on switching to PHP::DateTime: - * The only thing ever stored in the database *IS* Unix timestamps. Doing - * anything other than that is unmanageable, yet some frameworks use 'server - * based' times in their systems, simply because they do not bother with - * daylight saving and only 'serve' one timezone! - * - * The second you have to manage 'real' time across timezones then daylight - * saving becomes essential, BUT only on the display side! Since the browser - * only provides a time offset, this is useless and to be honest should simply - * be ignored ( until it is upgraded to provide the correct information ;) - * ). So we need a 'display' function that takes a simple numeric epoch, and a - * separate timezone id into which the epoch is to be 'converted'. My W3C - * mapping works simply because ADOdb then converts that to it's own simple - * offset abbreviation - in my case GMT or BST. As long as DateTime passes the - * full 64 bit number the date range from 100AD is also preserved ( and - * further back if 2 digit years are disabled ). If I want to display the - * 'real' timezone with this 'time' then I just add it in place of ADOdb's - * 'timezone'. I am tempted to simply adjust the ADOdb class to take a - * timezone in place of the simple GMT switch it currently uses. - * - * The return path is just the reverse and simply needs to take the client - * display offset off prior to storage of the UTC epoch. SO we use - * DateTimeZone to get an offset value for the clients timezone and simply add - * or subtract this from a timezone agnostic display on the client end when - * entering new times. - * - * - * It's not really feasible to store dates in specific timezone, as most - * national/local timezones support DST - and that is a pain to support, as - * eg. sorting breaks when some timestamps get repeated. That's why it's - * usually better to store datetimes as either UTC datetime or plain unix - * timestamp. I usually go with the former - using database datetime type. - */ - -/** - * @category Horde - * @package Date - */ -class Horde_Date -{ - const DATE_SUNDAY = 0; - const DATE_MONDAY = 1; - const DATE_TUESDAY = 2; - const DATE_WEDNESDAY = 3; - const DATE_THURSDAY = 4; - const DATE_FRIDAY = 5; - const DATE_SATURDAY = 6; - - const MASK_SUNDAY = 1; - const MASK_MONDAY = 2; - const MASK_TUESDAY = 4; - const MASK_WEDNESDAY = 8; - const MASK_THURSDAY = 16; - const MASK_FRIDAY = 32; - const MASK_SATURDAY = 64; - const MASK_WEEKDAYS = 62; - const MASK_WEEKEND = 65; - const MASK_ALLDAYS = 127; - - const MASK_SECOND = 1; - const MASK_MINUTE = 2; - const MASK_HOUR = 4; - const MASK_DAY = 8; - const MASK_MONTH = 16; - const MASK_YEAR = 32; - const MASK_ALLPARTS = 63; - - const DATE_DEFAULT = 'Y-m-d H:i:s'; - const DATE_JSON = 'Y-m-d\TH:i:s'; - - /** - * Year - * - * @var integer - */ - protected $_year; - - /** - * Month - * - * @var integer - */ - protected $_month; - - /** - * Day - * - * @var integer - */ - protected $_mday; - - /** - * Hour - * - * @var integer - */ - protected $_hour = 0; - - /** - * Minute - * - * @var integer - */ - protected $_min = 0; - - /** - * Second - * - * @var integer - */ - protected $_sec = 0; - - /** - * String representation of the date's timezone. - * - * @var string - */ - protected $_timezone; - - /** - * Default format for __toString() - * - * @var string - */ - protected $_defaultFormat = self::DATE_DEFAULT; - - /** - * Default specs that are always supported. - * @var string - */ - protected static $_defaultSpecs = '%CdDeHImMnRStTyY'; - - /** - * Internally supported strftime() specifiers. - * @var string - */ - protected static $_supportedSpecs = ''; - - /** - * Map of required correction masks. - * - * @see __set() - * - * @var array - */ - protected static $_corrections = array( - 'year' => self::MASK_YEAR, - 'month' => self::MASK_MONTH, - 'mday' => self::MASK_DAY, - 'hour' => self::MASK_HOUR, - 'min' => self::MASK_MINUTE, - 'sec' => self::MASK_SECOND, - ); - - protected $_formatCache = array(); - - /** - * Builds a new date object. If $date contains date parts, use them to - * initialize the object. - * - * Recognized formats: - * - arrays with keys 'year', 'month', 'mday', 'day' - * 'hour', 'min', 'minute', 'sec' - * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec' - * - yyyy-mm-dd hh:mm:ss - * - yyyymmddhhmmss - * - yyyymmddThhmmssZ - * - yyyymmdd (might conflict with unix timestamps between 31 Oct 1966 and - * 03 Mar 1973) - * - unix timestamps - * - anything parsed by strtotime()/DateTime. - * - * @throws Horde_Date_Exception - */ - public function __construct($date = null, $timezone = null) - { - if (!self::$_supportedSpecs) { - self::$_supportedSpecs = self::$_defaultSpecs; - if (function_exists('nl_langinfo')) { - self::$_supportedSpecs .= 'bBpxX'; - } - } - - if (func_num_args() > 2) { - // Handle args in order: year month day hour min sec tz - $this->_initializeFromArgs(func_get_args()); - return; - } - - $this->_initializeTimezone($timezone); - - if (is_null($date)) { - return; - } - - if (is_string($date)) { - $date = trim($date, '"'); - } - - if (is_object($date)) { - $this->_initializeFromObject($date); - } elseif (is_array($date)) { - $this->_initializeFromArray($date); - } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})(?:\.\d+)?(Z?)$/', $date, $parts)) { - $this->_year = (int)$parts[1]; - $this->_month = (int)$parts[2]; - $this->_mday = (int)$parts[3]; - $this->_hour = (int)$parts[4]; - $this->_min = (int)$parts[5]; - $this->_sec = (int)$parts[6]; - if ($parts[7]) { - $this->_initializeTimezone('UTC'); - } - } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $date, $parts) && - $parts[2] > 0 && $parts[2] <= 12 && - $parts[3] > 0 && $parts[3] <= 31) { - $this->_year = (int)$parts[1]; - $this->_month = (int)$parts[2]; - $this->_mday = (int)$parts[3]; - $this->_hour = $this->_min = $this->_sec = 0; - } elseif ((string)(int)$date == $date) { - // Try as a timestamp. - $parts = @getdate($date); - if ($parts) { - $this->_year = $parts['year']; - $this->_month = $parts['mon']; - $this->_mday = $parts['mday']; - $this->_hour = $parts['hours']; - $this->_min = $parts['minutes']; - $this->_sec = $parts['seconds']; - } - } else { - // Use date_create() so we can catch errors with PHP 5.2. Use - // "new DateTime() once we require 5.3. - $parsed = date_create($date); - if (!$parsed) { - throw new Horde_Date_Exception(sprintf(Horde_Date_Translation::t("Failed to parse time string (%s)"), $date)); - } - $parsed->setTimezone(new DateTimeZone(date_default_timezone_get())); - $this->_year = (int)$parsed->format('Y'); - $this->_month = (int)$parsed->format('m'); - $this->_mday = (int)$parsed->format('d'); - $this->_hour = (int)$parsed->format('H'); - $this->_min = (int)$parsed->format('i'); - $this->_sec = (int)$parsed->format('s'); - $this->_initializeTimezone(date_default_timezone_get()); - } - } - - /** - * Returns a simple string representation of the date object - * - * @return string This object converted to a string. - */ - public function __toString() - { - try { - return $this->format($this->_defaultFormat); - } catch (Exception $e) { - return ''; - } - } - - /** - * Returns a DateTime object representing this object. - * - * @return DateTime - */ - public function toDateTime() - { - $date = new DateTime(null, new DateTimeZone($this->_timezone)); - $date->setDate($this->_year, $this->_month, $this->_mday); - $date->setTime($this->_hour, $this->_min, $this->_sec); - return $date; - } - - /** - * Converts a date in the proleptic Gregorian calendar to the no of days - * since 24th November, 4714 B.C. - * - * Returns the no of days since Monday, 24th November, 4714 B.C. in the - * proleptic Gregorian calendar (which is 24th November, -4713 using - * 'Astronomical' year numbering, and 1st January, 4713 B.C. in the - * proleptic Julian calendar). This is also the first day of the 'Julian - * Period' proposed by Joseph Scaliger in 1583, and the number of days - * since this date is known as the 'Julian Day'. (It is not directly - * to do with the Julian calendar, although this is where the name - * is derived from.) - * - * The algorithm is valid for all years (positive and negative), and - * also for years preceding 4714 B.C. - * - * Algorithm is from PEAR::Date_Calc - * - * @author Monte Ohrt <monte@ispi.net> - * @author Pierre-Alain Joye <pajoye@php.net> - * @author Daniel Convissor <danielc@php.net> - * @author C.A. Woodcock <c01234@netcomuk.co.uk> - * - * @return integer The number of days since 24th November, 4714 B.C. - */ - public function toDays() - { - if (function_exists('GregorianToJD')) { - return gregoriantojd($this->_month, $this->_mday, $this->_year); - } - - $day = $this->_mday; - $month = $this->_month; - $year = $this->_year; - - if ($month > 2) { - // March = 0, April = 1, ..., December = 9, - // January = 10, February = 11 - $month -= 3; - } else { - $month += 9; - --$year; - } - - $hb_negativeyear = $year < 0; - $century = intval($year / 100); - $year = $year % 100; - - if ($hb_negativeyear) { - // Subtract 1 because year 0 is a leap year; - // And N.B. that we must treat the leap years as occurring - // one year earlier than they do, because for the purposes - // of calculation, the year starts on 1st March: - // - return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) + - intval((1461 * $year + 1) / 4) + - intval((153 * $month + 2) / 5) + - $day + 1721118; - } else { - return intval(146097 * $century / 4) + - intval(1461 * $year / 4) + - intval((153 * $month + 2) / 5) + - $day + 1721119; - } - } - - /** - * Converts number of days since 24th November, 4714 B.C. (in the proleptic - * Gregorian calendar, which is year -4713 using 'Astronomical' year - * numbering) to Gregorian calendar date. - * - * Returned date belongs to the proleptic Gregorian calendar, using - * 'Astronomical' year numbering. - * - * The algorithm is valid for all years (positive and negative), and - * also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'), - * and so the only limitation is platform-dependent (for 32-bit systems - * the maximum year would be something like about 1,465,190 A.D.). - * - * N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'. - * - * Algorithm is from PEAR::Date_Calc - * - * @author Monte Ohrt <monte@ispi.net> - * @author Pierre-Alain Joye <pajoye@php.net> - * @author Daniel Convissor <danielc@php.net> - * @author C.A. Woodcock <c01234@netcomuk.co.uk> - * - * @param int $days the number of days since 24th November, 4714 B.C. - * @param string $format the string indicating how to format the output - * - * @return Horde_Date A Horde_Date object representing the date. - */ - public static function fromDays($days) - { - if (function_exists('JDToGregorian')) { - list($month, $day, $year) = explode('/', JDToGregorian($days)); - } else { - $days = intval($days); - - $days -= 1721119; - $century = floor((4 * $days - 1) / 146097); - $days = floor(4 * $days - 1 - 146097 * $century); - $day = floor($days / 4); - - $year = floor((4 * $day + 3) / 1461); - $day = floor(4 * $day + 3 - 1461 * $year); - $day = floor(($day + 4) / 4); - - $month = floor((5 * $day - 3) / 153); - $day = floor(5 * $day - 3 - 153 * $month); - $day = floor(($day + 5) / 5); - - $year = $century * 100 + $year; - if ($month < 10) { - $month +=3; - } else { - $month -=9; - ++$year; - } - } - - return new Horde_Date($year, $month, $day); - } - - /** - * Getter for the date and time properties. - * - * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or - * 'sec'. - * - * @return integer The property value, or null if not set. - */ - public function __get($name) - { - if ($name == 'day') { - $name = 'mday'; - } - - return $this->{'_' . $name}; - } - - /** - * Setter for the date and time properties. - * - * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or - * 'sec'. - * @param integer $value The property value. - */ - public function __set($name, $value) - { - if ($name == 'timezone') { - $this->_initializeTimezone($value); - return; - } - if ($name == 'day') { - $name = 'mday'; - } - - if ($name != 'year' && $name != 'month' && $name != 'mday' && - $name != 'hour' && $name != 'min' && $name != 'sec') { - throw new InvalidArgumentException('Undefined property ' . $name); - } - - $down = $value < $this->{'_' . $name}; - $this->{'_' . $name} = $value; - $this->_correct(self::$_corrections[$name], $down); - $this->_formatCache = array(); - } - - /** - * Returns whether a date or time property exists. - * - * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or - * 'sec'. - * - * @return boolen True if the property exists and is set. - */ - public function __isset($name) - { - if ($name == 'day') { - $name = 'mday'; - } - return ($name == 'year' || $name == 'month' || $name == 'mday' || - $name == 'hour' || $name == 'min' || $name == 'sec') && - isset($this->{'_' . $name}); - } - - /** - * Adds a number of seconds or units to this date, returning a new Date - * object. - */ - public function add($factor) - { - $d = clone($this); - if (is_array($factor) || is_object($factor)) { - foreach ($factor as $property => $value) { - $d->$property += $value; - } - } else { - $d->sec += $factor; - } - - return $d; - } - - /** - * Subtracts a number of seconds or units from this date, returning a new - * Horde_Date object. - */ - public function sub($factor) - { - if (is_array($factor)) { - foreach ($factor as &$value) { - $value *= -1; - } - } else { - $factor *= -1; - } - - return $this->add($factor); - } - - /** - * Converts this object to a different timezone. - * - * @param string $timezone The new timezone. - * - * @return Horde_Date This object. - */ - public function setTimezone($timezone) - { - $date = $this->toDateTime(); - $date->setTimezone(new DateTimeZone($timezone)); - $this->_timezone = $timezone; - $this->_year = (int)$date->format('Y'); - $this->_month = (int)$date->format('m'); - $this->_mday = (int)$date->format('d'); - $this->_hour = (int)$date->format('H'); - $this->_min = (int)$date->format('i'); - $this->_sec = (int)$date->format('s'); - $this->_formatCache = array(); - return $this; - } - - /** - * Sets the default date format used in __toString() - * - * @param string $format - */ - public function setDefaultFormat($format) - { - $this->_defaultFormat = $format; - } - - /** - * Returns the day of the week (0 = Sunday, 6 = Saturday) of this date. - * - * @return integer The day of the week. - */ - public function dayOfWeek() - { - if ($this->_month > 2) { - $month = $this->_month - 2; - $year = $this->_year; - } else { - $month = $this->_month + 10; - $year = $this->_year - 1; - } - - $day = (floor((13 * $month - 1) / 5) + - $this->_mday + ($year % 100) + - floor(($year % 100) / 4) + - floor(($year / 100) / 4) - 2 * - floor($year / 100) + 77); - - return (int)($day - 7 * floor($day / 7)); - } - - /** - * Returns the day number of the year (1 to 365/366). - * - * @return integer The day of the year. - */ - public function dayOfYear() - { - return $this->format('z') + 1; - } - - /** - * Returns the week of the month. - * - * @return integer The week number. - */ - public function weekOfMonth() - { - return ceil($this->_mday / 7); - } - - /** - * Returns the week of the year, first Monday is first day of first week. - * - * @return integer The week number. - */ - public function weekOfYear() - { - return $this->format('W'); - } - - /** - * Returns the number of weeks in the given year (52 or 53). - * - * @param integer $year The year to count the number of weeks in. - * - * @return integer $numWeeks The number of weeks in $year. - */ - public static function weeksInYear($year) - { - // Find the last Thursday of the year. - $date = new Horde_Date($year . '-12-31'); - while ($date->dayOfWeek() != self::DATE_THURSDAY) { - --$date->mday; - } - return $date->weekOfYear(); - } - - /** - * Sets the date of this object to the $nth weekday of $weekday. - * - * @param integer $weekday The day of the week (0 = Sunday, etc). - * @param integer $nth The $nth $weekday to set to (defaults to 1). - */ - public function setNthWeekday($weekday, $nth = 1) - { - if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) { - return; - } - - if ($nth < 0) { // last $weekday of month - $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year); - $last = $this->dayOfWeek(); - $this->_mday += ($weekday - $last); - if ($this->_mday > $lastday) - $this->_mday -= 7; - } - else { - $this->_mday = 1; - $first = $this->dayOfWeek(); - if ($weekday < $first) { - $this->_mday = 8 + $weekday - $first; - } else { - $this->_mday = $weekday - $first + 1; - } - $diff = 7 * $nth - 7; - $this->_mday += $diff; - $this->_correct(self::MASK_DAY, $diff < 0); - } - } - - /** - * Is the date currently represented by this object a valid date? - * - * @return boolean Validity, counting leap years, etc. - */ - public function isValid() - { - return ($this->_year >= 0 && $this->_year <= 9999); - } - - /** - * Compares this date to another date object to see which one is - * greater (later). Assumes that the dates are in the same - * timezone. - * - * @param mixed $other The date to compare to. - * - * @return integer == 0 if they are on the same date - * >= 1 if $this is greater (later) - * <= -1 if $other is greater (later) - */ - public function compareDate($other) - { - if (!($other instanceof Horde_Date)) { - $other = new Horde_Date($other); - } - - if ($this->_year != $other->year) { - return $this->_year - $other->year; - } - if ($this->_month != $other->month) { - return $this->_month - $other->month; - } - - return $this->_mday - $other->mday; - } - - /** - * Returns whether this date is after the other. - * - * @param mixed $other The date to compare to. - * - * @return boolean True if this date is after the other. - */ - public function after($other) - { - return $this->compareDate($other) > 0; - } - - /** - * Returns whether this date is before the other. - * - * @param mixed $other The date to compare to. - * - * @return boolean True if this date is before the other. - */ - public function before($other) - { - return $this->compareDate($other) < 0; - } - - /** - * Returns whether this date is the same like the other. - * - * @param mixed $other The date to compare to. - * - * @return boolean True if this date is the same like the other. - */ - public function equals($other) - { - return $this->compareDate($other) == 0; - } - - /** - * Compares this to another date object by time, to see which one - * is greater (later). Assumes that the dates are in the same - * timezone. - * - * @param mixed $other The date to compare to. - * - * @return integer == 0 if they are at the same time - * >= 1 if $this is greater (later) - * <= -1 if $other is greater (later) - */ - public function compareTime($other) - { - if (!($other instanceof Horde_Date)) { - $other = new Horde_Date($other); - } - - if ($this->_hour != $other->hour) { - return $this->_hour - $other->hour; - } - if ($this->_min != $other->min) { - return $this->_min - $other->min; - } - - return $this->_sec - $other->sec; - } - - /** - * Compares this to another date object, including times, to see - * which one is greater (later). Assumes that the dates are in the - * same timezone. - * - * @param mixed $other The date to compare to. - * - * @return integer == 0 if they are equal - * >= 1 if $this is greater (later) - * <= -1 if $other is greater (later) - */ - public function compareDateTime($other) - { - if (!($other instanceof Horde_Date)) { - $other = new Horde_Date($other); - } - - if ($diff = $this->compareDate($other)) { - return $diff; - } - - return $this->compareTime($other); - } - - /** - * Returns number of days between this date and another. - * - * @param Horde_Date $other The other day to diff with. - * - * @return integer The absolute number of days between the two dates. - */ - public function diff($other) - { - return abs($this->toDays() - $other->toDays()); - } - - /** - * Returns the time offset for local time zone. - * - * @param boolean $colon Place a colon between hours and minutes? - * - * @return string Timezone offset as a string in the format +HH:MM. - */ - public function tzOffset($colon = true) - { - return $colon ? $this->format('P') : $this->format('O'); - } - - /** - * Returns the unix timestamp representation of this date. - * - * @return integer A unix timestamp. - */ - public function timestamp() - { - if ($this->_year >= 1970 && $this->_year < 2038) { - return mktime($this->_hour, $this->_min, $this->_sec, - $this->_month, $this->_mday, $this->_year); - } - return $this->format('U'); - } - - /** - * Returns the unix timestamp representation of this date, 12:00am. - * - * @return integer A unix timestamp. - */ - public function datestamp() - { - if ($this->_year >= 1970 && $this->_year < 2038) { - return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year); - } - $date = new DateTime($this->format('Y-m-d')); - return $date->format('U'); - } - - /** - * Formats date and time to be passed around as a short url parameter. - * - * @return string Date and time. - */ - public function dateString() - { - return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday); - } - - /** - * Formats date and time to the ISO format used by JSON. - * - * @return string Date and time. - */ - public function toJson() - { - return $this->format(self::DATE_JSON); - } - - /** - * Formats date and time to the RFC 2445 iCalendar DATE-TIME format. - * - * @param boolean $floating Whether to return a floating date-time - * (without time zone information). - * - * @return string Date and time. - */ - public function toiCalendar($floating = false) - { - if ($floating) { - return $this->format('Ymd\THis'); - } - $dateTime = $this->toDateTime(); - $dateTime->setTimezone(new DateTimeZone('UTC')); - return $dateTime->format('Ymd\THis\Z'); - } - - /** - * Formats time using the specifiers available in date() or in the DateTime - * class' format() method. - * - * To format in languages other than English, use strftime() instead. - * - * @param string $format - * - * @return string Formatted time. - */ - public function format($format) - { - if (!isset($this->_formatCache[$format])) { - $this->_formatCache[$format] = $this->toDateTime()->format($format); - } - return $this->_formatCache[$format]; - } - - /** - * Formats date and time using strftime() format. - * - * @return string strftime() formatted date and time. - */ - public function strftime($format) - { - if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) { - return strftime($format, $this->timestamp()); - } else { - return $this->_strftime($format); - } - } - - /** - * Formats date and time using a limited set of the strftime() format. - * - * @return string strftime() formatted date and time. - */ - protected function _strftime($format) - { - return preg_replace( - array('/%b/e', - '/%B/e', - '/%C/e', - '/%d/e', - '/%D/e', - '/%e/e', - '/%H/e', - '/%I/e', - '/%m/e', - '/%M/e', - '/%n/', - '/%p/e', - '/%R/e', - '/%S/e', - '/%t/', - '/%T/e', - '/%x/e', - '/%X/e', - '/%y/e', - '/%Y/', - '/%%/'), - array('$this->_strftime(Horde_Nls::getLangInfo(constant(\'ABMON_\' . (int)$this->_month)))', - '$this->_strftime(Horde_Nls::getLangInfo(constant(\'MON_\' . (int)$this->_month)))', - '(int)($this->_year / 100)', - 'sprintf(\'%02d\', $this->_mday)', - '$this->_strftime(\'%m/%d/%y\')', - 'sprintf(\'%2d\', $this->_mday)', - 'sprintf(\'%02d\', $this->_hour)', - 'sprintf(\'%02d\', $this->_hour == 0 ? 12 : ($this->_hour > 12 ? $this->_hour - 12 : $this->_hour))', - 'sprintf(\'%02d\', $this->_month)', - 'sprintf(\'%02d\', $this->_min)', - "\n", - '$this->_strftime(Horde_Nls::getLangInfo($this->_hour < 12 ? AM_STR : PM_STR))', - '$this->_strftime(\'%H:%M\')', - 'sprintf(\'%02d\', $this->_sec)', - "\t", - '$this->_strftime(\'%H:%M:%S\')', - '$this->_strftime(Horde_Nls::getLangInfo(D_FMT))', - '$this->_strftime(Horde_Nls::getLangInfo(T_FMT))', - 'substr(sprintf(\'%04d\', $this->_year), -2)', - (int)$this->_year, - '%'), - $format); - } - - /** - * Corrects any over- or underflows in any of the date's members. - * - * @param integer $mask We may not want to correct some overflows. - * @param integer $down Whether to correct the date up or down. - */ - protected function _correct($mask = self::MASK_ALLPARTS, $down = false) - { - if ($mask & self::MASK_SECOND) { - if ($this->_sec < 0 || $this->_sec > 59) { - $mask |= self::MASK_MINUTE; - - $this->_min += (int)($this->_sec / 60); - $this->_sec %= 60; - if ($this->_sec < 0) { - $this->_min--; - $this->_sec += 60; - } - } - } - - if ($mask & self::MASK_MINUTE) { - if ($this->_min < 0 || $this->_min > 59) { - $mask |= self::MASK_HOUR; - - $this->_hour += (int)($this->_min / 60); - $this->_min %= 60; - if ($this->_min < 0) { - $this->_hour--; - $this->_min += 60; - } - } - } - - if ($mask & self::MASK_HOUR) { - if ($this->_hour < 0 || $this->_hour > 23) { - $mask |= self::MASK_DAY; - - $this->_mday += (int)($this->_hour / 24); - $this->_hour %= 24; - if ($this->_hour < 0) { - $this->_mday--; - $this->_hour += 24; - } - } - } - - if ($mask & self::MASK_MONTH) { - $this->_correctMonth($down); - /* When correcting the month, always correct the day too. Months - * have different numbers of days. */ - $mask |= self::MASK_DAY; - } - - if ($mask & self::MASK_DAY) { - while ($this->_mday > 28 && - $this->_mday > Horde_Date_Utils::daysInMonth($this->_month, $this->_year)) { - if ($down) { - $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month + 1, $this->_year) - Horde_Date_Utils::daysInMonth($this->_month, $this->_year); - } else { - $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month, $this->_year); - $this->_month++; - } - $this->_correctMonth($down); - } - while ($this->_mday < 1) { - --$this->_month; - $this->_correctMonth($down); - $this->_mday += Horde_Date_Utils::daysInMonth($this->_month, $this->_year); - } - } - } - - /** - * Corrects the current month. - * - * This cannot be done in _correct() because that would also trigger a - * correction of the day, which would result in an infinite loop. - * - * @param integer $down Whether to correct the date up or down. - */ - protected function _correctMonth($down = false) - { - $this->_year += (int)($this->_month / 12); - $this->_month %= 12; - if ($this->_month < 1) { - $this->_year--; - $this->_month += 12; - } - } - - /** - * Handles args in order: year month day hour min sec tz - */ - protected function _initializeFromArgs($args) - { - $tz = (isset($args[6])) ? array_pop($args) : null; - $this->_initializeTimezone($tz); - - $args = array_slice($args, 0, 6); - $keys = array('year' => 1, 'month' => 1, 'mday' => 1, 'hour' => 0, 'min' => 0, 'sec' => 0); - $date = array_combine(array_slice(array_keys($keys), 0, count($args)), $args); - $date = array_merge($keys, $date); - - $this->_initializeFromArray($date); - } - - protected function _initializeFromArray($date) - { - if (isset($date['year']) && is_string($date['year']) && strlen($date['year']) == 2) { - if ($date['year'] > 70) { - $date['year'] += 1900; - } else { - $date['year'] += 2000; - } - } - - foreach ($date as $key => $val) { - if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) { - $this->{'_'. $key} = (int)$val; - } - } - - // If $date['day'] is present and numeric we may have been passed - // a Horde_Form_datetime array. - if (isset($date['day']) && - (string)(int)$date['day'] == $date['day']) { - $this->_mday = (int)$date['day']; - } - // 'minute' key also from Horde_Form_datetime - if (isset($date['minute']) && - (string)(int)$date['minute'] == $date['minute']) { - $this->_min = (int)$date['minute']; - } - - $this->_correct(); - } - - protected function _initializeFromObject($date) - { - if ($date instanceof DateTime) { - $this->_year = (int)$date->format('Y'); - $this->_month = (int)$date->format('m'); - $this->_mday = (int)$date->format('d'); - $this->_hour = (int)$date->format('H'); - $this->_min = (int)$date->format('i'); - $this->_sec = (int)$date->format('s'); - $this->_initializeTimezone($date->getTimezone()->getName()); - } else { - $is_horde_date = $date instanceof Horde_Date; - foreach (array('year', 'month', 'mday', 'hour', 'min', 'sec') as $key) { - if ($is_horde_date || isset($date->$key)) { - $this->{'_' . $key} = (int)$date->$key; - } - } - if (!$is_horde_date) { - $this->_correct(); - } else { - $this->_initializeTimezone($date->timezone); - } - } - } - - protected function _initializeTimezone($timezone) - { - if (empty($timezone)) { - $timezone = date_default_timezone_get(); - } - $this->_timezone = $timezone; - } - -} - -/** - * @category Horde - * @package Date - */ - -/** - * Horde Date wrapper/logic class, including some calculation - * functions. - * - * @category Horde - * @package Date - */ -class Horde_Date_Utils -{ - /** - * Returns whether a year is a leap year. - * - * @param integer $year The year. - * - * @return boolean True if the year is a leap year. - */ - public static function isLeapYear($year) - { - if (strlen($year) != 4 || preg_match('/\D/', $year)) { - return false; - } - - return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0); - } - - /** - * Returns the date of the year that corresponds to the first day of the - * given week. - * - * @param integer $week The week of the year to find the first day of. - * @param integer $year The year to calculate for. - * - * @return Horde_Date The date of the first day of the given week. - */ - public static function firstDayOfWeek($week, $year) - { - return new Horde_Date(sprintf('%04dW%02d', $year, $week)); - } - - /** - * Returns the number of days in the specified month. - * - * @param integer $month The month - * @param integer $year The year. - * - * @return integer The number of days in the month. - */ - public static function daysInMonth($month, $year) - { - static $cache = array(); - if (!isset($cache[$year][$month])) { - $date = new DateTime(sprintf('%04d-%02d-01', $year, $month)); - $cache[$year][$month] = $date->format('t'); - } - return $cache[$year][$month]; - } - - /** - * Returns a relative, natural language representation of a timestamp - * - * @todo Wider range of values ... maybe future time as well? - * @todo Support minimum resolution parameter. - * - * @param mixed $time The time. Any format accepted by Horde_Date. - * @param string $date_format Format to display date if timestamp is - * more then 1 day old. - * @param string $time_format Format to display time if timestamp is 1 - * day old. - * - * @return string The relative time (i.e. 2 minutes ago) - */ - public static function relativeDateTime($time, $date_format = '%x', - $time_format = '%X') - { - $date = new Horde_Date($time); - - $delta = time() - $date->timestamp(); - if ($delta < 60) { - return sprintf(Horde_Date_Translation::ngettext("%d second ago", "%d seconds ago", $delta), $delta); - } - - $delta = round($delta / 60); - if ($delta < 60) { - return sprintf(Horde_Date_Translation::ngettext("%d minute ago", "%d minutes ago", $delta), $delta); - } - - $delta = round($delta / 60); - if ($delta < 24) { - return sprintf(Horde_Date_Translation::ngettext("%d hour ago", "%d hours ago", $delta), $delta); - } - - if ($delta > 24 && $delta < 48) { - $date = new Horde_Date($time); - return sprintf(Horde_Date_Translation::t("yesterday at %s"), $date->strftime($time_format)); - } - - $delta = round($delta / 24); - if ($delta < 7) { - return sprintf(Horde_Date_Translation::t("%d days ago"), $delta); - } - - if (round($delta / 7) < 5) { - $delta = round($delta / 7); - return sprintf(Horde_Date_Translation::ngettext("%d week ago", "%d weeks ago", $delta), $delta); - } - - // Default to the user specified date format. - return $date->strftime($date_format); - } - - /** - * Tries to convert strftime() formatters to date() formatters. - * - * Unsupported formatters will be removed. - * - * @param string $format A strftime() formatting string. - * - * @return string A date() formatting string. - */ - public static function strftime2date($format) - { - $replace = array( - '/%a/' => 'D', - '/%A/' => 'l', - '/%d/' => 'd', - '/%e/' => 'j', - '/%j/' => 'z', - '/%u/' => 'N', - '/%w/' => 'w', - '/%U/' => '', - '/%V/' => 'W', - '/%W/' => '', - '/%b/' => 'M', - '/%B/' => 'F', - '/%h/' => 'M', - '/%m/' => 'm', - '/%C/' => '', - '/%g/' => '', - '/%G/' => 'o', - '/%y/' => 'y', - '/%Y/' => 'Y', - '/%H/' => 'H', - '/%I/' => 'h', - '/%i/' => 'g', - '/%M/' => 'i', - '/%p/' => 'A', - '/%P/' => 'a', - '/%r/' => 'h:i:s A', - '/%R/' => 'H:i', - '/%S/' => 's', - '/%T/' => 'H:i:s', - '/%X/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(T_FMT))', - '/%z/' => 'O', - '/%Z/' => '', - '/%c/' => '', - '/%D/' => 'm/d/y', - '/%F/' => 'Y-m-d', - '/%s/' => 'U', - '/%x/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(D_FMT))', - '/%n/' => "\n", - '/%t/' => "\t", - '/%%/' => '%' - ); - - return preg_replace(array_keys($replace), array_values($replace), $format); - } - -}
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/Horde_Date_Recurrence.php
Deleted
@@ -1,1673 +0,0 @@ -<?php - -/** - * This is a modified copy of Horde/Date/Recurrence.php - * Pull the latest version of this file from the PEAR channel of the Horde - * project at http://pear.horde.org by installing the Horde_Date package. - */ - -if (!class_exists('Horde_Date')) - require_once(dirname(__FILE__) . '/Horde_Date.php'); - -// minimal required implementation of Horde_Date_Translation to avoid a huge dependency nightmare -class Horde_Date_Translation -{ - function t($arg) { return $arg; } - function ngettext($sing, $plur, $num) { return ($num > 1 ? $plur : $sing); } -} - - -/** - * This file contains the Horde_Date_Recurrence class and according constants. - * - * Copyright 2007-2012 Horde LLC (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.horde.org/licenses/lgpl21. - * - * @category Horde - * @package Date - */ - -/** - * The Horde_Date_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> - * @category Horde - * @package Date - */ -class Horde_Date_Recurrence -{ - /** No Recurrence **/ - const RECUR_NONE = 0; - - /** Recurs daily. */ - const RECUR_DAILY = 1; - - /** Recurs weekly. */ - const RECUR_WEEKLY = 2; - - /** Recurs monthly on the same date. */ - const RECUR_MONTHLY_DATE = 3; - - /** Recurs monthly on the same week day. */ - const RECUR_MONTHLY_WEEKDAY = 4; - - /** Recurs yearly on the same date. */ - const RECUR_YEARLY_DATE = 5; - - /** Recurs yearly on the same day of the year. */ - const RECUR_YEARLY_DAY = 6; - - /** Recurs yearly on the same week day. */ - const RECUR_YEARLY_WEEKDAY = 7; - - /** - * The start time of the event. - * - * @var Horde_Date - */ - public $start; - - /** - * The end date of the recurrence interval. - * - * @var Horde_Date - */ - public $recurEnd = null; - - /** - * The number of recurrences. - * - * @var integer - */ - public $recurCount = null; - - /** - * The type of recurrence this event follows. RECUR_* constant. - * - * @var integer - */ - public $recurType = self::RECUR_NONE; - - /** - * The length of time between recurrences. The time unit depends on the - * recurrence type. - * - * @var integer - */ - public $recurInterval = 1; - - /** - * Any additional recurrence data. - * - * @var integer - */ - public $recurData = null; - - /** - * BYDAY recurrence number - * - * @var integer - */ - public $recurNthDay = null; - - /** - * BYMONTH recurrence data - * - * @var array - */ - public $recurMonths = array(); - - /** - * All the exceptions from recurrence for this event. - * - * @var array - */ - public $exceptions = array(); - - /** - * All the dates this recurrence has been marked as completed. - * - * @var array - */ - public $completions = array(); - - /** - * Constructor. - * - * @param Horde_Date $start Start of the recurring event. - */ - public function __construct($start) - { - $this->start = new Horde_Date($start); - } - - /** - * Resets the class properties. - */ - public function reset() - { - $this->recurEnd = null; - $this->recurCount = null; - $this->recurType = self::RECUR_NONE; - $this->recurInterval = 1; - $this->recurData = null; - $this->exceptions = array(); - $this->completions = array(); - } - - /** - * 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). - */ - public 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. - */ - public function setRecurOnDay($dayMask) - { - $this->recurData = $dayMask; - } - - /** - * - * @param integer $nthDay The nth weekday of month to repeat events on - */ - public function setRecurNthWeekday($nth) - { - $this->recurNthDay = (int)$nth; - } - - /** - * - * @return integer The nth weekday of month to repeat events. - */ - public function getRecurNthWeekday() - { - return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7); - } - - /** - * Specifies the months for yearly (weekday) recurrence - * - * @param array $months List of months (integers) this event recurs on. - */ - function setRecurByMonth($months) - { - $this->recurMonths = (array)$months; - } - - /** - * Returns a list of months this yearly event recurs on - * - * @return array List of months (integers) this event recurs on. - */ - function getRecurByMonth() - { - return $this->recurMonths; - } - - /** - * 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. - */ - public function getRecurOnDays() - { - return $this->recurData; - } - - /** - * Returns whether this event has a specific recurrence type. - * - * @param integer $recurrence RECUR_* constant of the - * recurrence type to check for. - * - * @return boolean True if the event has the specified recurrence type. - */ - public function hasRecurType($recurrence) - { - return ($recurrence == $this->recurType); - } - - /** - * Sets a recurrence type for this event. - * - * @param integer $recurrence A RECUR_* constant. - */ - public function setRecurType($recurrence) - { - $this->recurType = $recurrence; - } - - /** - * Returns recurrence type of this event. - * - * @return integer A RECUR_* constant. - */ - public function getRecurType() - { - return $this->recurType; - } - - /** - * Returns a description of this event's recurring type. - * - * @return string Human readable recurring type. - */ - public function getRecurName() - { - switch ($this->getRecurType()) { - case self::RECUR_NONE: return Horde_Date_Translation::t("No recurrence"); - case self::RECUR_DAILY: return Horde_Date_Translation::t("Daily"); - case self::RECUR_WEEKLY: return Horde_Date_Translation::t("Weekly"); - case self::RECUR_MONTHLY_DATE: - case self::RECUR_MONTHLY_WEEKDAY: return Horde_Date_Translation::t("Monthly"); - case self::RECUR_YEARLY_DATE: - case self::RECUR_YEARLY_DAY: - case self::RECUR_YEARLY_WEEKDAY: return Horde_Date_Translation::t("Yearly"); - } - } - - /** - * Sets the length of time between recurrences of this event. - * - * @param integer $interval The time between recurrences. - */ - public 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. - */ - public function getRecurInterval() - { - return $this->recurInterval; - } - - /** - * Sets the number of recurrences of this event. - * - * @param integer $count The number of recurrences. - */ - public 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. - */ - public 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. - */ - public function hasRecurCount() - { - return isset($this->recurCount); - } - - /** - * Sets the start date of the recurrence interval. - * - * @param Horde_Date $start The recurrence start. - */ - public function setRecurStart($start) - { - $this->start = clone $start; - } - - /** - * Retrieves the start date of the recurrence interval. - * - * @return Horde_Date The recurrence start. - */ - public function getRecurStart() - { - return $this->start; - } - - /** - * Sets the end date of the recurrence interval. - * - * @param Horde_Date $end The recurrence end. - */ - public function setRecurEnd($end) - { - if (!empty($end)) { - // Recurrence counts and end dates are mutually exclusive. - $this->recurCount = null; - $this->recurEnd = clone $end; - } else { - $this->recurEnd = $end; - } - } - - /** - * Retrieves the end date of the recurrence interval. - * - * @return Horde_Date The recurrence end. - */ - public function getRecurEnd() - { - return $this->recurEnd; - } - - /** - * Returns whether this event has a recurrence end. - * - * @return boolean True if this recurrence ends. - */ - public 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|string $after 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. - */ - public function nextRecurrence($after) - { - if (!($after instanceof Horde_Date)) { - $after = new Horde_Date($after); - } else { - $after = clone($after); - } - - // Make sure $after and $this->start are in the same TZ - $after->setTimezone($this->start->timezone); - if ($this->start->compareDateTime($after) >= 0) { - return clone $this->start; - } - - if ($this->recurInterval == 0) { - return false; - } - - switch ($this->getRecurType()) { - case self::RECUR_DAILY: - $diff = $this->start->diff($after); - $recur = ceil($diff / $this->recurInterval); - if ($this->recurCount && $recur >= $this->recurCount) { - return false; - } - - $recur *= $this->recurInterval; - $next = $this->start->add(array('day' => $recur)); - if ((!$this->hasRecurEnd() || - $next->compareDateTime($this->recurEnd) <= 0) && - $next->compareDateTime($after) >= 0) { - return $next; - } - break; - - case self::RECUR_WEEKLY: - if (empty($this->recurData)) { - return false; - } - - $start_week = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'), - $this->start->year); - $start_week->timezone = $this->start->timezone; - $start_week->hour = $this->start->hour; - $start_week->min = $this->start->min; - $start_week->sec = $this->start->sec; - - // Make sure we are not at the ISO-8601 first week of year while - // still in month 12...OR in the ISO-8601 last week of year while - // in month 1 and adjust the year accordingly. - $week = $after->format('W'); - if ($week == 1 && $after->month == 12) { - $theYear = $after->year + 1; - } elseif ($week >= 52 && $after->month == 1) { - $theYear = $after->year - 1; - } else { - $theYear = $after->year; - } - - $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear); - $after_week->timezone = $this->start->timezone; - $after_week_end = clone $after_week; - $after_week_end->mday += 7; - - $diff = $start_week->diff($after_week); - $interval = $this->recurInterval * 7; - $repeats = floor($diff / $interval); - if ($diff % $interval < 7) { - $recur = $diff; - } else { - /** - * If the after_week is not in the first week interval the - * search needs to skip ahead a complete interval. The way it is - * calculated here means that an event that occurs every second - * week on Monday and Wednesday with the event actually starting - * on Tuesday or Wednesday will only have one incidence in the - * first week. - */ - $recur = $interval * ($repeats + 1); - } - - if ($this->hasRecurCount()) { - $recurrences = 0; - /** - * Correct the number of recurrences by the number of events - * that lay between the start of the start week and the - * recurrence start. - */ - $next = clone $start_week; - while ($next->compareDateTime($this->start) < 0) { - if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) { - $recurrences--; - } - ++$next->mday; - } - if ($repeats > 0) { - $weekdays = $this->recurData; - $total_recurrences_per_week = 0; - while ($weekdays > 0) { - if ($weekdays % 2) { - $total_recurrences_per_week++; - } - $weekdays = ($weekdays - ($weekdays % 2)) / 2; - } - $recurrences += $total_recurrences_per_week * $repeats; - } - } - - $next = clone $start_week; - $next->mday += $recur; - while ($next->compareDateTime($after) < 0 && - $next->compareDateTime($after_week_end) < 0) { - if ($this->hasRecurCount() - && $next->compareDateTime($after) < 0 - && $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) { - $recurrences++; - } - ++$next->mday; - } - if ($this->hasRecurCount() && - $recurrences >= $this->recurCount) { - return false; - } - 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; - } - 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 self::RECUR_MONTHLY_DATE: - $start = clone $this->start; - if ($after->compareDateTime($start) < 0) { - $after = clone $start; - } else { - $after = clone $after; - } - - // 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; - } - - // 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; - } - - // 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 self::RECUR_MONTHLY_WEEKDAY: - // Start with the start date of the event. - $estart = clone $this->start; - - // What day of the week, and week of the month, do we recur on? - if (isset($this->recurNthDay)) { - $nth = $this->recurNthDay; - $weekday = log($this->recurData, 2); - } else { - $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; - - $next = clone $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 self::RECUR_YEARLY_DATE: - // Start with the start date of the event. - $estart = clone $this->start; - $after = clone $after; - - 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_Utils::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 self::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 = clone $this->start; - $estart->year += floor($count - 1) * $this->recurInterval; - - // Now add the difference to the required day of year. - $estart->mday += $dayofyear - $estart->dayOfYear(); - - // Add an interval if the estimation was wrong. - if ($estart->compareDate($after) < 0) { - $estart->year += $this->recurInterval; - $estart->mday += $dayofyear - $estart->dayOfYear(); - } - - // We've gone past the end of recurrence; give up. - if ($this->hasRecurEnd() && - $this->recurEnd->compareDateTime($estart) < 0) { - return false; - } - - return $estart; - - case self::RECUR_YEARLY_WEEKDAY: - // Start with the start date of the event. - $estart = clone $this->start; - - // What day of the week, and week of the month, do we recur on? - if (isset($this->recurNthDay)) { - $nth = $this->recurNthDay; - $weekday = log($this->recurData, 2); - } else { - $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; - - $next = clone $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. - */ - public 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($next->add(array('day' => 1))); - } - - 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. - */ - public 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. - */ - public 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. - */ - public 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. - */ - public 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. - */ - public function getExceptions() - { - return $this->exceptions; - } - - /** - * Adds a 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. - */ - public function addCompletion($year, $month, $mday) - { - $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday); - } - - /** - * Deletes a 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. - */ - public 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 a completion exists for the given date. - */ - public 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. - */ - public 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. - */ - public function fromRRule10($rrule) - { - $this->reset(); - - if (!$rrule) { - return; - } - - if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) { - // No recurrence data - event does not recur. - $this->setRecurType(self::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(self::RECUR_DAILY); - break; - - case 'W': - $this->setRecurType(self::RECUR_WEEKLY); - if (!empty($remainder)) { - $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::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, - Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, - Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, - Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, - Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, - Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, - Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY, - ); - $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); - } - break; - - case 'MP': - $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); - break; - - case 'MD': - $this->setRecurType(self::RECUR_MONTHLY_DATE); - break; - - case 'YM': - $this->setRecurType(self::RECUR_YEARLY_DATE); - break; - - case 'YD': - $this->setRecurType(self::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, '#') === 0) { - $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, - 'hour' => 23, - 'min' => 59, - 'sec' => 59))); - } - } - } - - /** - * 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. - */ - public function toRRule10($calendar) - { - switch ($this->recurType) { - case self::RECUR_NONE: - return ''; - - case self::RECUR_DAILY: - $rrule = 'D' . $this->recurInterval; - break; - - case self::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 self::RECUR_MONTHLY_DATE: - $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday); - break; - - case self::RECUR_MONTHLY_WEEKDAY: - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; - } - - $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); - $rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()]; - - break; - - case self::RECUR_YEARLY_DATE: - $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month); - break; - - case self::RECUR_YEARLY_DAY: - $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear(); - break; - - default: - return ''; - } - - if ($this->hasRecurEnd()) { - $recurEnd = clone $this->recurEnd; - return $rrule . ' ' . $calendar->_exportDateTime($recurEnd); - } - - return $rrule . ' #' . (int)$this->getRecurCount(); - } - - /** - * Parses an iCalendar 2.0 recurrence rule. - * - * @link http://rfc.net/rfc2445.html#s4.3.10 - * @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. - */ - public function fromRRule20($rrule) - { - $this->reset(); - - // Parse the recurrence rule into keys and values. - $rdata = array(); - $parts = explode(';', $rrule); - foreach ($parts as $part) { - list($key, $value) = explode('=', $part, 2); - $rdata[strtoupper($key)] = $value; - } - - if (isset($rdata['FREQ'])) { - // Always default the recurInterval to 1. - $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); - - $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, - ); - - switch (strtoupper($rdata['FREQ'])) { - case 'DAILY': - $this->setRecurType(self::RECUR_DAILY); - break; - - case 'WEEKLY': - $this->setRecurType(self::RECUR_WEEKLY); - if (isset($rdata['BYDAY'])) { - $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::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, - Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, - Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, - Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, - Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, - Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, - Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY); - $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); - } - break; - - case 'MONTHLY': - if (isset($rdata['BYDAY'])) { - $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); - if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { - $this->setRecurOnDay($maskdays[$m[2]]); - $this->setRecurNthWeekday($m[1]); - } - } else { - $this->setRecurType(self::RECUR_MONTHLY_DATE); - } - break; - - case 'YEARLY': - if (isset($rdata['BYYEARDAY'])) { - $this->setRecurType(self::RECUR_YEARLY_DAY); - } elseif (isset($rdata['BYDAY'])) { - $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); - if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { - $this->setRecurOnDay($maskdays[$m[2]]); - $this->setRecurNthWeekday($m[1]); - } - if ($rdata['BYMONTH']) { - $months = explode(',', $rdata['BYMONTH']); - $this->setRecurByMonth($months); - } - } else { - $this->setRecurType(self::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, - 'hour' => 23, - 'min' => 59, - 'sec' => 59))); - } - if (isset($rdata['COUNT'])) { - $this->setRecurCount($rdata['COUNT']); - } - } else { - // No recurrence data - event does not recur. - $this->setRecurType(self::RECUR_NONE); - } - } - - /** - * Creates an iCalendar 2.0 recurrence rule. - * - * @link http://rfc.net/rfc2445.html#s4.3.10 - * @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. - */ - public function toRRule20($calendar) - { - switch ($this->recurType) { - case self::RECUR_NONE: - return ''; - - case self::RECUR_DAILY: - $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; - break; - - case self::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 self::RECUR_MONTHLY_DATE: - $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval; - break; - - case self::RECUR_MONTHLY_WEEKDAY: - if (isset($this->recurNthDay)) { - $nth_weekday = $this->recurNthDay; - $day_of_week = log($this->recurData, 2); - } else { - $day_of_week = $this->start->dayOfWeek(); - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; - } - } - $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); - $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval - . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; - break; - - case self::RECUR_YEARLY_DATE: - $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; - break; - - case self::RECUR_YEARLY_DAY: - $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval - . ';BYYEARDAY=' . $this->start->dayOfYear(); - break; - - case self::RECUR_YEARLY_WEEKDAY: - if (isset($this->recurNthDay)) { - $nth_weekday = $this->recurNthDay; - $day_of_week = log($this->recurData, 2); - } else { - $day_of_week = $this->start->dayOfWeek(); - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; - } - } - $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month; - $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); - $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval - . ';BYDAY=' - . $nth_weekday - . $vcaldays[$day_of_week] - . ';BYMONTH=' . $this->start->month; - break; - } - - if ($this->hasRecurEnd()) { - $recurEnd = clone $this->recurEnd; - $rrule .= ';UNTIL=' . $calendar->_exportDateTime($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. - */ - public function fromHash($hash) - { - $this->reset(); - - if (!isset($hash['interval']) || !isset($hash['cycle'])) { - $this->setRecurType(self::RECUR_NONE); - return false; - } - - $this->setRecurInterval((int)$hash['interval']); - - $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, - ); - - $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(self::RECUR_DAILY); - break; - - case 'weekly': - $this->setRecurType(self::RECUR_WEEKLY); - $parse_day = true; - $set_daymask = true; - break; - - case 'monthly': - if (!isset($hash['daynumber'])) { - $this->setRecurType(self::RECUR_NONE); - return false; - } - - switch ($hash['type']) { - case 'daynumber': - $this->setRecurType(self::RECUR_MONTHLY_DATE); - $update_daynumber = true; - break; - - case 'weekday': - $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); - $this->setRecurNthWeekday($hash['daynumber']); - $parse_day = true; - $set_daymask = true; - break; - } - break; - - case 'yearly': - if (!isset($hash['type'])) { - $this->setRecurType(self::RECUR_NONE); - return false; - } - - switch ($hash['type']) { - case 'monthday': - $this->setRecurType(self::RECUR_YEARLY_DATE); - $update_month = true; - $update_daynumber = true; - break; - - case 'yearday': - if (!isset($hash['month'])) { - $this->setRecurType(self::RECUR_NONE); - return false; - } - - $this->setRecurType(self::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(self::RECUR_NONE); - return false; - } - - $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); - $this->setRecurNthWeekday($hash['daynumber']); - $parse_day = true; - $set_daymask = true; - - if ($hash['month'] && isset($month2number[$hash['month']])) { - $this->setRecurByMonth($month2number[$hash['month']]); - } - break; - } - } - - if (isset($hash['range-type']) && isset($hash['range'])) { - switch ($hash['range-type']) { - case 'number': - $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(self::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::DATE_MONDAY, - 'tuesday' => Horde_Date::DATE_TUESDAY, - 'wednesday' => Horde_Date::DATE_WEDNESDAY, - 'thursday' => Horde_Date::DATE_THURSDAY, - 'friday' => Horde_Date::DATE_FRIDAY, - 'saturday' => Horde_Date::DATE_SATURDAY, - 'sunday' => Horde_Date::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) { - if (isset($month2number[$hash['month']])) { - $this->start->month = $month2number[$hash['month']]; - } - } - - if ($update_daynumber) { - if (!isset($hash['daynumber'])) { - $this->setRecurType(self::RECUR_NONE); - return false; - } - - $this->start->mday = $hash['daynumber']; - } - - if ($update_weekday) { - $this->setNthWeekday($nth_weekday); - } - } - - // 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. - */ - public function toHash() - { - if ($this->getRecurType() == self::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 self::RECUR_DAILY: - $hash['cycle'] = 'daily'; - break; - - case self::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 self::RECUR_MONTHLY_DATE: - $hash['cycle'] = 'monthly'; - $hash['type'] = 'daynumber'; - $hash['daynumber'] = $start->mday; - break; - - case self::RECUR_MONTHLY_WEEKDAY: - $hash['cycle'] = 'monthly'; - $hash['type'] = 'weekday'; - $hash['daynumber'] = $start->weekOfMonth(); - $hash['day'] = array ($day2number[$start->dayOfWeek()]); - break; - - case self::RECUR_YEARLY_DATE: - $hash['cycle'] = 'yearly'; - $hash['type'] = 'monthday'; - $hash['daynumber'] = $start->mday; - $hash['month'] = $month2number[$start->month]; - break; - - case self::RECUR_YEARLY_DAY: - $hash['cycle'] = 'yearly'; - $hash['type'] = 'yearday'; - $hash['daynumber'] = $start->dayOfYear(); - break; - - case self::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; - } - - /** - * Returns a simple object suitable for json transport representing this - * object. - * - * Possible properties are: - * - t: type - * - i: interval - * - e: end date - * - c: count - * - d: data - * - co: completions - * - ex: exceptions - * - * @return object A simple object. - */ - public function toJson() - { - $json = new stdClass; - $json->t = $this->recurType; - $json->i = $this->recurInterval; - if ($this->hasRecurEnd()) { - $json->e = $this->recurEnd->toJson(); - } - if ($this->recurCount) { - $json->c = $this->recurCount; - } - if ($this->recurData) { - $json->d = $this->recurData; - } - if ($this->completions) { - $json->co = $this->completions; - } - if ($this->exceptions) { - $json->ex = $this->exceptions; - } - return $json; - } - -}
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/Horde_Kolab_Format_XML_configuration.php
Deleted
@@ -1,76 +0,0 @@ -<?php - -/** - * Kolab XML handler for configuration (KEP:9). - * - * @author Aleksander Machniak <machniak@kolabsys.com> - * - * Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -class Horde_Kolab_Format_XML_configuration extends Horde_Kolab_Format_XML { - /** - * Specific data fields for the configuration object - * - * @var Kolab - */ - var $_fields_specific; - - var $_root_version = 2.1; - - /** - * Constructor - */ - function Horde_Kolab_Format_XML_configuration($params = array()) - { - $this->_root_name = 'configuration'; - - // Specific configuration fields, in kolab format specification order - $this->_fields_specific = array( - 'application' => array ( - 'type' => HORDE_KOLAB_XML_TYPE_STRING, - 'value' => HORDE_KOLAB_XML_VALUE_MAYBE_MISSING, - ), - 'type' => array( - 'type' => HORDE_KOLAB_XML_TYPE_STRING, - 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, - ), - ); - - // Dictionary fields - if (!empty($params['subtype']) && preg_match('/^dictionary.*/', $params['subtype'])) { - $this->_fields_specific = array_merge($this->_fields_specific, array( - 'language' => array ( - 'type' => HORDE_KOLAB_XML_TYPE_STRING, - 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, - ), - 'e' => array( - 'type' => HORDE_KOLAB_XML_TYPE_MULTIPLE, - 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, - 'array' => array( - 'type' => HORDE_KOLAB_XML_TYPE_STRING, - 'value' => HORDE_KOLAB_XML_VALUE_NOT_EMPTY, - ), - ), - )); - } - - parent::Horde_Kolab_Format_XML($params); - - unset($this->_fields_basic['body']); - unset($this->_fields_basic['categories']); - unset($this->_fields_basic['sensitivity']); - } -}
View file
kolab-syncroton-2.1.0.tar.gz/config/main.inc.php.dist -> kolab-syncroton-2.2.0.tar.gz/config/main.inc.php.dist
Changed
@@ -14,12 +14,19 @@ // Type of ActiveSync cache. Supported values: 'db', 'apc' and 'memcache'. // Note: This is only for some additional data like timezones mapping. -// Main ActiveSync cache uses Roundcube SQL database -$rcmail_config['activesync_cache'] = null; +$rcmail_config['activesync_cache'] = 'db'; // lifetime of ActiveSync cache // possible units: s, m, h, d, w -$rcmail_config['activesync_cache_lifetime'] = '10d'; +$rcmail_config['activesync_cache_ttl'] = '1d'; + +// Type of ActiveSync Auth cache. Supported values: 'db', 'apc' and 'memcache'. +// Note: This is only for username canonification map. +$rcmail_config['activesync_auth_cache'] = 'db'; + +// lifetime of ActiveSync Auth cache +// possible units: s, m, h, d, w +$rcmail_config['activesync_auth_cache_ttl'] = '1d'; // List of global addressbooks (GAL) // Note: If empty 'autocomplete_addressbooks' setting will be used
View file
kolab-syncroton-2.1.0.tar.gz/docs/SQL/mysql.initial.sql -> kolab-syncroton-2.2.0.tar.gz/docs/SQL/mysql.initial.sql
Changed
@@ -3,50 +3,9 @@ `name` varchar(255) NOT NULL, `description` varchar(255) DEFAULT NULL, `policy_key` varchar(64) NOT NULL, - `allow_bluetooth` int(11) DEFAULT NULL, - `allow_browser` int(11) DEFAULT NULL, - `allow_camera` int(11) DEFAULT NULL, - `allow_consumer_email` int(11) DEFAULT NULL, - `allow_desktop_sync` int(11) DEFAULT NULL, - `allow_h_t_m_l_email` int(11) DEFAULT NULL, - `allow_internet_sharing` int(11) DEFAULT NULL, - `allow_ir_d_a` int(11) DEFAULT NULL, - `allow_p_o_p_i_m_a_p_email` int(11) DEFAULT NULL, - `allow_remote_desktop` int(11) DEFAULT NULL, - `allow_simple_device_password` int(11) DEFAULT NULL, - `allow_s_m_i_m_e_encryption_algorithm_negotiation` int(11) DEFAULT NULL, - `allow_s_m_i_m_e_soft_certs` int(11) DEFAULT NULL, - `allow_storage_card` int(11) DEFAULT NULL, - `allow_text_messaging` int(11) DEFAULT NULL, - `allow_unsigned_applications` int(11) DEFAULT NULL, - `allow_unsigned_installation_packages` int(11) DEFAULT NULL, - `allow_wifi` int(11) DEFAULT NULL, - `alphanumeric_device_password_required` int(11) DEFAULT NULL, - `approved_application_list` varchar(255) DEFAULT NULL, - `attachments_enabled` int(11) DEFAULT NULL, - `device_password_enabled` int(11) DEFAULT NULL, - `device_password_expiration` int(11) DEFAULT NULL, - `device_password_history` int(11) DEFAULT NULL, - `max_attachment_size` int(11) DEFAULT NULL, - `max_calendar_age_filter` int(11) DEFAULT NULL, - `max_device_password_failed_attempts` int(11) DEFAULT NULL, - `max_email_age_filter` int(11) DEFAULT NULL, - `max_email_body_truncation_size` int(11) DEFAULT NULL, - `max_email_h_t_m_l_body_truncation_size` int(11) DEFAULT NULL, - `max_inactivity_time_device_lock` int(11) DEFAULT NULL, - `min_device_password_complex_characters` int(11) DEFAULT NULL, - `min_device_password_length` int(11) DEFAULT NULL, - `password_recovery_enabled` int(11) DEFAULT NULL, - `require_device_encryption` int(11) DEFAULT NULL, - `require_encrypted_s_m_i_m_e_messages` int(11) DEFAULT NULL, - `require_encryption_s_m_i_m_e_algorithm` int(11) DEFAULT NULL, - `require_manual_sync_when_roaming` int(11) DEFAULT NULL, - `require_signed_s_m_i_m_e_algorithm` int(11) DEFAULT NULL, - `require_signed_s_m_i_m_e_messages` int(11) DEFAULT NULL, - `require_storage_card_encryption` int(11) DEFAULT NULL, - `unapproved_in_r_o_m_application_list` varchar(255) DEFAULT NULL, + `json_policy` blob NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB; +); CREATE TABLE IF NOT EXISTS `syncroton_device` ( `id` varchar(40) NOT NULL, @@ -106,9 +65,9 @@ CREATE TABLE IF NOT EXISTS `syncroton_content` ( `id` varchar(40) NOT NULL, - `device_id` varchar(40) DEFAULT NULL, - `folder_id` varchar(40) DEFAULT NULL, - `contentid` varchar(128) DEFAULT NULL, + `device_id` varchar(40) NOT NULL, + `folder_id` varchar(40) NOT NULL, + `contentid` varchar(128) NOT NULL, `creation_time` datetime DEFAULT NULL, `creation_synckey` int(11) NOT NULL, `is_deleted` tinyint(1) DEFAULT '0', @@ -153,4 +112,4 @@ PRIMARY KEY(`name`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2013011600'); +INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2013040900');
View file
kolab-syncroton-2.2.0.tar.gz/docs/SQL/mysql/2013040700.sql
Added
@@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS `syncroton_policy`; +CREATE TABLE IF NOT EXISTS `syncroton_policy` ( + `id` varchar(40) NOT NULL, + `name` varchar(255) NOT NULL, + `description` varchar(255) DEFAULT NULL, + `policy_key` varchar(64) NOT NULL, + `json_policy` blob NOT NULL, + PRIMARY KEY (`id`) +);
View file
kolab-syncroton-2.2.0.tar.gz/docs/SQL/mysql/2013040900.sql
Added
@@ -0,0 +1,7 @@ +DELETE FROM `syncroton_content` WHERE `device_id` IS NULL; +DELETE FROM `syncroton_content` WHERE `folder_id` IS NULL; +DELETE FROM `syncroton_content` WHERE `contentid` IS NULL; +ALTER TABLE `syncroton_content` + MODIFY `device_id` varchar(40) NOT NULL, + MODIFY `folder_id` varchar(40) NOT NULL, + MODIFY `contentid` varchar(128) NOT NULL;
View file
kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/README.md
Added
@@ -0,0 +1,102 @@ +Roundcube Framework +=================== + +INTRODUCTION +------------ +The Roundcube Framework is the basic library used for the Roundcube Webmail +application. It is an extract of classes providing the core functionality for +an email system. They can be used individually or as package for the following +tasks: + +- IMAP mailbox access with optional caching +- MIME message handling +- Email message creation and sending through SMTP +- General caching utilities using the local database +- Database abstraction using PDO +- VCard parsing and writing + + +INSTALLATION +------------ +Copy all files of this directory to your project or install it in the default +include_path directory of your webserver. Some classes of the framework require +one or multiple of the following [PEAR][pear] libraries: + +- Mail_Mime 1.8.1 or newer +- Mail_mimeDecode 1.5.5 or newer +- Net_SMTP (latest from https://github.com/pear/Net_SMTP/) +- Net_IDNA2 0.1.1 or newer +- Auth_SASL 1.0.6 or newer + + +USAGE +----- +The Roundcube Framework provides a bootstrapping file which registers an +autoloader and sets up the environment necessary for the Roundcube classes. +In order to make use of the framework, simply include the bootstrap.php file +from this directory in your application and start using the classes by simply +instantiating them. + +If you wanna use more complex functionality like IMAP access with database +caching or plugins, the rcube singleton helps you loading the necessary files: + +```php +<?php + +define('RCUBE_CONFIG_DIR', '<path-to-config-directory>'); +define('RCUBE_PLUGINS_DIR', '<path-to-roundcube-plugins-directory'); + +require_once '<path-to-roundcube-framework/bootstrap.php'; + +$rcube = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); +$imap = $rcube->get_storage(); + +// do cool stuff here... + +?> +``` + +LICENSE +------- +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License (**with exceptions +for plugins**) as published by the Free Software Foundation, either +version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see [www.gnu.org/licenses/][gpl]. + +This file forms part of the Roundcube Webmail Framework for which the +following exception is added: Plugins which merely make function calls to the +Roundcube Webmail Framework, and for that purpose include it by reference +shall not be considered modifications of the software. + +If you wish to use this file in another project or create a modified +version that will not be part of the Roundcube Webmail Framework, you +may remove the exception above and use this source code under the +original version of the license. + +For more details about licensing and the exceptions for skins and plugins +see [roundcube.net/license][license] + + +CONTACT +------- +For any bug reports or feature requests please refer to the tracking system +at [trac.roundcube.net][tracreport] or subscribe to our mailing list. +See [roundcube.net/support][support] for details. + +You're always welcome to send a message to the project admins: +hello(at)roundcube(dot)net + + +[pear]: http://pear.php.net +[gpl]: http://www.gnu.org/licenses/ +[license]: http://roundcube.net/license +[support]: http://roundcube.net/support +[tracreport]: http://trac.roundcube.net/wiki/Howto_ReportIssues \ No newline at end of file
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/bootstrap.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/bootstrap.php
Changed
@@ -31,21 +31,30 @@ // critical PHP settings here. Only these, which doesn't provide // an error/warning in the logs later. See (#1486307). 'mbstring.func_overload' => 0, - 'suhosin.session.encrypt' => 0, - 'session.auto_start' => 0, - 'file_uploads' => 1, 'magic_quotes_runtime' => 0, 'magic_quotes_sybase' => 0, // #1488506 ); + +// check these additional ini settings if not called via CLI +if (php_sapi_name() != 'cli') { + $config += array( + 'suhosin.session.encrypt' => 0, + 'file_uploads' => 1, + ); +} + foreach ($config as $optname => $optval) { - if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) { - die("ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n" - ."Check your PHP configuration (including php_admin_flag)."); + $ini_optval = filter_var(ini_get($optname), FILTER_VALIDATE_BOOLEAN); + if ($optval != $ini_optval && @ini_set($optname, $optval) === false) { + $error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n" + . "Check your PHP configuration (including php_admin_flag)."; + if (defined('STDERR')) fwrite(STDERR, $error); else echo $error; + exit(1); } } // framework constants -define('RCUBE_VERSION', '0.9-git'); +define('RCUBE_VERSION', '1.0-git'); define('RCUBE_CHARSET', 'UTF-8'); if (!defined('RCUBE_LIB_DIR')) { @@ -284,32 +293,6 @@ /** - * Remove single and double quotes from a given string - * - * @param string Input value - * - * @return string Dequoted string - */ -function strip_quotes($str) -{ - return str_replace(array("'", '"'), '', $str); -} - - -/** - * Remove new lines characters from given string - * - * @param string $str Input value - * - * @return string Stripped string - */ -function strip_newlines($str) -{ - return preg_replace('/[\r\n]/', '', $str); -} - - -/** * Compose a valid representation of name and e-mail address * * @param string $email E-mail address
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/html.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/html.php
Changed
@@ -21,7 +21,7 @@ * Class for HTML code creation * * @package Framework - * @subpackage HTML + * @subpackage View */ class html { @@ -35,6 +35,7 @@ public static $common_attrib = array('id','class','style','title','align'); public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script'); + /** * Constructor * @@ -217,7 +218,7 @@ $attr = array('src' => $attr); } return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib, - array('src','name','width','height','border','frameborder'))); + array('src','name','width','height','border','frameborder','onload'))); } /** @@ -287,7 +288,7 @@ } // attributes with no value - if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) { + if (in_array($key, array('checked', 'multiple', 'disabled', 'selected', 'autofocus'))) { if ($value) { $attrib_arr[] = $key . '="' . $key . '"'; } @@ -332,7 +333,16 @@ */ public static function quote($str) { - return @htmlspecialchars($str, ENT_COMPAT, RCUBE_CHARSET); + static $flags; + + if (!$flags) { + $flags = ENT_COMPAT; + if (defined('ENT_SUBSTITUTE')) { + $flags |= ENT_SUBSTITUTE; + } + } + + return @htmlspecialchars($str, $flags, RCUBE_CHARSET); } } @@ -340,16 +350,18 @@ /** * Class to create an HTML input field * - * @package HTML + * @package Framework + * @subpackage View */ class html_inputfield extends html { protected $tagname = 'input'; protected $type = 'text'; protected $allowed = array( - 'type','name','value','size','tabindex','autocapitalize', + 'type','name','value','size','tabindex','autocapitalize','required', 'autocomplete','checked','onchange','onclick','disabled','readonly', - 'spellcheck','results','maxlength','src','multiple','placeholder', + 'spellcheck','results','maxlength','src','multiple','accept', + 'placeholder','autofocus', ); /** @@ -395,7 +407,8 @@ /** * Class to create an HTML password field * - * @package HTML + * @package Framework + * @subpackage View */ class html_passwordfield extends html_inputfield { @@ -405,9 +418,9 @@ /** * Class to create an hidden HTML input field * - * @package HTML + * @package Framework + * @subpackage View */ - class html_hiddenfield extends html { protected $tagname = 'input'; @@ -455,7 +468,8 @@ /** * Class to create HTML radio buttons * - * @package HTML + * @package Framework + * @subpackage View */ class html_radiobutton extends html_inputfield { @@ -485,7 +499,8 @@ /** * Class to create HTML checkboxes * - * @package HTML + * @package Framework + * @subpackage View */ class html_checkbox extends html_inputfield { @@ -515,7 +530,8 @@ /** * Class to create an HTML textarea * - * @package HTML + * @package Framework + * @subpackage View */ class html_textarea extends html { @@ -573,7 +589,8 @@ * print $select->show('CH'); * </pre> * - * @package HTML + * @package Framework + * @subpackage View */ class html_select extends html { @@ -638,7 +655,8 @@ /** * Class to build an HTML table * - * @package HTML + * @package Framework + * @subpackage View */ class html_table extends html { @@ -660,6 +678,11 @@ { $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array(); $this->attrib = array_merge($attrib, $default_attrib); + + if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') { + $this->tagname = $attrib['tagname']; + $this->allowed = self::$common_attrib; + } } /** @@ -761,6 +784,11 @@ $index = $this->rowindex; } + // make sure row object exists (#1489094) + if (!$this->rows[$index]) { + $this->rows[$index] = new stdClass; + } + $this->rows[$index]->attrib = $attr; } @@ -798,19 +826,20 @@ if (!empty($this->header)) { $rowcontent = ''; foreach ($this->header as $c => $col) { - $rowcontent .= self::tag('td', $col->attrib, $col->content); + $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content); } - $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)); + $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) : + self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib); } foreach ($this->rows as $r => $row) { $rowcontent = ''; foreach ($row->cells as $c => $col) { - $rowcontent .= self::tag('td', $col->attrib, $col->content); + $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content); } if ($r < $this->rowindex || count($row->cells)) { - $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib); + $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib); } } @@ -819,7 +848,7 @@ } // add <tbody> - $this->content = $thead . self::tag('tbody', null, $tbody); + $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody); unset($this->attrib['cols'], $this->attrib['rowsonly']); return parent::show(); @@ -844,4 +873,22 @@ $this->rowindex = 0; } + /** + * Getter for the corresponding tag name for table row elements + */ + private function _row_tagname() + { + static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div'); + return $row_tagnames[$this->tagname] ?: $row_tagnames['*']; + } + + /** + * Getter for the corresponding tag name for table cell elements + */ + private function _col_tagname() + { + static $col_tagnames = array('table' => 'td', '*' => 'span'); + return $col_tagnames[$this->tagname] ?: $col_tagnames['*']; + } + }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube.php
Changed
@@ -99,20 +99,20 @@ protected $texts; protected $caches = array(); protected $shutdown_functions = array(); - protected $expunge_cache = false; /** * This implements the 'singleton' design pattern * * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants + * @param string Environment name to run (e.g. live, dev, test) * * @return rcube The one and only instance */ - static function get_instance($mode = 0) + static function get_instance($mode = 0, $env = '') { if (!self::$instance) { - self::$instance = new rcube(); + self::$instance = new rcube($env); self::$instance->init($mode); } @@ -123,10 +123,10 @@ /** * Private constructor */ - protected function __construct() + protected function __construct($env = '') { // load configuration - $this->config = new rcube_config; + $this->config = new rcube_config($env); $this->plugins = new rcube_dummy_plugin_api; register_shutdown_function(array($this, 'shutdown')); @@ -258,6 +258,39 @@ /** + * Initialize and get shared cache object + * + * @param string $name Cache identifier + * @param bool $packed Enables/disables data serialization + * + * @return rcube_cache_shared Cache object + */ + public function get_cache_shared($name, $packed=true) + { + $shared_name = "shared_$name"; + + if (!array_key_exists($shared_name, $this->caches)) { + $opt = strtolower($name) . '_cache'; + $type = $this->config->get($opt); + $ttl = $this->config->get($opt . '_ttl'); + + if (!$type) { + // cache is disabled + return $this->caches[$shared_name] = null; + } + + if ($ttl === null) { + $ttl = $this->config->get('shared_cache_ttl', '10d'); + } + + $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed); + } + + return $this->caches[$shared_name]; + } + + + /** * Create SMTP object and connect to server * * @param boolean True if connection should be established @@ -345,6 +378,7 @@ 'auth_pw' => $this->config->get("{$driver}_auth_pw"), 'debug' => (bool) $this->config->get("{$driver}_debug"), 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), + 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), 'timeout' => (int) $this->config->get("{$driver}_timeout"), 'skip_deleted' => (bool) $this->config->get('skip_deleted'), 'driver' => $driver, @@ -405,6 +439,7 @@ $sess_domain = $this->config->get('session_domain'); $sess_path = $this->config->get('session_path'); $lifetime = $this->config->get('session_lifetime', 0) * 60; + $is_secure = $this->config->get('use_https') || rcube_utils::https_check(); // set session domain if ($sess_domain) { @@ -419,45 +454,55 @@ ini_set('session.gc_maxlifetime', $lifetime * 2); } - ini_set('session.cookie_secure', rcube_utils::https_check()); + ini_set('session.cookie_secure', $is_secure); ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); - ini_set('session.serialize_handler', 'php'); ini_set('session.cookie_httponly', 1); // use database for storing session data $this->session = new rcube_session($this->get_dbh(), $this->config); - $this->session->register_gc_handler(array($this, 'temp_gc')); - $this->session->register_gc_handler(array($this, 'cache_gc')); - + $this->session->register_gc_handler(array($this, 'gc')); $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); $this->session->set_ip_check($this->config->get('ip_check')); // start PHP session (if not in CLI mode) if ($_SERVER['REMOTE_ADDR']) { - session_start(); + $this->session->start(); } } /** + * Garbage collector - cache/temp cleaner + */ + public function gc() + { + rcube_cache::gc(); + rcube_cache_shared::gc(); + $this->get_storage()->cache_gc(); + + $this->gc_temp(); + } + + + /** * Garbage collector function for temp files. * Remove temp files older than two days */ - public function temp_gc() + public function gc_temp() { $tmp = unslashify($this->config->get('temp_dir')); $expire = time() - 172800; // expire in 48 hours if ($tmp && ($dir = opendir($tmp))) { while (($fname = readdir($dir)) !== false) { - if ($fname{0} == '.') { + if ($fname[0] == '.') { continue; } - if (filemtime($tmp.'/'.$fname) < $expire) { + if (@filemtime($tmp.'/'.$fname) < $expire) { @unlink($tmp.'/'.$fname); } } @@ -468,14 +513,21 @@ /** - * Garbage collector for cache entries. - * Set flag to expunge caches on shutdown + * Runs garbage collector with probability based on + * session settings. This is intended for environments + * without a session. */ - public function cache_gc() + public function gc_run() { - // because this gc function is called before storage is initialized, - // we just set a flag to expunge storage cache on shutdown. - $this->expunge_cache = true; + $probability = (int) ini_get('session.gc_probability'); + $divisor = (int) ini_get('session.gc_divisor'); + + if ($divisor > 0 && $probability > 0) { + $random = mt_rand(1, $divisor); + if ($random <= $probability) { + $this->gc(); + } + } } @@ -859,6 +911,14 @@ call_user_func($function); } + // write session data as soon as possible and before + // closing database connection, don't do this before + // registered shutdown functions, they may need the session + // Note: this will run registered gc handlers (ie. cache gc) + if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { + $this->session->write_close(); + } + if (is_object($this->smtp)) { $this->smtp->disconnect(); } @@ -870,9 +930,6 @@ } if (is_object($this->storage)) { - if ($this->expunge_cache) { - $this->storage->expunge_cache(); - } $this->storage->close(); } } @@ -1073,14 +1130,20 @@ { // handle PHP exceptions if (is_object($arg) && is_a($arg, 'Exception')) { - $err = array( + $arg = array( 'type' => 'php', 'code' => $arg->getCode(), 'line' => $arg->getLine(), 'file' => $arg->getFile(), 'message' => $arg->getMessage(), ); - $arg = $err; + } + else if (is_string($arg)) { + $arg = array('message' => $arg, 'type' => 'php'); + } + + if (empty($arg['code'])) { + $arg['code'] = 500; } // installer @@ -1090,14 +1153,24 @@ return; } - if (($log || $terminate) && $arg['type'] && $arg['message']) { + $cli = php_sapi_name() == 'cli'; + + if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) { $arg['fatal'] = $terminate; self::log_bug($arg); } - // display error page and terminate script - if ($terminate && is_object(self::$instance->output)) { - self::$instance->output->raise_error($arg['code'], $arg['message']); + // terminate script + if ($terminate) { + // display error page + if (is_object(self::$instance->output)) { + self::$instance->output->raise_error($arg['code'], $arg['message']); + } + else if ($cli) { + fwrite(STDERR, 'ERROR: ' . $arg['message']); + } + + exit(1); } } @@ -1136,7 +1209,7 @@ if (!self::write_log('errors', $log_entry)) { // send error to PHPs error handler if write_log didn't succeed - trigger_error($arg_arr['message']); + trigger_error($arg_arr['message'], E_USER_WARNING); } } @@ -1274,6 +1347,188 @@ return $_SESSION['language']; } } + + /** + * Unique Message-ID generator. + * + * @return string Message-ID + */ + public function gen_message_id() + { + $local_part = md5(uniqid('rcube'.mt_rand(), true)); + $domain_part = $this->user->get_username('domain'); + + // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) + if (!preg_match('/\.[a-z]+$/i', $domain_part)) { + foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { + $host = preg_replace('/:[0-9]+$/', '', $host); + if ($host && preg_match('/\.[a-z]+$/i', $host)) { + $domain_part = $host; + } + } + } + + return sprintf('<%s@%s>', $local_part, $domain_part); + } + + /** + * Send the given message using the configured method. + * + * @param object $message Reference to Mail_MIME object + * @param string $from Sender address string + * @param array $mailto Array of recipient address strings + * @param array $error SMTP error array (reference) + * @param string $body_file Location of file with saved message body (reference), + * used when delay_file_io is enabled + * @param array $options SMTP options (e.g. DSN request) + * + * @return boolean Send status. + */ + public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null) + { + $plugin = $this->plugins->exec_hook('message_before_send', array( + 'message' => $message, + 'from' => $from, + 'mailto' => $mailto, + 'options' => $options, + )); + + $from = $plugin['from']; + $mailto = $plugin['mailto']; + $options = $plugin['options']; + $message = $plugin['message']; + $headers = $message->headers(); + + // send thru SMTP server using custom SMTP library + if ($this->config->get('smtp_server')) { + // generate list of recipients + $a_recipients = array($mailto); + + if (strlen($headers['Cc'])) + $a_recipients[] = $headers['Cc']; + if (strlen($headers['Bcc'])) + $a_recipients[] = $headers['Bcc']; + + // clean Bcc from header for recipients + $send_headers = $headers; + unset($send_headers['Bcc']); + // here too, it because txtHeaders() below use $message->_headers not only $send_headers + unset($message->_headers['Bcc']); + + $smtp_headers = $message->txtHeaders($send_headers, true); + + if ($message->getParam('delay_file_io')) { + // use common temp dir + $temp_dir = $this->config->get('temp_dir'); + $body_file = tempnam($temp_dir, 'rcmMsg'); + if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$mime_result->getMessage()), + TRUE, FALSE); + return false; + } + $msg_body = fopen($body_file, 'r'); + } + else { + $msg_body = $message->get(); + } + + // send message + if (!is_object($this->smtp)) { + $this->smtp_init(true); + } + + $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); + $response = $this->smtp->get_response(); + $error = $this->smtp->get_error(); + + // log error + if (!$sent) { + self::raise_error(array('code' => 800, 'type' => 'smtp', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE); + } + } + // send mail using PHP's mail() function + else { + // unset some headers because they will be added by the mail() function + $headers_enc = $message->headers($headers); + $headers_php = $message->_headers; + unset($headers_php['To'], $headers_php['Subject']); + + // reset stored headers and overwrite + $message->_headers = array(); + $header_str = $message->txtHeaders($headers_php); + + // #1485779 + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) { + $headers_enc['To'] = implode(', ', $m[1]); + } + } + + $msg_body = $message->get(); + + if (PEAR::isError($msg_body)) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$msg_body->getMessage()), + TRUE, FALSE); + } + else { + $delim = $this->config->header_delimiter(); + $to = $headers_enc['To']; + $subject = $headers_enc['Subject']; + $header_str = rtrim($header_str); + + if ($delim != "\r\n") { + $header_str = str_replace("\r\n", $delim, $header_str); + $msg_body = str_replace("\r\n", $delim, $msg_body); + $to = str_replace("\r\n", $delim, $to); + $subject = str_replace("\r\n", $delim, $subject); + } + + if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN)) + $sent = mail($to, $subject, $msg_body, $header_str); + else + $sent = mail($to, $subject, $msg_body, $header_str, "-f$from"); + } + } + + if ($sent) { + $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body)); + + // remove MDN headers after sending + unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); + + // get all recipients + if ($headers['Cc']) + $mailto .= $headers['Cc']; + if ($headers['Bcc']) + $mailto .= $headers['Bcc']; + if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m)) + $mailto = implode(', ', array_unique($m[1])); + + if ($this->config->get('smtp_log')) { + self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", + $this->user->get_username(), + $_SERVER['REMOTE_ADDR'], + $mailto, + !empty($response) ? join('; ', $response) : '')); + } + } + + if (is_resource($msg_body)) { + fclose($msg_body); + } + + $message->_headers = array(); + $message->headers($headers); + + return $sent; + } + }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_addressbook.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_addressbook.php
Changed
@@ -309,9 +309,14 @@ * List all active contact groups of this source * * @param string Optional search string to match group name + * @param int Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * * @return array Indexed list of contact groups, each a hash array */ - function list_groups($search = null) + function list_groups($search = null, $mode = 0) { /* empty for address books don't supporting groups */ return array(); @@ -370,9 +375,10 @@ /** * Add the given contact records the a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be added - * @return int Number of contacts added + * @param string Group identifier + * @param array|string List of contact identifiers to be added + * + * @return int Number of contacts added */ function add_to_group($group_id, $ids) { @@ -383,9 +389,10 @@ /** * Remove the given contact records from a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be removed - * @return int Number of deleted group members + * @param string Group identifier + * @param array|string List of contact identifiers to be removed + * + * @return int Number of deleted group members */ function remove_from_group($group_id, $ids) { @@ -425,7 +432,7 @@ $out = array_merge($out, (array)$values); } else { - list($f, $type) = explode(':', $c); + list(, $type) = explode(':', $c); $out[$type] = array_merge((array)$out[$type], (array)$values); } } @@ -524,6 +531,21 @@ } /** + * Create a unique key for sorting contacts + */ + public static function compose_contact_key($contact, $sort_col) + { + $key = $contact[$sort_col] . ':' . $contact['sourceid']; + + // add email to a key to not skip contacts with the same name (#1488375) + if (!empty($contact['email'])) { + $key .= ':' . implode(':', (array)$contact['email']); + } + + return $key; + } + + /** * Compare search value with contact data * * @param string $colname Data name
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_base_replacer.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_base_replacer.php
Changed
@@ -21,7 +21,7 @@ * using a predefined base * * @package Framework - * @subpackage Core + * @subpackage Utils * @author Thomas Bruederli <roundcube@gmail.com> */ class rcube_base_replacer @@ -44,8 +44,8 @@ public function replace($body) { return preg_replace_callback(array( - '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui', - '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui', + '/(src|background|href)=(["\']?)([^"\'\s>]+)(\2|\s|>)/i', + '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/i', ), array($this, 'callback'), $body); }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_browser.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_browser.php
Changed
@@ -20,7 +20,7 @@ * Provide details about the client's browser based on the User-Agent header * * @package Framework - * @subpackage Core + * @subpackage Utils */ class rcube_browser {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_cache.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_cache.php
Changed
@@ -38,6 +38,7 @@ private $type; private $userid; private $prefix; + private $table; private $ttl; private $packed; private $index; @@ -71,8 +72,9 @@ $this->db = function_exists('apc_exists'); // APC 3.1.4 required } else { - $this->type = 'db'; - $this->db = $rcube->get_dbh(); + $this->type = 'db'; + $this->db = $rcube->get_dbh(); + $this->table = $this->db->table_name('cache'); } // convert ttl string to seconds @@ -145,7 +147,7 @@ */ function write($key, $data) { - return $this->write_record($key, $this->packed ? serialize($data) : $data); + return $this->write_record($key, $this->serialize($data)); } @@ -192,20 +194,31 @@ */ function expunge() { - if ($this->type == 'db' && $this->db) { + if ($this->type == 'db' && $this->db && $this->ttl) { $this->db->query( - "DELETE FROM ".$this->db->table_name('cache'). + "DELETE FROM ".$this->table. " WHERE user_id = ?". " AND cache_key LIKE ?". - " AND " . $this->db->unixtimestamp('created')." < ?", + " AND expires < " . $this->db->now(), $this->userid, - $this->prefix.'.%', - time() - $this->ttl); + $this->prefix.'.%'); } } /** + * Remove expired records of all caches + */ + static function gc() + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->query("DELETE FROM " . $db->table_name('cache') . " WHERE expires < " . $db->now()); + } + + + /** * Writes the cache back to the DB. */ function close() @@ -219,7 +232,7 @@ if ($this->cache_changes[$key]) { // Make sure we're not going to write unchanged data // by comparing current md5 sum with the sum calculated on DB read - $data = $this->packed ? serialize($data) : $data; + $data = $this->serialize($data); if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) { $this->write_record($key, $data); @@ -255,7 +268,7 @@ if ($data) { $md5sum = md5($data); - $data = $this->packed ? unserialize($data) : $data; + $data = $this->unserialize($data); if ($nostore) { return $data; @@ -271,7 +284,7 @@ else { $sql_result = $this->db->limitquery( "SELECT data, cache_key". - " FROM ".$this->db->table_name('cache'). + " FROM " . $this->table. " WHERE user_id = ?". " AND cache_key = ?". // for better performance we allow more records for one key @@ -283,7 +296,7 @@ $key = substr($sql_arr['cache_key'], strlen($this->prefix)+1); $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null; if ($sql_arr['data']) { - $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data']; + $data = $this->unserialize($sql_arr['data']); } if ($nostore) { @@ -326,7 +339,7 @@ // Remove NULL rows (here we don't need to check if the record exist) if ($data == 'N;') { $this->db->query( - "DELETE FROM ".$this->db->table_name('cache'). + "DELETE FROM " . $this->table. " WHERE user_id = ?". " AND cache_key = ?", $this->userid, $key); @@ -337,8 +350,10 @@ // update existing cache record if ($key_exists) { $result = $this->db->query( - "UPDATE ".$this->db->table_name('cache'). - " SET created = ". $this->db->now().", data = ?". + "UPDATE " . $this->table. + " SET created = " . $this->db->now(). + ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL'). + ", data = ?". " WHERE user_id = ?". " AND cache_key = ?", $data, $this->userid, $key); @@ -348,9 +363,9 @@ // for better performance we allow more records for one key // so, no need to check if record exist (see rcube_cache::read_record()) $result = $this->db->query( - "INSERT INTO ".$this->db->table_name('cache'). - " (created, user_id, cache_key, data)". - " VALUES (".$this->db->now().", ?, ?, ?)", + "INSERT INTO " . $this->table. + " (created, expires, user_id, cache_key, data)". + " VALUES (" . $this->db->now() . ", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?, ?)", $this->userid, $key, $data); } @@ -364,7 +379,6 @@ * @param string $key Cache key name or pattern * @param boolean $prefix_mode Enable it to clear all keys starting * with prefix specified in $key - * */ private function remove_record($key=null, $prefix_mode=false) { @@ -412,7 +426,7 @@ } $this->db->query( - "DELETE FROM ".$this->db->table_name('cache'). + "DELETE FROM " . $this->table. " WHERE user_id = ?" . $where, $this->userid); } @@ -553,4 +567,28 @@ // This way each cache will have its own index return sprintf('%d:%s%s', $this->userid, $this->prefix, 'INDEX'); } + + /** + * Serializes data for storing + */ + private function serialize($data) + { + if ($this->type == 'db') { + return $this->db->encode($data, $this->packed); + } + + return $this->packed ? serialize($data) : $data; + } + + /** + * Unserializes serialized data + */ + private function unserialize($data) + { + if ($this->type == 'db') { + return $this->db->decode($data, $this->packed); + } + + return $this->packed ? @unserialize($data) : $data; + } }
View file
kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_cache_shared.php
Added
@@ -0,0 +1,581 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2011-2013, The Roundcube Dev Team | + | Copyright (C) 2011-2013, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Shared (cross-user) caching engine | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + +-----------------------------------------------------------------------+ +*/ + + +/** + * Interface class for accessing Roundcube shared cache + * + * @package Framework + * @subpackage Cache + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + */ +class rcube_cache_shared +{ + /** + * Instance of database handler + * + * @var rcube_db|Memcache|bool + */ + private $db; + private $type; + private $prefix; + private $ttl; + private $packed; + private $index; + private $table; + private $cache = array(); + private $cache_changes = array(); + private $cache_sums = array(); + + + /** + * Object constructor. + * + * @param string $type Engine type ('db' or 'memcache' or 'apc') + * @param string $prefix Key name prefix + * @param string $ttl Expiration time of memcache/apc items + * @param bool $packed Enables/disabled data serialization. + * It's possible to disable data serialization if you're sure + * stored data will be always a safe string + */ + function __construct($type, $prefix='', $ttl=0, $packed=true) + { + $rcube = rcube::get_instance(); + $type = strtolower($type); + + if ($type == 'memcache') { + $this->type = 'memcache'; + $this->db = $rcube->get_memcache(); + } + else if ($type == 'apc') { + $this->type = 'apc'; + $this->db = function_exists('apc_exists'); // APC 3.1.4 required + } + else { + $this->type = 'db'; + $this->db = $rcube->get_dbh(); + $this->table = $this->db->table_name('cache_shared'); + } + + // convert ttl string to seconds + $ttl = get_offset_sec($ttl); + if ($ttl > 2592000) $ttl = 2592000; + + $this->ttl = $ttl; + $this->packed = $packed; + $this->prefix = $prefix; + } + + + /** + * Returns cached value. + * + * @param string $key Cache key name + * + * @return mixed Cached value + */ + function get($key) + { + if (!array_key_exists($key, $this->cache)) { + return $this->read_record($key); + } + + return $this->cache[$key]; + } + + + /** + * Sets (add/update) value in cache. + * + * @param string $key Cache key name + * @param mixed $data Cache data + */ + function set($key, $data) + { + $this->cache[$key] = $data; + $this->cache_changed = true; + $this->cache_changes[$key] = true; + } + + + /** + * Returns cached value without storing it in internal memory. + * + * @param string $key Cache key name + * + * @return mixed Cached value + */ + function read($key) + { + if (array_key_exists($key, $this->cache)) { + return $this->cache[$key]; + } + + return $this->read_record($key, true); + } + + + /** + * Sets (add/update) value in cache and immediately saves + * it in the backend, no internal memory will be used. + * + * @param string $key Cache key name + * @param mixed $data Cache data + * + * @param boolean True on success, False on failure + */ + function write($key, $data) + { + return $this->write_record($key, $this->serialize($data)); + } + + + /** + * Clears the cache. + * + * @param string $key Cache key name or pattern + * @param boolean $prefix_mode Enable it to clear all keys starting + * with prefix specified in $key + */ + function remove($key=null, $prefix_mode=false) + { + // Remove all keys + if ($key === null) { + $this->cache = array(); + $this->cache_changed = false; + $this->cache_changes = array(); + $this->cache_sums = array(); + } + // Remove keys by name prefix + else if ($prefix_mode) { + foreach (array_keys($this->cache) as $k) { + if (strpos($k, $key) === 0) { + $this->cache[$k] = null; + $this->cache_changes[$k] = false; + unset($this->cache_sums[$k]); + } + } + } + // Remove one key by name + else { + $this->cache[$key] = null; + $this->cache_changes[$key] = false; + unset($this->cache_sums[$key]); + } + + // Remove record(s) from the backend + $this->remove_record($key, $prefix_mode); + } + + + /** + * Remove cache records older than ttl + */ + function expunge() + { + if ($this->type == 'db' && $this->db && $this->ttl) { + $this->db->query( + "DELETE FROM " . $this->table + . " WHERE cache_key LIKE ?" + . " AND expires < " . $this->db->now(), + $this->prefix . '.%'); + } + } + + + /** + * Remove expired records of all caches + */ + static function gc() + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->query("DELETE FROM " . $db->table_name('cache_shared') . " WHERE expires < " . $db->now()); + } + + + /** + * Writes the cache back to the DB. + */ + function close() + { + if (!$this->cache_changed) { + return; + } + + foreach ($this->cache as $key => $data) { + // The key has been used + if ($this->cache_changes[$key]) { + // Make sure we're not going to write unchanged data + // by comparing current md5 sum with the sum calculated on DB read + $data = $this->serialize($data); + + if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) { + $this->write_record($key, $data); + } + } + } + + $this->write_index(); + } + + + /** + * Reads cache entry. + * + * @param string $key Cache key name + * @param boolean $nostore Enable to skip in-memory store + * + * @return mixed Cached value + */ + private function read_record($key, $nostore=false) + { + if (!$this->db) { + return null; + } + + if ($this->type != 'db') { + if ($this->type == 'memcache') { + $data = $this->db->get($this->ckey($key)); + } + else if ($this->type == 'apc') { + $data = apc_fetch($this->ckey($key)); + } + + if ($data) { + $md5sum = md5($data); + $data = $this->unserialize($data); + + if ($nostore) { + return $data; + } + + $this->cache_sums[$key] = $md5sum; + $this->cache[$key] = $data; + } + else { + $this->cache[$key] = null; + } + } + else { + $sql_result = $this->db->limitquery( + "SELECT data, cache_key". + " FROM " . $this->table . + " WHERE cache_key = ?". + // for better performance we allow more records for one key + // get the newer one + " ORDER BY created DESC", + 0, 1, $this->prefix . '.' . $key); + + if ($sql_arr = $this->db->fetch_assoc($sql_result)) { + $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null; + if ($sql_arr['data']) { + $data = $this->unserialize($sql_arr['data']); + } + + if ($nostore) { + return $data; + } + + $this->cache[$key] = $data; + $this->cache_sums[$key] = $md5sum; + } + else { + $this->cache[$key] = null; + } + } + + return $this->cache[$key]; + } + + + /** + * Writes single cache record into DB. + * + * @param string $key Cache key name + * @param mxied $data Serialized cache data + * + * @param boolean True on success, False on failure + */ + private function write_record($key, $data) + { + if (!$this->db) { + return false; + } + + if ($this->type == 'memcache' || $this->type == 'apc') { + return $this->add_record($this->ckey($key), $data); + } + + $key_exists = array_key_exists($key, $this->cache_sums); + $key = $this->prefix . '.' . $key; + + // Remove NULL rows (here we don't need to check if the record exist) + if ($data == 'N;') { + $this->db->query("DELETE FROM " . $this->table . " WHERE cache_key = ?", $key); + return true; + } + + // update existing cache record + if ($key_exists) { + $result = $this->db->query( + "UPDATE " . $this->table . + " SET created = " . $this->db->now() . + ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . + ", data = ?". + " WHERE cache_key = ?", + $data, $key); + } + // add new cache record + else { + // for better performance we allow more records for one key + // so, no need to check if record exist (see rcube_cache::read_record()) + $result = $this->db->query( + "INSERT INTO ".$this->table. + " (created, expires, cache_key, data)". + " VALUES (".$this->db->now().", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?)", + $key, $data); + } + + return $this->db->affected_rows($result); + } + + + /** + * Deletes the cache record(s). + * + * @param string $key Cache key name or pattern + * @param boolean $prefix_mode Enable it to clear all keys starting + * with prefix specified in $key + */ + private function remove_record($key=null, $prefix_mode=false) + { + if (!$this->db) { + return; + } + + if ($this->type != 'db') { + $this->load_index(); + + // Remove all keys + if ($key === null) { + foreach ($this->index as $key) { + $this->delete_record($key, false); + } + $this->index = array(); + } + // Remove keys by name prefix + else if ($prefix_mode) { + foreach ($this->index as $k) { + if (strpos($k, $key) === 0) { + $this->delete_record($k); + } + } + } + // Remove one key by name + else { + $this->delete_record($key); + } + + return; + } + + // Remove all keys (in specified cache) + if ($key === null) { + $where = " WHERE cache_key LIKE " . $this->db->quote($this->prefix.'.%'); + } + // Remove keys by name prefix + else if ($prefix_mode) { + $where = " WHERE cache_key LIKE " . $this->db->quote($this->prefix.'.'.$key.'%'); + } + // Remove one key by name + else { + $where = " WHERE cache_key = " . $this->db->quote($this->prefix.'.'.$key); + } + + $this->db->query("DELETE FROM " . $this->table . $where); + } + + + /** + * Adds entry into memcache/apc DB. + * + * @param string $key Cache key name + * @param mxied $data Serialized cache data + * @param bollean $index Enables immediate index update + * + * @param boolean True on success, False on failure + */ + private function add_record($key, $data, $index=false) + { + if ($this->type == 'memcache') { + $result = $this->db->replace($key, $data, MEMCACHE_COMPRESSED, $this->ttl); + if (!$result) { + $result = $this->db->set($key, $data, MEMCACHE_COMPRESSED, $this->ttl); + } + } + else if ($this->type == 'apc') { + if (apc_exists($key)) { + apc_delete($key); + } + $result = apc_store($key, $data, $this->ttl); + } + + // Update index + if ($index && $result) { + $this->load_index(); + + if (array_search($key, $this->index) === false) { + $this->index[] = $key; + $data = serialize($this->index); + $this->add_record($this->ikey(), $data); + } + } + + return $result; + } + + + /** + * Deletes entry from memcache/apc DB. + */ + private function delete_record($key, $index=true) + { + if ($this->type == 'memcache') { + // #1488592: use 2nd argument + $this->db->delete($this->ckey($key), 0); + } + else { + apc_delete($this->ckey($key)); + } + + if ($index) { + if (($idx = array_search($key, $this->index)) !== false) { + unset($this->index[$idx]); + } + } + } + + + /** + * Writes the index entry into memcache/apc DB. + */ + private function write_index() + { + if (!$this->db) { + return; + } + + if ($this->type == 'db') { + return; + } + + $this->load_index(); + + // Make sure index contains new keys + foreach ($this->cache as $key => $value) { + if ($value !== null) { + if (array_search($key, $this->index) === false) { + $this->index[] = $key; + } + } + } + + $data = serialize($this->index); + $this->add_record($this->ikey(), $data); + } + + + /** + * Gets the index entry from memcache/apc DB. + */ + private function load_index() + { + if (!$this->db) { + return; + } + + if ($this->index !== null) { + return; + } + + $index_key = $this->ikey(); + + if ($this->type == 'memcache') { + $data = $this->db->get($index_key); + } + else if ($this->type == 'apc') { + $data = apc_fetch($index_key); + } + + $this->index = $data ? unserialize($data) : array(); + } + + + /** + * Creates cache key name (for memcache and apc) + * + * @param string $key Cache key name + * + * @return string Cache key + */ + private function ckey($key) + { + return $this->prefix . ':' . $key; + } + + + /** + * Creates index cache key name (for memcache and apc) + * + * @return string Cache key + */ + private function ikey() + { + // This way each cache will have its own index + return $this->prefix . 'INDEX'; + } + + /** + * Serializes data for storing + */ + private function serialize($data) + { + if ($this->type == 'db') { + return $this->db->encode($data, $this->packed); + } + + return $this->packed ? serialize($data) : $data; + } + + /** + * Unserializes serialized data + */ + private function unserialize($data) + { + if ($this->type == 'db') { + return $this->db->decode($data, $this->packed); + } + + return $this->packed ? @unserialize($data) : $data; + } +}
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_charset.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_charset.php
Changed
@@ -674,23 +674,27 @@ // Prioritize charsets according to current language (#1485669) switch ($language) { - case 'ja_JP': // for Japanese + case 'ja_JP': $prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win'); break; - case 'zh_CN': // for Chinese (Simplified) - case 'zh_TW': // for Chinese (Traditional) + case 'zh_CN': + case 'zh_TW': $prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW'); break; - case 'ko_KR': // for Korean + case 'ko_KR': $prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR'); break; - case 'ru_RU': // for Russian + case 'ru_RU': $prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R'); break; + case 'tr_TR': + $prio = array('UTF-8', 'ISO-8859-9', 'WINDOWS-1254'); + break; + default: $prio = array('UTF-8', 'SJIS', 'GB2312', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_config.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_config.php
Changed
@@ -26,6 +26,8 @@ { const DEFAULT_SKIN = 'larry'; + private $env = ''; + private $basedir = 'config/'; private $prop = array(); private $errors = array(); private $userprefs = array(); @@ -43,14 +45,21 @@ 'reply_mode' => 'top_posting', 'refresh_interval' => 'keep_alive', 'min_refresh_interval' => 'min_keep_alive', + 'messages_cache_ttl' => 'message_cache_lifetime', + 'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl', ); /** * Object constructor + * + * @param string Environment suffix for config files to load */ - public function __construct() + public function __construct($env = '') { + $this->env = $env; + $this->basedir = RCUBE_CONFIG_DIR; + $this->load(); // Defaults, that we do not require you to configure, @@ -67,16 +76,26 @@ */ private function load() { - // load main config file - if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'main.inc.php')) - $this->errors[] = 'main.inc.php was not found.'; + // Load default settings + if (!$this->load_from_file('defaults.inc.php')) { + $this->errors[] = 'defaults.inc.php was not found.'; + } - // load database config - if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'db.inc.php')) - $this->errors[] = 'db.inc.php was not found.'; + // load main config file + if (!$this->load_from_file('config.inc.php')) { + // Old configuration files + if (!$this->load_from_file('main.inc.php') || + !$this->load_from_file('db.inc.php')) { + $this->errors[] = 'config.inc.php was not found.'; + } + else if (rand(1,100) == 10) { // log warning on every 100th request (average) + trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING); + } + } // load host-specific configuration - $this->load_host_config(); + if (!empty($_SERVER['HTTP_HOST'])) + $this->load_host_config(); // set skin (with fallback to old 'skin_path' property) if (empty($this->prop['skin'])) { @@ -153,7 +172,7 @@ } if ($fname) { - $this->load_from_file(RCUBE_CONFIG_DIR . $fname); + $this->load_from_file($fname); } } @@ -162,19 +181,25 @@ * Read configuration from a file * and merge with the already stored config values * - * @param string $fpath Full path to the config file to be loaded + * @param string $file Name of the config file to be loaded * @return booelan True on success, false on failure */ - public function load_from_file($fpath) + public function load_from_file($file) { - if (is_file($fpath) && is_readable($fpath)) { + $fpath = $this->resolve_path($file); + if ($fpath && (is_file($fpath) || file_exists($fpath)) && is_readable($fpath)) { // use output buffering, we don't need any output here ob_start(); include($fpath); ob_end_clean(); - if (is_array($rcmail_config)) { - $this->prop = array_merge($this->prop, $rcmail_config, $this->userprefs); + if (is_array($config)) { + $this->merge($config); + return true; + } + // deprecated name of config variable + else if (is_array($rcmail_config)) { + $this->merge($rcmail_config); return true; } } @@ -182,6 +207,30 @@ return false; } + /** + * Helper method to resolve the absolute path to the given config file. + * This also takes the 'env' property into account. + */ + public function resolve_path($file, $use_env = true) + { + if (strpos($file, '/') === false) { + $file = rtrim($this->basedir, '/') . '/' . $file; + + if (!realpath($file) === false) { + $file = realpath($file); + } + } + + // check if <file>-env.ini exists + if ($file && $use_env && !empty($this->env)) { + $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $file); + if (is_file($envfile)) + return $envfile; + } + + return $file; + } + /** * Getter for a specific config parameter @@ -195,9 +244,6 @@ if (isset($this->prop[$name])) { $result = $this->prop[$name]; } - else if (isset($this->legacy_props[$name])) { - return $this->get($this->legacy_props[$name], $def); - } else { $result = $def; } @@ -241,6 +287,7 @@ public function merge($prefs) { $this->prop = array_merge($this->prop, $prefs, $this->userprefs); + $this->fix_legacy_props(); } @@ -273,6 +320,8 @@ $this->userprefs = $prefs; $this->prop = array_merge($this->prop, $prefs); + $this->fix_legacy_props(); + // override timezone settings with client values if ($this->prop['timezone'] == 'auto') { $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value']; @@ -435,4 +484,18 @@ return date_default_timezone_get(); } + /** + * Convert legacy options into new ones + */ + private function fix_legacy_props() + { + foreach ($this->legacy_props as $new => $old) { + if (isset($this->prop[$old])) { + if (!isset($this->prop[$new])) { + $this->prop[$new] = $this->prop[$old]; + } + unset($this->prop[$old]); + } + } + } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_contacts.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_contacts.php
Changed
@@ -137,16 +137,34 @@ * List all active contact groups of this source * * @param string Search string to match group name + * @param int Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * * @return array Indexed list of contact groups, each a hash array */ - function list_groups($search = null) + function list_groups($search = null, $mode = 0) { $results = array(); if (!$this->groups) return $results; - $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : ''; + if ($search) { + switch (intval($mode)) { + case 1: + $sql_filter = $this->db->ilike('name', $search); + break; + case 2: + $sql_filter = $this->db->ilike('name', $search . '%'); + break; + default: + $sql_filter = $this->db->ilike('name', '%' . $search . '%'); + } + + $sql_filter = " AND $sql_filter"; + } $sql_result = $this->db->query( "SELECT * FROM ".$this->db->table_name($this->db_groups). @@ -626,10 +644,6 @@ $insert_id = $this->db->insert_id($this->db_name); } - // also add the newly created contact to the active group - if ($insert_id && $this->group_id) - $this->add_to_group($this->group_id, $insert_id); - $this->cache = null; return $insert_id; @@ -883,9 +897,10 @@ /** * Add the given contact records the a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be added - * @return int Number of contacts added + * @param string Group identifier + * @param array|string List of contact identifiers to be added + * + * @return int Number of contacts added */ function add_to_group($group_id, $ids) { @@ -930,9 +945,10 @@ /** * Remove the given contact records from a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be removed - * @return int Number of deleted group members + * @param string Group identifier + * @param array|string List of contact identifiers to be removed + * + * @return int Number of deleted group members */ function remove_from_group($group_id, $ids) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_content_filter.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_content_filter.php
Changed
@@ -20,7 +20,7 @@ * PHP stream filter to detect html/javascript code in attachments * * @package Framework - * @subpackage Core + * @subpackage Utils */ class rcube_content_filter extends php_user_filter {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_csv2vcard.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_csv2vcard.php
Changed
@@ -130,6 +130,21 @@ 'work_state' => 'region:work', 'home_city_short' => 'locality:home', 'home_state_short' => 'region:home', + + // Atmail + 'date_of_birth' => 'birthday', + 'email' => 'email:pref', + 'home_mobile' => 'phone:cell', + 'home_zip' => 'zipcode:home', + 'info' => 'notes', + 'user_photo' => 'photo', + 'url' => 'website:homepage', + 'work_company' => 'organization', + 'work_dept' => 'departament', + 'work_fax' => 'phone:work,fax', + 'work_mobile' => 'phone:work,cell', + 'work_title' => 'jobtitle', + 'work_zip' => 'zipcode:work', ); /** @@ -230,8 +245,29 @@ 'work_phone' => "Work Phone", 'work_address' => "Work Address", //'work_address_2' => "Work Address 2", + 'work_city' => "Work City", 'work_country' => "Work Country", + 'work_state' => "Work State", 'work_zipcode' => "Work ZipCode", + + // Atmail + 'date_of_birth' => "Date of Birth", + 'email' => "Email", + //'email_2' => "Email2", + //'email_3' => "Email3", + //'email_4' => "Email4", + //'email_5' => "Email5", + 'home_mobile' => "Home Mobile", + 'home_zip' => "Home Zip", + 'info' => "Info", + 'user_photo' => "User Photo", + 'url' => "URL", + 'work_company' => "Work Company", + 'work_dept' => "Work Dept", + 'work_fax' => "Work Fax", + 'work_mobile' => "Work Mobile", + 'work_title' => "Work Title", + 'work_zip' => "Work Zip", ); protected $local_label_map = array(); @@ -268,7 +304,6 @@ { // convert to UTF-8 $head = substr($csv, 0, 4096); - $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1? $charset = rcube_charset::detect($head, RCUBE_CHARSET); $csv = rcube_charset::convert($csv, $charset); $head = ''; @@ -276,7 +311,7 @@ $this->map = array(); // Parse file - foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) { + foreach (preg_split("/[\r\n]+/", $csv) as $line) { $elements = $this->parse_line($line); if (empty($elements)) { continue; @@ -353,6 +388,12 @@ if (!empty($this->local_label_map)) { for ($i = 0; $i < $size; $i++) { $label = $this->local_label_map[$elements[$i]]; + + // special localization label + if ($label && $label[0] == '_') { + $label = substr($label, 1); + } + if ($label && !empty($this->csv2vcard_map[$label])) { $map2[$i] = $this->csv2vcard_map[$label]; } @@ -384,9 +425,13 @@ $contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d']; } + // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00" foreach (array('birthday', 'anniversary') as $key) { - if (!empty($contact[$key]) && $contact[$key] == '0/0/00') { // @TODO: localization? - unset($contact[$key]); + if (!empty($contact[$key])) { + $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]); + if (empty($date)) { + unset($contact[$key]); + } } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_db.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_db.php
Changed
@@ -47,6 +47,7 @@ 'identifier_end' => '"', ); + const DEBUG_LINE_LENGTH = 4096; /** * Factory, returns driver-specific instance of the class @@ -70,7 +71,7 @@ $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver; $class = "rcube_db_$driver"; - if (!class_exists($class)) { + if (!$driver || !class_exists($class)) { rcube::raise_error(array('code' => 600, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => "Configuration error. Unsupported database driver: $driver"), @@ -99,27 +100,15 @@ $this->db_dsnw_array = self::parse_dsn($db_dsnw); $this->db_dsnr_array = self::parse_dsn($db_dsnr); - - // Initialize driver class - $this->init(); - } - - /** - * Initialization of the object with driver specific code - */ - protected function init() - { - // To be used by driver classes } /** * Connect to specific database * - * @param array $dsn DSN for DB connections - * - * @return PDO database handle + * @param array $dsn DSN for DB connections + * @param string $mode Connection mode (r|w) */ - protected function dsn_connect($dsn) + protected function dsn_connect($dsn, $mode) { $this->db_error = false; $this->db_error_msg = null; @@ -128,7 +117,7 @@ $dsn_string = $this->dsn_string($dsn); $dsn_options = $this->dsn_options($dsn); - if ($db_pconn) { + if ($this->db_pconn) { $dsn_options[PDO::ATTR_PERSISTENT] = true; } @@ -157,9 +146,10 @@ return null; } + $this->dbh = $dbh; + $this->db_mode = $mode; + $this->db_connected = true; $this->conn_configure($dsn, $dbh); - - return $dbh; } /** @@ -182,16 +172,6 @@ } /** - * Driver-specific database character set setting - * - * @param string $charset Character set name - */ - protected function set_charset($charset) - { - $this->query("SET NAMES 'utf8'"); - } - - /** * Connect to appropriate database depending on the operation * * @param string $mode Connection mode (r|w) @@ -218,23 +198,14 @@ $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array; - $this->dbh = $this->dsn_connect($dsn); - $this->db_connected = is_object($this->dbh); + $this->dsn_connect($dsn, $mode); // use write-master when read-only fails - if (!$this->db_connected && $mode == 'r') { - $mode = 'w'; - $this->dbh = $this->dsn_connect($this->db_dsnw_array); - $this->db_connected = is_object($this->dbh); + if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) { + $this->dsn_connect($this->db_dsnw_array, 'w'); } - if ($this->db_connected) { - $this->db_mode = $mode; - $this->set_charset('utf8'); - } - else { - $this->conn_failure = true; - } + $this->conn_failure = !$this->db_connected; } /** @@ -255,6 +226,11 @@ protected function debug($query) { if ($this->options['debug_mode']) { + if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) { + $diff = $len - self::DEBUG_LINE_LENGTH; + $query = substr($query, 0, self::DEBUG_LINE_LENGTH) + . "... [truncated $diff bytes]"; + } rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';'); } } @@ -362,8 +338,10 @@ */ protected function _query($query, $offset, $numrows, $params) { + $query = trim($query); + // Read or write ? - $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w'; + $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; $this->db_connect($mode); @@ -405,21 +383,25 @@ $this->db_error_msg = null; // send query - $query = $this->dbh->query($query); + $result = $this->dbh->query($query); - if ($query === false) { + if ($result === false) { $error = $this->dbh->errorInfo(); - $this->db_error = true; - $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); - rcube::raise_error(array('code' => 500, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => $this->db_error_msg), true, false); + if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') { + $this->db_error = true; + $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg . " (SQL Query: $query)" + ), true, false); + } } - $this->last_result = $query; + $this->last_result = $result; - return $query; + return $result; } /** @@ -439,6 +421,32 @@ } /** + * Get number of rows for a SQL query + * If no query handle is specified, the last query will be taken as reference + * + * @param mixed $result Optional query handle + * @return mixed Number of rows or false on failure + * @deprecated This method shows very poor performance and should be avoided. + */ + public function num_rows($result = null) + { + if ($result || ($result === null && ($result = $this->last_result))) { + // repeat query with SELECT COUNT(*) ... + if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) { + $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM); + return $query ? intval($query->fetchColumn(0)) : false; + } + else { + $num = count($result->fetchAll()); + $result->execute(); // re-execute query because there's no seek(0) + return $num; + } + } + + return false; + } + + /** * Get last inserted record ID * * @param string $table Table name (to find the incremented sequence) @@ -571,7 +579,7 @@ * Formats input so it can be safely used in a query * * @param mixed $input Value to quote - * @param string $type Type of data + * @param string $type Type of data (integer, bool, ident) * * @return string Quoted/converted string for use in query */ @@ -586,6 +594,10 @@ return 'NULL'; } + if ($type == 'ident') { + return $this->quote_identifier($input); + } + // create DB handle if not available if (!$this->dbh) { $this->db_connect('r'); @@ -604,6 +616,22 @@ } /** + * Escapes a string so it can be safely used in a query + * + * @param string $str A string to escape + * + * @return string Escaped string for use in a query + */ + public function escape($str) + { + if (is_null($str)) { + return 'NULL'; + } + + return substr($this->quote($str), 1, -1); + } + + /** * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote @@ -618,6 +646,20 @@ } /** + * Escapes a string so it can be safely used in a query + * + * @param string $str A string to escape + * + * @return string Escaped string for use in a query + * @deprecated Replaced by rcube_db::escape + * @see rcube_db::escape + */ + public function escapeSimple($str) + { + return $this->escape($str); + } + + /** * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote @@ -635,24 +677,32 @@ $name[] = $start . $elem . $end; } - return implode($name, '.'); + return implode($name, '.'); } /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { - return "now()"; + if ($interval) { + $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL '; + $add .= $interval > 0 ? intval($interval) : intval($interval) * -1; + $add .= ' SECOND'; + } + + return "now()" . $add; } /** * Return list of elements for use with SQL's IN clause * * @param array $arr Input array - * @param string $type Type of data + * @param string $type Type of data (integer, bool, ident) * * @return string Comma-separated list of quoted values for use in query */ @@ -728,12 +778,19 @@ /** * Encodes non-UTF-8 characters in string/array/object (recursive) * - * @param mixed $input Data to fix + * @param mixed $input Data to fix + * @param bool $serialized Enable serialization * * @return mixed Properly UTF-8 encoded data */ - public static function encode($input) + public static function encode($input, $serialized = false) { + // use Base64 encoding to workaround issues with invalid + // or null characters in serialized string (#1489142) + if ($serialized) { + return base64_encode(serialize($input)); + } + if (is_object($input)) { foreach (get_object_vars($input) as $idx => $value) { $input->$idx = self::encode($value); @@ -744,6 +801,7 @@ foreach ($input as $idx => $value) { $input[$idx] = self::encode($value); } + return $input; } @@ -753,12 +811,24 @@ /** * Decodes encoded UTF-8 string/object/array (recursive) * - * @param mixed $input Input data + * @param mixed $input Input data + * @param bool $serialized Enable serialization * * @return mixed Decoded data */ - public static function decode($input) + public static function decode($input, $serialized = false) { + // use Base64 encoding to workaround issues with invalid + // or null characters in serialized string (#1489142) + if ($serialized) { + // Keep backward compatybility where base64 wasn't used + if (strpos(substr($input, 0, 16), ':') !== false) { + return self::decode(@unserialize($input)); + } + + return @unserialize(base64_decode($input)); + } + if (is_object($input)) { foreach (get_object_vars($input) as $idx => $value) { $input->$idx = self::decode($value); @@ -786,17 +856,26 @@ { $rcube = rcube::get_instance(); - // return table name if configured - $config_key = 'db_table_'.$table; - - if ($name = $rcube->config->get($config_key)) { - return $name; + // add prefix to the table name if configured + if ($prefix = $rcube->config->get('db_prefix')) { + return $prefix . $table; } return $table; } /** + * Set class option value + * + * @param string $name Option name + * @param mixed $value Option value + */ + public function set_option($name, $value) + { + $this->options[$name] = $value; + } + + /** * MDB2 DSN string parser * * @param string $sequence Secuence name
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_db_mssql.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_db_mssql.php
Changed
@@ -29,38 +29,52 @@ public $db_provider = 'mssql'; /** - * Driver initialization + * Object constructor + * + * @param string $db_dsnw DSN for read/write operations + * @param string $db_dsnr Optional DSN for read only operations + * @param bool $pconn Enables persistent connections */ - protected function init() + public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { + parent::__construct($db_dsnw, $db_dsnr, $pconn); + $this->options['identifier_start'] = '['; $this->options['identifier_end'] = ']'; } /** - * Character setting + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler */ - protected function set_charset($charset) + protected function conn_configure($dsn, $dbh) { - // UTF-8 is default + // Set date format in case of non-default language (#1488918) + $this->query("SET DATEFORMAT ymd"); } /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { + if ($interval) { + $interval = intval($interval); + return "dateadd(second, $interval, getdate())"; + } + return "getdate()"; } /** * Return SQL statement to convert a field value into a unix timestamp * - * This method is deprecated and should not be used anymore due to limitations - * of timestamp functions in Mysql (year 2038 problem) - * * @param string $field Field name * * @return string SQL statement to use in query @@ -100,26 +114,30 @@ { $limit = intval($limit); $offset = intval($offset); + $end = $offset + $limit; - $orderby = stristr($query, 'ORDER BY'); - if ($orderby !== false) { - $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc'; - $order = str_ireplace('ORDER BY', '', $orderby); - $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order)); + // query without OFFSET + if (!$offset) { + $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query); + return $query; } - $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query); + $orderby = stristr($query, 'ORDER BY'); + $offset += 1; - $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl'; if ($orderby !== false) { - $query .= ' ORDER BY ' . $order . ' '; - $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC'; + $query = trim(substr($query, 0, -1 * strlen($orderby))); } - $query .= ') AS outer_tbl'; - if ($orderby !== false) { - $query .= ' ORDER BY ' . $order . ' ' . $sort; + else { + // it shouldn't happen, paging without sorting has not much sense + // @FIXME: I don't know how to build paging query without ORDER BY + $orderby = "ORDER BY 1"; } + $query = preg_replace('/^SELECT\s/i', '', $query); + $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)" + . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]"; + return $query; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_db_mysql.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_db_mysql.php
Changed
@@ -30,16 +30,40 @@ public $db_provider = 'mysql'; /** - * Driver initialization/configuration + * Object constructor + * + * @param string $db_dsnw DSN for read/write operations + * @param string $db_dsnr Optional DSN for read only operations + * @param bool $pconn Enables persistent connections */ - protected function init() + public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + rcube::raise_error(array('code' => 600, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "MySQL driver requires PHP >= 5.3, current version is " . PHP_VERSION), + true, true); + } + + parent::__construct($db_dsnw, $db_dsnr, $pconn); + // SQL identifiers quoting $this->options['identifier_start'] = '`'; $this->options['identifier_end'] = '`'; } /** + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler + */ + protected function conn_configure($dsn, $dbh) + { + $this->query("SET NAMES 'utf8'"); + } + + /** * Abstract SQL statement for value concatenation * * @return string SQL statement to be used in query @@ -127,7 +151,7 @@ $result[PDO::MYSQL_ATTR_FOUND_ROWS] = true; // Enable AUTOCOMMIT mode (#1488902) - $dsn_options[PDO::ATTR_AUTOCOMMIT] = true; + $result[PDO::ATTR_AUTOCOMMIT] = true; return $result; } @@ -147,7 +171,7 @@ $result = $this->query('SHOW VARIABLES'); - while ($sql_arr = $this->fetch_array($result)) { + while ($row = $this->fetch_array($result)) { $this->variables[$row[0]] = $row[1]; } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_db_pgsql.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_db_pgsql.php
Changed
@@ -29,6 +29,17 @@ public $db_provider = 'postgres'; /** + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler + */ + protected function conn_configure($dsn, $dbh) + { + $this->query("SET NAMES 'utf8'"); + } + + /** * Get last inserted record ID * * @param string $table Table name (to find the incremented sequence) @@ -53,19 +64,20 @@ /** * Return correct name for a specific database sequence * - * @param string $sequence Secuence name + * @param string $table Table name * * @return string Translated sequence name */ - protected function sequence_name($sequence) + protected function sequence_name($table) { - $rcube = rcube::get_instance(); + // Note: we support only one sequence per table + // Note: The sequence name must be <table_name>_seq + $sequence = $table . '_seq'; + $rcube = rcube::get_instance(); // return sequence name if configured - $config_key = 'db_sequence_'.$sequence; - - if ($name = $rcube->config->get($config_key)) { - return $name; + if ($prefix = $rcube->config->get('db_prefix')) { + return $prefix . $sequence; } return $sequence; @@ -74,9 +86,6 @@ /** * Return SQL statement to convert a field value into a unix timestamp * - * This method is deprecated and should not be used anymore due to limitations - * of timestamp functions in Mysql (year 2038 problem) - * * @param string $field Field name * * @return string SQL statement to use in query @@ -88,6 +97,24 @@ } /** + * Return SQL function for current time and date + * + * @param int $interval Optional interval (in seconds) to add/subtract + * + * @return string SQL function to use in query + */ + public function now($interval = 0) + { + if ($interval) { + $add = ' ' . ($interval > 0 ? '+' : '-') . " interval '"; + $add .= $interval > 0 ? intval($interval) : intval($interval) * -1; + $add .= " seconds'"; + } + + return "now()" . $add; + } + + /** * Return SQL statement for case insensitive LIKE * * @param string $column Field name @@ -129,4 +156,38 @@ return isset($this->variables[$varname]) ? $this->variables[$varname] : $default; } + /** + * Returns PDO DSN string from DSN array + * + * @param array $dsn DSN parameters + * + * @return string DSN string + */ + protected function dsn_string($dsn) + { + $params = array(); + $result = 'pgsql:'; + + if ($dsn['hostspec']) { + $params[] = 'host=' . $dsn['hostspec']; + } + else if ($dsn['socket']) { + $params[] = 'host=' . $dsn['socket']; + } + + if ($dsn['port']) { + $params[] = 'port=' . $dsn['port']; + } + + if ($dsn['database']) { + $params[] = 'dbname=' . $dsn['database']; + } + + if (!empty($params)) { + $result .= implode(';', $params); + } + + return $result; + } + }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_db_sqlite.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_db_sqlite.php
Changed
@@ -29,13 +29,6 @@ public $db_provider = 'sqlite'; /** - * Database character set - */ - protected function set_charset($charset) - { - } - - /** * Prepare connection */ protected function conn_prepare($dsn) @@ -56,10 +49,6 @@ */ protected function conn_configure($dsn, $dbh) { - // we emulate via callback some missing functions - $dbh->sqliteCreateFunction('unix_timestamp', array('rcube_db_sqlite', 'sqlite_unix_timestamp'), 1); - $dbh->sqliteCreateFunction('now', array('rcube_db_sqlite', 'sqlite_now'), 0); - // Initialize database structure in file is empty if (!empty($dsn['database']) && !filesize($dsn['database'])) { $data = file_get_contents(RCUBE_INSTALL_PATH . 'SQL/sqlite.initial.sql'); @@ -83,30 +72,32 @@ } /** - * Callback for sqlite: unix_timestamp() + * Return SQL statement to convert a field value into a unix timestamp + * + * @param string $field Field name + * + * @return string SQL statement to use in query + * @deprecated */ - public static function sqlite_unix_timestamp($timestamp = '') + public function unixtimestamp($field) { - $timestamp = trim($timestamp); - if (!$timestamp) { - $ret = time(); - } - else if (!preg_match('/^[0-9]+$/s', $timestamp)) { - $ret = strtotime($timestamp); - } - else { - $ret = $timestamp; - } - - return $ret; + return "strftime('%s', $field)"; } /** - * Callback for sqlite: now() + * Return SQL function for current time and date + * + * @param int $interval Optional interval (in seconds) to add/subtract + * + * @return string SQL function to use in query */ - public static function sqlite_now() + public function now($interval = 0) { - return date("Y-m-d H:i:s"); + if ($interval) { + $add = ($interval > 0 ? '+' : '') . intval($interval) . ' seconds'; + } + + return "datetime('now'" . ($add ? ",'$add'" : "") . ")"; } /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_db_sqlsrv.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_db_sqlsrv.php
Changed
@@ -29,29 +29,46 @@ public $db_provider = 'mssql'; /** - * Driver initialization + * Object constructor + * + * @param string $db_dsnw DSN for read/write operations + * @param string $db_dsnr Optional DSN for read only operations + * @param bool $pconn Enables persistent connections */ - protected function init() + public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { + parent::__construct($db_dsnw, $db_dsnr, $pconn); + $this->options['identifier_start'] = '['; $this->options['identifier_end'] = ']'; } /** - * Database character set setting + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler */ - protected function set_charset($charset) + protected function conn_configure($dsn, $dbh) { - // UTF-8 is default + // Set date format in case of non-default language (#1488918) + $this->query("SET DATEFORMAT ymd"); } /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { + if ($interval) { + $interval = intval($interval); + return "dateadd(second, $interval, getdate())"; + } + return "getdate()"; } @@ -100,26 +117,30 @@ { $limit = intval($limit); $offset = intval($offset); + $end = $offset + $limit; - $orderby = stristr($query, 'ORDER BY'); - if ($orderby !== false) { - $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc'; - $order = str_ireplace('ORDER BY', '', $orderby); - $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order)); + // query without OFFSET + if (!$offset) { + $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query); + return $query; } - $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query); + $orderby = stristr($query, 'ORDER BY'); + $offset += 1; - $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl'; if ($orderby !== false) { - $query .= ' ORDER BY ' . $order . ' '; - $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC'; + $query = trim(substr($query, 0, -1 * strlen($orderby))); } - $query .= ') AS outer_tbl'; - if ($orderby !== false) { - $query .= ' ORDER BY ' . $order . ' ' . $sort; + else { + // it shouldn't happen, paging without sorting has not much sense + // @FIXME: I don't know how to build paging query without ORDER BY + $orderby = "ORDER BY 1"; } + $query = preg_replace('/^SELECT\s/i', '', $query); + $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)" + . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]"; + return $query; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_enriched.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_enriched.php
Changed
@@ -118,7 +118,7 @@ $quoted = ''; $lines = explode('<br>', $a[2]); - foreach ($lines as $n => $line) + foreach ($lines as $line) $quoted .= '>'.$line.'<br>'; $body = $a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_html2text.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_html2text.php
Changed
@@ -571,55 +571,65 @@ */ protected function _convert_blockquotes(&$text) { - if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) { - $level = 0; - $diff = 0; - foreach ($matches[0] as $m) { - if ($m[0][0] == '<' && $m[0][1] == '/') { + $level = 0; + $offset = 0; + while (($start = strpos($text, '<blockquote', $offset)) !== false) { + $offset = $start + 12; + do { + $end = strpos($text, '</blockquote>', $offset); + $next = strpos($text, '<blockquote', $offset); + + // nested <blockquote>, skip + if ($next !== false && $next < $end) { + $offset = $next + 12; + $level++; + } + // nested </blockquote> tag + if ($end !== false && $level > 0) { + $offset = $end + 12; $level--; - if ($level < 0) { - $level = 0; // malformed HTML: go to next blockquote - } - else if ($level > 0) { - // skip inner blockquote - } - else { - $end = $m[1]; - $len = $end - $taglen - $start; - // Get blockquote content - $body = substr($text, $start + $taglen - $diff, $len); - - // Set text width - $p_width = $this->width; - if ($this->width > 0) $this->width -= 2; - // Convert blockquote content - $body = trim($body); - $this->_converter($body); - // Add citation markers and create PRE block - $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body)); - $body = '<pre>' . htmlspecialchars($body) . '</pre>'; - // Re-set text width - $this->width = $p_width; - // Replace content - $text = substr($text, 0, $start - $diff) - . $body . substr($text, $end + strlen($m[0]) - $diff); - - $diff = $len + $taglen + strlen($m[0]) - strlen($body); - unset($body); - } } - else { - if ($level == 0) { - $start = $m[1]; - $taglen = strlen($m[0]); - } - $level ++; + // found matching end tag + else if ($end !== false && $level == 0) { + $taglen = strpos($text, '>', $start) - $start; + $startpos = $start + $taglen + 1; + + // get blockquote content + $body = trim(substr($text, $startpos, $end - $startpos)); + + // adjust text wrapping width + $p_width = $this->width; + if ($this->width > 0) $this->width -= 2; + + // replace content with inner blockquotes + $this->_converter($body); + + // resore text width + $this->width = $p_width; + + // Add citation markers and create <pre> block + $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_ballback'), trim($body)); + $body = '<pre>' . htmlspecialchars($body) . '</pre>'; + + $text = substr($text, 0, $start) . $body . "\n" . substr($text, $end + 13); + $offset = 0; + break; } - } + } while ($end || $next); } } /** + * Callback function to correctly add citation markers for blockquote contents + */ + public function blockquote_citation_ballback($m) + { + $line = ltrim($m[2]); + $space = $line[0] == '>' ? '' : ' '; + return $m[1] . '>' . $space . $line; + } + + /** * Callback function for preg_replace_callback use. * * @param array PREG matches
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_image.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_image.php
Changed
@@ -77,7 +77,8 @@ } /** - * Resize image to a given size + * Resize image to a given size. Use only to shrink an image. + * If an image is smaller than specified size it will be not resized. * * @param int $size Max width/height size * @param string $filename Output filename @@ -92,6 +93,10 @@ $convert = $rcube->config->get('im_convert_path', false); $props = $this->props(); + if (empty($props)) { + return false; + } + if (!$filename) { $filename = $this->image_file; } @@ -100,7 +105,6 @@ if ($convert) { $p['out'] = $filename; $p['in'] = $this->image_file; - $p['size'] = $size.'x'.$size; $type = $props['type']; if (!$type && ($data = $this->identify())) { @@ -115,14 +119,41 @@ $type = 'jpg'; } - $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75); - $p['-opts'] = array('-resize' => $p['size'].'>'); + // If only one dimension is greater than the limit convert doesn't + // work as expected, we need to calculate new dimensions + $scale = $size / max($props['width'], $props['height']); - if (in_array($type, explode(',', $p['types']))) { // Valid type? - $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {intype}:{in} {type}:{out}', $p); + // if file is smaller than the limit, we do nothing + // but copy original file to destination file + if ($scale >= 1 && $p['intype'] == $type) { + $result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false; + } + else { + if ($scale >= 1) { + $width = $props['width']; + $height = $props['height']; + } + else { + $width = intval($props['width'] * $scale); + $height = intval($props['height'] * $scale); + } + + $valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif"; + + $p += array( + 'type' => $type, + 'quality' => 75, + 'size' => $width . 'x' . $height, + ); + + if (in_array($type, explode(',', $valid_types))) { // Valid type? + $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip' + . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p); + } } if ($result === '') { + @chmod($filename, 0600); return $type; } } @@ -131,49 +162,62 @@ if ($props['gd_type']) { if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) { $image = imagecreatefromjpeg($this->image_file); + $type = 'jpg'; } else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) { $image = imagecreatefromgif($this->image_file); + $type = 'gid'; } else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) { $image = imagecreatefrompng($this->image_file); + $type = 'png'; } else { // @TODO: print error to the log? return false; } - $scale = $size / max($props['width'], $props['height']); - $width = $props['width'] * $scale; - $height = $props['height'] * $scale; - - $new_image = imagecreatetruecolor($width, $height); - - // Fix transparency of gif/png image - if ($props['gd_type'] != IMAGETYPE_JPEG) { - imagealphablending($new_image, false); - imagesavealpha($new_image, true); - $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); - imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent); + if ($image === false) { + return false; } - imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']); - $image = $new_image; + $scale = $size / max($props['width'], $props['height']); - if ($props['gd_type'] == IMAGETYPE_JPEG) { - $result = imagejpeg($image, $filename, 75); - $type = 'jpg'; + // Imagemagick resize is implemented in shrinking mode (see -resize argument above) + // we do the same here, if an image is smaller than specified size + // we do nothing but copy original file to destination file + if ($scale >= 1) { + $result = $this->image_file == $filename || copy($this->image_file, $filename); } - elseif($props['gd_type'] == IMAGETYPE_GIF) { - $result = imagegif($image, $filename); - $type = 'gid'; - } - elseif($props['gd_type'] == IMAGETYPE_PNG) { - $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); - $type = 'png'; + else { + $width = intval($props['width'] * $scale); + $height = intval($props['height'] * $scale); + $new_image = imagecreatetruecolor($width, $height); + + // Fix transparency of gif/png image + if ($props['gd_type'] != IMAGETYPE_JPEG) { + imagealphablending($new_image, false); + imagesavealpha($new_image, true); + $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); + imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent); + } + + imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']); + $image = $new_image; + + if ($props['gd_type'] == IMAGETYPE_JPEG) { + $result = imagejpeg($image, $filename, 75); + } + elseif($props['gd_type'] == IMAGETYPE_GIF) { + $result = imagegif($image, $filename); + } + elseif($props['gd_type'] == IMAGETYPE_PNG) { + $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); + } } if ($result) { + @chmod($filename, 0600); return $type; } } @@ -211,9 +255,10 @@ $p['out'] = $filename; $p['type'] = self::$extensions[$type]; - $result = rcube::exec($convert . ' 2>&1 -colorspace RGB -quality 75 {in} {type}:{out}', $p); + $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p); if ($result === '') { + @chmod($filename, 0600); return true; } } @@ -245,6 +290,11 @@ else if ($type == self::TYPE_PNG) { $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); } + + if ($result) { + @chmod($filename, 0600); + return true; + } } // @TODO: print error to the log?
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_imap.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_imap.php
Changed
@@ -308,14 +308,7 @@ */ public function set_folder($folder) { - if ($this->folder == $folder) { - return; - } - $this->folder = $folder; - - // clear messagecount cache for this folder - $this->clear_messagecount($folder); } @@ -626,7 +619,7 @@ } if ($mode == 'THREADS') { - $res = $this->fetch_threads($folder, $force); + $res = $this->threads($folder); $count = $res->count(); if ($status) { @@ -656,11 +649,11 @@ $keys[] = 'ALL'; } if ($status) { - $keys[] = 'MAX'; + $keys[] = 'MAX'; } } - // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here + // @TODO: if $mode == 'ALL' we could try to use cache index here // get message count using (E)SEARCH // not very performant but more precise (using UNDELETED) @@ -791,7 +784,7 @@ $threads = $mcache->get_thread($folder); } else { - $threads = $this->fetch_threads($folder); + $threads = $this->threads($folder); } return $this->fetch_thread_headers($folder, $threads, $page, $slice); @@ -800,32 +793,47 @@ /** * Method for fetching threads data * - * @param string $folder Folder name - * @param bool $force Use IMAP server, no cache + * @param string $folder Folder name * * @return rcube_imap_thread Thread data object */ - function fetch_threads($folder, $force = false) + function threads($folder) { - if (!$force && ($mcache = $this->get_mcache_engine())) { + if ($mcache = $this->get_mcache_engine()) { // don't store in self's internal cache, cache has it's own internal cache return $mcache->get_thread($folder); } - if (empty($this->icache['threads'])) { - if (!$this->check_connection()) { - return new rcube_result_thread(); + if (!empty($this->icache['threads'])) { + if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) { + return $this->icache['threads']; } + } - // get all threads - $result = $this->conn->thread($folder, $this->threading, - $this->options['skip_deleted'] ? 'UNDELETED' : '', true); + // get all threads + $result = $this->threads_direct($folder); - // add to internal (fast) cache - $this->icache['threads'] = $result; + // add to internal (fast) cache + return $this->icache['threads'] = $result; + } + + + /** + * Method for direct fetching of threads data + * + * @param string $folder Folder name + * + * @return rcube_imap_thread Thread data object + */ + function threads_direct($folder) + { + if (!$this->check_connection()) { + return new rcube_result_thread(); } - return $this->icache['threads']; + // get all threads + return $this->conn->thread($folder, $this->threading, + $this->options['skip_deleted'] ? 'UNDELETED' : '', true); } @@ -981,7 +989,7 @@ // use memory less expensive (and quick) method for big result set $index = clone $this->index('', $this->sort_field, $this->sort_order); // get messages uids for one page... - $index->slice($start_msg, min($cnt-$from, $this->page_size)); + $index->slice($from, min($cnt-$from, $this->page_size)); if ($slice) { $index->slice(-$slice, $slice); @@ -1096,16 +1104,17 @@ /** - * Returns current status of folder + * Returns current status of a folder (compared to the last time use) * * We compare the maximum UID to determine the number of * new messages because the RECENT flag is not reliable. * * @param string $folder Folder name + * @param array $diff Difference data * - * @return int Folder status + * @return int Folder status */ - public function folder_status($folder = null) + public function folder_status($folder = null, &$diff = array()) { if (!strlen($folder)) { $folder = $this->folder; @@ -1126,6 +1135,9 @@ // got new messages if ($new['maxuid'] > $old['maxuid']) { $result += 1; + // get new message UIDs range, that can be used for example + // to get the data of these messages + $diff['new'] = ($old['maxuid'] + 1 < $new['maxuid'] ? ($old['maxuid']+1).':' : '') . $new['maxuid']; } // some messages has been deleted if ($new['cnt'] < $old['cnt']) { @@ -1176,12 +1188,15 @@ * @param string $folder Folder to get index from * @param string $sort_field Sort column * @param string $sort_order Sort order [ASC, DESC] + * @param bool $no_threads Get not threaded index + * @param bool $no_search Get index not limited to search result (optionally) * * @return rcube_result_index|rcube_result_thread List of messages (UIDs) */ - public function index($folder = '', $sort_field = NULL, $sort_order = NULL) - { - if ($this->threading) { + public function index($folder = '', $sort_field = NULL, $sort_order = NULL, + $no_threads = false, $no_search = false + ) { + if (!$no_threads && $this->threading) { return $this->thread_index($folder, $sort_field, $sort_order); } @@ -1193,43 +1208,50 @@ // we have a saved search result, get index from there if ($this->search_string) { - if ($this->search_threads) { - $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field); + if ($this->search_set->is_empty()) { + return new rcube_result_index($folder, '* SORT'); } - // use message index sort as default sorting - if (!$this->sort_field || $this->search_sorted) { - if ($this->sort_field && $this->search_sort_field != $this->sort_field) { - $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field); - } + // search result is an index with the same sorting? + if (($this->search_set instanceof rcube_result_index) + && ((!$this->sort_field && !$this->search_sorted) || + ($this->search_sorted && $this->search_sort_field == $this->sort_field)) + ) { $index = $this->search_set; } - else if (!$this->check_connection()) { - return new rcube_result_index(); - } - else { - $index = $this->conn->index($folder, $this->search_set->get(), - $this->sort_field, $this->options['skip_deleted'], true, true); + // $no_search is enabled when we are not interested in + // fetching index for search result, e.g. to sort + // threaded search result we can use full mailbox index. + // This makes possible to use index from cache + else if (!$no_search) { + if (!$this->sort_field) { + // No sorting needed, just build index from the search result + // @TODO: do we need to sort by UID here? + $search = $this->search_set->get_compressed(); + $index = new rcube_result_index($folder, '* ESEARCH ALL ' . $search); + } + else { + $index = $this->index_direct($folder, $this->search_charset, + $this->sort_field, $this->search_set); + } } - if ($this->sort_order != $index->get_parameters('ORDER')) { - $index->revert(); - } + if (isset($index)) { + if ($this->sort_order != $index->get_parameters('ORDER')) { + $index->revert(); + } - return $index; + return $index; + } } // check local cache if ($mcache = $this->get_mcache_engine()) { - $index = $mcache->get_index($folder, $this->sort_field, $this->sort_order); - } - // fetch from IMAP server - else { - $index = $this->index_direct( - $folder, $this->sort_field, $this->sort_order); + return $mcache->get_index($folder, $this->sort_field, $this->sort_order); } - return $index; + // fetch from IMAP server + return $this->index_direct($folder, $this->sort_field, $this->sort_order); } @@ -1237,22 +1259,24 @@ * Return sorted list of message UIDs ignoring current search settings. * Doesn't uses cache by default. * - * @param string $folder Folder to get index from - * @param string $sort_field Sort column - * @param string $sort_order Sort order [ASC, DESC] - * @param bool $skip_cache Disables cache usage + * @param string $folder Folder to get index from + * @param string $sort_field Sort column + * @param string $sort_order Sort order [ASC, DESC] + * @param rcube_result_* $search Optional messages set to limit the result * * @return rcube_result_index Sorted list of message UIDs */ - public function index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true) + public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null) { - if (!$skip_cache && ($mcache = $this->get_mcache_engine())) { - $index = $mcache->get_index($folder, $sort_field, $sort_order); + if (!empty($search)) { + $search = $this->search_set->get_compressed(); } + // use message index sort as default sorting - else if (!$sort_field) { + if (!$sort_field) { // use search result from count() if possible - if ($this->options['skip_deleted'] && !empty($this->icache['undeleted_idx']) + if (empty($search) && $this->options['skip_deleted'] + && !empty($this->icache['undeleted_idx']) && $this->icache['undeleted_idx']->get_parameters('ALL') !== null && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder ) { @@ -1262,8 +1286,12 @@ return new rcube_result_index(); } else { - $index = $this->conn->search($folder, - 'ALL' .($this->options['skip_deleted'] ? ' UNDELETED' : ''), true); + $query = $this->options['skip_deleted'] ? 'UNDELETED' : ''; + if ($search) { + $query = trim($query . ' UID ' . $search); + } + + $index = $this->conn->search($folder, $query, true); } } else if (!$this->check_connection()) { @@ -1272,13 +1300,18 @@ // fetch complete message index else { if ($this->get_capability('SORT')) { - $index = $this->conn->sort($folder, $sort_field, - $this->options['skip_deleted'] ? 'UNDELETED' : '', true); + $query = $this->options['skip_deleted'] ? 'UNDELETED' : ''; + if ($search) { + $query = trim($query . ' UID ' . $search); + } + + $index = $this->conn->sort($folder, $sort_field, $query, true); } if (empty($index) || $index->is_error()) { - $index = $this->conn->index($folder, "1:*", $sort_field, - $this->options['skip_deleted'], false, true); + $index = $this->conn->index($folder, $search ? $search : "1:*", + $sort_field, $this->options['skip_deleted'], + $search ? true : false, true); } } @@ -1311,7 +1344,7 @@ } else { // get all threads (default sort order) - $threads = $this->fetch_threads($folder); + $threads = $this->threads($folder); } $this->set_sort_order($sort_field, $sort_order); @@ -1322,9 +1355,10 @@ /** - * Sort threaded result, using THREAD=REFS method + * Sort threaded result, using THREAD=REFS method if available. + * If not, use any method and re-sort the result in THREAD=REFS way. * - * @param rcube_result_thread $threads Threads result set + * @param rcube_result_thread $threads Threads result set */ protected function sort_threads($threads) { @@ -1336,17 +1370,16 @@ // THREAD=REFERENCES: sorting by sent date of root message // THREAD=REFS: sorting by the most recent date in each thread - if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) { - $index = $this->index_direct($this->folder, $this->sort_field, $this->sort_order, false); + if ($this->threading != 'REFS' || ($this->sort_field && $this->sort_field != 'date')) { + $sortby = $this->sort_field ? $this->sort_field : 'date'; + $index = $this->index($this->folder, $sortby, $this->sort_order, true, true); if (!$index->is_empty()) { $threads->sort($index); } } - else { - if ($this->sort_order != $threads->get_parameters('ORDER')) { - $threads->revert(); - } + else if ($this->sort_order != $threads->get_parameters('ORDER')) { + $threads->revert(); } } @@ -1419,8 +1452,6 @@ */ protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL) { - $orig_criteria = $criteria; - if (!$this->check_connection()) { if ($this->threading) { return new rcube_result_thread(); @@ -1634,9 +1665,15 @@ // Example of structure for malformed MIME message: // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL) if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain' - && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') { + && strtolower($structure[0].'/'.$structure[1]) == 'text/plain' + ) { + // A special known case "Content-type: text" (#1488968) + if ($headers->ctype == 'text') { + $structure[1] = 'plain'; + $headers->ctype = 'text/plain'; + } // we can handle single-part messages, by simple fix in structure (#1486898) - if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) { + else if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) { $structure[0] = $m[1]; $structure[1] = $m[2]; } @@ -1660,11 +1697,21 @@ $struct = $this->structure_part($structure, 0, '', $headers); } - // don't trust given content-type - if (empty($struct->parts) && !empty($headers->ctype)) { - $struct->mime_id = '1'; - $struct->mimetype = strtolower($headers->ctype); - list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); + // some workarounds on simple messages... + if (empty($struct->parts)) { + // ...don't trust given content-type + if (!empty($headers->ctype)) { + $struct->mime_id = '1'; + $struct->mimetype = strtolower($headers->ctype); + list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); + } + + // ...and charset (there's a case described in #1488968 where invalid content-type + // results in invalid charset in BODYSTRUCTURE) + if (!empty($headers->charset) && $headers->charset != $struct->ctype_parameters['charset']) { + $struct->charset = $headers->charset; + $struct->ctype_parameters['charset'] = $headers->charset; + } } $headers->structure = $struct; @@ -2045,17 +2092,18 @@ /** * Fetch message body of a specific message from the server * - * @param int $uid Message UID - * @param string $part Part number - * @param rcube_message_part $o_part Part object created by get_structure() - * @param mixed $print True to print part, ressource to write part contents in - * @param resource $fp File pointer to save the message part - * @param boolean $skip_charset_conv Disables charset conversion - * @param int $max_bytes Only read this number of bytes + * @param int Message UID + * @param string Part number + * @param rcube_message_part Part object created by get_structure() + * @param mixed True to print part, resource to write part contents in + * @param resource File pointer to save the message part + * @param boolean Disables charset conversion + * @param int Only read this number of bytes + * @param boolean Enables formatting of text/* parts bodies * * @return string Message/part body if not printed */ - public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0) + public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0, $formatted=true) { if (!$this->check_connection()) { return null; @@ -2074,8 +2122,9 @@ } if ($o_part && $o_part->size) { + $formatted = $formatted && $o_part->ctype_primary == 'text'; $body = $this->conn->handlePartBody($this->folder, $uid, true, - $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text', $max_bytes); + $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $formatted, $max_bytes); } if ($fp || $print) { @@ -2220,13 +2269,14 @@ /** * Append a mail message (source) to a specific folder * - * @param string $folder Target folder - * @param string $message The message source string or filename - * @param string $headers Headers string if $message contains only the body - * @param boolean $is_file True if $message is a filename - * @param array $flags Message flags - * @param mixed $date Message internal date - * @param bool $binary Enables BINARY append + * @param string $folder Target folder + * @param string|array $message The message source string or filename + * or array (of strings and file pointers) + * @param string $headers Headers string if $message contains only the body + * @param boolean $is_file True if $message is a filename + * @param array $flags Message flags + * @param mixed $date Message internal date + * @param bool $binary Enables BINARY append * * @return int|bool Appended message UID or True on success, False on error */ @@ -2317,10 +2367,7 @@ // move messages $moved = $this->conn->move($uids, $from_mbox, $to_mbox); - // send expunge command in order to have the moved message - // really deleted from the source folder if ($moved) { - $this->expunge_message($uids, $from_mbox, false); $this->clear_messagecount($from_mbox); $this->clear_messagecount($to_mbox); } @@ -2622,7 +2669,6 @@ if ($list_extended) { // unsubscribe non-existent folders, remove from the list - // we can do this only when LIST response is available if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) { foreach ($a_folders as $idx => $folder) { if (($opts = $this->conn->data['LIST'][$folder]) @@ -2635,19 +2681,14 @@ } } else { - // unsubscribe non-existent folders, remove them from the list, - // we can do this only when LIST response is available - if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) { - foreach ($a_folders as $idx => $folder) { - if (!isset($this->conn->data['LIST'][$folder]) - || in_array('\\Noselect', $this->conn->data['LIST'][$folder]) - ) { - // Some servers returns \Noselect for existing folders - if (!$this->folder_exists($folder)) { - $this->conn->unsubscribe($folder); - unset($a_folders[$idx]); - } - } + // unsubscribe non-existent folders, remove them from the list + if (is_array($a_folders) && !empty($a_folders) && $name == '*') { + $existing = $this->list_folders($root, $name); + $nonexisting = array_diff($a_folders, $existing); + $a_folders = array_diff($a_folders, $nonexisting); + + foreach ($nonexisting as $folder) { + $this->conn->unsubscribe($folder); } } } @@ -2710,7 +2751,7 @@ // filter folders list according to rights requirements if ($rights && $this->get_capability('ACL')) { - $a_folders = $this->filter_rights($a_folders, $rights); + $a_mboxes = $this->filter_rights($a_mboxes, $rights); } // filter folders and sort them @@ -2766,7 +2807,6 @@ */ private function list_folders_update(&$result, $type = null) { - $delim = $this->get_hierarchy_delimiter(); $namespace = $this->get_namespace(); $search = array(); @@ -3355,7 +3395,6 @@ { if (!empty($this->options['fetch_headers'])) { $headers = explode(' ', $this->options['fetch_headers']); - $headers = array_map('strtoupper', $headers); } else { $headers = array(); @@ -3365,7 +3404,7 @@ $headers = array_merge($headers, $this->all_headers); } - return implode(' ', array_unique($headers)); + return $headers; } @@ -3678,7 +3717,7 @@ { if ($this->caching && !$this->cache) { $rcube = rcube::get_instance(); - $ttl = $rcube->config->get('message_cache_lifetime', '10d'); + $ttl = $rcube->config->get('imap_cache_ttl', '10d'); $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl); } @@ -3726,21 +3765,6 @@ } } - /** - * Delete outdated cache entries - */ - public function expunge_cache() - { - if ($this->mcache) { - $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d'); - $this->mcache->expunge($ttl); - } - - if ($this->cache) { - $this->cache->expunge(); - } - } - /* -------------------------------- * message caching methods @@ -3774,8 +3798,9 @@ if ($this->messages_caching && !$this->mcache) { $rcube = rcube::get_instance(); if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) { + $ttl = $rcube->config->get('messages_cache_ttl', '10d'); $this->mcache = new rcube_imap_cache( - $dbh, $this, $userid, $this->options['skip_deleted']); + $dbh, $this, $userid, $this->options['skip_deleted'], $ttl); } } @@ -3797,6 +3822,15 @@ } + /** + * Delete outdated cache entries + */ + function cache_gc() + { + rcube_imap_cache::gc(); + } + + /* -------------------------------- * protected methods * --------------------------------*/ @@ -3830,7 +3864,7 @@ $delimiter = $this->get_hierarchy_delimiter(); // find default folders and skip folders starting with '.' - foreach ($a_folders as $i => $folder) { + foreach ($a_folders as $folder) { if ($folder[0] == '.') { continue; } @@ -4090,9 +4124,9 @@ return $this->index($folder, $sort_field, $sort_order); } - public function message_index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true) + public function message_index_direct($folder, $sort_field = null, $sort_order = null) { - return $this->index_direct($folder, $sort_field, $sort_order, $skip_cache); + return $this->index_direct($folder, $sort_field, $sort_order); } public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_imap_cache.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_imap_cache.php
Changed
@@ -49,6 +49,13 @@ private $userid; /** + * Expiration time in seconds + * + * @var int + */ + private $ttl; + + /** * Internal (in-memory) cache * * @var array @@ -83,13 +90,25 @@ /** * Object constructor. + * + * @param rcube_db $db DB handler + * @param rcube_imap $imap IMAP handler + * @param int $userid User identifier + * @param bool $skip_deleted skip_deleted flag + * @param string $ttl Expiration time of memcache/apc items + * */ - function __construct($db, $imap, $userid, $skip_deleted) + function __construct($db, $imap, $userid, $skip_deleted, $ttl=0) { + // convert ttl string to seconds + $ttl = get_offset_sec($ttl); + if ($ttl > 2592000) $ttl = 2592000; + $this->db = $db; $this->imap = $imap; $this->userid = $userid; $this->skip_deleted = $skip_deleted; + $this->ttl = $ttl; } @@ -215,9 +234,7 @@ * Return messages thread. * If threaded index doesn't exist or is invalid, will be updated. * - * @param string $mailbox Folder name - * @param string $sort_field Sorting column - * @param string $sort_order Sorting order (ASC|DESC) + * @param string $mailbox Folder name * * @return array Messages threaded index */ @@ -256,19 +273,11 @@ if ($index === null) { // Get mailbox data (UIDVALIDITY, counters, etc.) for status check $mbox_data = $this->imap->folder_data($mailbox); - - if ($mbox_data['EXISTS']) { - // get all threads (default sort order) - $threads = $this->imap->fetch_threads($mailbox, true); - } - else { - $threads = new rcube_result_thread($mailbox, '* THREAD'); - } - - $index['object'] = $threads; + // Get THREADS result + $index['object'] = $this->get_thread_data($mailbox, $mbox_data); // insert/update - $this->add_thread_row($mailbox, $threads, $mbox_data, $exists); + $this->add_thread_row($mailbox, $index['object'], $mbox_data, $exists); } $this->icache[$mailbox]['thread'] = $index; @@ -407,8 +416,8 @@ return; } - $msg = serialize($this->db->encode(clone $message)); $flags = 0; + $msg = clone $message; if (!empty($message->flags)) { foreach ($this->flags as $idx => $flag) { @@ -417,30 +426,49 @@ } } } + unset($msg->flags); + $msg = $this->db->encode($msg, true); // update cache record (even if it exists, the update // here will work as select, assume row exist if affected_rows=0) if (!$force) { $res = $this->db->query( "UPDATE ".$this->db->table_name('cache_messages') - ." SET flags = ?, data = ?, changed = ".$this->db->now() + ." SET flags = ?, data = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') ." WHERE user_id = ?" ." AND mailbox = ?" ." AND uid = ?", $flags, $msg, $this->userid, $mailbox, (int) $message->uid); - if ($this->db->affected_rows()) { + if ($this->db->affected_rows($res)) { return; } } + $this->db->set_option('ignore_key_errors', true); + // insert new record - $this->db->query( + $res = $this->db->query( "INSERT INTO ".$this->db->table_name('cache_messages') - ." (user_id, mailbox, uid, flags, changed, data)" - ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)", + ." (user_id, mailbox, uid, flags, expires, data)" + ." VALUES (?, ?, ?, ?, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?)", $this->userid, $mailbox, (int) $message->uid, $flags, $msg); + + // race-condition, insert failed so try update (#1489146) + // thanks to ignore_key_errors "duplicate row" errors will be ignored + if ($force && !$res && !$this->db->is_error($res)) { + $this->db->query( + "UPDATE ".$this->db->table_name('cache_messages') + ." SET expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') + .", flags = ?, data = ?" + ." WHERE user_id = ?" + ." AND mailbox = ?" + ." AND uid = ?", + $flags, $msg, $this->userid, $mailbox, (int) $message->uid); + } + + $this->db->set_option('ignore_key_errors', false); } @@ -481,11 +509,11 @@ $this->db->query( "UPDATE ".$this->db->table_name('cache_messages') - ." SET changed = ".$this->db->now() + ." SET expires = ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .", flags = flags ".($enabled ? "+ $idx" : "- $idx") ." WHERE user_id = ?" ." AND mailbox = ?" - .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "") + .(!empty($uids) ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "") ." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"), $this->userid, $mailbox); } @@ -604,23 +632,21 @@ /** - * Delete cache entries older than TTL - * - * @param string $ttl Lifetime of message cache entries + * Delete expired cache entries */ - function expunge($ttl) + static function gc() { - // get expiration timestamp - $ts = get_offset_time($ttl, -1); + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); - $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages') - ." WHERE changed < " . $this->db->fromunixtime($ts)); + $db->query("DELETE FROM ".$db->table_name('cache_messages') + ." WHERE expires < " . $db->now()); - $this->db->query("DELETE FROM ".$this->db->table_name('cache_index') - ." WHERE changed < " . $this->db->fromunixtime($ts)); + $db->query("DELETE FROM ".$db->table_name('cache_index') + ." WHERE expires < " . $db->now()); - $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread') - ." WHERE changed < " . $this->db->fromunixtime($ts)); + $db->query("DELETE FROM ".$db->table_name('cache_thread') + ." WHERE expires < " . $db->now()); } @@ -639,7 +665,7 @@ if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $data = explode('@', $sql_arr['data']); - $index = @unserialize($data[0]); + $index = $this->db->decode($data[0], true); unset($data[0]); if (empty($index)) { @@ -676,7 +702,7 @@ if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $data = explode('@', $sql_arr['data']); - $thread = @unserialize($data[0]); + $thread = $this->db->decode($data[0], true); unset($data[0]); if (empty($thread)) { @@ -702,7 +728,7 @@ $data, $mbox_data = array(), $exists = false, $modseq = null) { $data = array( - serialize($data), + $this->db->encode($data, true), $sort_field, (int) $this->skip_deleted, (int) $mbox_data['UIDVALIDITY'], @@ -712,20 +738,38 @@ $data = implode('@', $data); if ($exists) { - $sql_result = $this->db->query( + $res = $this->db->query( "UPDATE ".$this->db->table_name('cache_index') - ." SET data = ?, valid = 1, changed = ".$this->db->now() + ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') ." WHERE user_id = ?" ." AND mailbox = ?", $data, $this->userid, $mailbox); + + if ($this->db->affected_rows($res)) { + return; + } } - else { - $sql_result = $this->db->query( - "INSERT INTO ".$this->db->table_name('cache_index') - ." (user_id, mailbox, data, valid, changed)" - ." VALUES (?, ?, ?, 1, ".$this->db->now().")", - $this->userid, $mailbox, $data); + + $this->db->set_option('ignore_key_errors', true); + + $res = $this->db->query( + "INSERT INTO ".$this->db->table_name('cache_index') + ." (user_id, mailbox, valid, expires, data)" + ." VALUES (?, ?, 1, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .", ?)", + $this->userid, $mailbox, $data); + + // race-condition, insert failed so try update (#1489146) + // thanks to ignore_key_errors "duplicate row" errors will be ignored + if (!$exists && !$res && !$this->db->is_error($res)) { + $res = $this->db->query( + "UPDATE ".$this->db->table_name('cache_index') + ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') + ." WHERE user_id = ?" + ." AND mailbox = ?", + $data, $this->userid, $mailbox); } + + $this->db->set_option('ignore_key_errors', false); } @@ -735,28 +779,48 @@ private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false) { $data = array( - serialize($data), + $this->db->encode($data, true), (int) $this->skip_deleted, (int) $mbox_data['UIDVALIDITY'], (int) $mbox_data['UIDNEXT'], ); $data = implode('@', $data); + $expires = ($this->ttl ? $this->db->now($this->ttl) : 'NULL'); + if ($exists) { - $sql_result = $this->db->query( + $res = $this->db->query( "UPDATE ".$this->db->table_name('cache_thread') - ." SET data = ?, changed = ".$this->db->now() + ." SET data = ?, expires = $expires" ." WHERE user_id = ?" ." AND mailbox = ?", $data, $this->userid, $mailbox); + + if ($this->db->affected_rows($res)) { + return; + } } - else { - $sql_result = $this->db->query( - "INSERT INTO ".$this->db->table_name('cache_thread') - ." (user_id, mailbox, data, changed)" - ." VALUES (?, ?, ?, ".$this->db->now().")", - $this->userid, $mailbox, $data); + + $this->db->set_option('ignore_key_errors', true); + + $res = $this->db->query( + "INSERT INTO ".$this->db->table_name('cache_thread') + ." (user_id, mailbox, expires, data)" + ." VALUES (?, ?, $expires, ?)", + $this->userid, $mailbox, $data); + + // race-condition, insert failed so try update (#1489146) + // thanks to ignore_key_errors "duplicate row" errors will be ignored + if (!$exists && !$res && !$this->db->is_error($res)) { + $this->db->query( + "UPDATE ".$this->db->table_name('cache_thread') + ." SET expires = $expires, data = ?" + ." WHERE user_id = ?" + ." AND mailbox = ?", + $data, $this->userid, $mailbox); } + + $this->db->set_option('ignore_key_errors', false); } @@ -983,7 +1047,7 @@ $uids, true, array('FLAGS'), $index['modseq'], $qresync); if (!empty($result)) { - foreach ($result as $id => $msg) { + foreach ($result as $msg) { $uid = $msg->uid; // Remove deleted message if ($this->skip_deleted && !empty($msg->flags['DELETED'])) { @@ -1004,7 +1068,7 @@ $this->db->query( "UPDATE ".$this->db->table_name('cache_messages') - ." SET flags = ?, changed = ".$this->db->now() + ." SET flags = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') ." WHERE user_id = ?" ." AND mailbox = ?" ." AND uid = ?" @@ -1032,17 +1096,18 @@ } } - // Invalidate thread index (?) - if (!$index['valid']) { - $this->remove_thread($mailbox); - } - $sort_field = $index['sort_field']; $sort_order = $index['object']->get_parameters('ORDER'); $exists = true; // Validate index if (!$this->validate($mailbox, $index, $exists)) { + // Invalidate (remove) thread index + // if $exists=false it was already removed in validate() + if ($exists) { + $this->remove_thread($mailbox); + } + // Update index $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data); } @@ -1067,7 +1132,7 @@ */ private function build_message($sql_arr) { - $message = $this->db->decode(unserialize($sql_arr['data'])); + $message = $this->db->decode($sql_arr['data'], true); if ($message) { $message->flags = array(); @@ -1150,6 +1215,25 @@ return $index; } + + + /** + * Fetches thread data from IMAP server + */ + private function get_thread_data($mailbox, $mbox_data = array()) + { + if (empty($mbox_data)) { + $mbox_data = $this->imap->folder_data($mailbox); + } + + if ($mbox_data['EXISTS']) { + // get all threads (default sort order) + return $this->imap->threads_direct($mailbox); + } + + return new rcube_result_thread($mailbox, '* THREAD'); + } + } // for backward compat.
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_imap_generic.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_imap_generic.php
Changed
@@ -72,6 +72,8 @@ const COMMAND_CAPABILITY = 2; const COMMAND_LASTLINE = 4; + const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n + /** * Object constructor */ @@ -713,6 +715,10 @@ $auth_method = 'CHECK'; } + if (!empty($this->prefs['disabled_caps'])) { + $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']); + } + $result = false; // initialize connection @@ -746,7 +752,7 @@ } if ($this->prefs['timeout'] <= 0) { - $this->prefs['timeout'] = ini_get('default_socket_timeout'); + $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout'))); } // Connect @@ -794,23 +800,21 @@ // TLS connection if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { - if (version_compare(PHP_VERSION, '5.1.0', '>=')) { - $res = $this->execute('STARTTLS'); + $res = $this->execute('STARTTLS'); - if ($res[0] != self::ERROR_OK) { - $this->closeConnection(); - return false; - } - - if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { - $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); - $this->closeConnection(); - return false; - } + if ($res[0] != self::ERROR_OK) { + $this->closeConnection(); + return false; + } - // Now we're secure, capabilities need to be reread - $this->clearCapability(); + if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); + $this->closeConnection(); + return false; } + + // Now we're secure, capabilities need to be reread + $this->clearCapability(); } // Send ID info @@ -906,7 +910,7 @@ */ function closeConnection() { - if ($this->putLine($this->nextTag() . ' LOGOUT')) { + if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) { $this->readReply(); } @@ -1065,8 +1069,8 @@ /** * Executes EXPUNGE command * - * @param string $mailbox Mailbox name - * @param string $messages Message UIDs to expunge + * @param string $mailbox Mailbox name + * @param string|array $messages Message UIDs to expunge * * @return boolean True on success, False on error */ @@ -1077,17 +1081,20 @@ } if (!$this->data['READ-WRITE']) { - $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE'); + $this->setError(self::ERROR_READONLY, "Mailbox is read-only"); return false; } // Clear internal status cache unset($this->data['STATUS:'.$mailbox]); - if ($messages) - $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE); - else + if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) { + $messages = self::compressMessageSet($messages); + $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE); + } + else { $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE); + } if ($result == self::ERROR_OK) { $this->selected = null; // state has changed, need to reselect @@ -1324,9 +1331,8 @@ $folders[$mailbox] = array(); } - // store LSUB options only if not empty, this way - // we can detect a situation when LIST doesn't return specified folder - if (!empty($opts) || $cmd == 'LIST') { + // store folder options + if ($cmd == 'LIST') { // Add to options array if (empty($this->data['LIST'][$mailbox])) $this->data['LIST'][$mailbox] = $opts; @@ -1558,11 +1564,12 @@ } // message IDs - if (!empty($add)) + if (!empty($add)) { $add = $this->compressMessageSet($add); + } list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT', - array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : ''))); + array("($field)", $encoding, !empty($add) ? $add : 'ALL')); if ($code != self::ERROR_OK) { $response = null; @@ -1649,7 +1656,6 @@ } if (!empty($criteria)) { - $modseq = stripos($criteria, 'MODSEQ') !== false; $params .= ($params ? ' ' : '') . $criteria; } else { @@ -1788,7 +1794,6 @@ if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { $flags = explode(' ', strtoupper($matches[1])); if (in_array('\\DELETED', $flags)) { - $deleted[$id] = $id; continue; } } @@ -1933,7 +1938,7 @@ } if (!$this->data['READ-WRITE']) { - $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE'); + $this->setError(self::ERROR_READONLY, "Mailbox is read-only"); return false; } @@ -1980,7 +1985,6 @@ /** * Moves message(s) from one folder to another. - * Original message(s) will be marked as deleted. * * @param string|array $messages Message UID(s) * @param string $from Mailbox name @@ -1995,19 +1999,45 @@ } if (!$this->data['READ-WRITE']) { - $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE'); + $this->setError(self::ERROR_READONLY, "Mailbox is read-only"); return false; } - $r = $this->copy($messages, $from, $to); + // use MOVE command (RFC 6851) + if ($this->hasCapability('MOVE')) { + // Clear last COPYUID data + unset($this->data['COPYUID']); - if ($r) { // Clear internal status cache + unset($this->data['STATUS:'.$to]); unset($this->data['STATUS:'.$from]); - return $this->flag($from, $messages, 'DELETED'); + $result = $this->execute('UID MOVE', array( + $this->compressMessageSet($messages), $this->escape($to)), + self::COMMAND_NORESPONSE); + + return ($result == self::ERROR_OK); + } + + // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE + $result = $this->copy($messages, $from, $to); + + if ($result) { + // Clear internal status cache + unset($this->data['STATUS:'.$from]); + + $result = $this->flag($from, $messages, 'DELETED'); + + if ($messages == '*') { + // CLOSE+SELECT should be faster than EXPUNGE + $this->close(); + } + else { + $this->expunge($from, $messages); + } } - return $r; + + return $result; } /** @@ -2130,21 +2160,25 @@ else if ($name == 'RFC822') { $result[$id]->body = $value; } - else if ($name == 'BODY') { - $body = $this->tokenizeResponse($line, 1); - if ($value[0] == 'HEADER.FIELDS') - $headers = $body; - else if (!empty($value)) - $result[$id]->bodypart[$value[0]] = $body; + else if (stripos($name, 'BODY[') === 0) { + $name = str_replace(']', '', substr($name, 5)); + + if ($name == 'HEADER.FIELDS') { + // skip ']' after headers list + $this->tokenizeResponse($line, 1); + $headers = $this->tokenizeResponse($line, 1); + } + else if (strlen($name)) + $result[$id]->bodypart[$name] = $value; else - $result[$id]->body = $body; + $result[$id]->body = $value; } } // create array with header field:data if (!empty($headers)) { $headers = explode("\n", trim($headers)); - foreach ($headers as $hid => $resln) { + foreach ($headers as $resln) { if (ord($resln[0]) <= 32) { $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln); } else { @@ -2152,7 +2186,7 @@ } } - while (list($lines_key, $str) = each($lines)) { + foreach ($lines as $str) { list($field, $string) = explode(':', $str, 2); $field = strtolower($field); @@ -2237,24 +2271,53 @@ return $result; } - function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '') + /** + * Returns message(s) data (flags, headers, etc.) + * + * @param string $mailbox Mailbox name + * @param mixed $message_set Message(s) sequence identifier(s) or UID(s) + * @param bool $is_uid True if $message_set contains UIDs + * @param bool $bodystr Enable to add BODYSTRUCTURE data to the result + * @param array $add_headers List of additional headers + * + * @return bool|array List of rcube_message_header elements, False on error + */ + function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array()) { $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'); - if ($bodystr) + $headers = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO', + 'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY'); + + if (!empty($add_headers)) { + $add_headers = array_map('strtoupper', $add_headers); + $headers = array_unique(array_merge($headers, $add_headers)); + } + + if ($bodystr) { $query_items[] = 'BODYSTRUCTURE'; - $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' - . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY' - . ($add ? ' ' . trim($add) : '') - . ')]'; + } + + $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]'; $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items); return $result; } - function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') + /** + * Returns message data (flags, headers, etc.) + * + * @param string $mailbox Mailbox name + * @param int $id Message sequence identifier or UID + * @param bool $is_uid True if $id is an UID + * @param bool $bodystr Enable to add BODYSTRUCTURE data to the result + * @param array $add_headers List of additional headers + * + * @return bool|rcube_message_header Message data, False on error + */ + function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array()) { - $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); + $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers); if (is_array($a)) { return array_shift($a); } @@ -2418,6 +2481,7 @@ $key = $this->nextTag(); $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; $result = false; + $found = false; // send request if (!$this->putLine($request)) { @@ -2426,7 +2490,7 @@ } if ($binary) { - // WARNING: Use $formatting argument with care, this may break binary data stream + // WARNING: Use $formatted argument with care, this may break binary data stream $mode = -1; } @@ -2437,18 +2501,26 @@ break; } - if (!preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { + // skip irrelevant untagged responses (we have a result already) + if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { continue; } $line = $m[2]; - $last = substr($line, -1); // handle one line response - if ($line[0] == '(' && $last == ')') { + if ($line[0] == '(' && substr($line, -1) == ')') { // tokenize content inside brackets - $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\$)/', '', $line)); - $result = count($tokens) == 1 ? $tokens[0] : false; + // the content can be e.g.: (UID 9844 BODY[2.4] NIL) + $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line)); + + for ($i=0; $i<count($tokens); $i+=2) { + if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) { + $result = $tokens[$i+1]; + $found = true; + break; + } + } if ($result !== false) { if ($mode == 1) { @@ -2466,8 +2538,13 @@ else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) { $bytes = (int) $m[1]; $prev = ''; + $found = true; - while ($bytes > 0) { + // empty body + if (!$bytes) { + $result = ''; + } + else while ($bytes > 0) { $line = $this->readLine(8192); if ($line === NULL) { @@ -2549,11 +2626,11 @@ /** * Handler for IMAP APPEND command * - * @param string $mailbox Mailbox name - * @param string $message Message content - * @param array $flags Message flags - * @param string $date Message internal date - * @param bool $binary Enable BINARY append (RFC3516) + * @param string $mailbox Mailbox name + * @param string|array $message The message source string or array (of strings and file pointers) + * @param array $flags Message flags + * @param string $date Message internal date + * @param bool $binary Enable BINARY append (RFC3516) * * @return string|bool On success APPENDUID response (if available) or True, False on failure */ @@ -2567,13 +2644,28 @@ $binary = $binary && $this->getCapability('BINARY'); $literal_plus = !$binary && $this->prefs['literal+']; + $len = 0; + $msg = is_array($message) ? $message : array(&$message); + $chunk_size = 512000; + + for ($i=0, $cnt=count($msg); $i<$cnt; $i++) { + if (is_resource($msg[$i])) { + $stat = fstat($msg[$i]); + if ($stat === false) { + return false; + } + $len += $stat['size']; + } + else { + if (!$binary) { + $msg[$i] = str_replace("\r", '', $msg[$i]); + $msg[$i] = str_replace("\n", "\r\n", $msg[$i]); + } - if (!$binary) { - $message = str_replace("\r", '', $message); - $message = str_replace("\n", "\r\n", $message); + $len += strlen($msg[$i]); + } } - $len = strlen($message); if (!$len) { return false; } @@ -2598,7 +2690,32 @@ } } - if (!$this->putLine($message)) { + foreach ($msg as $msg_part) { + // file pointer + if (is_resource($msg_part)) { + rewind($msg_part); + while (!feof($msg_part) && $this->fp) { + $buffer = fread($msg_part, $chunk_size); + $this->putLine($buffer, false); + } + fclose($msg_part); + } + // string + else { + $size = strlen($msg_part); + + // Break up the data by sending one chunk (up to 512k) at a time. + // This approach reduces our peak memory usage + for ($offset = 0; $offset < $size; $offset += $chunk_size) { + $chunk = substr($msg_part, $offset, $chunk_size); + if (!$this->putLine($chunk, false)) { + return false; + } + } + } + } + + if (!$this->putLine('')) { // \r\n return false; } @@ -2637,94 +2754,23 @@ */ function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false) { - unset($this->data['APPENDUID']); - - if ($mailbox === null || $mailbox === '') { - return false; - } - // open message file - $in_fp = false; if (file_exists(realpath($path))) { - $in_fp = fopen($path, 'r'); + $fp = fopen($path, 'r'); } - if (!$in_fp) { + if (!$fp) { $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading"); return false; } - $body_separator = "\r\n\r\n"; - $len = filesize($path); - - if (!$len) { - return false; - } - + $message = array(); if ($headers) { - $headers = preg_replace('/[\r\n]+$/', '', $headers); - $len += strlen($headers) + strlen($body_separator); - } - - $binary = $binary && $this->getCapability('BINARY'); - $literal_plus = !$binary && $this->prefs['literal+']; - - // build APPEND command - $key = $this->nextTag(); - $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')'; - if (!empty($date)) { - $request .= ' ' . $this->escape($date); - } - $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}'; - - // send APPEND command - if ($this->putLine($request)) { - // Don't wait when LITERAL+ is supported - if (!$literal_plus) { - $line = $this->readReply(); - - if ($line[0] != '+') { - $this->parseResult($line, 'APPEND: '); - return false; - } - } - - // send headers with body separator - if ($headers) { - $this->putLine($headers . $body_separator, false); - } - - // send file - while (!feof($in_fp) && $this->fp) { - $buffer = fgets($in_fp, 4096); - $this->putLine($buffer, false); - } - fclose($in_fp); - - if (!$this->putLine('')) { // \r\n - return false; - } - - // read response - do { - $line = $this->readLine(); - } while (!$this->startsWith($line, $key, true, true)); - - // Clear internal status cache - unset($this->data['STATUS:'.$mailbox]); - - if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK) - return false; - else if (!empty($this->data['APPENDUID'])) - return $this->data['APPENDUID']; - else - return true; - } - else { - $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); + $message[] = trim($headers, "\r\n") . "\r\n\r\n"; } + $message[] = $fp; - return false; + return $this->append($mailbox, $message, $flags, $date, $binary); } /** @@ -2942,7 +2988,7 @@ } foreach ($entries as $name => $value) { - $entries[$name] = $this->escape($name) . ' ' . $this->escape($value); + $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true); } $entries = implode(' ', $entries); @@ -3439,25 +3485,24 @@ // Parenthesized list case '(': - case '[': $str = substr($str, 1); $result[] = self::tokenizeResponse($str); break; case ')': - case ']': $str = substr($str, 1); return $result; break; - // String atom, number, NIL, *, % + // String atom, number, astring, NIL, *, % default: // empty string if ($str === '' || $str === null) { break 2; } - // excluded chars: SP, CTL, ), [, ] - if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) { + // excluded chars: SP, CTL, ), DEL + // we do not exclude [ and ] (#1489223) + if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) { $result[] = $m[1] == 'NIL' ? NULL : $m[1]; $str = substr($str, strlen($m[1])); } @@ -3474,7 +3519,7 @@ if (is_array($element)) { reset($element); - while (list($key, $value) = each($element)) { + foreach ($element as $value) { $string .= ' ' . self::r_implode($value); } } @@ -3502,7 +3547,7 @@ // if less than 255 bytes long, let's not bother if (!$force && strlen($messages)<255) { return $messages; - } + } // see if it's already been compressed if (strpos($messages, ':') !== false) { @@ -3610,8 +3655,20 @@ */ static function strToTime($date) { - // support non-standard "GMTXXXX" literal - $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); + // Clean malformed data + $date = preg_replace( + array( + '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal + '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters + '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names + ), + array( + '\\1', + '', + '', + ), $date); + + $date = trim($date); // if date parsing fails, we have a date in non-rfc format // remove token from the end and try again @@ -3636,6 +3693,10 @@ $this->capability = explode(' ', strtoupper($str)); + if (!empty($this->prefs['disabled_caps'])) { + $this->capability = array_diff($this->capability, $this->prefs['disabled_caps']); + } + if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) { $this->prefs['literal+'] = true; } @@ -3681,9 +3742,10 @@ /** * Set the value of the debugging flag. * - * @param boolean $debug New value for the debugging flag. + * @param boolean $debug New value for the debugging flag. + * @param callback $handler Logging handler function * - * @since 0.5-stable + * @since 0.5-stable */ function setDebug($debug, $handler = null) { @@ -3694,12 +3756,18 @@ /** * Write the given debug text to the current debug output handler. * - * @param string $message Debug mesage text. + * @param string $message Debug mesage text. * - * @since 0.5-stable + * @since 0.5-stable */ private function debug($message) { + if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) { + $diff = $len - self::DEBUG_LINE_LENGTH; + $message = substr($message, 0, self::DEBUG_LINE_LENGTH) + . "... [truncated $diff bytes]"; + } + if ($this->resourceid) { $message = sprintf('[%s] %s', $this->resourceid, $message); }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_ldap.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_ldap.php
Changed
@@ -3,8 +3,8 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2006-2012, The Roundcube Dev Team | - | Copyright (C) 2011-2012, Kolab Systems AG | + | Copyright (C) 2006-2013, The Roundcube Dev Team | + | Copyright (C) 2011-2013, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -27,38 +27,50 @@ */ class rcube_ldap extends rcube_addressbook { - /** public properties */ + // public properties public $primary_key = 'ID'; - public $groups = false; - public $readonly = true; - public $ready = false; - public $group_id = 0; - public $coltypes = array(); - - /** private properties */ - protected $conn; - protected $prop = array(); + public $groups = false; + public $readonly = true; + public $ready = false; + public $group_id = 0; + public $coltypes = array(); + + // private properties + protected $ldap; + protected $prop = array(); protected $fieldmap = array(); + protected $filter = ''; protected $sub_filter; - protected $filter = ''; - protected $result = null; - protected $ldap_result = null; + protected $result; + protected $ldap_result; protected $mail_domain = ''; protected $debug = false; - private $base_dn = ''; + /** + * Group objectclass (lowercase) to member attribute mapping + * + * @var array + */ + private static $group_types = array( + 'group' => 'member', + 'groupofnames' => 'member', + 'kolabgroupofnames' => 'member', + 'groupofuniquenames' => 'uniqueMember', + 'kolabgroupofuniquenames' => 'uniqueMember', + 'univentiongroup' => 'uniqueMember', + 'groupofurls' => null, + ); + + private $base_dn = ''; private $groups_base_dn = ''; - private $group_url = null; + private $group_url; private $cache; - private $vlv_active = false; - private $vlv_count = 0; - /** * Object constructor * - * @param array $p LDAP connection properties + * @param array $p LDAP connection properties * @param boolean $debug Enables debug mode * @param string $mail_domain Current user mail domain name */ @@ -66,8 +78,7 @@ { $this->prop = $p; - if (isset($p['searchonly'])) - $this->searchonly = $p['searchonly']; + $fetch_attributes = array('objectClass'); // check if groups are configured if (is_array($p['groups']) && count($p['groups'])) { @@ -82,6 +93,21 @@ $this->prop['groups']['name_attr'] = 'cn'; if (empty($this->prop['groups']['scope'])) $this->prop['groups']['scope'] = 'sub'; + + // add group name attrib to the list of attributes to be fetched + $fetch_attributes[] = $this->prop['groups']['name_attr']; + } + if (is_array($p['group_filters']) && count($p['group_filters'])) { + $this->groups = true; + + foreach ($p['group_filters'] as $k => $group_filter) { + // set default name attribute to cn + if (empty($group_filter['name_attr']) && empty($this->prop['groups']['name_attr'])) + $this->prop['group_filters'][$k]['name_attr'] = $group_filter['name_attr'] = 'cn'; + + if ($group_filter['name_attr']) + $fetch_attributes[] = $group_filter['name_attr']; + } } // fieldmap property is given @@ -169,7 +195,7 @@ // Build sub_fields filter if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) { $this->sub_filter = ''; - foreach ($this->prop['sub_fields'] as $attr => $class) { + foreach ($this->prop['sub_fields'] as $class) { if (!empty($class)) { $class = is_array($class) ? array_pop($class) : $class; $this->sub_filter .= '(objectClass=' . $class . ')'; @@ -186,7 +212,24 @@ // initialize cache $rcube = rcube::get_instance(); - $this->cache = $rcube->get_cache('LDAP.' . asciiwords($this->prop['name']), 'db', 600); + if ($cache_type = $rcube->config->get('ldap_cache', 'db')) { + $cache_ttl = $rcube->config->get('ldap_cache_ttl', '10m'); + $cache_name = 'LDAP.' . asciiwords($this->prop['name']); + + $this->cache = $rcube->get_cache($cache_name, $cache_type, $cache_ttl); + } + + // determine which attributes to fetch + $this->prop['list_attributes'] = array_unique($fetch_attributes); + $this->prop['attributes'] = array_merge(array_values($this->fieldmap), $fetch_attributes); + foreach ($rcube->config->get('contactlist_fields') as $col) { + $this->prop['list_attributes'] = array_merge($this->prop['list_attributes'], $this->_map_field($col)); + } + + // initialize ldap wrapper object + $this->ldap = new rcube_ldap_generic($this->prop); + $this->ldap->set_cache($this->cache); + $this->ldap->set_debug($this->debug); $this->_connect(); } @@ -199,147 +242,149 @@ { $rcube = rcube::get_instance(); - if (!function_exists('ldap_connect')) - rcube::raise_error(array('code' => 100, 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "No ldap support in this installation of PHP"), - true, true); - - if (is_resource($this->conn)) + if ($this->ready) return true; if (!is_array($this->prop['hosts'])) $this->prop['hosts'] = array($this->prop['hosts']); - if (empty($this->prop['ldap_version'])) - $this->prop['ldap_version'] = 3; + // try to connect + bind for every host configured + // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable + // see http://www.php.net/manual/en/function.ldap-connect.php + foreach ($this->prop['hosts'] as $host) { + // skip host if connection failed + if (!$this->ldap->connect($host)) { + continue; + } - foreach ($this->prop['hosts'] as $host) - { - $host = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host)); - $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : ''); + // See if the directory is writeable. + if ($this->prop['writable']) { + $this->readonly = false; + } - $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]"); + $bind_pass = $this->prop['bind_pass']; + $bind_user = $this->prop['bind_user']; + $bind_dn = $this->prop['bind_dn']; - if ($lc = @ldap_connect($host, $this->prop['port'])) - { - if ($this->prop['use_tls'] === true) - if (!ldap_start_tls($lc)) - continue; + $this->base_dn = $this->prop['base_dn']; + $this->groups_base_dn = ($this->prop['groups']['base_dn']) ? + $this->prop['groups']['base_dn'] : $this->base_dn; - $this->_debug("S: OK"); + // User specific access, generate the proper values to use. + if ($this->prop['user_specific']) { + // No password set, use the session password + if (empty($bind_pass)) { + $bind_pass = $rcube->get_user_password(); + } - ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['ldap_version']); - $this->prop['host'] = $host; - $this->conn = $lc; + // Get the pieces needed for variable replacement. + if ($fu = $rcube->get_user_email()) + list($u, $d) = explode('@', $fu); + else + $d = $this->mail_domain; - if (isset($this->prop['referrals'])) - ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->prop['referrals']); - break; - } - $this->_debug("S: NOT OK"); - } + $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string - // See if the directory is writeable. - if ($this->prop['writable']) { - $this->readonly = false; - } + $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u); - if (!is_resource($this->conn)) { - rcube::raise_error(array('code' => 100, 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not connect to any LDAP server, last tried $hostname"), true); + // Search for the dn to use to authenticate + if ($this->prop['search_base_dn'] && $this->prop['search_filter']) { + $search_bind_dn = strtr($this->prop['search_bind_dn'], $replaces); + $search_base_dn = strtr($this->prop['search_base_dn'], $replaces); + $search_filter = strtr($this->prop['search_filter'], $replaces); - return false; - } + $cache_key = 'DN.' . md5("$host:$search_bind_dn:$search_base_dn:$search_filter:" + .$this->prop['search_bind_pw']); - $bind_pass = $this->prop['bind_pass']; - $bind_user = $this->prop['bind_user']; - $bind_dn = $this->prop['bind_dn']; + if ($this->cache && ($dn = $this->cache->get($cache_key))) { + $replaces['%dn'] = $dn; + } + else { + $ldap = $this->ldap; + if (!empty($search_bind_dn) && !empty($this->prop['search_bind_pw'])) { + // To protect from "Critical extension is unavailable" error + // we need to use a separate LDAP connection + if (!empty($this->prop['vlv'])) { + $ldap = new rcube_ldap_generic($this->prop); + $ldap->set_debug($this->debug); + $ldap->set_cache($this->cache); + if (!$ldap->connect($host)) { + continue; + } + } + + if (!$ldap->bind($search_bind_dn, $this->prop['search_bind_pw'])) { + continue; // bind failed, try next host + } + } - $this->base_dn = $this->prop['base_dn']; - $this->groups_base_dn = ($this->prop['groups']['base_dn']) ? - $this->prop['groups']['base_dn'] : $this->base_dn; + $res = $ldap->search($search_base_dn, $search_filter, 'sub', array('uid')); + if ($res) { + $res->rewind(); + $replaces['%dn'] = $res->get_dn(); + } - // User specific access, generate the proper values to use. - if ($this->prop['user_specific']) { - // No password set, use the session password - if (empty($bind_pass)) { - $bind_pass = $rcube->get_user_password(); - } + if ($ldap != $this->ldap) { + $ldap->close(); + } + } - // Get the pieces needed for variable replacement. - if ($fu = $rcube->get_user_email()) - list($u, $d) = explode('@', $fu); - else - $d = $this->mail_domain; + // DN not found + if (empty($replaces['%dn'])) { + if (!empty($this->prop['search_dn_default'])) + $replaces['%dn'] = $this->prop['search_dn_default']; + else { + rcube::raise_error(array( + 'code' => 100, 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "DN not found using LDAP search."), true); + continue; + } + } - $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string + if ($this->cache && !empty($replaces['%dn'])) { + $this->cache->set($cache_key, $replaces['%dn']); + } + } - $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u); + // Replace the bind_dn and base_dn variables. + $bind_dn = strtr($bind_dn, $replaces); + $this->base_dn = strtr($this->base_dn, $replaces); + $this->groups_base_dn = strtr($this->groups_base_dn, $replaces); - if ($this->prop['search_base_dn'] && $this->prop['search_filter']) { - if (!empty($this->prop['search_bind_dn']) && !empty($this->prop['search_bind_pw'])) { - $this->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']); + if (empty($bind_user)) { + $bind_user = $u; } + } - // Search for the dn to use to authenticate - $this->prop['search_base_dn'] = strtr($this->prop['search_base_dn'], $replaces); - $this->prop['search_filter'] = strtr($this->prop['search_filter'], $replaces); - - $this->_debug("S: searching with base {$this->prop['search_base_dn']} for {$this->prop['search_filter']}"); - - $res = @ldap_search($this->conn, $this->prop['search_base_dn'], $this->prop['search_filter'], array('uid')); - if ($res) { - if (($entry = ldap_first_entry($this->conn, $res)) - && ($bind_dn = ldap_get_dn($this->conn, $entry)) - ) { - $this->_debug("S: search returned dn: $bind_dn"); - $dn = ldap_explode_dn($bind_dn, 1); - $replaces['%dn'] = $dn[0]; - } + if (empty($bind_pass)) { + $this->ready = true; + } + else { + if (!empty($bind_dn)) { + $this->ready = $this->ldap->bind($bind_dn, $bind_pass); + } + else if (!empty($this->prop['auth_cid'])) { + $this->ready = $this->ldap->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user); } else { - $this->_debug("S: ".ldap_error($this->conn)); + $this->ready = $this->ldap->sasl_bind($bind_user, $bind_pass); } + } - // DN not found - if (empty($replaces['%dn'])) { - if (!empty($this->prop['search_dn_default'])) - $replaces['%dn'] = $this->prop['search_dn_default']; - else { - rcube::raise_error(array( - 'code' => 100, 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "DN not found using LDAP search."), true); - return false; - } - } + // connection established, we're done here + if ($this->ready) { + break; } - // Replace the bind_dn and base_dn variables. - $bind_dn = strtr($bind_dn, $replaces); - $this->base_dn = strtr($this->base_dn, $replaces); - $this->groups_base_dn = strtr($this->groups_base_dn, $replaces); + } // end foreach hosts - if (empty($bind_user)) { - $bind_user = $u; - } - } + if (!is_resource($this->ldap->conn)) { + rcube::raise_error(array('code' => 100, 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not connect to any LDAP server, last tried $host"), true); - if (empty($bind_pass)) { - $this->ready = true; - } - else { - if (!empty($bind_dn)) { - $this->ready = $this->bind($bind_dn, $bind_pass); - } - else if (!empty($this->prop['auth_cid'])) { - $this->ready = $this->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user); - } - else { - $this->ready = $this->sasl_bind($bind_user, $bind_pass); - } + return false; } return $this->ready; @@ -347,112 +392,47 @@ /** - * Bind connection with (SASL-) user and password - * - * @param string $authc Authentication user - * @param string $pass Bind password - * @param string $authz Autorization user - * - * @return boolean True on success, False on error + * Close connection to LDAP server */ - public function sasl_bind($authc, $pass, $authz=null) + function close() { - if (!$this->conn) { - return false; - } - - if (!function_exists('ldap_sasl_bind')) { - rcube::raise_error(array('code' => 100, 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Unable to bind: ldap_sasl_bind() not exists"), - true, true); + if ($this->ldap) { + $this->ldap->close(); } - - if (!empty($authz)) { - $authz = 'u:' . $authz; - } - - if (!empty($this->prop['auth_method'])) { - $method = $this->prop['auth_method']; - } - else { - $method = 'DIGEST-MD5'; - } - - $this->_debug("C: Bind [mech: $method, authc: $authc, authz: $authz] [pass: $pass]"); - - if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) { - $this->_debug("S: OK"); - return true; - } - - $this->_debug("S: ".ldap_error($this->conn)); - - rcube::raise_error(array( - 'code' => ldap_errno($this->conn), 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)), - true); - - return false; } /** - * Bind connection with DN and password - * - * @param string Bind DN - * @param string Bind password + * Returns address book name * - * @return boolean True on success, False on error + * @return string Address book name */ - public function bind($dn, $pass) + function get_name() { - if (!$this->conn) { - return false; - } - - $this->_debug("C: Bind [dn: $dn] [pass: $pass]"); - - if (@ldap_bind($this->conn, $dn, $pass)) { - $this->_debug("S: OK"); - return true; - } - - $this->_debug("S: ".ldap_error($this->conn)); - - rcube::raise_error(array( - 'code' => ldap_errno($this->conn), 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)), - true); - - return false; + return $this->prop['name']; } /** - * Close connection to LDAP server + * Set internal list page + * + * @param number Page number to list */ - function close() + function set_page($page) { - if ($this->conn) - { - $this->_debug("C: Close"); - ldap_unbind($this->conn); - $this->conn = null; - } + $this->list_page = (int)$page; + $this->ldap->set_vlv_page($this->list_page, $this->page_size); } - /** - * Returns address book name + * Set internal page size * - * @return string Address book name + * @param number Number of records to display on one page */ - function get_name() + function set_pagesize($size) { - return $this->prop['name']; + $this->page_size = (int)$size; + $this->ldap->set_vlv_page($this->list_page, $this->page_size); } @@ -512,16 +492,14 @@ */ function list_records($cols=null, $subset=0) { - if ($this->prop['searchonly'] && empty($this->filter) && !$this->group_id) - { + if ($this->prop['searchonly'] && empty($this->filter) && !$this->group_id) { $this->result = new rcube_result_set(0); $this->result->searchonly = true; return $this->result; } // fetch group members recursively - if ($this->group_id && $this->group_data['dn']) - { + if ($this->group_id && $this->group_data['dn']) { $entries = $this->list_group_members($this->group_data['dn']); // make list of entries unique and sort it @@ -535,34 +513,35 @@ $entries['count'] = count($entries); $this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size); } - else - { - // add general filter to query - if (!empty($this->prop['filter']) && empty($this->filter)) - $this->set_search_set($this->prop['filter']); + else { + $prop = $this->group_id ? $this->group_data : $this->prop; + $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn; + + // use global search filter + if (!empty($this->filter)) + $prop['filter'] = $this->filter; // exec LDAP search if no result resource is stored - if ($this->conn && !$this->ldap_result) - $this->_exec_search(); + if ($this->ready && !$this->ldap_result) + $this->ldap_result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop); // count contacts for this user $this->result = $this->count(); // we have a search result resource - if ($this->ldap_result && $this->result->count > 0) - { + if ($this->ldap_result && $this->result->count > 0) { // sorting still on the ldap server - if ($this->sort_col && $this->prop['scope'] !== 'base' && !$this->vlv_active) - ldap_sort($this->conn, $this->ldap_result, $this->sort_col); + if ($this->sort_col && $prop['scope'] !== 'base' && !$this->ldap->vlv_active) + $this->ldap_result->sort($this->sort_col); // get all entries from the ldap server - $entries = ldap_get_entries($this->conn, $this->ldap_result); + $entries = $this->ldap_result->entries(); } } // end else // start and end of the page - $start_row = $this->vlv_active ? 0 : $this->result->first; + $start_row = $this->ldap->vlv_active ? 0 : $this->result->first; $start_row = $subset < 0 ? $start_row + $this->page_size + $subset : $start_row; $last_row = $this->result->first + $this->page_size; $last_row = $subset != 0 ? $start_row + abs($subset) : $last_row; @@ -587,43 +566,34 @@ // fetch group object if (empty($entries)) { - $result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL')); - if ($result === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + $attribs = array('dn','objectClass','member','uniqueMember','memberURL'); + $entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs); + if ($entries === false) { return $group_members; } - - $entries = @ldap_get_entries($this->conn, $result); } - for ($i=0; $i < $entries['count']; $i++) - { + for ($i=0; $i < $entries['count']; $i++) { $entry = $entries[$i]; + $attrs = array(); - if (empty($entry['objectclass'])) - continue; + foreach ((array)$entry['objectclass'] as $objectclass) { + if (strtolower($objectclass) == 'groupofurls') { + $members = $this->_list_group_memberurl($dn, $entry, $count); + $group_members = array_merge($group_members, $members); + } + else if (($member_attr = $this->get_group_member_attr(array($objectclass), '')) + && ($member_attr = strtolower($member_attr)) && !in_array($member_attr, $attrs) + ) { + $members = $this->_list_group_members($dn, $entry, $member_attr, $count); + $group_members = array_merge($group_members, $members); + $attrs[] = $member_attr; + } - foreach ((array)$entry['objectclass'] as $objectclass) - { - switch (strtolower($objectclass)) { - case "group": - case "groupofnames": - case "kolabgroupofnames": - $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'member', $count)); - break; - case "groupofuniquenames": - case "kolabgroupofuniquenames": - $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'uniquemember', $count)); - break; - case "groupofurls": - $group_members = array_merge($group_members, $this->_list_group_memberurl($dn, $entry, $count)); - break; + if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) { + break 2; } } - - if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) - break; } return array_filter($group_members); @@ -642,28 +612,24 @@ // Use the member attributes to return an array of member ldap objects // NOTE that the member attribute is supposed to contain a DN $group_members = array(); - if (empty($entry[$attr])) + if (empty($entry[$attr])) { return $group_members; + } // read these attributes for all members - $attrib = $count ? array('dn') : array_values($this->fieldmap); - $attrib[] = 'objectClass'; + $attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes']; $attrib[] = 'member'; $attrib[] = 'uniqueMember'; $attrib[] = 'memberURL'; - for ($i=0; $i < $entry[$attr]['count']; $i++) - { + $filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)'; + + for ($i=0; $i < $entry[$attr]['count']; $i++) { if (empty($entry[$attr][$i])) continue; - $result = @ldap_read($this->conn, $entry[$attr][$i], '(objectclass=*)', - $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']); - - $members = @ldap_get_entries($this->conn, $result); - if ($members == false) - { - $this->_debug("S: ".ldap_error($this->conn)); + $members = $this->ldap->read_entries($entry[$attr][$i], $filter, $attrib); + if ($members == false) { $members = array(); } @@ -689,34 +655,22 @@ { $group_members = array(); - for ($i=0; $i < $entry['memberurl']['count']; $i++) - { + for ($i=0; $i < $entry['memberurl']['count']; $i++) { // extract components from url if (!preg_match('!ldap:///([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m)) continue; // add search filter if any $filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3]; - $func = $m[2] == 'sub' ? 'ldap_search' : ($m[2] == 'base' ? 'ldap_read' : 'ldap_list'); - - $attrib = $count ? array('dn') : array_values($this->fieldmap); - if ($result = @$func($this->conn, $m[1], $filter, - $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) - ) { - $this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]); - } - else { - $this->_debug("S: ".ldap_error($this->conn)); - return $group_members; - } - - $entries = @ldap_get_entries($this->conn, $result); - for ($j = 0; $j < $entries['count']; $j++) - { - if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count)) - $group_members = array_merge($group_members, $nested_group_members); - else - $group_members[] = $entries[$j]; + $attrs = $count ? array('dn','objectClass') : $this->prop['list_attributes']; + if ($result = $this->ldap->search($m[1], $filter, $m[2], $attrs, $this->group_data)) { + $entries = $result->entries(); + for ($j = 0; $j < $entries['count']; $j++) { + if (self::is_group_entry($entries[$j]) && ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))) + $group_members = array_merge($group_members, $nested_group_members); + else + $group_members[] = $entries[$j]; + } } } @@ -752,14 +706,11 @@ $mode = intval($mode); // special treatment for ID-based search - if ($fields == 'ID' || $fields == $this->primary_key) - { + if ($fields == 'ID' || $fields == $this->primary_key) { $ids = !is_array($value) ? explode(',', $value) : $value; $result = new rcube_result_set(); - foreach ($ids as $id) - { - if ($rec = $this->get_record($id, true)) - { + foreach ($ids as $id) { + if ($rec = $this->get_record($id, true)) { $result->add($rec); $result->count++; } @@ -771,34 +722,20 @@ $rcube = rcube::get_instance(); $list_fields = $rcube->config->get('contactlist_fields'); - if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $list_fields)) - { - // add general filter to query - if (!empty($this->prop['filter']) && empty($this->filter)) - $this->set_search_set($this->prop['filter']); - - // set VLV controls with encoded search string - $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size, $value); - - $function = $this->_scope2func($this->prop['scope']); - $this->ldap_result = @$function($this->conn, $this->base_dn, $this->filter ? $this->filter : '(objectclass=*)', - array_values($this->fieldmap), 0, $this->page_size, (int)$this->prop['timelimit']); - + if ($this->prop['vlv_search'] && $this->ready && join(',', (array)$fields) == join(',', $list_fields)) { $this->result = new rcube_result_set(0); - if (!$this->ldap_result) { - $this->_debug("S: ".ldap_error($this->conn)); + $search_suffix = $this->prop['fuzzy_search'] && $mode != 1 ? '*' : ''; + $ldap_data = $this->ldap->search($this->base_dn, $this->prop['filter'], $this->prop['scope'], $this->prop['attributes'], + array('search' => $value . $search_suffix /*, 'sort' => $this->prop['sort'] */)); + if ($ldap_data === false) { return $this->result; } - $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)"); - // get all entries of this page and post-filter those that really match the query - $search = mb_strtolower($value); - $entries = ldap_get_entries($this->conn, $this->ldap_result); - - for ($i = 0; $i < $entries['count']; $i++) { - $rec = $this->_ldap2result($entries[$i]); + $search = mb_strtolower($value); + foreach ($ldap_data as $i => $entry) { + $rec = $this->_ldap2result($entry); foreach ($fields as $f) { foreach ((array)$rec[$f] as $val) { if ($this->compare_search_value($f, $val, $search, $mode)) { @@ -824,31 +761,27 @@ } } - if ($fields == '*') - { + if ($fields == '*') { // search_fields are required for fulltext search - if (empty($this->prop['search_fields'])) - { + if (empty($this->prop['search_fields'])) { $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch'); $this->result = new rcube_result_set(); return $this->result; } - if (is_array($this->prop['search_fields'])) - { + if (is_array($this->prop['search_fields'])) { foreach ($this->prop['search_fields'] as $field) { - $filter .= "($field=$wp" . $this->_quote_string($value) . "$ws)"; + $filter .= "($field=$wp" . rcube_ldap_generic::quote_string($value) . "$ws)"; } } } - else - { + else { foreach ((array)$fields as $idx => $field) { $val = is_array($value) ? $value[$idx] : $value; if ($attrs = $this->_map_field($field)) { if (count($attrs) > 1) $filter .= '(|'; foreach ($attrs as $f) - $filter .= "($f=$wp" . $this->_quote_string($val) . "$ws)"; + $filter .= "($f=$wp" . rcube_ldap_generic::quote_string($val) . "$ws)"; if (count($attrs) > 1) $filter .= ')'; } @@ -859,6 +792,8 @@ // add required (non empty) fields filter $req_filter = ''; foreach ((array)$required as $field) { + if (in_array($field, (array)$fields)) // required field is already in search filter + continue; if ($attrs = $this->_map_field($field)) { if (count($attrs) > 1) $req_filter .= '(|'; @@ -881,7 +816,6 @@ // set filter string and execute search $this->set_search_set($filter); - $this->_exec_search(); if ($select) $this->list_records(); @@ -900,20 +834,21 @@ function count() { $count = 0; - if ($this->conn && $this->ldap_result) { - $count = $this->vlv_active ? $this->vlv_count : ldap_count_entries($this->conn, $this->ldap_result); + if ($this->ldap_result) { + $count = $this->ldap_result->count(); } else if ($this->group_id && $this->group_data['dn']) { $count = count($this->list_group_members($this->group_data['dn'], true)); } - else if ($this->conn) { - // We have a connection but no result set, attempt to get one. - if (empty($this->filter)) { - // The filter is not set, set it. - $this->filter = $this->prop['filter']; - } + // We have a connection but no result set, attempt to get one. + else if ($this->ready) { + $prop = $this->group_id ? $this->group_data : $this->prop; + $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn; - $count = (int) $this->_exec_search(true); + if (!empty($this->filter)) { // Use global search filter + $prop['filter'] = $this->filter; + } + $count = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], array('dn'), $prop, true); } return new rcube_result_set($count, ($this->list_page-1) * $this->page_size); @@ -943,28 +878,16 @@ { $res = $this->result = null; - if ($this->conn && $dn) - { + if ($this->ready && $dn) { $dn = self::dn_decode($dn); - $this->_debug("C: Read [dn: $dn] [(objectclass=*)]"); - - if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', array_values($this->fieldmap))) { - $this->_debug("S: OK"); - - $entry = ldap_first_entry($this->conn, $ldap_result); - - if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) { - $rec = array_change_key_case($rec, CASE_LOWER); - } - } - else { - $this->_debug("S: ".ldap_error($this->conn)); + if ($rec = $this->ldap->get_entry($dn)) { + $rec = array_change_key_case($rec, CASE_LOWER); } // Use ldap_list to get subentries like country (c) attribute (#1488123) if (!empty($rec) && $this->sub_filter) { - if ($entries = $this->ldap_list($dn, $this->sub_filter, array_keys($this->prop['sub_fields']))) { + if ($entries = $this->ldap->list_entries($dn, $this->sub_filter, array_keys($this->prop['sub_fields']))) { foreach ($entries as $entry) { $lrec = array_change_key_case($entry, CASE_LOWER); $rec = array_merge($lrec, $rec); @@ -976,7 +899,7 @@ // Add in the dn for the entry. $rec['dn'] = $dn; $res = $this->_ldap2result($rec); - $this->result = new rcube_result_set(); + $this->result = new rcube_result_set(1); $this->result->add($res); } } @@ -1023,7 +946,6 @@ $mail_field = $this->fieldmap['email']; // try to extract surname and firstname from displayname - $reverse_map = array_flip($this->fieldmap); $name_parts = preg_split('/[\s,.]+/', $save_data['name']); if ($sn_field && $missing[$sn_field]) { @@ -1090,12 +1012,12 @@ } // Build the new entries DN. - $dn = $this->prop['LDAP_rdn'].'='.$this->_quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->base_dn; + $dn = $this->prop['LDAP_rdn'].'='.rcube_ldap_generic::quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->base_dn; // Remove attributes that need to be added separately (child objects) $xfields = array(); if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) { - foreach ($this->prop['sub_fields'] as $xf => $xclass) { + foreach (array_keys($this->prop['sub_fields']) as $xf) { if (!empty($newentry[$xf])) { $xfields[$xf] = $newentry[$xf]; unset($newentry[$xf]); @@ -1103,19 +1025,19 @@ } } - if (!$this->ldap_add($dn, $newentry)) { + if (!$this->ldap->add($dn, $newentry)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } foreach ($xfields as $xidx => $xf) { - $xdn = $xidx.'='.$this->_quote_string($xf).','.$dn; + $xdn = $xidx.'='.rcube_ldap_generic::quote_string($xf).','.$dn; $xf = array( $xidx => $xf, 'objectClass' => (array) $this->prop['sub_fields'][$xidx], ); - $this->ldap_add($xdn, $xf); + $this->ldap->add($xdn, $xf); } $dn = self::dn_encode($dn); @@ -1158,7 +1080,7 @@ } } - foreach ($this->fieldmap as $col => $fld) { + foreach ($this->fieldmap as $fld) { if ($fld) { $val = $ldap_data[$fld]; $old = $old_data[$fld]; @@ -1221,7 +1143,7 @@ // Update the entry as required. if (!empty($deletedata)) { // Delete the fields. - if (!$this->ldap_mod_del($dn, $deletedata)) { + if (!$this->ldap->mod_del($dn, $deletedata)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1231,17 +1153,17 @@ // Handle RDN change if ($replacedata[$this->prop['LDAP_rdn']]) { $newdn = $this->prop['LDAP_rdn'].'=' - .$this->_quote_string($replacedata[$this->prop['LDAP_rdn']], true) + .rcube_ldap_generic::quote_string($replacedata[$this->prop['LDAP_rdn']], true) .','.$this->base_dn; if ($dn != $newdn) { $newrdn = $this->prop['LDAP_rdn'].'=' - .$this->_quote_string($replacedata[$this->prop['LDAP_rdn']], true); + .rcube_ldap_generic::quote_string($replacedata[$this->prop['LDAP_rdn']], true); unset($replacedata[$this->prop['LDAP_rdn']]); } } // Replace the fields. if (!empty($replacedata)) { - if (!$this->ldap_mod_replace($dn, $replacedata)) { + if (!$this->ldap->mod_replace($dn, $replacedata)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1257,8 +1179,8 @@ // remove sub-entries if (!empty($subdeldata)) { foreach ($subdeldata as $fld => $val) { - $subdn = $fld.'='.$this->_quote_string($val).','.$dn; - if (!$this->ldap_delete($subdn)) { + $subdn = $fld.'='.rcube_ldap_generic::quote_string($val).','.$dn; + if (!$this->ldap->delete($subdn)) { return false; } } @@ -1266,7 +1188,7 @@ if (!empty($newdata)) { // Add the fields. - if (!$this->ldap_mod_add($dn, $newdata)) { + if (!$this->ldap->mod_add($dn, $newdata)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1274,7 +1196,7 @@ // Handle RDN change if (!empty($newrdn)) { - if (!$this->ldap_rename($dn, $newrdn, null, true)) { + if (!$this->ldap->rename($dn, $newrdn, null, true)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1285,8 +1207,7 @@ // change the group membership of the contact if ($this->groups) { $group_ids = $this->get_record_groups($dn); - foreach ($group_ids as $group_id) - { + foreach (array_keys($group_ids) as $group_id) { $this->remove_from_group($group_id, $dn); $this->add_to_group($group_id, $newdn); } @@ -1298,12 +1219,12 @@ // add sub-entries if (!empty($subnewdata)) { foreach ($subnewdata as $fld => $val) { - $subdn = $fld.'='.$this->_quote_string($val).','.$dn; + $subdn = $fld.'='.rcube_ldap_generic::quote_string($val).','.$dn; $xf = array( $fld => $val, 'objectClass' => (array) $this->prop['sub_fields'][$fld], ); - $this->ldap_add($subdn, $xf); + $this->ldap->add($subdn, $xf); } } @@ -1331,9 +1252,9 @@ // Need to delete all sub-entries first if ($this->sub_filter) { - if ($entries = $this->ldap_list($dn, $this->sub_filter)) { + if ($entries = $this->ldap->list_entries($dn, $this->sub_filter)) { foreach ($entries as $entry) { - if (!$this->ldap_delete($entry['dn'])) { + if (!$this->ldap->delete($entry['dn'])) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1342,7 +1263,7 @@ } // Delete the record. - if (!$this->ldap_delete($dn)) { + if (!$this->ldap->delete($dn)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1351,7 +1272,7 @@ if ($this->groups) { $dn = self::dn_encode($dn); $group_ids = $this->get_record_groups($dn); - foreach ($group_ids as $group_id) { + foreach (array_keys($group_ids) as $group_id) { $this->remove_from_group($group_id, $dn); } } @@ -1366,8 +1287,8 @@ */ function delete_all() { - //searching for contact entries - $dn_list = $this->ldap_list($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)'); + // searching for contact entries + $dn_list = $this->ldap->list_entries($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)'); if (!empty($dn_list)) { foreach ($dn_list as $idx => $entry) { @@ -1384,6 +1305,10 @@ */ protected function add_autovalues(&$attrs) { + if (empty($this->prop['autovalues'])) { + return; + } + $attrvals = array(); foreach ($attrs as $k => $v) { $attrvals['{'.$k.'}'] = is_array($v) ? $v[0] : $v; @@ -1391,117 +1316,26 @@ foreach ((array)$this->prop['autovalues'] as $lf => $templ) { if (empty($attrs[$lf])) { - // replace {attr} placeholders with concrete attribute values - $templ = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals)); - - if (strpos($templ, '(') !== false) - $attrs[$lf] = eval("return ($templ);"); - else - $attrs[$lf] = $templ; - } - } - } + if (strpos($templ, '(') !== false) { + // replace {attr} placeholders with (escaped!) attribute values to be safely eval'd + $code = preg_replace('/\{\w+\}/', '', strtr($templ, array_map('addslashes', $attrvals))); + $fn = create_function('', "return ($code);"); + if (!$fn) { + rcube::raise_error(array( + 'code' => 505, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Expression parse error on: ($code)"), true, false); + continue; + } - /** - * Execute the LDAP search based on the stored credentials - */ - private function _exec_search($count = false) - { - if ($this->ready) - { - $filter = $this->filter ? $this->filter : '(objectclass=*)'; - $function = $this->_scope2func($this->prop['scope'], $ns_function); - - $this->_debug("C: Search [$filter][dn: $this->base_dn]"); - - // when using VLV, we get the total count by... - if (!$count && $function != 'ldap_read' && $this->prop['vlv'] && !$this->group_id) { - // ...either reading numSubOrdinates attribute - if ($this->prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $this->base_dn, $this->prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) { - $counts = ldap_get_entries($this->conn, $result_count); - for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++) - $this->vlv_count += $counts[$j]['numsubordinates'][0]; - $this->_debug("D: total numsubordinates = " . $this->vlv_count); + $attrs[$lf] = $fn(); } - else if (!function_exists('ldap_parse_virtuallist_control')) // ...or by fetching all records dn and count them - $this->vlv_count = $this->_exec_search(true); - - $this->vlv_active = $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size); - } - - // only fetch dn for count (should keep the payload low) - $attrs = $count ? array('dn') : array_values($this->fieldmap); - if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter, - $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) - ) { - // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result - if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { - if (ldap_parse_result($this->conn, $this->ldap_result, - $errcode, $matcheddn, $errmsg, $referrals, $serverctrls) - && $serverctrls // can be null e.g. in case of adm. limit error - ) { - ldap_parse_virtuallist_control($this->conn, $serverctrls, - $last_offset, $this->vlv_count, $vresult); - $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count"); - } - else { - $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn))); - } + else { + // replace {attr} placeholders with concrete attribute values + $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals)); } - - $entries_count = ldap_count_entries($this->conn, $this->ldap_result); - $this->_debug("S: $entries_count record(s)"); - - return $count ? $entries_count : true; - } - else { - $this->_debug("S: ".ldap_error($this->conn)); } } - - return false; - } - - /** - * Choose the right PHP function according to scope property - */ - private function _scope2func($scope, &$ns_function = null) - { - switch ($scope) { - case 'sub': - $function = $ns_function = 'ldap_search'; - break; - case 'base': - $function = $ns_function = 'ldap_read'; - break; - default: - $function = 'ldap_list'; - $ns_function = 'ldap_read'; - break; - } - - return $function; - } - - /** - * Set server controls for Virtual List View (paginated listing) - */ - private function _vlv_set_controls($prop, $list_page, $page_size, $search = null) - { - $sort_ctrl = array('oid' => "1.2.840.113556.1.4.473", 'value' => $this->_sort_ber_encode((array)$prop['sort'])); - $vlv_ctrl = array('oid' => "2.16.840.1.113730.3.4.9", 'value' => $this->_vlv_ber_encode(($offset = ($list_page-1) * $page_size + 1), $page_size, $search), 'iscritical' => true); - - $sort = (array)$prop['sort']; - $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value'])) . " ($sort[0]);" - . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)"); - - if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) { - $this->_debug("S: ".ldap_error($this->conn)); - $this->set_error(self::ERROR_SEARCH, 'vlvnotsupported'); - return false; - } - - return true; } @@ -1510,12 +1344,20 @@ */ private function _ldap2result($rec) { - $out = array(); + $out = array('_type' => 'person'); + $fieldmap = $this->fieldmap; if ($rec['dn']) $out[$this->primary_key] = self::dn_encode($rec['dn']); - foreach ($this->fieldmap as $rf => $lf) + // determine record type + if (self::is_group_entry($rec)) { + $out['_type'] = 'group'; + $out['readonly'] = true; + $fieldmap['name'] = $this->group_data['name_attr'] ? $this->group_data['name_attr'] : $this->prop['groups']['name_attr']; + } + + foreach ($fieldmap as $rf => $lf) { for ($i=0; $i < $rec[$lf]['count']; $i++) { if (!($value = $rec[$lf][$i])) @@ -1577,8 +1419,10 @@ if (is_array($colprop['serialized'])) { foreach ($colprop['serialized'] as $subtype => $delim) { $key = $col.':'.$subtype; - foreach ((array)$save_cols[$key] as $i => $val) - $save_cols[$key][$i] = join($delim, array($val['street'], $val['locality'], $val['zipcode'], $val['country'])); + foreach ((array)$save_cols[$key] as $i => $val) { + $values = array($val['street'], $val['locality'], $val['zipcode'], $val['country']); + $save_cols[$key][$i] = count(array_filter($values)) ? join($delim, $values) : null; + } } } } @@ -1616,11 +1460,11 @@ { // list of known attribute aliases static $aliases = array( - 'gn' => 'givenname', + 'gn' => 'givenname', 'rfc822mailbox' => 'email', - 'userid' => 'uid', - 'emailaddress' => 'email', - 'pkcs9email' => 'email', + 'userid' => 'uid', + 'emailaddress' => 'email', + 'pkcs9email' => 'email', ); list($name, $limit) = explode(':', $namev, 2); @@ -1629,6 +1473,15 @@ return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix; } + /** + * Determines whether the given LDAP entry is a group record + */ + private static function is_group_entry($entry) + { + $classes = array_map('strtolower', (array)$entry['objectclass']); + + return count(array_intersect(array_keys(self::$group_types), $classes)) > 0; + } /** * Prints debug info to the log @@ -1645,55 +1498,27 @@ * Activate/deactivate debug mode * * @param boolean $dbg True if LDAP commands should be logged - * @access public */ function set_debug($dbg = true) { $this->debug = $dbg; - } - - /** - * Quotes attribute value string - * - * @param string $str Attribute value - * @param bool $dn True if the attribute is a DN - * - * @return string Quoted string - */ - private static function _quote_string($str, $dn=false) - { - // take firt entry if array given - if (is_array($str)) - $str = reset($str); - - if ($dn) - $replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c', - '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23'); - else - $replace = array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c', - '/'=>'\2f'); - - return strtr($str, $replace); + if ($this->ldap) { + $this->ldap->set_debug($dbg); + } } /** * Setter for the current group - * (empty, has to be re-implemented by extending class) */ function set_group($group_id) { - if ($group_id) - { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); - + if ($group_id) { $this->group_id = $group_id; - $this->group_data = $group_cache[$group_id]; + $this->group_data = $this->get_group_entry($group_id); } - else - { + else { $this->group_id = 0; $this->group_data = null; } @@ -1703,28 +1528,32 @@ * List all active contact groups of this source * * @param string Optional search string to match group name + * @param int Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * * @return array Indexed list of contact groups, each a hash array */ - function list_groups($search = null) + function list_groups($search = null, $mode = 0) { - if (!$this->groups) + if (!$this->groups) { return array(); + } - // use cached list for searching - $this->cache->expunge(); - if (!$search || ($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); + $group_cache = $this->_fetch_groups(); + $groups = array(); - $groups = array(); if ($search) { - $search = mb_strtolower($search); foreach ($group_cache as $group) { - if (strpos(mb_strtolower($group['name']), $search) !== false) + if ($this->compare_search_value('name', $group['name'], $search, $mode)) { $groups[] = $group; + } } } - else + else { $groups = $group_cache; + } return array_values($groups); } @@ -1732,80 +1561,140 @@ /** * Fetch groups from server */ - private function _fetch_groups($vlv_page = 0) + private function _fetch_groups($vlv_page = null) { - $base_dn = $this->groups_base_dn; - $filter = $this->prop['groups']['filter']; - $name_attr = $this->prop['groups']['name_attr']; + // special case: list groups from 'group_filters' config + if ($vlv_page === null && !empty($this->prop['group_filters'])) { + $groups = array(); + + // list regular groups configuration as special filter + if (!empty($this->prop['groups']['filter'])) { + $id = '__groups__'; + $groups[$id] = array('ID' => $id, 'name' => rcube_label('groups'), 'virtual' => true) + $this->prop['groups']; + } + + foreach ($this->prop['group_filters'] as $id => $prop) { + $groups[$id] = $prop + array('ID' => $id, 'name' => ucfirst($id), 'virtual' => true, 'base_dn' => $this->base_dn); + } + + return $groups; + } + + if ($this->cache && $vlv_page === null && ($groups = $this->cache->get('groups')) !== null) { + return $groups; + } + + $base_dn = $this->groups_base_dn; + $filter = $this->prop['groups']['filter']; + $scope = $this->prop['groups']['scope']; + $name_attr = $this->prop['groups']['name_attr']; $email_attr = $this->prop['groups']['email_attr'] ? $this->prop['groups']['email_attr'] : 'mail'; $sort_attrs = $this->prop['groups']['sort'] ? (array)$this->prop['groups']['sort'] : array($name_attr); - $sort_attr = $sort_attrs[0]; + $sort_attr = $sort_attrs[0]; - $this->_debug("C: Search [$filter][dn: $base_dn]"); + $ldap = $this->ldap; // use vlv to list groups if ($this->prop['groups']['vlv']) { $page_size = 200; - if (!$this->prop['groups']['sort']) + if (!$this->prop['groups']['sort']) { $this->prop['groups']['sort'] = $sort_attrs; - $vlv_active = $this->_vlv_set_controls($this->prop['groups'], $vlv_page+1, $page_size); + } + + $ldap = clone $this->ldap; + $ldap->set_config($this->prop['groups']); + $ldap->set_vlv_page($vlv_page+1, $page_size); } - $function = $this->_scope2func($this->prop['groups']['scope'], $ns_function); - $res = @$function($this->conn, $base_dn, $filter, array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr))); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + $attrs = array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)); + $ldap_data = $ldap->search($base_dn, $filter, $scope, $attrs, $this->prop['groups']); + + if ($ldap_data === false) { return array(); } - $ldap_data = ldap_get_entries($this->conn, $res); - $this->_debug("S: ".ldap_count_entries($this->conn, $res)." record(s)"); - - $groups = array(); + $groups = array(); $group_sortnames = array(); - $group_count = $ldap_data["count"]; - for ($i=0; $i < $group_count; $i++) - { - $group_name = is_array($ldap_data[$i][$name_attr]) ? $ldap_data[$i][$name_attr][0] : $ldap_data[$i][$name_attr]; - $group_id = self::dn_encode($group_name); + $group_count = $ldap_data->count(); + + foreach ($ldap_data as $entry) { + if (!$entry['dn']) // DN is mandatory + $entry['dn'] = $ldap_data->get_dn(); + + $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + $group_id = self::dn_encode($entry['dn']); $groups[$group_id]['ID'] = $group_id; - $groups[$group_id]['dn'] = $ldap_data[$i]['dn']; + $groups[$group_id]['dn'] = $entry['dn']; $groups[$group_id]['name'] = $group_name; - $groups[$group_id]['member_attr'] = $this->get_group_member_attr($ldap_data[$i]['objectclass']); + $groups[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']); // list email attributes of a group - for ($j=0; $ldap_data[$i][$email_attr] && $j < $ldap_data[$i][$email_attr]['count']; $j++) { - if (strpos($ldap_data[$i][$email_attr][$j], '@') > 0) - $groups[$group_id]['email'][] = $ldap_data[$i][$email_attr][$j]; + for ($j=0; $entry[$email_attr] && $j < $entry[$email_attr]['count']; $j++) { + if (strpos($entry[$email_attr][$j], '@') > 0) + $groups[$group_id]['email'][] = $entry[$email_attr][$j]; } - $group_sortnames[] = mb_strtolower($ldap_data[$i][$sort_attr][0]); + $group_sortnames[] = mb_strtolower($entry[$sort_attr][0]); } // recursive call can exit here - if ($vlv_page > 0) + if ($vlv_page > 0) { return $groups; + } // call recursively until we have fetched all groups - while ($vlv_active && $group_count == $page_size) - { - $next_page = $this->_fetch_groups(++$vlv_page); - $groups = array_merge($groups, $next_page); + while ($this->prop['groups']['vlv'] && $group_count == $page_size) { + $next_page = $this->_fetch_groups(++$vlv_page); + $groups = array_merge($groups, $next_page); $group_count = count($next_page); } // when using VLV the list of groups is already sorted - if (!$this->prop['groups']['vlv']) + if (!$this->prop['groups']['vlv']) { array_multisort($group_sortnames, SORT_ASC, SORT_STRING, $groups); + } // cache this - $this->cache->set('groups', $groups); + if ($this->cache) { + $this->cache->set('groups', $groups); + } return $groups; } /** + * Fetch a group entry from LDAP and save in local cache + */ + private function get_group_entry($group_id) + { + $group_cache = $this->_fetch_groups(); + + // add group record to cache if it isn't yet there + if (!isset($group_cache[$group_id])) { + $name_attr = $this->prop['groups']['name_attr']; + $dn = self::dn_decode($group_id); + + if ($list = $this->ldap->read_entries($dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL',$name_attr,$this->fieldmap['email']))) { + $entry = $list[0]; + $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + $group_cache[$group_id]['ID'] = $group_id; + $group_cache[$group_id]['dn'] = $dn; + $group_cache[$group_id]['name'] = $group_name; + $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']); + } + else { + $group_cache[$group_id] = false; + } + + if ($this->cache) { + $this->cache->set('groups', $group_cache); + } + } + + return $group_cache[$group_id]; + } + + /** * Get group properties such as name and email address(es) * * @param string Group identifier @@ -1813,10 +1702,7 @@ */ function get_group($group_id) { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); - - $group_data = $group_cache[$group_id]; + $group_data = $this->get_group_entry($group_id); unset($group_data['dn'], $group_data['member_attr']); return $group_data; @@ -1830,24 +1716,24 @@ */ function create_group($group_name) { - $base_dn = $this->groups_base_dn; - $new_dn = "cn=$group_name,$base_dn"; - $new_gid = self::dn_encode($group_name); + $new_dn = 'cn=' . rcube_ldap_generic::quote_string($group_name, true) . ',' . $this->groups_base_dn; + $new_gid = self::dn_encode($new_dn); $member_attr = $this->get_group_member_attr(); - $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; - - $new_entry = array( + $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; + $new_entry = array( 'objectClass' => $this->prop['groups']['object_classes'], $name_attr => $group_name, $member_attr => '', ); - if (!$this->ldap_add($new_dn, $new_entry)) { + if (!$this->ldap->add($new_dn, $new_entry)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->cache->remove('groups'); + if ($this->cache) { + $this->cache->remove('groups'); + } return array('id' => $new_gid, 'name' => $group_name); } @@ -1860,19 +1746,18 @@ */ function delete_group($group_id) { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); + $group_cache = $this->_fetch_groups(); + $del_dn = $group_cache[$group_id]['dn']; - $base_dn = $this->groups_base_dn; - $group_name = $group_cache[$group_id]['name']; - $del_dn = "cn=$group_name,$base_dn"; - - if (!$this->ldap_delete($del_dn)) { + if (!$this->ldap->delete($del_dn)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->cache->remove('groups'); + if ($this->cache) { + unset($group_cache[$group_id]); + $this->cache->set('groups', $group_cache); + } return true; } @@ -1887,21 +1772,19 @@ */ function rename_group($group_id, $new_name, &$new_gid) { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); + $group_cache = $this->_fetch_groups(); + $old_dn = $group_cache[$group_id]['dn']; + $new_rdn = "cn=" . rcube_ldap_generic::quote_string($new_name, true); + $new_gid = self::dn_encode($new_rdn . ',' . $this->groups_base_dn); - $base_dn = $this->groups_base_dn; - $group_name = $group_cache[$group_id]['name']; - $old_dn = "cn=$group_name,$base_dn"; - $new_rdn = "cn=$new_name"; - $new_gid = self::dn_encode($new_name); - - if (!$this->ldap_rename($old_dn, $new_rdn, null, true)) { + if (!$this->ldap->rename($old_dn, $new_rdn, null, true)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->cache->remove('groups'); + if ($this->cache) { + $this->cache->remove('groups'); + } return $new_name; } @@ -1909,66 +1792,71 @@ /** * Add the given contact records the a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be added - * @return int Number of contacts added + * @param string Group identifier + * @param array|string List of contact identifiers to be added + * + * @return int Number of contacts added */ function add_to_group($group_id, $contact_ids) { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); + $group_cache = $this->_fetch_groups(); + $member_attr = $group_cache[$group_id]['member_attr']; + $group_dn = $group_cache[$group_id]['dn']; + $new_attrs = array(); - if (!is_array($contact_ids)) + if (!is_array($contact_ids)) { $contact_ids = explode(',', $contact_ids); + } - $base_dn = $this->groups_base_dn; - $group_name = $group_cache[$group_id]['name']; - $member_attr = $group_cache[$group_id]['member_attr']; - $group_dn = "cn=$group_name,$base_dn"; - - $new_attrs = array(); - foreach ($contact_ids as $id) + foreach ($contact_ids as $id) { $new_attrs[$member_attr][] = self::dn_decode($id); + } - if (!$this->ldap_mod_add($group_dn, $new_attrs)) { + if (!$this->ldap->mod_add($group_dn, $new_attrs)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return 0; } - $this->cache->remove('groups'); + if ($this->cache) { + $this->cache->remove('groups'); + } - return count($new_attrs['member']); + return count($new_attrs[$member_attr]); } /** * Remove the given contact records from a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be removed - * @return int Number of deleted group members + * @param string Group identifier + * @param array|string List of contact identifiers to be removed + * + * @return int Number of deleted group members */ function remove_from_group($group_id, $contact_ids) { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); - - $base_dn = $this->groups_base_dn; - $group_name = $group_cache[$group_id]['name']; + $group_cache = $this->_fetch_groups(); $member_attr = $group_cache[$group_id]['member_attr']; - $group_dn = "cn=$group_name,$base_dn"; + $group_dn = $group_cache[$group_id]['dn']; + $del_attrs = array(); + + if (!is_array($contact_ids)) { + $contact_ids = explode(',', $contact_ids); + } - $del_attrs = array(); - foreach (explode(",", $contact_ids) as $id) + foreach ($contact_ids as $id) { $del_attrs[$member_attr][] = self::dn_decode($id); + } - if (!$this->ldap_mod_del($group_dn, $del_attrs)) { + if (!$this->ldap->mod_del($group_dn, $del_attrs)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return 0; } - $this->cache->remove('groups'); + if ($this->cache) { + $this->cache->remove('groups'); + } - return count($del_attrs['member']); + return count($del_attrs[$member_attr]); } /** @@ -1981,204 +1869,61 @@ */ function get_record_groups($contact_id) { - if (!$this->groups) + if (!$this->groups) { return array(); + } $base_dn = $this->groups_base_dn; $contact_dn = self::dn_decode($contact_id); $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; $member_attr = $this->get_group_member_attr(); $add_filter = ''; + if ($member_attr != 'member' && $member_attr != 'uniqueMember') $add_filter = "($member_attr=$contact_dn)"; $filter = strtr("(|(member=$contact_dn)(uniqueMember=$contact_dn)$add_filter)", array('\\' => '\\\\')); - $this->_debug("C: Search [$filter][dn: $base_dn]"); - - $res = @ldap_search($this->conn, $base_dn, $filter, array($name_attr)); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + $ldap_data = $this->ldap->search($base_dn, $filter, 'sub', array('dn', $name_attr)); + if ($res === false) { return array(); } - $ldap_data = ldap_get_entries($this->conn, $res); - $this->_debug("S: ".ldap_count_entries($this->conn, $res)." record(s)"); $groups = array(); - for ($i=0; $i<$ldap_data["count"]; $i++) - { - $group_name = $ldap_data[$i][$name_attr][0]; - $group_id = self::dn_encode($group_name); - $groups[$group_id] = $group_id; + foreach ($ldap_data as $entry) { + if (!$entry['dn']) + $entry['dn'] = $ldap_data->get_dn(); + $group_name = $entry[$name_attr][0]; + $group_id = self::dn_encode($entry['dn']); + $groups[$group_id] = $group_name; } + return $groups; } /** * Detects group member attribute name */ - private function get_group_member_attr($object_classes = array()) + private function get_group_member_attr($object_classes = array(), $default = 'member') { if (empty($object_classes)) { $object_classes = $this->prop['groups']['object_classes']; } + if (!empty($object_classes)) { foreach ((array)$object_classes as $oc) { - switch (strtolower($oc)) { - case 'group': - case 'groupofnames': - case 'kolabgroupofnames': - $member_attr = 'member'; - break; - - case 'groupofuniquenames': - case 'kolabgroupofuniquenames': - $member_attr = 'uniqueMember'; - break; + if ($attr = self::$group_types[strtolower($oc)]) { + return $attr; } } } - if (!empty($member_attr)) { - return $member_attr; - } - if (!empty($this->prop['groups']['member_attr'])) { return $this->prop['groups']['member_attr']; } - return 'member'; - } - - - /** - * Generate BER encoded string for Virtual List View option - * - * @param integer List offset (first record) - * @param integer Records per page - * @return string BER encoded option value - */ - private function _vlv_ber_encode($offset, $rpp, $search = '') - { - # this string is ber-encoded, php will prefix this value with: - # 04 (octet string) and 10 (length of 16 bytes) - # the code behind this string is broken down as follows: - # 30 = ber sequence with a length of 0e (14) bytes following - # 02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0) - # 02 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24) - # a0 = type context-specific/constructed with a length of 06 (6) bytes following - # 02 = type integer with 2 bytes following (offset): 01 01 (ie 1) - # 02 = type integer with 2 bytes following (contentCount): 01 00 - - # whith a search string present: - # 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here) - # 81 indicates a user string is present where as a a0 indicates just a offset search - # 81 = type context-specific/constructed with a length of 06 (6) bytes following - - # the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the - # encoding of integer values (note: these values are in - # two-complement form so since offset will never be negative bit 8 of the - # leftmost octet should never by set to 1): - # 8.3.2: If the contents octets of an integer value encoding consist - # of more than one octet, then the bits of the first octet (rightmost) and bit 8 - # of the second (to the left of first octet) octet: - # a) shall not all be ones; and - # b) shall not all be zero - - if ($search) - { - $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search); - $ber_val = self::_string2hex($search); - $str = self::_ber_addseq($ber_val, '81'); - } - else - { - # construct the string from right to left - $str = "020100"; # contentCount - - $ber_val = self::_ber_encode_int($offset); // returns encoded integer value in hex format - - // calculate octet length of $ber_val - $str = self::_ber_addseq($ber_val, '02') . $str; - - // now compute length over $str - $str = self::_ber_addseq($str, 'a0'); - } - - // now tack on records per page - $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str; - - // now tack on sequence identifier and length - $str = self::_ber_addseq($str, '30'); - - return pack('H'.strlen($str), $str); - } - - - /** - * create ber encoding for sort control - * - * @param array List of cols to sort by - * @return string BER encoded option value - */ - private function _sort_ber_encode($sortcols) - { - $str = ''; - foreach (array_reverse((array)$sortcols) as $col) { - $ber_val = self::_string2hex($col); - - # 30 = ber sequence with a length of octet value - # 04 = octet string with a length of the ascii value - $oct = self::_ber_addseq($ber_val, '04'); - $str = self::_ber_addseq($oct, '30') . $str; - } - - // now tack on sequence identifier and length - $str = self::_ber_addseq($str, '30'); - - return pack('H'.strlen($str), $str); - } - - /** - * Add BER sequence with correct length and the given identifier - */ - private static function _ber_addseq($str, $identifier) - { - $len = dechex(strlen($str)/2); - if (strlen($len) % 2 != 0) - $len = '0'.$len; - - return $identifier . $len . $str; - } - - /** - * Returns BER encoded integer value in hex format - */ - private static function _ber_encode_int($offset) - { - $val = dechex($offset); - $prefix = ''; - - // check if bit 8 of high byte is 1 - if (preg_match('/^[89abcdef]/', $val)) - $prefix = '00'; - - if (strlen($val)%2 != 0) - $prefix .= '0'; - - return $prefix . $val; + return $default; } - /** - * Returns ascii string encoded in hex - */ - private static function _string2hex($str) - { - $hex = ''; - for ($i=0; $i < strlen($str); $i++) - $hex .= dechex(ord($str[$i])); - return $hex; - } /** * HTML-safe DN string encoding @@ -2207,130 +1952,4 @@ return base64_decode($str); } - /** - * Wrapper for ldap_add() - */ - protected function ldap_add($dn, $entry) - { - $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true)); - - $res = ldap_add($this->conn, $dn, $entry); - if ($res === false) { - $this->_debug("S: ".ldap_error($this->conn)); - return false; - } - - $this->_debug("S: OK"); - return true; - } - - /** - * Wrapper for ldap_delete() - */ - protected function ldap_delete($dn) - { - $this->_debug("C: Delete [dn: $dn]"); - - $res = ldap_delete($this->conn, $dn); - if ($res === false) { - $this->_debug("S: ".ldap_error($this->conn)); - return false; - } - - $this->_debug("S: OK"); - return true; - } - - /** - * Wrapper for ldap_mod_replace() - */ - protected function ldap_mod_replace($dn, $entry) - { - $this->_debug("C: Replace [dn: $dn]: ".print_r($entry, true)); - - if (!ldap_mod_replace($this->conn, $dn, $entry)) { - $this->_debug("S: ".ldap_error($this->conn)); - return false; - } - - $this->_debug("S: OK"); - return true; - } - - /** - * Wrapper for ldap_mod_add() - */ - protected function ldap_mod_add($dn, $entry) - { - $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true)); - - if (!ldap_mod_add($this->conn, $dn, $entry)) { - $this->_debug("S: ".ldap_error($this->conn)); - return false; - } - - $this->_debug("S: OK"); - return true; - } - - /** - * Wrapper for ldap_mod_del() - */ - protected function ldap_mod_del($dn, $entry) - { - $this->_debug("C: Delete [dn: $dn]: ".print_r($entry, true)); - - if (!ldap_mod_del($this->conn, $dn, $entry)) { - $this->_debug("S: ".ldap_error($this->conn)); - return false; - } - - $this->_debug("S: OK"); - return true; - } - - /** - * Wrapper for ldap_rename() - */ - protected function ldap_rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true) - { - $this->_debug("C: Rename [dn: $dn] [dn: $newrdn]"); - - if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) { - $this->_debug("S: ".ldap_error($this->conn)); - return false; - } - - $this->_debug("S: OK"); - return true; - } - - /** - * Wrapper for ldap_list() - */ - protected function ldap_list($dn, $filter, $attrs = array('')) - { - $list = array(); - $this->_debug("C: List [dn: $dn] [{$filter}]"); - - if ($result = ldap_list($this->conn, $dn, $filter, $attrs)) { - $list = ldap_get_entries($this->conn, $result); - - if ($list === false) { - $this->_debug("S: ".ldap_error($this->conn)); - return array(); - } - - $count = $list['count']; - unset($list['count']); - - $this->_debug("S: $count record(s)"); - } - else { - $this->_debug("S: ".ldap_error($this->conn)); - } - - return $list; - } - }
View file
kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_ldap_generic.php
Added
@@ -0,0 +1,1049 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | Roundcube/rcube_ldap_generic.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2006-2013, The Roundcube Dev Team | + | Copyright (C) 2012-2013, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Provide basic functionality for accessing LDAP directories | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Aleksander Machniak <machniak@kolabsys.com> | + +-----------------------------------------------------------------------+ +*/ + + +/* + LDAP connection properties + -------------------------- + + $prop = array( + 'host' => '<ldap-server-address>', + // or + 'hosts' => array('directory.verisign.com'), + 'port' => 389, + 'use_tls' => true|false, + 'ldap_version' => 3, // using LDAPv3 + 'auth_method' => '', // SASL authentication method (for proxy auth), e.g. DIGEST-MD5 + 'attributes' => array('dn'), // List of attributes to read from the server + 'vlv' => false, // Enable Virtual List View to more efficiently fetch paginated data (if server supports it) + 'config_root_dn' => 'cn=config', // Root DN to read config (e.g. vlv indexes) from + 'numsub_filter' => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting + 'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit. + 'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit. + 'network_timeout' => 10, // The timeout (in seconds) for connect + bind arrempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x + 'referrals' => true|false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups + ); +*/ + +/** + * Model class to access an LDAP directories + * + * @package Framework + * @subpackage LDAP + */ +class rcube_ldap_generic +{ + const UPDATE_MOD_ADD = 1; + const UPDATE_MOD_DELETE = 2; + const UPDATE_MOD_REPLACE = 4; + const UPDATE_MOD_FULL = 7; + + public $conn; + public $vlv_active = false; + + /** private properties */ + protected $cache = null; + protected $config = array(); + protected $attributes = array('dn'); + protected $entries = null; + protected $result = null; + protected $debug = false; + protected $list_page = 1; + protected $page_size = 10; + protected $vlv_config = null; + + + /** + * Object constructor + * + * @param array $p LDAP connection properties + */ + function __construct($p) + { + $this->config = $p; + + if (is_array($p['attributes'])) + $this->attributes = $p['attributes']; + + if (!is_array($p['hosts']) && !empty($p['host'])) + $this->config['hosts'] = array($p['host']); + } + + /** + * Activate/deactivate debug mode + * + * @param boolean $dbg True if LDAP commands should be logged + */ + public function set_debug($dbg = true) + { + $this->debug = $dbg; + } + + /** + * Set connection options + * + * @param mixed $opt Option name as string or hash array with multiple options + * @param mixed $val Option value + */ + public function set_config($opt, $val = null) + { + if (is_array($opt)) + $this->config = array_merge($this->config, $opt); + else + $this->config[$opt] = $value; + } + + /** + * Enable caching by passing an instance of rcube_cache to be used by this object + * + * @param object rcube_cache Instance or False to disable caching + */ + public function set_cache($cache_engine) + { + $this->cache = $cache_engine; + } + + /** + * Set properties for VLV-based paging + * + * @param number $page Page number to list (starting at 1) + * @param number $size Number of entries to display on one page + */ + public function set_vlv_page($page, $size = 10) + { + $this->list_page = $page; + $this->page_size = $size; + } + + /** + * Establish a connection to the LDAP server + */ + public function connect($host = null) + { + if (!function_exists('ldap_connect')) { + rcube::raise_error(array('code' => 100, 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "No ldap support in this installation of PHP"), + true); + return false; + } + + if (is_resource($this->conn) && $this->config['host'] == $host) + return true; + + if (empty($this->config['ldap_version'])) + $this->config['ldap_version'] = 3; + + // iterate over hosts if none specified + if (!$host) { + if (!is_array($this->config['hosts'])) + $this->config['hosts'] = array($this->config['hosts']); + + foreach ($this->config['hosts'] as $host) { + if ($this->connect($host)) { + return true; + } + } + + return false; + } + + // open connection to the given $host + $host = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host)); + $hostname = $host . ($this->config['port'] ? ':'.$this->config['port'] : ''); + + $this->_debug("C: Connect to $hostname [{$this->config['name']}]"); + + if ($lc = @ldap_connect($host, $this->config['port'])) { + if ($this->config['use_tls'] === true) + if (!ldap_start_tls($lc)) + continue; + + $this->_debug("S: OK"); + + ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->config['ldap_version']); + $this->config['host'] = $host; + $this->conn = $lc; + + if (!empty($this->config['network_timeout'])) + ldap_set_option($lc, LDAP_OPT_NETWORK_TIMEOUT, $this->config['network_timeout']); + + if (isset($this->config['referrals'])) + ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->config['referrals']); + } + else { + $this->_debug("S: NOT OK"); + } + + if (!is_resource($this->conn)) { + rcube::raise_error(array('code' => 100, 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not connect to any LDAP server, last tried $hostname"), + true); + return false; + } + + return true; + } + + /** + * Bind connection with (SASL-) user and password + * + * @param string $authc Authentication user + * @param string $pass Bind password + * @param string $authz Autorization user + * + * @return boolean True on success, False on error + */ + public function sasl_bind($authc, $pass, $authz=null) + { + if (!$this->conn) { + return false; + } + + if (!function_exists('ldap_sasl_bind')) { + rcube::raise_error(array('code' => 100, 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Unable to bind: ldap_sasl_bind() not exists"), + true); + return false; + } + + if (!empty($authz)) { + $authz = 'u:' . $authz; + } + + if (!empty($this->config['auth_method'])) { + $method = $this->config['auth_method']; + } + else { + $method = 'DIGEST-MD5'; + } + + $this->_debug("C: SASL Bind [mech: $method, authc: $authc, authz: $authz, pass: $pass]"); + + if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) { + $this->_debug("S: OK"); + return true; + } + + $this->_debug("S: ".ldap_error($this->conn)); + + rcube::raise_error(array( + 'code' => ldap_errno($this->conn), 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "SASL Bind failed for authcid=$authc ".ldap_error($this->conn)), + true); + return false; + } + + /** + * Bind connection with DN and password + * + * @param string $dn Bind DN + * @param string $pass Bind password + * + * @return boolean True on success, False on error + */ + public function bind($dn, $pass) + { + if (!$this->conn) { + return false; + } + + $this->_debug("C: Bind $dn [pass: $pass]"); + + if (@ldap_bind($this->conn, $dn, $pass)) { + $this->_debug("S: OK"); + return true; + } + + $this->_debug("S: ".ldap_error($this->conn)); + + rcube::raise_error(array( + 'code' => ldap_errno($this->conn), 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)), + true); + + return false; + } + + /** + * Close connection to LDAP server + */ + public function close() + { + if ($this->conn) { + $this->_debug("C: Close"); + ldap_unbind($this->conn); + $this->conn = null; + } + } + + /** + * Return the last result set + * + * @return object rcube_ldap_result Result object + */ + function get_result() + { + return $this->result; + } + + /** + * Get a specific LDAP entry, identified by its DN + * + * @param string $dn Record identifier + * @return array Hash array + */ + function get_entry($dn) + { + $rec = null; + + if ($this->conn && $dn) { + $this->_debug("C: Read $dn [(objectclass=*)]"); + + if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', $this->attributes)) { + $this->_debug("S: OK"); + + if ($entry = ldap_first_entry($this->conn, $ldap_result)) { + $rec = ldap_get_attributes($this->conn, $entry); + } + } + else { + $this->_debug("S: ".ldap_error($this->conn)); + } + + if (!empty($rec)) { + $rec['dn'] = $dn; // Add in the dn for the entry. + } + } + + return $rec; + } + + /** + * Execute the LDAP search based on the stored credentials + * + * @param string $base_dn The base DN to query + * @param string $filter The LDAP filter for search + * @param string $scope The LDAP scope (list|sub|base) + * @param array $attrs List of entry attributes to read + * @param array $prop Hash array with query configuration properties: + * - sort: array of sort attributes (has to be in sync with the VLV index) + * - search: search string used for VLV controls + * @param boolean $count_only Set to true if only entry count is requested + * + * @return mixed rcube_ldap_result object or number of entries (if count_only=true) or false on error + */ + public function search($base_dn, $filter = '', $scope = 'sub', $attrs = array('dn'), $prop = array(), $count_only = false) + { + if (!$this->conn) { + return false; + } + + if (empty($filter)) { + $filter = '(objectclass=*)'; + } + + $this->_debug("C: Search $base_dn for $filter"); + + $function = self::scope2func($scope, $ns_function); + + // find available VLV index for this query + if (!$count_only && ($vlv_sort = $this->_find_vlv($base_dn, $filter, $scope, $prop['sort']))) { + // when using VLV, we get the total count by... + // ...either reading numSubOrdinates attribute + if (($sub_filter = $this->config['numsub_filter']) && + ($result_count = @$ns_function($this->conn, $base_dn, $sub_filter, array('numSubOrdinates'), 0, 0, 0)) + ) { + $counts = ldap_get_entries($this->conn, $result_count); + for ($vlv_count = $j = 0; $j < $counts['count']; $j++) + $vlv_count += $counts[$j]['numsubordinates'][0]; + $this->_debug("D: total numsubordinates = " . $vlv_count); + } + // ...or by fetching all records dn and count them + else if (!function_exists('ldap_parse_virtuallist_control')) { + $vlv_count = $this->search($base_dn, $filter, $scope, array('dn'), $prop, true); + } + + $this->vlv_active = $this->_vlv_set_controls($vlv_sort, $this->list_page, $this->page_size, $prop['search']); + } + else { + $this->vlv_active = false; + } + + // only fetch dn for count (should keep the payload low) + if ($ldap_result = @$function($this->conn, $base_dn, $filter, + $attrs, 0, (int)$this->config['sizelimit'], (int)$this->config['timelimit']) + ) { + // when running on a patched PHP we can use the extended functions + // to retrieve the total count from the LDAP search result + if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { + if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) { + ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult); + $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$vlv_count"); + } + else { + $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn))); + } + } + else if ($this->debug) { + $this->_debug("S: ".ldap_count_entries($this->conn, $ldap_result)." record(s) found"); + } + + $this->result = new rcube_ldap_result($this->conn, $ldap_result, $base_dn, $filter, $vlv_count); + + return $count_only ? $this->result->count() : $this->result; + } + else { + $this->_debug("S: ".ldap_error($this->conn)); + } + + return false; + } + + /** + * Modify an LDAP entry on the server + * + * @param string $dn Entry DN + * @param array $params Hash array of entry attributes + * @param int $mode Update mode (UPDATE_MOD_ADD | UPDATE_MOD_DELETE | UPDATE_MOD_REPLACE) + */ + public function modify($dn, $parms, $mode = 255) + { + // TODO: implement this + + return false; + } + + /** + * Wrapper for ldap_add() + * + * @see ldap_add() + */ + public function add($dn, $entry) + { + $this->_debug("C: Add $dn: ".print_r($entry, true)); + + $res = ldap_add($this->conn, $dn, $entry); + if ($res === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_delete() + * + * @see ldap_delete() + */ + public function delete($dn) + { + $this->_debug("C: Delete $dn"); + + $res = ldap_delete($this->conn, $dn); + if ($res === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_mod_replace() + * + * @see ldap_mod_replace() + */ + public function mod_replace($dn, $entry) + { + $this->_debug("C: Replace $dn: ".print_r($entry, true)); + + if (!ldap_mod_replace($this->conn, $dn, $entry)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_mod_add() + * + * @see ldap_mod_add() + */ + public function mod_add($dn, $entry) + { + $this->_debug("C: Add $dn: ".print_r($entry, true)); + + if (!ldap_mod_add($this->conn, $dn, $entry)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_mod_del() + * + * @see ldap_mod_del() + */ + public function mod_del($dn, $entry) + { + $this->_debug("C: Delete $dn: ".print_r($entry, true)); + + if (!ldap_mod_del($this->conn, $dn, $entry)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_rename() + * + * @see ldap_rename() + */ + public function rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true) + { + $this->_debug("C: Rename $dn to $newrdn"); + + if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_list() + ldap_get_entries() + * + * @see ldap_list() + * @see ldap_get_entries() + */ + public function list_entries($dn, $filter, $attributes = array('dn')) + { + $list = array(); + $this->_debug("C: List $dn [{$filter}]"); + + if ($result = ldap_list($this->conn, $dn, $filter, $attributes)) { + $list = ldap_get_entries($this->conn, $result); + + if ($list === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return array(); + } + + $count = $list['count']; + unset($list['count']); + + $this->_debug("S: $count record(s)"); + } + else { + $this->_debug("S: ".ldap_error($this->conn)); + } + + return $list; + } + + /** + * Wrapper for ldap_read() + ldap_get_entries() + * + * @see ldap_read() + * @see ldap_get_entries() + */ + public function read_entries($dn, $filter, $attributes = null) + { + $this->_debug("C: Read $dn [{$filter}]"); + + if ($this->conn && $dn) { + if (!$attributes) + $attributes = $this->attributes; + + $result = @ldap_read($this->conn, $dn, $filter, $attributes, 0, (int)$this->config['sizelimit'], (int)$this->config['timelimit']); + if ($result === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return ldap_get_entries($this->conn, $result); + } + + return false; + } + + /** + * Choose the right PHP function according to scope property + * + * @param string $scope The LDAP scope (sub|base|list) + * @param string $ns_function Function to be used for numSubOrdinates queries + * @return string PHP function to be used to query directory + */ + public static function scope2func($scope, &$ns_function = null) + { + switch ($scope) { + case 'sub': + $function = $ns_function = 'ldap_search'; + break; + case 'base': + $function = $ns_function = 'ldap_read'; + break; + default: + $function = 'ldap_list'; + $ns_function = 'ldap_read'; + break; + } + + return $function; + } + + /** + * Convert the given scope integer value to a string representation + */ + public static function scopeint2str($scope) + { + switch ($scope) { + case 2: return 'sub'; + case 1: return 'one'; + case 0: return 'base'; + default: $this->_debug("Scope $scope is not a valid scope integer"); + } + + return ''; + } + + /** + * Escapes the given value according to RFC 2254 so that it can be safely used in LDAP filters. + * + * @param string $val Value to quote + * @return string The escaped value + */ + public static function escape_value($val) + { + return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', + '\\'=>'\5c', '/'=>'\2f')); + } + + /** + * Escapes a DN value according to RFC 2253 + * + * @param string $dn DN value o quote + * @return string The escaped value + */ + public static function escape_dn($dn) + { + return strtr($str, array(','=>'\2c', '='=>'\3d', '+'=>'\2b', + '<'=>'\3c', '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', + '"'=>'\22', '#'=>'\23')); + } + + /** + * Normalize a LDAP result by converting entry attributes arrays into single values + * + * @param array $result LDAP result set fetched with ldap_get_entries() + * @return array Hash array with normalized entries, indexed by their DNs + */ + public static function normalize_result($result) + { + if (!is_array($result)) { + return array(); + } + + $entries = array(); + for ($i = 0; $i < $result['count']; $i++) { + $key = $result[$i]['dn'] ? $result[$i]['dn'] : $i; + $entries[$key] = self::normalize_entry($result[$i]); + } + + return $entries; + } + + /** + * Turn an LDAP entry into a regular PHP array with attributes as keys. + * + * @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries() + * @return array Hash array with attributes as keys + */ + public static function normalize_entry($entry) + { + $rec = array(); + for ($i=0; $i < $entry['count']; $i++) { + $attr = $entry[$i]; + if ($entry[$attr]['count'] == 1) { + switch ($attr) { + case 'objectclass': + $rec[$attr] = array(strtolower($entry[$attr][0])); + break; + default: + $rec[$attr] = $entry[$attr][0]; + break; + } + } + else { + for ($j=0; $j < $entry[$attr]['count']; $j++) { + $rec[$attr][$j] = $entry[$attr][$j]; + } + } + } + + return $rec; + } + + /** + * Set server controls for Virtual List View (paginated listing) + */ + private function _vlv_set_controls($sort, $list_page, $page_size, $search = null) + { + $sort_ctrl = array('oid' => "1.2.840.113556.1.4.473", 'value' => self::_sort_ber_encode((array)$sort)); + $vlv_ctrl = array('oid' => "2.16.840.1.113730.3.4.9", 'value' => self::_vlv_ber_encode(($offset = ($list_page-1) * $page_size + 1), $page_size, $search), 'iscritical' => true); + + $this->_debug("C: Set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value'])) . " ($sort[0]);" + . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size; $search)"); + + if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) { + $this->_debug("S: ".ldap_error($this->conn)); + $this->set_error(self::ERROR_SEARCH, 'vlvnotsupported'); + return false; + } + + return true; + } + + /** + * Returns unified attribute name (resolving aliases) + */ + private static function _attr_name($namev) + { + // list of known attribute aliases + static $aliases = array( + 'gn' => 'givenname', + 'rfc822mailbox' => 'email', + 'userid' => 'uid', + 'emailaddress' => 'email', + 'pkcs9email' => 'email', + ); + + list($name, $limit) = explode(':', $namev, 2); + $suffix = $limit ? ':'.$limit : ''; + + return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix; + } + + /** + * Quotes attribute value string + * + * @param string $str Attribute value + * @param bool $dn True if the attribute is a DN + * + * @return string Quoted string + */ + public static function quote_string($str, $dn=false) + { + // take firt entry if array given + if (is_array($str)) + $str = reset($str); + + if ($dn) + $replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c', + '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23'); + else + $replace = array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c', + '/'=>'\2f'); + + return strtr($str, $replace); + } + + /** + * Prints debug info to the log + */ + private function _debug($str) + { + if ($this->debug && class_exists('rcube')) { + rcube::write_log('ldap', $str); + } + } + + + /***************** Virtual List View (VLV) related utility functions **************** */ + + /** + * Return the search string value to be used in VLV controls + */ + private function _vlv_search($sort, $search) + { + foreach ($search as $attr => $value) { + if (!in_array(strtolower($attr), $sort)) { + $this->_debug("d: Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")"); + return null; + } else { + return $value; + } + } + } + + /** + * Find a VLV index matching the given query attributes + * + * @return string Sort attribute or False if no match + */ + private function _find_vlv($base_dn, $filter, $scope, $sort_attrs = null) + { + if (!$this->config['vlv'] || $scope == 'base') { + return false; + } + + // get vlv config + $vlv_config = $this->_read_vlv_config(); + + if ($vlv = $vlv_config[$base_dn]) { + $this->_debug("D: Found a VLV for $base_dn"); + + if ($vlv['filter'] == strtolower($filter) || stripos($filter, '(&'.$vlv['filter'].'(') === 0) { + $this->_debug("D: Filter matches"); + if ($vlv['scope'] == $scope) { + // Not passing any sort attributes means you don't care + if (empty($sort_attrs) || in_array($sort_attrs, $vlv['sort'])) { + return $vlv['sort'][0]; + } + } + else { + $this->_debug("D: Scope does not match"); + } + } + else { + $this->_debug("D: Filter does not match"); + } + } + else { + $this->_debug("D: No VLV for $base_dn"); + } + + return false; + } + + /** + * Return VLV indexes and searches including necessary configuration + * details. + */ + private function _read_vlv_config() + { + if (empty($this->config['vlv']) || empty($this->config['config_root_dn'])) { + return array(); + } + // return hard-coded VLV config + else if (is_array($this->config['vlv'])) { + return $this->config['vlv']; + } + + // return cached result + if (is_array($this->vlv_config)) { + return $this->vlv_config; + } + + if ($this->cache && ($cached_config = $this->cache->get('vlvconfig'))) { + $this->vlv_config = $cached_config; + return $this->vlv_config; + } + + $this->vlv_config = array(); + + $ldap_result = ldap_search($this->conn, $this->config['config_root_dn'], '(objectclass=vlvsearch)', array('*'), 0, 0, 0); + $vlv_searches = new rcube_ldap_result($this->conn, $ldap_result, $this->config['config_root_dn'], '(objectclass=vlvsearch)'); + + if ($vlv_searches->count() < 1) { + $this->_debug("D: Empty result from search for '(objectclass=vlvsearch)' on '$config_root_dn'"); + return array(); + } + + foreach ($vlv_searches->entries(true) as $vlv_search_dn => $vlv_search_attrs) { + // Multiple indexes may exist + $ldap_result = ldap_search($this->conn, $vlv_search_dn, '(objectclass=vlvindex)', array('*'), 0, 0, 0); + $vlv_indexes = new rcube_ldap_result($this->conn, $ldap_result, $vlv_search_dn, '(objectclass=vlvindex)'); + + // Reset this one for each VLV search. + $_vlv_sort = array(); + foreach ($vlv_indexes->entries(true) as $vlv_index_dn => $vlv_index_attrs) { + $_vlv_sort[] = explode(' ', $vlv_index_attrs['vlvsort']); + } + + $this->vlv_config[$vlv_search_attrs['vlvbase']] = array( + 'scope' => self::scopeint2str($vlv_search_attrs['vlvscope']), + 'filter' => strtolower($vlv_search_attrs['vlvfilter']), + 'sort' => $_vlv_sort, + ); + } + + // cache this + if ($this->cache) + $this->cache->set('vlvconfig', $this->vlv_config); + + $this->_debug("D: Refreshed VLV config: " . var_export($this->vlv_config, true)); + + return $this->vlv_config; + } + + /** + * Generate BER encoded string for Virtual List View option + * + * @param integer List offset (first record) + * @param integer Records per page + * + * @return string BER encoded option value + */ + private static function _vlv_ber_encode($offset, $rpp, $search = '') + { + /* + this string is ber-encoded, php will prefix this value with: + 04 (octet string) and 10 (length of 16 bytes) + the code behind this string is broken down as follows: + 30 = ber sequence with a length of 0e (14) bytes following + 02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0) + 02 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24) + a0 = type context-specific/constructed with a length of 06 (6) bytes following + 02 = type integer with 2 bytes following (offset): 01 01 (ie 1) + 02 = type integer with 2 bytes following (contentCount): 01 00 + + with a search string present: + 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here) + 81 indicates a user string is present where as a a0 indicates just a offset search + 81 = type context-specific/constructed with a length of 06 (6) bytes following + + The following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the + encoding of integer values (note: these values are in + two-complement form so since offset will never be negative bit 8 of the + leftmost octet should never by set to 1): + 8.3.2: If the contents octets of an integer value encoding consist + of more than one octet, then the bits of the first octet (rightmost) + and bit 8 of the second (to the left of first octet) octet: + a) shall not all be ones; and + b) shall not all be zero + */ + + if ($search) { + $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search); + $ber_val = self::_string2hex($search); + $str = self::_ber_addseq($ber_val, '81'); + } + else { + // construct the string from right to left + $str = "020100"; # contentCount + + $ber_val = self::_ber_encode_int($offset); // returns encoded integer value in hex format + + // calculate octet length of $ber_val + $str = self::_ber_addseq($ber_val, '02') . $str; + + // now compute length over $str + $str = self::_ber_addseq($str, 'a0'); + } + + // now tack on records per page + $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str; + + // now tack on sequence identifier and length + $str = self::_ber_addseq($str, '30'); + + return pack('H'.strlen($str), $str); + } + + /** + * create ber encoding for sort control + * + * @param array List of cols to sort by + * @return string BER encoded option value + */ + private static function _sort_ber_encode($sortcols) + { + $str = ''; + foreach (array_reverse((array)$sortcols) as $col) { + $ber_val = self::_string2hex($col); + + // 30 = ber sequence with a length of octet value + // 04 = octet string with a length of the ascii value + $oct = self::_ber_addseq($ber_val, '04'); + $str = self::_ber_addseq($oct, '30') . $str; + } + + // now tack on sequence identifier and length + $str = self::_ber_addseq($str, '30'); + + return pack('H'.strlen($str), $str); + } + + /** + * Add BER sequence with correct length and the given identifier + */ + private static function _ber_addseq($str, $identifier) + { + $len = dechex(strlen($str)/2); + if (strlen($len) % 2 != 0) + $len = '0'.$len; + + return $identifier . $len . $str; + } + + /** + * Returns BER encoded integer value in hex format + */ + private static function _ber_encode_int($offset) + { + $val = dechex($offset); + $prefix = ''; + + // check if bit 8 of high byte is 1 + if (preg_match('/^[89abcdef]/', $val)) + $prefix = '00'; + + if (strlen($val)%2 != 0) + $prefix .= '0'; + + return $prefix . $val; + } + + /** + * Returns ascii string encoded in hex + */ + private static function _string2hex($str) + { + $hex = ''; + for ($i=0; $i < strlen($str); $i++) { + $hex .= dechex(ord($str[$i])); + } + return $hex; + } + +}
View file
kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_ldap_result.php
Added
@@ -0,0 +1,130 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | Roundcube/rcube_ldap_result.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2006-2013, The Roundcube Dev Team | + | Copyright (C) 2013, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Model class that represents an LDAP search result | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + + +/** + * Model class representing an LDAP search result + * + * @package Framework + * @subpackage LDAP + */ +class rcube_ldap_result implements Iterator +{ + public $conn; + public $ldap; + public $base_dn; + public $filter; + + private $count = null; + private $current = null; + private $iteratorkey = 0; + + /** + * Default constructor + * + * @param resource $conn LDAP link identifier + * @param resource $ldap LDAP result entry identifier + * @param string $base_dn Base DN used to get this result + * @param string $filter Filter query used to get this result + * @param integer $count Record count value (pre-calculated) + */ + function __construct($conn, $ldap, $base_dn, $filter, $count = null) + { + $this->conn = $conn; + $this->ldap = $ldap; + $this->base_dn = $base_dn; + $this->filter = $filter; + $this->count = $count; + } + + /** + * Wrapper for ldap_sort() + */ + public function sort($attr) + { + return ldap_sort($this->conn, $this->ldap, $attr); + } + + /** + * Get entries count + */ + public function count() + { + if (!isset($this->count)) + $this->count = ldap_count_entries($this->conn, $this->ldap); + + return $this->count; + } + + /** + * Wrapper for ldap_get_entries() + * + * @param boolean $normalize Optionally normalize the entries to a list of hash arrays + * @return array List of LDAP entries + */ + public function entries($normalize = false) + { + $entries = ldap_get_entries($this->conn, $this->ldap); + return $normalize ? rcube_ldap_generic::normalize_result($entries) : $entries; + } + + /** + * Wrapper for ldap_get_dn() using the current entry pointer + */ + public function get_dn() + { + return $this->current ? ldap_get_dn($this->conn, $this->current) : null; + } + + + /*** Implements the PHP 5 Iterator interface to make foreach work ***/ + + function current() + { + $attrib = ldap_get_attributes($this->conn, $this->current); + $attrib['dn'] = ldap_get_dn($this->conn, $this->current); + return $attrib; + } + + function key() + { + return $this->iteratorkey; + } + + function rewind() + { + $this->iteratorkey = 0; + $this->current = ldap_first_entry($this->conn, $this->ldap); + } + + function next() + { + $this->iteratorkey++; + $this->current = ldap_next_entry($this->conn, $this->current); + } + + function valid() + { + return (bool)$this->current; + } + +}
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_message.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_message.php
Changed
@@ -85,15 +85,16 @@ $this->headers = $this->storage->get_message($uid); - if (!$this->headers) + if (!$this->headers) { return; + } $this->mime = new rcube_mime($this->headers->charset); - $this->subject = $this->mime->decode_mime_string($this->headers->subject); + $this->subject = $this->headers->get('subject'); list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1)); - $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid])); + $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$this->folder.':'.$uid])); $this->opt = array( 'safe' => $this->is_safe, 'prefer_html' => $this->app->config->get('prefer_html'), @@ -125,15 +126,11 @@ */ public function get_header($name, $raw = false) { - if (empty($this->headers)) + if (empty($this->headers)) { return null; + } - if ($this->headers->$name) - $value = $this->headers->$name; - else if ($this->headers->others[$name]) - $value = $this->headers->others[$name]; - - return $raw ? $value : $this->mime->decode_header($value); + return $this->headers->get($name, !$raw); } @@ -144,8 +141,7 @@ */ public function set_safe($safe = true) { - $this->is_safe = $safe; - $_SESSION['safe_messages'][$this->uid] = $this->is_safe; + $_SESSION['safe_messages'][$this->folder.':'.$this->uid] = $this->is_safe = $safe; } @@ -153,12 +149,13 @@ * Compose a valid URL for getting a message part * * @param string $mime_id Part MIME-ID + * @param mixed $embed Mimetype class for parts to be embedded * @return string URL or false if part does not exist */ public function get_part_url($mime_id, $embed = false) { if ($this->mime_parts[$mime_id]) - return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : ''); + return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1&_mimeclass=' . $embed : ''); else return false; } @@ -171,10 +168,11 @@ * @param resource $fp File pointer to save the message part * @param boolean $skip_charset_conv Disables charset conversion * @param int $max_bytes Only read this number of bytes + * @param boolean $formatted Enables formatting of text/* parts bodies * * @return string Part content */ - public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0) + public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0, $formatted = true) { if ($part = $this->mime_parts[$mime_id]) { // stored in message structure (winmail/inline-uuencode) @@ -188,45 +186,89 @@ // get from IMAP $this->storage->set_folder($this->folder); - return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv, $max_bytes); + return $this->storage->get_message_part($this->uid, $mime_id, $part, + NULL, $fp, $skip_charset_conv, $max_bytes, $formatted); } } /** - * Determine if the message contains a HTML part + * Determine if the message contains a HTML part. This must to be + * a real part not an attachment (or its part) + * This must to be + * a real part not an attachment (or its part) * - * @param bool $recursive Enables checking in all levels of the structure - * @param bool $enriched Enables checking for text/enriched parts too + * @param bool $enriched Enables checking for text/enriched parts too * * @return bool True if a HTML is available, False if not */ - function has_html_part($recursive = true, $enriched = false) + function has_html_part($enriched = false) { // check all message parts - foreach ($this->parts as $part) { + foreach ($this->mime_parts as $part) { if ($part->mimetype == 'text/html' || ($enriched && $part->mimetype == 'text/enriched')) { - // Level check, we'll skip e.g. HTML attachments - if (!$recursive) { - $level = explode('.', $part->mime_id); + // Skip if part is an attachment, don't use is_attachment() here + if ($part->filename) { + continue; + } - // Skip if level too deep or part has a file name - if (count($level) > 2 || $part->filename) { - continue; + $level = explode('.', $part->mime_id); + + // Check if the part belongs to higher-level's alternative/related + while (array_pop($level) !== null) { + if (!count($level)) { + return true; } - // HTML part can be on the lower level, if not... - if (count($level) > 1) { - array_pop($level); - $parent = $this->mime_parts[join('.', $level)]; - // ... parent isn't multipart/alternative or related - if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') { - continue; - } + $parent = $this->mime_parts[join('.', $level)]; + if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') { + continue 2; } } - return true; + if ($part->size) { + return true; + } + } + } + + return false; + } + + + /** + * Determine if the message contains a text/plain part. This must to be + * a real part not an attachment (or its part) + * + * @return bool True if a plain text part is available, False if not + */ + function has_text_part() + { + // check all message parts + foreach ($this->mime_parts as $part) { + if ($part->mimetype == 'text/plain') { + // Skip if part is an attachment, don't use is_attachment() here + if ($part->filename) { + continue; + } + + $level = explode('.', $part->mime_id); + + // Check if the part belongs to higher-level's alternative/related + while (array_pop($level) !== null) { + if (!count($level)) { + return true; + } + + $parent = $this->mime_parts[join('.', $level)]; + if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') { + continue 2; + } + } + + if ($part->size) { + return true; + } } } @@ -321,8 +363,8 @@ $mimetype = $structure->real_mimetype; // parse headers from message/rfc822 part - if (!isset($structure->headers['subject'])) { - list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192)); + if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) { + list($headers, ) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768)); $structure->headers = rcube_mime::parse_headers($headers); } } @@ -330,7 +372,8 @@ $mimetype = $structure->mimetype; // show message headers - if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) { + if ($recursive && is_array($structure->headers) && + (isset($structure->headers['subject']) || $structure->headers['from'] || $structure->headers['to'])) { $c = new stdClass; $c->type = 'headers'; $c->headers = $structure->headers; @@ -444,14 +487,6 @@ $this->parts[] = $c; } - // add html part as attachment - if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { - $html_part = $structure->parts[$html_part]; - $html_part->mimetype = 'text/html'; - - $this->attachments[] = $html_part; - } - // add unsupported/unrecognized parts to attachments list if ($attach_part) { $this->attachments[] = $structure->parts[$attach_part]; @@ -468,6 +503,17 @@ $this->parts[] = $p; } + // this is an S/MIME ecrypted message -> create a plaintext body with the according message + else if ($mimetype == 'application/pkcs7-mime') { + $p = new stdClass; + $p->type = 'content'; + $p->ctype_primary = 'text'; + $p->ctype_secondary = 'plain'; + $p->mimetype = 'text/plain'; + $p->realtype = 'application/pkcs7-mime'; + + $this->parts[] = $p; + } // message contains multiple parts else if (is_array($structure->parts) && !empty($structure->parts)) { // iterate over parts @@ -525,10 +571,6 @@ if (!empty($mail_part->filename)) { $this->attachments[] = $mail_part; } - // list html part as attachment (here the part is most likely inside a multipart/related part) - else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) { - $this->attachments[] = $mail_part; - } } // part message/* else if ($primary_type == 'message') { @@ -604,8 +646,8 @@ $img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/'; foreach ($this->inline_parts as $inline_object) { - $part_url = $this->get_part_url($inline_object->mime_id, true); - if ($inline_object->content_id) + $part_url = $this->get_part_url($inline_object->mime_id, $inline_object->ctype_primary); + if (isset($inline_object->content_id)) $a_replaces['cid:'.$inline_object->content_id] = $part_url; if ($inline_object->content_location) { $a_replaces[$inline_object->content_location] = $part_url; @@ -745,7 +787,7 @@ $uupart->size = strlen($uupart->body); $uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid; - $ctype = rcube_mime::content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); + $ctype = rcube_mime::file_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); $uupart->mimetype = $ctype; list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_message_header.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_message_header.php
Changed
@@ -215,7 +215,12 @@ $value = $this->others[$name]; } - return $decode ? rcube_mime::decode_header($value, $this->charset) : $value; + if ($decode) { + $value = rcube_mime::decode_header($value, $this->charset); + $value = rcube_charset::clean($value); + } + + return $value; } /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_mime.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_mime.php
Changed
@@ -127,10 +127,11 @@ * @param int $max List only this number of addresses * @param boolean $decode Decode address strings * @param string $fallback Fallback charset if none specified + * @param boolean $addronly Return flat array with e-mail addresses only * - * @return array Indexed list of addresses + * @return array Indexed list of addresses */ - static function decode_address_list($input, $max = null, $decode = true, $fallback = null) + static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false) { $a = self::parse_address_list($input, $decode, $fallback); $out = array(); @@ -145,20 +146,21 @@ foreach ($a as $val) { $j++; $address = trim($val['address']); - $name = trim($val['name']); - if ($name && $address && $name != $address) - $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); - else if ($address) - $string = $address; - else if ($name) - $string = $name; - - $out[$j] = array( - 'name' => $name, - 'mailto' => $address, - 'string' => $string - ); + if ($addronly) { + $out[$j] = $address; + } + else { + $name = trim($val['name']); + if ($name && $address && $name != $address) + $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); + else if ($address) + $string = $address; + else if ($name) + $string = $name; + + $out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string); + } if ($max && $j==$max) break; @@ -359,6 +361,11 @@ $address = $m[1]; $name = ''; } + // special case (#1489092) + else if (preg_match('/(\s*<MAILER-DAEMON>)$/', $val, $m)) { + $address = 'MAILER-DAEMON'; + $name = substr($val, 0, -strlen($m[1])); + } else { $name = $val; } @@ -476,13 +483,20 @@ $q_level = 0; foreach ($text as $idx => $line) { - if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) { - $q = strlen(str_replace(' ', '', $regs[0])); - $line = substr($line, strlen($regs[0])); - - if ($q == $q_level && $line - && isset($text[$last]) - && $text[$last][strlen($text[$last])-1] == ' ' + if (preg_match('/^(>+)/', $line, $m)) { + // remove quote chars + $q = strlen($m[1]); + $line = preg_replace('/^>+/', '', $line); + // remove (optional) space-staffing + $line = preg_replace('/^ /', '', $line); + + // The same paragraph (We join current line with the previous one) when: + // - the same level of quoting + // - previous line was flowed + // - previous line contains more than only one single space (and quote char(s)) + if ($q == $q_level + && isset($text[$last]) && $text[$last][strlen($text[$last])-1] == ' ' + && !preg_match('/^>+ {0,1}$/', $text[$last]) ) { $text[$last] .= $line; unset($text[$idx]); @@ -535,10 +549,13 @@ foreach ($text as $idx => $line) { if ($line != '-- ') { - if ($line[0] == '>' && preg_match('/^(>+ {0,1})+/', $line, $regs)) { - $level = substr_count($regs[0], '>'); + if (preg_match('/^(>+)/', $line, $m)) { + // remove quote chars + $level = strlen($m[1]); + $line = preg_replace('/^>+/', '', $line); + // remove (optional) space-staffing and spaces before the line end + $line = preg_replace('/(^ | +$)/', '', $line); $prefix = str_repeat('>', $level) . ' '; - $line = rtrim(substr($line, strlen($regs[0]))); $line = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix", false, $charset); } else if ($line) { @@ -556,81 +573,122 @@ /** - * Improved wordwrap function. + * Improved wordwrap function with multibyte support. + * The code is based on Zend_Text_MultiByte::wordWrap(). * - * @param string $string Text to wrap - * @param int $width Line width - * @param string $break Line separator - * @param bool $cut Enable to cut word - * @param string $charset Charset of $string + * @param string $string Text to wrap + * @param int $width Line width + * @param string $break Line separator + * @param bool $cut Enable to cut word + * @param string $charset Charset of $string + * @param bool $wrap_quoted When enabled quoted lines will not be wrapped * * @return string Text */ - public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null) + public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true) { - if ($charset && function_exists('mb_internal_encoding')) { + // Note: Never try to use iconv instead of mbstring functions here + // Iconv's substr/strlen are 100x slower (#1489113) + + if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) { mb_internal_encoding($charset); } - $para = preg_split('/\r?\n/', $string); - $string = ''; + // Convert \r\n to \n, this is our line-separator + $string = str_replace("\r\n", "\n", $string); + $separator = "\n"; // must be 1 character length + $result = array(); - while (count($para)) { - $line = array_shift($para); - if ($line[0] == '>') { - $string .= $line.$break; - continue; - } + while (($stringLength = mb_strlen($string)) > 0) { + $breakPos = mb_strpos($string, $separator, 0); - $list = explode(' ', $line); - $len = 0; - while (count($list)) { - $line = array_shift($list); - $l = mb_strlen($line); - $newlen = $len + $l + ($len ? 1 : 0); + // quoted line (do not wrap) + if ($wrap_quoted && $string[0] == '>') { + if ($breakPos === $stringLength - 1 || $breakPos === false) { + $subString = $string; + $cutLength = null; + } + else { + $subString = mb_substr($string, 0, $breakPos); + $cutLength = $breakPos + 1; + } + } + // next line found and current line is shorter than the limit + else if ($breakPos !== false && $breakPos < $width) { + if ($breakPos === $stringLength - 1) { + $subString = $string; + $cutLength = null; + } + else { + $subString = mb_substr($string, 0, $breakPos); + $cutLength = $breakPos + 1; + } + } + else { + $subString = mb_substr($string, 0, $width); - if ($newlen <= $width) { - $string .= ($len ? ' ' : '').$line; - $len += (1 + $l); + // last line + if ($breakPos === false && $subString === $string) { + $cutLength = null; } else { - if ($l > $width) { - if ($cut) { - $start = 0; - while ($l) { - $str = mb_substr($line, $start, $width); - $strlen = mb_strlen($str); - $string .= ($len ? $break : '').$str; - $start += $strlen; - $l -= $strlen; - $len = $strlen; + $nextChar = mb_substr($string, $width, 1); + + if ($nextChar === ' ' || $nextChar === $separator) { + $afterNextChar = mb_substr($string, $width + 1, 1); + + if ($afterNextChar === false) { + $subString .= $nextChar; + } + + $cutLength = mb_strlen($subString) + 1; + } + else { + $spacePos = mb_strrpos($subString, ' ', 0); + + if ($spacePos !== false) { + $subString = mb_substr($subString, 0, $spacePos); + $cutLength = $spacePos + 1; + } + else if ($cut === false && $breakPos === false) { + $subString = $string; + $cutLength = null; + } + else if ($cut === false) { + $spacePos = mb_strpos($string, ' ', 0); + + if ($spacePos !== false && $spacePos < $breakPos) { + $subString = mb_substr($string, 0, $spacePos); + $cutLength = $spacePos + 1; + } + else { + $subString = mb_substr($string, 0, $breakPos); + $cutLength = $breakPos + 1; } } else { - $string .= ($len ? $break : '').$line; - if (count($list)) { - $string .= $break; - } - $len = 0; + $subString = mb_substr($subString, 0, $width); + $cutLength = $width; } } - else { - $string .= $break.$line; - $len = $l; - } } } - if (count($para)) { - $string .= $break; + $result[] = $subString; + + if ($cutLength !== null) { + $string = mb_substr($string, $cutLength, ($stringLength - $cutLength)); + } + else { + break; } } - if ($charset && function_exists('mb_internal_encoding')) { + if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) { mb_internal_encoding(RCUBE_CHARSET); } - return $string; + return implode($break, $result); } @@ -663,7 +721,16 @@ // try fileinfo extension if available if (!$mime_type && function_exists('finfo_open')) { - if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) { + // null as a 2nd argument should be the same as no argument + // this however is not true on all systems/versions + if ($mime_magic) { + $finfo = finfo_open(FILEINFO_MIME, $mime_magic); + } + else { + $finfo = finfo_open(FILEINFO_MIME); + } + + if ($finfo) { if ($is_stream) $mime_type = finfo_buffer($finfo, $path); else @@ -709,21 +776,27 @@ // load mapping file $file_paths = array(); - if ($mime_types = rcube::get_instance()->config->get('mime_types')) + if ($mime_types = rcube::get_instance()->config->get('mime_types')) { $file_paths[] = $mime_types; + } // try common locations - $file_paths[] = '/etc/mime.types'; - $file_paths[] = '/etc/httpd/mime.types'; - $file_paths[] = '/etc/httpd2/mime.types'; - $file_paths[] = '/etc/apache/mime.types'; - $file_paths[] = '/etc/apache2/mime.types'; - $file_paths[] = '/usr/local/etc/httpd/conf/mime.types'; - $file_paths[] = '/usr/local/etc/apache/conf/mime.types'; + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + $file_paths[] = 'C:/xampp/apache/conf/mime.types.'; + } + else { + $file_paths[] = '/etc/mime.types'; + $file_paths[] = '/etc/httpd/mime.types'; + $file_paths[] = '/etc/httpd2/mime.types'; + $file_paths[] = '/etc/apache/mime.types'; + $file_paths[] = '/etc/apache2/mime.types'; + $file_paths[] = '/usr/local/etc/httpd/conf/mime.types'; + $file_paths[] = '/usr/local/etc/apache/conf/mime.types'; + } foreach ($file_paths as $fp) { - if (is_readable($fp)) { - $lines = file($fp, FILE_IGNORE_NEW_LINES); + if (@is_readable($fp)) { + $lines = file($fp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); break; } } @@ -745,11 +818,35 @@ // fallback to some well-known types most important for daily emails if (empty($mime_types)) { - $mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); - $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff'); + $mime_extensions = (array) @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); - foreach ($mime_extensions as $ext => $mime) + foreach ($mime_extensions as $ext => $mime) { $mime_types[$mime][] = $ext; + } + } + + // Add some known aliases that aren't included by some mime.types (#1488891) + // the order is important here so standard extensions have higher prio + $aliases = array( + 'image/gif' => array('gif'), + 'image/png' => array('png'), + 'image/x-png' => array('png'), + 'image/jpeg' => array('jpg', 'jpeg', 'jpe'), + 'image/jpg' => array('jpg', 'jpeg', 'jpe'), + 'image/pjpeg' => array('jpg', 'jpeg', 'jpe'), + 'image/tiff' => array('tif'), + 'message/rfc822' => array('eml'), + 'text/x-mail' => array('eml'), + ); + + foreach ($aliases as $mime => $exts) { + $mime_types[$mime] = array_unique(array_merge((array) $mime_types[$mime], $exts)); + + foreach ($exts as $ext) { + if (!isset($mime_extensions[$ext])) { + $mime_extensions[$ext] = $mime; + } + } } return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_output.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_output.php
Changed
@@ -162,7 +162,7 @@ header("Cache-Control: private, must-revalidate"); } else { - header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0"); + header("Cache-Control: private, no-cache, no-store, must-revalidate, post-check=0, pre-check=0"); header("Pragma: no-cache"); } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_plugin.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_plugin.php
Changed
@@ -60,6 +60,14 @@ */ public $noframe = false; + /** + * A list of config option names that can be modified + * by the user via user interface (with save-prefs command) + * + * @var array + */ + public $allowed_prefs; + protected $home; protected $urlbase; private $mytask; @@ -84,6 +92,16 @@ abstract function init(); /** + * Provide information about this + * + * @return array Meta information about a plugin or false if not implemented + */ + public static function info() + { + return false; + } + + /** * Attempt to load the given plugin which is required for the current plugin * * @param string Plugin name @@ -209,7 +227,7 @@ $rcube->load_language($lang, $add); // add labels to client - if ($add2client) { + if ($add2client && method_exists($rcube->output, 'add_label')) { if (is_array($add2client)) { $js_labels = array_map(array($this, 'label_map_callback'), $add2client); } @@ -222,6 +240,24 @@ } /** + * Wrapper for add_label() adding the plugin ID as domain + */ + public function add_label() + { + $rcube = rcube::get_instance(); + + if (method_exists($rcube->output, 'add_label')) { + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) { + $args = $args[0]; + } + + $args = array_map(array($this, 'label_map_callback'), $args); + $rcube->output->add_label($args); + } + } + + /** * Wrapper for rcube::gettext() adding the plugin ID as domain * * @param string $p Message identifier @@ -237,7 +273,7 @@ /** * Register this plugin to be responsible for a specific task * - * @param string $task Task name (only characters [a-z0-9_.-] are allowed) + * @param string $task Task name (only characters [a-z0-9_-] are allowed) */ public function register_task($task) { @@ -372,6 +408,10 @@ */ private function label_map_callback($key) { + if (strpos($key, $this->ID.'.') === 0) { + return $key; + } + return $this->ID.'.'.$key; } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_plugin_api.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_plugin_api.php
Changed
@@ -36,6 +36,7 @@ public $task = ''; public $output; public $handlers = array(); + public $allowed_prefs = array(); protected $plugins = array(); protected $tasks = array(); @@ -202,6 +203,11 @@ $plugin->init(); $this->plugins[$plugin_name] = $plugin; } + + if (!empty($plugin->allowed_prefs)) { + $this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs); + } + return true; } } @@ -222,6 +228,119 @@ } /** + * Get information about a specific plugin. + * This is either provided my a plugin's info() method or extracted from a package.xml or a composer.json file + * + * @param string Plugin name + * @return array Meta information about a plugin or False if plugin was not found + */ + public function get_info($plugin_name) + { + static $composer_lock, $license_uris = array( + 'Apache' => 'http://www.apache.org/licenses/LICENSE-2.0.html', + 'Apache-2' => 'http://www.apache.org/licenses/LICENSE-2.0.html', + 'Apache-1' => 'http://www.apache.org/licenses/LICENSE-1.0', + 'Apache-1.1' => 'http://www.apache.org/licenses/LICENSE-1.1', + 'GPL' => 'http://www.gnu.org/licenses/gpl.html', + 'GPLv2' => 'http://www.gnu.org/licenses/gpl-2.0.html', + 'GPL-2.0' => 'http://www.gnu.org/licenses/gpl-2.0.html', + 'GPLv3' => 'http://www.gnu.org/licenses/gpl-3.0.html', + 'GPL-3.0' => 'http://www.gnu.org/licenses/gpl-3.0.html', + 'GPL-3.0+' => 'http://www.gnu.org/licenses/gpl.html', + 'GPL-2.0+' => 'http://www.gnu.org/licenses/gpl.html', + 'LGPL' => 'http://www.gnu.org/licenses/lgpl.html', + 'LGPLv2' => 'http://www.gnu.org/licenses/lgpl-2.0.html', + 'LGPLv2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html', + 'LGPLv3' => 'http://www.gnu.org/licenses/lgpl.html', + 'LGPL-2.0' => 'http://www.gnu.org/licenses/lgpl-2.0.html', + 'LGPL-2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html', + 'LGPL-3.0' => 'http://www.gnu.org/licenses/lgpl.html', + 'LGPL-3.0+' => 'http://www.gnu.org/licenses/lgpl.html', + 'BSD' => 'http://opensource.org/licenses/bsd-license.html', + 'BSD-2-Clause' => 'http://opensource.org/licenses/BSD-2-Clause', + 'BSD-3-Clause' => 'http://opensource.org/licenses/BSD-3-Clause', + 'FreeBSD' => 'http://opensource.org/licenses/BSD-2-Clause', + 'MIT' => 'http://www.opensource.org/licenses/mit-license.php', + 'PHP' => 'http://opensource.org/licenses/PHP-3.0', + 'PHP-3' => 'http://www.php.net/license/3_01.txt', + 'PHP-3.0' => 'http://www.php.net/license/3_0.txt', + 'PHP-3.01' => 'http://www.php.net/license/3_01.txt', + ); + + $dir = dir($this->dir); + $fn = unslashify($dir->path) . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + $info = false; + + if (!class_exists($plugin_name)) + include($fn); + + if (class_exists($plugin_name)) + $info = $plugin_name::info(); + + // fall back to composer.json file + if (!$info) { + $composer = INSTALL_PATH . "/plugins/$plugin_name/composer.json"; + if (file_exists($composer) && ($json = @json_decode(file_get_contents($composer), true))) { + list($info['vendor'], $info['name']) = explode('/', $json['name']); + $info['license'] = $json['license']; + if ($license_uri = $license_uris[$info['license']]) + $info['license_uri'] = $license_uri; + } + + // read local composer.lock file (once) + if (!isset($composer_lock)) { + $composer_lock = @json_decode(@file_get_contents(INSTALL_PATH . "/composer.lock"), true); + if ($composer_lock['packages']) { + foreach ($composer_lock['packages'] as $i => $package) { + $composer_lock['installed'][$package['name']] = $package; + } + } + } + + // load additional information from local composer.lock file + if ($lock = $composer_lock['installed'][$json['name']]) { + $info['version'] = $lock['version']; + $info['uri'] = $lock['homepage'] ? $lock['homepage'] : $lock['source']['uri']; + $info['src_uri'] = $lock['dist']['uri'] ? $lock['dist']['uri'] : $lock['source']['uri']; + } + } + + // fall back to package.xml file + if (!$info) { + $package = INSTALL_PATH . "/plugins/$plugin_name/package.xml"; + if (file_exists($package) && ($file = file_get_contents($package))) { + $doc = new DOMDocument(); + $doc->loadXML($file); + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0"); + + // XPaths of plugin metadata elements + $metadata = array( + 'name' => 'string(//rc:package/rc:name)', + 'version' => 'string(//rc:package/rc:version/rc:release)', + 'license' => 'string(//rc:package/rc:license)', + 'license_uri' => 'string(//rc:package/rc:license/@uri)', + 'src_uri' => 'string(//rc:package/rc:srcuri)', + 'uri' => 'string(//rc:package/rc:uri)', + ); + + foreach ($metadata as $key => $path) { + $info[$key] = $xpath->evaluate($path); + } + + // dependent required plugins (can be used, but not included in config) + $deps = $xpath->evaluate('//rc:package/rc:dependencies/rc:required/rc:package/rc:name'); + for ($i = 0; $i < $deps->length; $i++) { + $dn = $deps->item($i)->nodeValue; + $info['requires'][] = $dn; + } + } + } + + return $info; + } + + /** * Allows a plugin object to register a callback for a certain hook * * @param string $hook Hook name @@ -372,7 +491,7 @@ /** * Register this plugin to be responsible for a specific task * - * @param string $task Task name (only characters [a-z0-9_.-] are allowed) + * @param string $task Task name (only characters [a-z0-9_-] are allowed) * @param string $owner Plugin name that registers this action */ public function register_task($task, $owner) @@ -382,7 +501,7 @@ return true; } - if ($task != asciiwords($task)) { + if ($task != asciiwords($task, true)) { rcube::raise_error(array('code' => 526, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Invalid task name: $task."
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_result_set.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_result_set.php
Changed
@@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2006-2011, The Roundcube Dev Team | + | Copyright (C) 2006-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -17,20 +17,22 @@ */ /** - * Roundcube result set class. + * Roundcube result set class + * * Representing an address directory result set. + * Implenets Iterator and thus be used in foreach() loops. * * @package Framework * @subpackage Addressbook */ -class rcube_result_set +class rcube_result_set implements Iterator { - var $count = 0; - var $first = 0; - var $current = 0; - var $searchonly = false; - var $records = array(); + public $count = 0; + public $first = 0; + public $searchonly = false; + public $records = array(); + private $current = 0; function __construct($c=0, $f=0) { @@ -51,18 +53,39 @@ function first() { $this->current = 0; - return $this->records[$this->current++]; + return $this->records[$this->current]; + } + + function seek($i) + { + $this->current = $i; + } + + /*** PHP 5 Iterator interface ***/ + + function rewind() + { + $this->current = 0; + } + + function current() + { + return $this->records[$this->current]; + } + + function key() + { + return $this->current; } - // alias for iterate() function next() { return $this->iterate(); } - function seek($i) + function valid() { - $this->current = $i; + return isset($this->records[$this->current]); } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_session.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_session.php
Changed
@@ -32,6 +32,8 @@ private $ip; private $start; private $changed; + private $time_diff = 0; + private $reloaded = false; private $unsets = array(); private $gc_handlers = array(); private $cookiename = 'roundcube_sessauth'; @@ -41,6 +43,7 @@ private $secret = ''; private $ip_check = false; private $logging = false; + private $storage; private $memcache; @@ -51,18 +54,21 @@ { $this->db = $db; $this->start = microtime(true); - $this->ip = $_SERVER['REMOTE_ADDR']; + $this->ip = rcube_utils::remote_addr(); $this->logging = $config->get('log_session', false); $lifetime = $config->get('session_lifetime', 1) * 60; $this->set_lifetime($lifetime); // use memcache backend - if ($config->get('session_storage', 'db') == 'memcache') { + $this->storage = $config->get('session_storage', 'db'); + if ($this->storage == 'memcache') { $this->memcache = rcube::get_instance()->get_memcache(); // set custom functions for PHP session management if memcache is available if ($this->memcache) { + ini_set('session.serialize_handler', 'php'); + session_set_save_handler( array($this, 'open'), array($this, 'close'), @@ -78,7 +84,9 @@ true, true); } } - else { + else if ($this->storage != 'php') { + ini_set('session.serialize_handler', 'php'); + // set custom functions for PHP session management session_set_save_handler( array($this, 'open'), @@ -86,7 +94,23 @@ array($this, 'db_read'), array($this, 'db_write'), array($this, 'db_destroy'), - array($this, 'db_gc')); + array($this, 'gc')); + } + } + + + /** + * Wrapper for session_start() + */ + public function start() + { + session_start(); + + // copy some session properties to object vars + if ($this->storage == 'php') { + $this->key = session_id(); + $this->ip = $_SESSION['__IP']; + $this->changed = $_SESSION['__MTIME']; } } @@ -115,6 +139,25 @@ /** + * Wrapper for session_write_close() + */ + public function write_close() + { + if ($this->storage == 'php') { + $_SESSION['__IP'] = $this->ip; + $_SESSION['__MTIME'] = time(); + } + + session_write_close(); + + // write_close() is called on script shutdown, see rcube::shutdown() + // execute cleanup functionality if enabled by session gc handler + // we do this after closing the session for better performance + $this->gc_shutdown(); + } + + + /** * Read session data from database * * @param string Session ID @@ -124,14 +167,16 @@ public function db_read($key) { $sql_result = $this->db->query( - "SELECT vars, ip, changed FROM ".$this->db->table_name('session') - ." WHERE sess_id = ?", $key); + "SELECT vars, ip, changed, " . $this->db->now() . " AS ts" + . " FROM " . $this->db->table_name('session') + . " WHERE sess_id = ?", $key); if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { - $this->changed = strtotime($sql_arr['changed']); - $this->ip = $sql_arr['ip']; - $this->vars = base64_decode($sql_arr['vars']); - $this->key = $key; + $this->time_diff = time() - strtotime($sql_arr['ts']); + $this->changed = strtotime($sql_arr['changed']); + $this->ip = $sql_arr['ip']; + $this->vars = base64_decode($sql_arr['vars']); + $this->key = $key; return !empty($this->vars) ? (string) $this->vars : ''; } @@ -151,8 +196,9 @@ */ public function db_write($key, $vars) { - $ts = microtime(true); - $now = $this->db->fromunixtime((int)$ts); + $now = $this->db->now(); + $table = $this->db->table_name('session'); + $ts = microtime(true); // no session row in DB (db_read() returns false) if (!$this->key) { @@ -170,22 +216,19 @@ $newvars = $this->_fixvars($vars, $oldvars); if ($newvars !== $oldvars) { - $this->db->query( - sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?", - $this->db->table_name('session'), $now), - base64_encode($newvars), $key); + $this->db->query("UPDATE $table " + . "SET changed = $now, vars = ? WHERE sess_id = ?", + base64_encode($newvars), $key); } - else if ($ts - $this->changed > $this->lifetime / 2) { - $this->db->query("UPDATE ".$this->db->table_name('session') - ." SET changed=$now WHERE sess_id=?", $key); + else if ($ts - $this->changed + $this->time_diff > $this->lifetime / 2) { + $this->db->query("UPDATE $table SET changed = $now" + . " WHERE sess_id = ?", $key); } } else { - $this->db->query( - sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ". - "VALUES (?, ?, ?, %s, %s)", - $this->db->table_name('session'), $now, $now), - $key, base64_encode($vars), (string)$this->ip); + $this->db->query("INSERT INTO $table (sess_id, vars, ip, created, changed)" + . " VALUES (?, ?, ?, $now, $now)", + $key, base64_encode($vars), (string)$this->ip); } return true; @@ -200,8 +243,18 @@ if ($oldvars !== null) { $a_oldvars = $this->unserialize($oldvars); if (is_array($a_oldvars)) { - foreach ((array)$this->unsets as $k) - unset($a_oldvars[$k]); + // remove unset keys on oldvars + foreach ((array)$this->unsets as $var) { + if (isset($a_oldvars[$var])) { + unset($a_oldvars[$var]); + } + else { + $path = explode('.', $var); + $k = array_pop($path); + $node = &$this->get_node($path, $a_oldvars); + unset($node[$k]); + } + } $newvars = $this->serialize(array_merge( (array)$a_oldvars, (array)$this->unserialize($vars))); @@ -235,25 +288,6 @@ /** - * Garbage collecting function - * - * @param string Session lifetime in seconds - * @return boolean True on success - */ - public function db_gc($maxlifetime) - { - // just delete all expired sessions - $this->db->query( - sprintf("DELETE FROM %s WHERE changed < %s", - $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime))); - - $this->gc(); - - return true; - } - - - /** * Read session data from memcache * * @param string Session ID @@ -299,9 +333,9 @@ $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars; - if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) { + if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) { return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), - MEMCACHE_COMPRESSED, $this->lifetime); + MEMCACHE_COMPRESSED, $this->lifetime + 60); } return true; @@ -329,11 +363,11 @@ /** * Execute registered garbage collector routines */ - public function gc() + public function gc($maxlifetime) { - foreach ($this->gc_handlers as $fct) { - call_user_func($fct); - } + // move gc execution to the script shutdown function + // see rcube::shutdown() and rcube_session::write_close() + return $this->gc_enabled = $maxlifetime; } @@ -355,6 +389,25 @@ /** + * Garbage collector handler to run on script shutdown + */ + protected function gc_shutdown() + { + if ($this->gc_enabled) { + // just delete all expired sessions + if ($this->storage == 'db') { + $this->db->query("DELETE FROM " . $this->db->table_name('session') + . " WHERE changed < " . $this->db->now(-$this->gc_enabled)); + } + + foreach ($this->gc_handlers as $fct) { + call_user_func($fct); + } + } + } + + + /** * Generate and set new session id * * @param boolean $destroy If enabled the current session will be destroyed @@ -371,9 +424,32 @@ /** + * Append the given value to the certain node in the session data array + * + * @param string Path denoting the session variable where to append the value + * @param string Key name under which to append the new value (use null for appending to an indexed list) + * @param mixed Value to append to the session data array + */ + public function append($path, $key, $value) + { + // re-read session data from DB because it might be outdated + if (!$this->reloaded && microtime(true) - $this->start > 0.5) { + $this->reload(); + $this->reloaded = true; + $this->start = microtime(true); + } + + $node = &$this->get_node(explode('.', $path), $_SESSION); + + if ($key !== null) $node[$key] = $value; + else $node[] = $value; + } + + + /** * Unset a session variable * - * @param string Varibale name + * @param string Variable name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5) * @return boolean True on success */ public function remove($var=null) @@ -383,7 +459,16 @@ } $this->unsets[] = $var; - unset($_SESSION[$var]); + + if (isset($_SESSION[$var])) { + unset($_SESSION[$var]); + } + else { + $path = explode('.', $var); + $key = array_pop($path); + $node = &$this->get_node($path, $_SESSION); + unset($node[$key]); + } return true; } @@ -395,7 +480,7 @@ public function kill() { $this->vars = null; - $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed) + $this->ip = rcube_utils::remote_addr(); // update IP (might have changed) $this->destroy(session_id()); rcube_utils::setcookie($this->cookiename, '-del-', time() - 60); } @@ -415,6 +500,23 @@ session_decode($data); } + /** + * Returns a reference to the node in data array referenced by the given path. + * e.g. ['compose','attachments'] will return $_SESSION['compose']['attachments'] + */ + private function &get_node($path, &$data_arr) + { + $node = &$data_arr; + if (!empty($path)) { + foreach ((array)$path as $key) { + if (!isset($node[$key])) + $node[$key] = array(); + $node = &$node[$key]; + } + } + + return $node; + } /** * Serialize session data @@ -592,10 +694,10 @@ function check_auth() { $this->cookie = $_COOKIE[$this->cookiename]; - $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true; + $result = $this->ip_check ? rcube_utils::remote_addr() == $this->ip : true; if (!$result) { - $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']); + $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . rcube_utils::remote_addr()); } if ($result && $this->_mkcookie($this->now) != $this->cookie) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_smtp.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_smtp.php
Changed
@@ -33,6 +33,8 @@ // define headers delimiter const SMTP_MIME_CRLF = "\r\n"; + const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n + /** * SMTP Connection and authentication @@ -119,7 +121,7 @@ } // try to connect to server and exit on failure - $result = $this->conn->connect($smtp_timeout); + $result = $this->conn->connect($CONFIG['smtp_timeout']); if (PEAR::isError($result)) { $this->response[] = "Connection failed: ".$result->getMessage(); @@ -327,6 +329,12 @@ */ public function debug_handler(&$smtp, $message) { + if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) { + $diff = $len - self::DEBUG_LINE_LENGTH; + $message = substr($message, 0, self::DEBUG_LINE_LENGTH) + . "... [truncated $diff bytes]"; + } + rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message)); } @@ -433,9 +441,9 @@ $recipients = rcube_utils::explode_quoted_string(',', $recipients); reset($recipients); - while (list($k, $recipient) = each($recipients)) { + foreach ($recipients as $recipient) { $a = rcube_utils::explode_quoted_string(' ', $recipient); - while (list($k2, $word) = each($a)) { + foreach ($a as $word) { if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') { $word = preg_replace('/^<|>$/', '', trim($word)); if (in_array($word, $addresses) === false) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_spellchecker.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_spellchecker.php
Changed
@@ -31,7 +31,7 @@ private $lang; private $rc; private $error; - private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.]([^\w]|$)/'; + private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.](?=\W|$)/'; private $options = array(); private $dict; private $have_dict; @@ -84,6 +84,9 @@ if ($this->engine == 'pspell') { $this->matches = $this->_pspell_check($this->content); } + else if ($this->engine == 'enchant') { + $this->matches = $this->_enchant_check($this->content); + } else { $this->matches = $this->_googie_check($this->content); } @@ -115,6 +118,9 @@ if ($this->engine == 'pspell') { return $this->_pspell_suggestions($word); } + else if ($this->engine == 'enchant') { + return $this->_enchant_suggestions($word); + } return $this->_googie_suggestions($word); } @@ -133,6 +139,9 @@ if ($this->engine == 'pspell') { return $this->_pspell_words($text, $is_html); } + else if ($this->engine == 'enchant') { + return $this->_enchant_words($text, $is_html); + } return $this->_googie_words($text, $is_html); } @@ -314,11 +323,6 @@ if (!$this->plink) { if (!extension_loaded('pspell')) { $this->error = "Pspell extension not available"; - rcube::raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => $this->error), true, false); - return; } @@ -331,6 +335,141 @@ } + /** + * Checks the text using enchant + * + * @param string $text Text content for spellchecking + */ + private function _enchant_check($text) + { + // init spellchecker + $this->_enchant_init(); + + if (!$this->enchant_dictionary) { + return array(); + } + + // tokenize + $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + + $diff = 0; + $matches = array(); + + foreach ($text as $w) { + $word = trim($w[0]); + $pos = $w[1] - $diff; + $len = mb_strlen($word); + + // skip exceptions + if ($this->is_exception($word)) { + } + else if (!enchant_dict_check($this->enchant_dictionary, $word)) { + $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word); + + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) { + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + } + + $matches[] = array($word, $pos, $len, null, $suggestions); + } + + $diff += (strlen($word) - $len); + } + + return $matches; + } + + + /** + * Returns the misspelled words + */ + private function _enchant_words($text = null, $is_html=false) + { + $result = array(); + + if ($text) { + // init spellchecker + $this->_enchant_init(); + + if (!$this->enchant_dictionary) { + return array(); + } + + // With Enchant we don't need to get suggestions to return misspelled words + if ($is_html) { + $text = $this->html2text($text); + } + + $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + + foreach ($text as $w) { + $word = trim($w[0]); + + // skip exceptions + if ($this->is_exception($word)) { + continue; + } + + if (!enchant_dict_check($this->enchant_dictionary, $word)) { + $result[] = $word; + } + } + + return $result; + } + + foreach ($this->matches as $m) { + $result[] = $m[0]; + } + + return $result; + } + + + /** + * Returns suggestions for misspelled word + */ + private function _enchant_suggestions($word) + { + // init spellchecker + $this->_enchant_init(); + + if (!$this->enchant_dictionary) { + return array(); + } + + $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word); + + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + + return is_array($suggestions) ? $suggestions : array(); + } + + + /** + * Initializes PSpell dictionary + */ + private function _enchant_init() + { + if (!$this->enchant_broker) { + if (!extension_loaded('enchant')) { + $this->error = "Enchant extension not available"; + return; + } + + $this->enchant_broker = enchant_broker_init(); + } + + if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) { + $this->error = "Unable to load dictionary for selected language using Enchant"; + return; + } + + $this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang); + } + + private function _googie_check($text) { // spell check uri is configured @@ -372,9 +511,19 @@ fclose($fp); } + // parse HTTP response + if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) { + $http_status = $m[1]; + if ($http_status != '200') + $this->error = 'HTTP ' . $m[1] . $m[2]; + } + if (!$store) { $this->error = "Empty result from spelling engine"; } + else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m) && $m[1]) { + $this->error = "Error code $m[1] returned"; + } preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER); @@ -588,7 +737,7 @@ if (empty($plugin['abort'])) { $dict = array(); - $this->rc->db->query( + $sql_result = $this->rc->db->query( "SELECT data FROM ".$this->rc->db->table_name('dictionary') ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_storage.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_storage.php
Changed
@@ -61,8 +61,6 @@ 'MAIL-FOLLOWUP-TO', 'MAIL-REPLY-TO', 'RETURN-PATH', - 'DELIVERED-TO', - 'ENVELOPE-TO', ); const UNKNOWN = 0; @@ -540,12 +538,13 @@ /** * Append a mail message (source) to a specific folder. * - * @param string $folder Target folder - * @param string $message The message source string or filename - * @param string $headers Headers string if $message contains only the body - * @param boolean $is_file True if $message is a filename - * @param array $flags Message flags - * @param mixed $date Message internal date + * @param string $folder Target folder + * @param string|array $message The message source string or filename + * or array (of strings and file pointers) + * @param string $headers Headers string if $message contains only the body + * @param boolean $is_file True if $message is a filename + * @param array $flags Message flags + * @param mixed $date Message internal date * * @return int|bool Appended message UID or True on success, False on error */ @@ -807,13 +806,14 @@ /** - * Returns current status of a folder + * Returns current status of a folder (compared to the last time use) * * @param string $folder Folder name + * @param array $diff Difference data * * @return int Folder status */ - abstract function folder_status($folder = null); + abstract function folder_status($folder = null, &$diff = array()); /** @@ -985,6 +985,6 @@ /** * Delete outdated cache entries */ - abstract function expunge_cache(); + abstract function cache_gc(); } // end class rcube_storage
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_string_replacer.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_string_replacer.php
Changed
@@ -28,22 +28,25 @@ public $mailto_pattern; public $link_pattern; private $values = array(); + private $options = array(); - function __construct() + function __construct($options = array()) { // Simplified domain expression for UTF8 characters handling // Support unicode/punycode in top-level domain part $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})'; $url1 = '.:;,'; - $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-'; + $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*-'; - $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/"; + $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]*[$url2]+)*)/"; $this->mailto_pattern = "/(" ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part ."@$utf_domain" // domain-part ."(\?[$url1$url2]+)?" // e.g. ?subject=test... .")/"; + + $this->options = $options; } /** @@ -89,15 +92,15 @@ if ($url) { $suffix = $this->parse_url_brackets($url); - $i = $this->add($prefix . html::a(array( - 'href' => $url_prefix . $url, - 'target' => '_blank' - ), rcube::Q($url)) . $suffix); + $attrib = (array)$this->options['link_attribs']; + $attrib['href'] = $url_prefix . $url; + + $i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix); } // Return valid link for recognized schemes, otherwise // return the unmodified string for unrecognized schemes. - return $i >= 0 ? $this->get_replacement($i) : $matches[0]; + return $i >= 0 ? $prefix . $this->get_replacement($i) : $matches[0]; } /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_user.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_user.php
Changed
@@ -495,9 +495,9 @@ "INSERT INTO ".$dbh->table_name('users'). " (created, last_login, username, mail_host, language)". " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)", - strip_newlines($data['user']), - strip_newlines($data['host']), - strip_newlines($data['language'])); + $data['user'], + $data['host'], + $data['language']); if ($user_id = $dbh->insert_id('users')) { // create rcube_user instance to make plugin hooks work @@ -517,7 +517,7 @@ if (empty($user_email)) { $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain); } - $email_list[] = strip_newlines($user_email); + $email_list[] = $user_email; } // identities_level check else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) { @@ -547,7 +547,6 @@ $record['name'] = $user_name != $record['email'] ? $user_name : ''; } - $record['name'] = strip_newlines($record['name']); $record['user_id'] = $user_id; $record['standard'] = $standard;
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_utils.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_utils.php
Changed
@@ -156,7 +156,7 @@ { // IPv6, but there's no build-in IPv6 support if (strpos($ip, ':') !== false && !defined('AF_INET6')) { - $parts = explode(':', $domain_part); + $parts = explode(':', $ip); $count = count($parts); if ($count > 8 || $count < 2) { @@ -360,12 +360,8 @@ return $value; } - // strip single quotes if magic_quotes_sybase is enabled - if (ini_get('magic_quotes_sybase')) { - $value = str_replace("''", "'", $value); - } // strip slashes if magic_quotes enabled - else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) { + if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) { $value = stripslashes($value); } @@ -404,7 +400,7 @@ $out = array(); $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST); - foreach ($src as $key => $value) { + foreach (array_keys($src) as $key) { $fname = $key[0] == '_' ? substr($key, 1) : $key; if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) { $out[$fname] = self::get_input_value($key, $mode); @@ -510,17 +506,24 @@ */ public static function file2class($mimetype, $filename) { + $mimetype = strtolower($mimetype); + $filename = strtolower($filename); + list($primary, $secondary) = explode('/', $mimetype); $classes = array($primary ? $primary : 'unknown'); + if ($secondary) { $classes[] = $secondary; } - if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) { - $classes[] = $m[1]; + + if (preg_match('/\.([a-z0-9]+)$/', $filename, $m)) { + if (!in_array($m[1], $classes)) { + $classes[] = $m[1]; + } } - return strtolower(join(" ", $classes)); + return join(" ", $classes); } @@ -663,6 +666,21 @@ /** + * Returns the real remote IP address + * + * @return string Remote IP address + */ + public static function remote_addr() + { + foreach (array('HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','REMOTE_ADDR') as $prop) { + if (!empty($_SERVER[$prop])) + return $_SERVER[$prop]; + } + + return ''; + } + + /** * Read a specific HTTP request header. * * @param string $name Header name @@ -726,11 +744,23 @@ return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1])); } else if (is_numeric($date)) { - return $date; + return (int) $date; } - // support non-standard "GMTXXXX" literal - $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); + // Clean malformed data + $date = preg_replace( + array( + '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal + '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters + '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names + ), + array( + '\\1', + '', + '', + ), $date); + + $date = trim($date); // if date parsing fails, we have a date in non-rfc format. // remove token from the end and try again @@ -743,7 +773,7 @@ $date = implode(' ', $d); } - return $ts; + return (int) $ts; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_vcard.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_vcard.php
Changed
@@ -90,7 +90,7 @@ */ public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array()) { - if (!empty($fielmap)) { + if (!empty($fieldmap)) { $this->extend_fieldmap($fieldmap); } @@ -481,7 +481,7 @@ $vcard_block = ''; $in_vcard_block = false; - foreach (preg_split("/[\r\n]+/", $data) as $i => $line) { + foreach (preg_split("/[\r\n]+/", $data) as $line) { if ($in_vcard_block && !empty($line)) { $vcard_block .= $line . "\n"; } @@ -491,7 +491,9 @@ if (preg_match('/^END:VCARD$/i', $line)) { // parse vcard $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); - if (!empty($obj->displayname) || !empty($obj->email)) { + // FN and N is required by vCard format (RFC 2426) + // on import we can be less restrictive, let's addressbook decide + if (!empty($obj->displayname) || !empty($obj->surname) || !empty($obj->firstname) || !empty($obj->email)) { $out[] = $obj; } @@ -513,7 +515,7 @@ * * @return string Cleaned vcard block */ - private static function cleanup($vcard) + public static function cleanup($vcard) { // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;) $vcard = preg_replace( @@ -712,9 +714,15 @@ $value[] = $attrvalues; } else if (is_bool($attrvalues)) { - // true means just tag, not tag=value, as in PHOTO;BASE64:... + // true means just a tag, not tag=value, as in PHOTO;BASE64:... if ($attrvalues) { - $attr .= strtoupper(";$attrname"); + // vCard v3 uses ENCODING=B (#1489183) + if ($attrname == 'base64') { + $attr .= ";ENCODING=B"; + } + else { + $attr .= strtoupper(";$attrname"); + } } } else { @@ -782,9 +790,30 @@ } return $result; } + + $s = strtr($s, $rep2); + } + + // some implementations (GMail) use non-standard backslash before colon (#1489085) + // we will handle properly any backslashed character - removing dummy backslahes + // return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';')); + + $s = str_replace("\r", '', $s); + $pos = 0; + + while (($pos = strpos($s, '\\', $pos)) !== false) { + $next = substr($s, $pos + 1, 1); + if ($next == 'n' || $next == 'N') { + $s = substr_replace($s, "\n", $pos, 2); + } + else { + $s = substr_replace($s, '', $pos, 1); + } + + $pos += 1; } - return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';')); + return $s; } /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Roundcube/rcube_washtml.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Roundcube/rcube_washtml.php
Changed
@@ -113,10 +113,9 @@ 'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value' ); - /* Block elements which could be empty but cannot be returned in short form (<tag />) */ - static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center', - 'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', - 'i', 'b', 'u', 'span', + /* Elements which could be empty and be returned in short form (<tag />) */ + static $void_elements = array('area', 'base', 'br', 'col', 'command', 'embed', 'hr', + 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ); /* State for linked objects in HTML */ @@ -134,12 +133,15 @@ /* Ignore these HTML tags but process their content */ private $_ignore_elements = array(); - /* Block elements which could be empty but cannot be returned in short form (<tag />) */ - private $_block_elements = array(); + /* Elements which could be empty and be returned in short form (<tag />) */ + private $_void_elements = array(); /* Allowed HTML attributes */ private $_html_attribs = array(); + /* Max nesting level */ + private $max_nesting_level; + /** * Class constructor @@ -149,9 +151,9 @@ $this->_html_elements = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ; $this->_html_attribs = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs); $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements); - $this->_block_elements = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements); + $this->_void_elements = array_flip((array)$p['void_elements']) + array_flip(self::$void_elements); - unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']); + unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['void_elements']); $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array()); } @@ -240,7 +242,8 @@ $value = $node->getAttribute($key); if (isset($this->_html_attribs[$key]) || - ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value) + ($key == 'href' && ($value = trim($value)) + && !preg_match('!^(javascript|vbscript|data:text)!i', $value) && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value)) ) { $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"'; @@ -283,12 +286,26 @@ * It output only allowed tags with allowed attributes * and allowed inline styles */ - private function dumpHtml($node) + private function dumpHtml($node, $level = 0) { if (!$node->hasChildNodes()) { return ''; } + $level++; + + if ($this->max_nesting_level > 0 && $level == $this->max_nesting_level - 1) { + // log error message once + if (!$this->max_nesting_level_error) { + $this->max_nesting_level_error = true; + rcube::raise_error(array('code' => 500, 'type' => 'php', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Maximum nesting level exceeded (xdebug.max_nesting_level={$this->max_nesting_level})"), + true, false); + } + return '<!-- ignored -->'; + } + $node = $node->firstChild; $dump = ''; @@ -298,19 +315,19 @@ $tagName = strtolower($node->tagName); if ($callback = $this->handlers[$tagName]) { $dump .= call_user_func($callback, $tagName, - $this->wash_attribs($node), $this->dumpHtml($node), $this); + $this->wash_attribs($node), $this->dumpHtml($node, $level), $this); } else if (isset($this->_html_elements[$tagName])) { - $content = $this->dumpHtml($node); + $content = $this->dumpHtml($node, $level); $dump .= '<' . $tagName . $this->wash_attribs($node) . - ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />'); + ($content === '' && isset($this->_void_elements[$tagName]) ? ' />' : ">$content</$tagName>"); } else if (isset($this->_ignore_elements[$tagName])) { $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->'; } else { $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' ignored -->'; - $dump .= $this->dumpHtml($node); // ignore tags not its content + $dump .= $this->dumpHtml($node, $level); // ignore tags not its content } break; @@ -323,14 +340,14 @@ break; case XML_HTML_DOCUMENT_NODE: - $dump .= $this->dumpHtml($node); + $dump .= $this->dumpHtml($node, $level); break; case XML_DOCUMENT_TYPE_NODE: break; default: - $dump . '<!-- node type ' . $node->nodeType . ' -->'; + $dump .= '<!-- node type ' . $node->nodeType . ' -->'; } } while($node = $node->nextSibling); @@ -357,6 +374,9 @@ $this->config['base_url'] = ''; } + // Detect max nesting level (for dumpHTML) (#1489110) + $this->max_nesting_level = (int) @ini_get('xdebug.max_nesting_level'); + @$node->loadHTML($html); return $this->dumpHtml($node); } @@ -390,6 +410,25 @@ ); $html = preg_replace($html_search, $html_replace, trim($html)); + //-> Replace all of those weird MS Word quotes and other high characters + $badwordchars=array( + "\xe2\x80\x98", // left single quote + "\xe2\x80\x99", // right single quote + "\xe2\x80\x9c", // left double quote + "\xe2\x80\x9d", // right double quote + "\xe2\x80\x94", // em dash + "\xe2\x80\xa6" // elipses + ); + $fixedwordchars=array( + "'", + "'", + '"', + '"', + '—', + '...' + ); + $html = str_replace($badwordchars,$fixedwordchars, $html); + // PCRE errors handling (#1486856), should we use something like for every preg_* use? if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) { $errstr = "Could not clean up HTML message! PCRE Error: $preg_error."; @@ -404,6 +443,7 @@ rcube::raise_error(array('code' => 620, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $errstr), true, false); + return ''; } @@ -412,7 +452,8 @@ // Remove invalid HTML comments (#1487759) // Don't remove valid conditional comments - $html = preg_replace('/<!--[^->[\n]*>/', '', $html); + // Don't remove MSOutlook (<!-->) conditional comments (#1489004) + $html = preg_replace('/<!--[^->\[\n]+>/', '', $html); // turn relative into absolute urls $html = self::resolve_base($html);
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Backend/ABackend.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Backend/ABackend.php
Changed
@@ -13,7 +13,6 @@ * * @package Backend */ - abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend { /** @@ -31,6 +30,12 @@ protected $_modelInterfaceName; + /** + * the constructor + * + * @param Zend_Db_Adapter_Abstract $_db + * @param string $_tablePrefix + */ public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_') { $this->_db = $_db; @@ -58,6 +63,12 @@ return $this->get($data['id']); } + /** + * convert iteratable object to array + * + * @param unknown $model + * @return array + */ protected function _convertModelToArray($model) { $data = array(); @@ -99,6 +110,12 @@ return $this->_getObject($data); } + /** + * convert array to object + * + * @param array $data + * @return object + */ protected function _getObject($data) { foreach ($data as $key => $value) { @@ -114,6 +131,10 @@ return new $this->_modelClassName($data); } + /** + * (non-PHPdoc) + * @see Syncroton_Backend_IBackend::delete() + */ public function delete($id) { $id = $id instanceof $this->_modelInterfaceName ? $id->id : $id; @@ -123,6 +144,10 @@ return (bool) $result; } + /** + * (non-PHPdoc) + * @see Syncroton_Backend_IBackend::update() + */ public function update($model) { if (! $model instanceof $this->_modelInterfaceName) { @@ -138,6 +163,11 @@ return $this->get($model->id); } + /** + * convert from camelCase to camel_case + * @param string $string + * @return string + */ protected function _fromCamelCase($string) { $string = lcfirst($string); @@ -145,6 +175,13 @@ return preg_replace_callback('/([A-Z])/', function ($string) {return '_' . strtolower($string[0]);}, $string); } + /** + * convert from camel_case to camelCase + * + * @param string $string + * @param bool $ucFirst + * @return string + */ protected function _toCamelCase($string, $ucFirst = true) { if ($ucFirst === true) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Backend/Policy.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Backend/Policy.php
Changed
@@ -21,4 +21,50 @@ protected $_modelClassName = 'Syncroton_Model_Policy'; protected $_modelInterfaceName = 'Syncroton_Model_IPolicy'; + + /** + * convert iteratable object to array + * + * @param unknown $model + * @return array + */ + protected function _convertModelToArray($model) + { + $policyValues = $model->getProperties('Provision'); + + $policy = array(); + + foreach ($policyValues as $policyName) { + if ($model->$policyName !== NULL) { + $policy[$policyName] = $model->$policyName; + } + + unset($model->$policyName); + } + + $data = parent::_convertModelToArray($model); + + $data['json_policy'] = Zend_Json::encode($policy); + + return $data; + } + + /** + * convert array to object + * + * @param array $data + * @return object + */ + protected function _getObject($data) + { + $policy = Zend_Json::decode($data['json_policy']); + + foreach ($policy as $policyKey => $policyValue) { + $data[$policyKey] = $policyValue; + } + + unset($data['json_policy']); + + return parent::_getObject($data); + } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/FolderSync.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/FolderSync.php
Changed
@@ -121,7 +121,8 @@ $this->_headers = array_merge($this->_headers, $optionsCommand->getHeaders()); } - $adds = array(); + $adds = array(); + $updates = array(); $deletes = array(); foreach($this->_classes as $class) { @@ -138,6 +139,13 @@ // retrieve all folders available in data backend $serverFolders = $dataController->getAllFolders(); + if ($this->_syncState->counter > 0) { + // retrieve all folders changed since last sync + $changedFolders = $dataController->getChangedFolders($this->_syncState->lastsync, $this->_syncTimeStamp); + } else { + $changedFolders = array(); + } + // retrieve all folders sent to client $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class); @@ -186,6 +194,17 @@ $adds[] = $add; } + // calculate changed entries + foreach ($changedFolders as $changedFolder) { + $change = $clientFolders[$changedFolder->serverId]; + + $change->displayName = $changedFolder->displayName; + $change->parentId = $changedFolder->parentId; + $change->type = $changedFolder->type; + + $updates[] = $change; + } + // calculate deleted entries $serverDiff = array_diff($clientFoldersIds, $serverFoldersIds); foreach ($serverDiff as $serverFolderId) { @@ -195,7 +214,7 @@ $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS)); - $count = count($adds) + /*count($changes) + */count($deletes); + $count = count($adds) + count($updates) + count($deletes); if($count > 0) { $this->_syncState->counter++; $this->_syncState->lastsync = $this->_syncTimeStamp; @@ -218,6 +237,14 @@ } } + foreach($updates as $folder) { + $update = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Update')); + + $folder->appendXML($update, $this->_device); + + $this->_folderBackend->update($folder); + } + foreach($deletes as $folder) { $delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete')); $delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->serverId));
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/GetItemEstimate.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/GetItemEstimate.php
Changed
@@ -137,7 +137,7 @@ } // folderState can be NULL in case of not existing folder - if (isset($collectionData['folder'])) { + if (isset($collectionData['folder']) && $collectionData['folder']->isDirty()) { $this->_folderBackend->update($collectionData['folder']); } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/ItemOperations.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/ItemOperations.php
Changed
@@ -34,6 +34,13 @@ protected $_fetches = array(); /** + * list of folder to empty + * + * @var array + */ + protected $_emptyFolderContents = array(); + + /** * parse MoveItems request * */ @@ -43,59 +50,13 @@ if (isset($xml->Fetch)) { foreach ($xml->Fetch as $fetch) { - $fetchArray = array( - 'store' => (string)$fetch->Store, - 'options' => array() - ); - - // try to fetch element from namespace AirSync - $airSync = $fetch->children('uri:AirSync'); - - if (isset($airSync->CollectionId)) { - $fetchArray['collectionId'] = (string)$airSync->CollectionId; - $fetchArray['serverId'] = (string)$airSync->ServerId; - } - - // try to fetch element from namespace Search - $search = $fetch->children('uri:Search'); - - if (isset($search->LongId)) { - $fetchArray['longId'] = (string)$search->LongId; - } - - // try to fetch element from namespace AirSyncBase - $airSyncBase = $fetch->children('uri:AirSyncBase'); - - if (isset($airSyncBase->FileReference)) { - $fetchArray['fileReference'] = (string)$airSyncBase->FileReference; - } - - if (isset($fetch->Options)) { - // try to fetch element from namespace AirSyncBase - $airSyncBase = $fetch->Options->children('uri:AirSyncBase'); - - if (isset($airSyncBase->BodyPreference)) { - foreach ($airSyncBase->BodyPreference as $bodyPreference) { - $type = (int) $bodyPreference->Type; - $fetchArray['options']['bodyPreferences'][$type] = array( - 'type' => $type - ); - - // optional - if (isset($bodyPreference->TruncationSize)) { - $fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize; - } - - // optional - if (isset($bodyPreference->AllOrNone)) { - $fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone; - } - } - } - - - } - $this->_fetches[] = $fetchArray; + $this->_fetches[] = $this->_handleFetch($fetch); + } + } + + if (isset($xml->EmptyFolderContents)) { + foreach ($xml->EmptyFolderContents as $emptyFolderContents) { + $this->_emptyFolderContents[] = $this->_handleEmptyFolderContents($emptyFolderContents); } } @@ -187,6 +148,106 @@ } } + foreach ($this->_emptyFolderContents as $emptyFolderContents) { + + try { + $folder = $this->_folderBackend->getFolder($this->_device, $emptyFolderContents['collectionId']); + + $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp); + $dataController->emptyFolderContents($emptyFolderContents['collectionId'], $emptyFolderContents['options']); + + $status = Syncroton_Command_ItemOperations::STATUS_SUCCESS; + } + catch (Syncroton_Exception_Status_ItemOperations $e) { + $status = $e->getCode(); + } + catch (Exception $e) { + $status = Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR; + } + + $emptyFolderContentsTag = $this->_outputDom->createElementNS('uri:ItemOperations', 'EmptyFolderContents'); + + $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', $status)); + $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $emptyFolderContents['collectionId'])); + + $response->appendChild($emptyFolderContentsTag); + } + return $this->_outputDom; } + + protected function _handleFetch(SimpleXMLElement $fetch) + { + $fetchArray = array( + 'store' => (string)$fetch->Store, + 'options' => array() + ); + + // try to fetch element from namespace AirSync + $airSync = $fetch->children('uri:AirSync'); + + if (isset($airSync->CollectionId)) { + $fetchArray['collectionId'] = (string)$airSync->CollectionId; + $fetchArray['serverId'] = (string)$airSync->ServerId; + } + + // try to fetch element from namespace Search + $search = $fetch->children('uri:Search'); + + if (isset($search->LongId)) { + $fetchArray['longId'] = (string)$search->LongId; + } + + // try to fetch element from namespace AirSyncBase + $airSyncBase = $fetch->children('uri:AirSyncBase'); + + if (isset($airSyncBase->FileReference)) { + $fetchArray['fileReference'] = (string)$airSyncBase->FileReference; + } + + if (isset($fetch->Options)) { + // try to fetch element from namespace AirSyncBase + $airSyncBase = $fetch->Options->children('uri:AirSyncBase'); + + if (isset($airSyncBase->BodyPreference)) { + foreach ($airSyncBase->BodyPreference as $bodyPreference) { + $type = (int) $bodyPreference->Type; + $fetchArray['options']['bodyPreferences'][$type] = array( + 'type' => $type + ); + + // optional + if (isset($bodyPreference->TruncationSize)) { + $fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize; + } + + // optional + if (isset($bodyPreference->AllOrNone)) { + $fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone; + } + } + } + } + + return $fetchArray; + } + + protected function _handleEmptyFolderContents(SimpleXMLElement $emptyFolderContent) + { + $folderArray = array( + 'collectiondId' => null, + 'options' => array('deleteSubFolders' => FALSE) + ); + + // try to fetch element from namespace AirSync + $airSync = $emptyFolderContent->children('uri:AirSync'); + + $folderArray['collectionId'] = (string)$airSync->CollectionId; + + if (isset($emptyFolderContent->Options)) { + $folderArray['options']['deleteSubFolders'] = isset($emptyFolderContent->Options->DeleteSubFolders); + } + + return $folderArray; + } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/Ping.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/Ping.php
Changed
@@ -89,8 +89,13 @@ $intervalEnd = $intervalStart + $lifeTime; $secondsLeft = $intervalEnd; + $folders = unserialize($this->_device->pingfolder); + if ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) { + $status = self::STATUS_MISSING_PARAMETERS; + } + if ($this->_logger instanceof Zend_Log) $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true)); @@ -104,7 +109,7 @@ $now = new DateTime('now', new DateTimeZone('utc')); - foreach ((array) $folders as $folderId) { + foreach ($folders as $folderId) { try { $folder = $this->_folderBackend->get($folderId); $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/Provision.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/Provision.php
Changed
@@ -60,7 +60,6 @@ if ($this->_device->remotewipe == self::REMOTEWIPE_REQUESTED && isset($xml->RemoteWipe->Status) && (int)$xml->RemoteWipe->Status == self::STATUS_SUCCESS) { $this->_device->remotewipe = self::REMOTEWIPE_CONFIRMED; - $this->_device = $this->_deviceBackend->update($this->_device); } // try to fetch element from Settings namespace @@ -74,9 +73,10 @@ $this->_device->os = $this->_deviceInformation->oS; $this->_device->oslanguage = $this->_deviceInformation->oSLanguage; $this->_device->phonenumber = $this->_deviceInformation->phoneNumber; - - $this->_device = $this->_deviceBackend->update($this->_device); - + } + + if ($this->_device->isDirty()) { + $this->_device = $this->_deviceBackend->update($this->_device); } } @@ -194,6 +194,6 @@ */ public static function generatePolicyKey() { - return sha1(mt_rand(). microtime()); + return mt_rand(1, 2147483647); } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/Settings.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/Settings.php
Changed
@@ -54,8 +54,10 @@ $this->_device->os = $this->_deviceInformation->oS; $this->_device->oslanguage = $this->_deviceInformation->oSLanguage; $this->_device->phonenumber = $this->_deviceInformation->phoneNumber; - - $this->_device = $this->_deviceBackend->update($this->_device); + + if ($this->_device->isDirty()) { + $this->_device = $this->_deviceBackend->update($this->_device); + } } if(isset($xml->UserInformation->Get)) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/Sync.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/Sync.php
Changed
@@ -113,60 +113,60 @@ public function handle() { // input xml - $xml = simplexml_import_dom($this->_requestBody); + $requestXML = simplexml_import_dom($this->_mergeSyncRequest($this->_requestBody, $this->_device)); - if (isset($xml->HeartbeatInterval)) { - $this->_heartbeatInterval = (int)$xml->HeartbeatInterval; - } elseif (isset($xml->Wait)) { - $this->_heartbeatInterval = (int)$xml->Wait * 60; + if (! isset($requestXML->Collections)) { + $this->_outputDom->documentElement->appendChild( + $this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML) + ); + + return $this->_outputDom; } - $this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100; + if (isset($requestXML->HeartbeatInterval)) { + $this->_heartbeatInterval = (int)$requestXML->HeartbeatInterval; + } elseif (isset($requestXML->Wait)) { + $this->_heartbeatInterval = (int)$requestXML->Wait * 60; + } + + $this->_globalWindowSize = isset($requestXML->WindowSize) ? (int)$requestXML->WindowSize : 100; if ($this->_globalWindowSize > $this->_maxWindowSize) { $this->_globalWindowSize = $this->_maxWindowSize; } + + // load options from lastsynccollection + $lastSyncCollection = array('options' => array()); + if (!empty($this->_device->lastsynccollection)) { + $lastSyncCollection = Zend_Json::decode($this->_device->lastsynccollection); + if (!array_key_exists('options', $lastSyncCollection) || !is_array($lastSyncCollection['options'])) { + $lastSyncCollection['options'] = array(); + } + } $collections = array(); - $isPartialRequest = isset($xml->Partial); - // try to restore collections from previous request - if ($isPartialRequest) { - $decodedCollections = Zend_Json::decode($this->_device->lastsynccollection); + foreach ($requestXML->Collections->Collection as $xmlCollection) { + $collectionId = (string)$xmlCollection->CollectionId; - if (is_array($decodedCollections)) { - foreach ($decodedCollections as $collection) { - $collections[$collection['collectionId']] = new Syncroton_Model_SyncCollection($collection); - } - } - } - - // Collections element is optional when Partial element is sent - if (isset($xml->Collections)) { - foreach ($xml->Collections->Collection as $xmlCollection) { - $collectionId = (string)$xmlCollection->CollectionId; - - // do we have to update a collection sent in previous sync request? - if ($isPartialRequest && isset($collections[$collectionId])) { - $collections[$collectionId]->setFromSimpleXMLElement($xmlCollection); - } else { - $collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection); - } - } + $collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection); - // store current value of $collections for next Sync command request - $collectionsToSave = array(); - - foreach ($collections as $collection) { - $collectionsToSave[$collection->collectionId] = $collection->toArray(); + // do we have to reuse the options from the previous request? + if (!isset($xmlCollection->Options) && array_key_exists($collectionId, $lastSyncCollection['options'])) { + $collections[$collectionId]->options = $lastSyncCollection['options'][$collectionId]; + if ($this->_logger instanceof Zend_Log) + $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " restored options to " . print_r($collections[$collectionId]->options, TRUE)); } + + // store current options for next Sync command request (sticky options) + $lastSyncCollection['options'][$collectionId] = $collections[$collectionId]->options; + } - $this->_device->lastsynccollection = Zend_Json::encode($collectionsToSave); + $this->_device->lastsynccollection = Zend_Json::encode($lastSyncCollection); - if ($this->_device->isDirty()) { - Syncroton_Registry::getDeviceBackend()->update($this->_device); - } + if ($this->_device->isDirty()) { + Syncroton_Registry::getDeviceBackend()->update($this->_device); } foreach ($collections as $collectionData) { @@ -412,13 +412,7 @@ { $sync = $this->_outputDom->documentElement; - if (count($this->_collections) == 0) { - $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML)); - - return $this->_outputDom; - } - - $collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections')); + $collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections'); $totalChanges = 0; @@ -433,7 +427,7 @@ $now = new DateTime(null, new DateTimeZone('utc')); foreach($this->_collections as $collectionData) { - // countinue immediately if folder does not exist + // continue immediately if folder does not exist if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) { break 2; @@ -488,23 +482,20 @@ // safe battery time by skipping folders which got synchronied less than Syncroton_Command_Ping::$quietTime seconds ago - if (($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) { + if ( ! $collectionData->syncState instanceof Syncroton_Model_SyncState || + ($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) { continue; } $dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp); - $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState); - // countinue immediately if there are any changes available - if ($hasChanges) { + if($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) { break 2; } } } - $this->_syncTimeStamp = clone $now; - // See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146 // // break if there are less than PingTimeout + 10 seconds left for the next loop @@ -574,11 +565,21 @@ $serverModifications = $collectionData->syncState->pendingdata; - } else { + } elseif ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) { + + // update _syncTimeStamp as $dataController->hasChanges might have spent some time + $this->_syncTimeStamp = new DateTime(null, new DateTimeZone('utc')); + try { // fetch entries added since last sync - $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder); - $allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']); + $allClientEntries = $this->_contentStateBackend->getFolderState( + $this->_device, + $collectionData->folder + ); + $allServerEntries = $dataController->getServerEntries( + $collectionData->collectionId, + $collectionData->options['filterType'] + ); // add entries $serverDiff = array_diff($allServerEntries, $allClientEntries); @@ -601,7 +602,12 @@ $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries); // fetch entries changed since last sync - $serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp, $collectionData->options['filterType']); + $serverModifications['changed'] = $dataController->getChangedEntries( + $collectionData->collectionId, + $collectionData->syncState->lastsync, + $this->_syncTimeStamp, + $collectionData->options['filterType'] + ); $serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']); foreach($serverModifications['changed'] as $id => $serverId) { @@ -643,7 +649,7 @@ } // collection header - $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); + $collection = $this->_outputDom->createElementNS('uri:AirSync', 'Collection'); if (!empty($collectionData->folder->class)) { $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class)); } @@ -752,7 +758,7 @@ $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData')); - + $dataController ->getEntry($collectionData, $serverId) ->appendXML($applicationData, $this->_device); @@ -760,12 +766,20 @@ $commands->appendChild($add); $collectionChanges++; + } catch (Syncroton_Exception_MemoryExhausted $seme) { + // continue to next entry, as there is not enough memory left for the current entry + // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command + if ($this->_logger instanceof Zend_Log) + $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId); + + continue; + } catch (Exception $e) { - if ($this->_logger instanceof Zend_Log) + if ($this->_logger instanceof Zend_Log) $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } - // mark as send to the client, even the conversion to xml might have failed + // mark as sent to the client, even the conversion to xml might have failed $newContentStates[] = new Syncroton_Model_Content(array( 'device_id' => $this->_device, 'folder_id' => $collectionData->folder, @@ -798,6 +812,14 @@ $commands->appendChild($change); $collectionChanges++; + } catch (Syncroton_Exception_MemoryExhausted $seme) { + // continue to next entry, as there is not enough memory left for the current entry + // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command + if ($this->_logger instanceof Zend_Log) + $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId); + + continue; + } catch (Exception $e) { if ($this->_logger instanceof Zend_Log) $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); @@ -846,29 +868,35 @@ // increase SyncKey if needed if (( - // sent the clients updates? + // sent the clients updates... ? !empty($clientModifications['added']) || !empty($clientModifications['changed']) || !empty($clientModifications['deleted']) ) || ( - // sends the server updates to the client? + // is the server sending updates to the client... ? $commands->hasChildNodes() === true ) || ( - // changed the pending data? + // changed the pending data... ? $collectionData->syncState->pendingdata != $serverModifications ) ) { - // then increase SyncKey + // ...then increase SyncKey $collectionData->syncState->counter++; } $syncKeyElement->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter)); - if ($this->_logger instanceof Zend_Log) - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is ". $collectionData->syncState->counter); + if ($this->_logger instanceof Zend_Log) + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is ". $collectionData->syncState->counter); + + if ($collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) { + $collections->appendChild($collection); + } } - if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState && - $collectionData->syncState->counter != $collectionData->syncKey) { + if (isset($collectionData->syncState) && + $collectionData->syncState instanceof Syncroton_Model_ISyncState && + $collectionData->syncState->counter != $collectionData->syncKey + ) { if ($this->_logger instanceof Zend_Log) $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " update syncState for collection: " . $collectionData->collectionId); @@ -929,21 +957,144 @@ throw $zdse; } - - - // store current filter type - try { - $folderState = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId); - $folderState->lastfiltertype = $collectionData->options['filterType']; + } + + // store current filter type + try { + $folderState = $this->_folderBackend->get($collectionData->folder); + $folderState->lastfiltertype = $collectionData->options['filterType']; + if ($folderState->isDirty()) { $this->_folderBackend->update($folderState); - } catch (Syncroton_Exception_NotFound $senf) { - // failed to get folderstate => should not happen but is also no problem in this state - if ($this->_logger instanceof Zend_Log) - $this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData->collectionId); } + } catch (Syncroton_Exception_NotFound $senf) { + // failed to get folderstate => should not happen but is also no problem in this state + if ($this->_logger instanceof Zend_Log) + $this->_logger->warn(__METHOD__ . '::' . __LINE__ . ' failed to get folder state for: ' . $collectionData->collectionId); } } - return $this->_outputDom; + if ($collections->hasChildNodes() === true) { + $sync->appendChild($collections); + } + + if ($sync->hasChildNodes()) { + return $this->_outputDom; + } + + return null; + } + + /** + * remove Commands and Supported from collections XML tree + * + * @param DOMDocument $document + * @return DOMDocument + */ + protected function _cleanUpXML(DOMDocument $document) + { + $cleanedDocument = clone $document; + + $xpath = new DomXPath($cleanedDocument); + $xpath->registerNamespace('AirSync', 'uri:AirSync'); + + $collections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection"); + + // remove Commands and Supported elements + foreach ($collections as $collection) { + foreach (array('Commands', 'Supported') as $element) { + $childrenToRemove = $collection->getElementsByTagName($element); + + foreach ($childrenToRemove as $childToRemove) { + $collection->removeChild($childToRemove); + } + } + } + + return $cleanedDocument; + } + + /** + * merge a partial XML document with the XML document from the previous request + * + * @param DOMDocument|null $requestBody + * @return SimpleXMLElement + */ + protected function _mergeSyncRequest($requestBody, Syncroton_Model_Device $device) + { + $lastSyncCollection = array(); + + if (!empty($device->lastsynccollection)) { + $lastSyncCollection = Zend_Json::decode($device->lastsynccollection); + if (!empty($lastSyncCollection['lastXML'])) { + $lastXML = new DOMDocument(); + $lastXML->loadXML($lastSyncCollection['lastXML']); + } + } + + if (! $requestBody instanceof DOMDocument && isset($lastXML) && $lastXML instanceof DOMDocument) { + $requestBody = $lastXML; + } elseif (! $requestBody instanceof DOMDocument) { + throw new Syncroton_Exception_UnexpectedValue('no xml body found'); + } + + if ($requestBody->getElementsByTagName('Partial')->length > 0) { + $partialBody = clone $requestBody; + $requestBody = $lastXML; + + $xpath = new DomXPath($requestBody); + $xpath->registerNamespace('AirSync', 'uri:AirSync'); + + foreach ($partialBody->documentElement->childNodes as $child) { + if (! $child instanceof DOMElement) { + continue; + } + + if ($child->tagName == 'Partial') { + continue; + } + + if ($child->tagName == 'Collections') { + foreach ($child->getElementsByTagName('Collection') as $updatedCollection) { + $collectionId = $updatedCollection->getElementsByTagName('CollectionId')->item(0)->nodeValue; + + $existingCollections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection[AirSync:CollectionId='$collectionId']"); + + if ($existingCollections->length > 0) { + $existingCollection = $existingCollections->item(0); + foreach ($updatedCollection->childNodes as $updatedCollectionChild) { + if (! $updatedCollectionChild instanceof DOMElement) { + continue; + } + + $duplicateChild = $existingCollection->getElementsByTagName($updatedCollectionChild->tagName); + + if ($duplicateChild->length > 0) { + $existingCollection->replaceChild($requestBody->importNode($updatedCollectionChild, TRUE), $duplicateChild->item(0)); + } else { + $existingCollection->appendChild($requestBody->importNode($updatedCollectionChild, TRUE)); + } + } + } else { + $importedCollection = $requestBody->importNode($updatedCollection, TRUE); + } + } + + } else { + $duplicateChild = $xpath->query("//AirSync:Sync/AirSync:{$child->tagName}"); + + if ($duplicateChild->length > 0) { + $requestBody->documentElement->replaceChild($requestBody->importNode($child, TRUE), $duplicateChild->item(0)); + } else { + $requestBody->documentElement->appendChild($requestBody->importNode($child, TRUE)); + } + } + } + } + + $lastSyncCollection['lastXML'] = $this->_cleanUpXML($requestBody)->saveXML(); + + $device->lastsynccollection = Zend_Json::encode($lastSyncCollection); + + return $requestBody; } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Command/Wbxml.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Command/Wbxml.php
Changed
@@ -180,7 +180,7 @@ if (!empty($this->_device->policyId)) { $policy = $this->_policyBackend->get($this->_device->policyId); - if($policy->policyKey != $this->_policyKey) { + if((int)$policy->policyKey != (int)$this->_policyKey) { $this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 142)); $sepn = new Syncroton_Exception_ProvisioningNeeded();
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Data/AData.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Data/AData.php
Changed
@@ -23,6 +23,17 @@ */ public static $changedEntries = array(); + /** + * used by unit tests only to simulated exhausted memory + */ + public static $exhaustedEntries = array(); + + /** + * the constructor + * + * @param Syncroton_Model_IDevice $_device + * @param DateTime $_timeStamp + */ public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp) { $this->_device = $_device; @@ -32,6 +43,13 @@ $this->_ownerId = '1234'; } + /** + * return one folder identified by id + * + * @param string $id + * @throws Syncroton_Exception_NotFound + * @return Syncroton_Model_Folder + */ public function getFolder($id) { $select = $this->_db->select() @@ -55,6 +73,10 @@ )); } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::createFolder() + */ public function createFolder(Syncroton_Model_IFolder $folder) { if (!in_array($folder->type, $this->_supportedFolderTypes)) { @@ -64,16 +86,21 @@ $id = !empty($folder->serverId) ? $folder->serverId : sha1(mt_rand(). microtime()); $this->_db->insert($this->_tablePrefix . 'data_folder', array( - 'id' => $id, - 'type' => $folder->type, - 'name' => $folder->displayName, - 'owner_id' => $this->_ownerId, - 'parent_id' => $folder->parentId + 'id' => $id, + 'type' => $folder->type, + 'name' => $folder->displayName, + 'owner_id' => $this->_ownerId, + 'parent_id' => $folder->parentId, + 'creation_time' => $this->_timestamp->format('Y-m-d H:i:s') )); return $this->getFolder($id); } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::createEntry() + */ public function createEntry($_folderId, Syncroton_Model_IEntry $_entry) { $id = sha1(mt_rand(). microtime()); @@ -88,6 +115,10 @@ return $id; } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::deleteEntry() + */ public function deleteEntry($_folderId, $_serverId, $_collectionData) { $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId; @@ -97,6 +128,10 @@ return (bool) $result; } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::deleteFolder() + */ public function deleteFolder($_folderId) { $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId; @@ -107,6 +142,19 @@ return (bool) $result; } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::emptyFolderContents() + */ + public function emptyFolderContents($folderId, $options) + { + return true; + } + + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::getAllFolders() + */ public function getAllFolders() { $select = $this->_db->select() @@ -132,6 +180,10 @@ return $result; } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::getChangedEntries() + */ public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL, $filterType = NULL) { if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) { @@ -142,6 +194,40 @@ } /** + * retrieve folders which were modified since last sync + * + * @param DateTime $startTimeStamp + * @param DateTime $endTimeStamp + * @return array list of Syncroton_Model_Folder + */ + public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp) + { + $select = $this->_db->select() + ->from($this->_tablePrefix . 'data_folder') + ->where('type IN (?)', $this->_supportedFolderTypes) + ->where('owner_id = ?', $this->_ownerId) + ->where('last_modified_time > ?', $startTimeStamp->format('Y-m-d H:i:s')) + ->where('last_modified_time <= ?', $endTimeStamp->format('Y-m-d H:i:s')); + + $stmt = $this->_db->query($select); + $folders = $stmt->fetchAll(); + $stmt = null; # see https://bugs.php.net/bug.php?id=44081 + + $result = array(); + + foreach ((array) $folders as $folder) { + $result[$folder['id']] = new Syncroton_Model_Folder(array( + 'serverId' => $folder['id'], + 'displayName' => $folder['name'], + 'type' => $folder['type'], + 'parentId' => $folder['parent_id'] + )); + } + + return $result; + } + + /** * @param Syncroton_Model_IFolder|string $_folderId * @param string $_filter * @return array @@ -164,6 +250,10 @@ return $ids; } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::getCountOfChanges() + */ public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState) { $allClientEntries = $contentBackend->getFolderState($this->_device, $folder); @@ -176,6 +266,10 @@ return count($addedEntries) + count($deletedEntries) + count($changedEntries); } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::getFileReference() + */ public function getFileReference($fileReference) { throw new Syncroton_Exception_NotFound('filereference not found'); @@ -186,7 +280,10 @@ * @see Syncroton_Data_IData::getEntry() */ public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId) - { + { + if (isset(self::$exhaustedEntries[get_class($this)]) && is_array(self::$exhaustedEntries[get_class($this)]) && in_array($serverId, self::$exhaustedEntries[get_class($this)])) { + throw new Syncroton_Exception_MemoryExhausted('memory exchausted for ' . $serverId); + } $select = $this->_db->select() ->from($this->_tablePrefix . 'data', array('data')) ->where('id = ?', $serverId); @@ -210,6 +307,10 @@ return !!$this->getCountOfChanges($contentBackend, $folder, $syncState); } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::moveItem() + */ public function moveItem($_srcFolderId, $_serverId, $_dstFolderId) { $this->_db->update($this->_tablePrefix . 'data', array( @@ -221,6 +322,10 @@ return $_serverId; } + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::updateEntry() + */ public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry) { $this->_db->update($this->_tablePrefix . 'data', array( @@ -230,15 +335,23 @@ 'id = ?' => $_serverId )); } - + + /** + * (non-PHPdoc) + * @see Syncroton_Data_IData::updateFolder() + */ public function updateFolder(Syncroton_Model_IFolder $folder) { $this->_db->update($this->_tablePrefix . 'data_folder', array( - 'name' => $folder->displayName, - 'parent_id' => $folder->parentId + 'name' => $folder->displayName, + 'parent_id' => $folder->parentId, + 'last_modified_time' => $this->_timestamp->format('Y-m-d H:i:s') ), array( - 'id = ?' => $folder->serverId + 'id = ?' => $folder->serverId, + 'owner_id = ?' => $this->_ownerId )); + + return $this->getFolder($folder->serverId); } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Data/IData.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Data/IData.php
Changed
@@ -43,7 +43,20 @@ */ public function deleteEntry($_folderId, $_serverId, $_collectionData); - public function deleteFolder($_folderId); + /** + * delete folder + * + * @param string $folderId + */ + public function deleteFolder($folderId); + + /** + * empty folder + * + * @param string $folderId + * @param array $options + */ + public function emptyFolderContents($folderId, $options); /** * return list off all folders @@ -53,6 +66,14 @@ public function getChangedEntries($folderId, DateTime $startTimeStamp, DateTime $endTimeStamp = NULL, $filterType = NULL); + /** + * retrieve folders which were modified since last sync + * + * @param DateTime $startTimeStamp + * @param DateTime $endTimeStamp + */ + public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp); + public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState); /**
View file
kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Exception/MemoryExhausted.php
Added
@@ -0,0 +1,20 @@ +<?php +/** + * Syncroton + * + * @package Syncroton + * @subpackage Exception + * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3 + * @copyright Copyright (c) 2013-2013 Metaways Infosystems GmbH (http://www.metaways.de) + * @author Lars Kneschke <l.kneschke@metaways.de> + */ + +/** + * exception for memory exhausted + * + * @package Syncroton + * @subpackage Exception + */ +class Syncroton_Exception_MemoryExhausted extends Syncroton_Exception +{ +}
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/AXMLEntry.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/AXMLEntry.php
Changed
@@ -93,11 +93,14 @@ * (non-PHPdoc) * @see Syncroton_Model_IEntry::getProperties() */ - public function getProperties() + public function getProperties($selectedNamespace = null) { $properties = array(); foreach($this->_properties as $namespace => $namespaceProperties) { + if ($selectedNamespace !== null && $namespace != $selectedNamespace) { + continue; + } $properties = array_merge($properties, array_keys($namespaceProperties)); } @@ -169,7 +172,8 @@ if (!ctype_print($value)) { $value = $this->_removeControlChars($value); } - $element->appendChild($element->ownerDocument->createTextNode($value)); + + $element->appendChild($element->ownerDocument->createTextNode($this->_enforeUTF8($value))); } } } @@ -186,6 +190,29 @@ } /** + * enforce >valid< utf-8 encoding + * + * @param string $dirty the string with maybe invalid utf-8 data + * @return string string with valid utf-8 + */ + protected function _enforeUTF8($dirty) + { + if (function_exists('iconv')) { + if (($clean = @iconv('UTF-8', 'UTF-8//IGNORE', $dirty)) !== false) { + return $clean; + } + } + + if (function_exists('mb_convert_encoding')) { + if (($clean = mb_convert_encoding($dirty, 'UTF-8', 'UTF-8')) !== false) { + return $clean; + } + } + + return $dirty; + } + + /** * * @param unknown_type $element * @throws InvalidArgumentException
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/Content.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/Content.php
Changed
@@ -15,18 +15,7 @@ * @package Syncroton * @subpackage Model */ -class Syncroton_Model_Content implements Syncroton_Model_IContent +class Syncroton_Model_Content extends Syncroton_Model_AEntry implements Syncroton_Model_IContent { - public function __construct(array $_data = array()) - { - $this->setFromArray($_data); - } - - public function setFromArray(array $_data) - { - foreach($_data as $key => $value) { - $this->$key = $value; - } - } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/Event.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/Event.php
Changed
@@ -65,32 +65,45 @@ ) ); - public function setFromArray(array $properties) - { - parent::setFromArray($properties); - - $this->_copyFieldsFromParent(); - } - - /** - * set properties from SimpleXMLElement object - * - * @param SimpleXMLElement $xmlCollection - * @throws InvalidArgumentException - */ - public function setFromSimpleXMLElement(SimpleXMLElement $properties) + /** + * (non-PHPdoc) + * @see Syncroton_Model_IEntry::appendXML() + * @todo handle Attendees element + */ + public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device) { - parent::setFromSimpleXMLElement($properties); - - $this->_copyFieldsFromParent(); + parent::appendXML($domParrent, $device); + + $exceptionElements = $domParrent->getElementsByTagName('Exception'); + $parentFields = array('AllDayEvent'/*, 'Attendees'*/, 'Body', 'BusyStatus'/*, 'Categories'*/, 'DtStamp', 'EndTime', 'Location', 'MeetingStatus', 'Reminder', 'ResponseType', 'Sensitivity', 'StartTime', 'Subject'); + + if ($exceptionElements->length > 0) { + $mainEventElement = $exceptionElements->item(0)->parentNode->parentNode; + + foreach ($mainEventElement->childNodes as $childNode) { + if (in_array($childNode->localName, $parentFields)) { + foreach ($exceptionElements as $exception) { + $elementsToLeftOut = $exception->getElementsByTagName($childNode->localName); + + foreach ($elementsToLeftOut as $elementToLeftOut) { + if ($elementToLeftOut->nodeValue == $childNode->nodeValue) { + $exception->removeChild($elementToLeftOut); + } + } + } + } + } + } } /** - * copy some fileds of the main event to the exception if they are missing - * these fields can be left out, if they have the same value in the main event - * and the exception + * some elements of an exception can be left out, if they have the same value + * like the main event + * + * this function copies these elements to the exception for backends which need + * this elements in the exceptions too. Tine 2.0 needs this for example. */ - protected function _copyFieldsFromParent() + public function copyFieldsFromParent() { if (isset($this->_elements['exceptions']) && is_array($this->_elements['exceptions'])) { foreach ($this->_elements['exceptions'] as $exception) { @@ -99,7 +112,7 @@ continue; } - $parentFields = array('allDayEvent', 'attendees', 'busyStatus', 'meetingStatus', 'sensitivity', 'subject'); + $parentFields = array('allDayEvent', 'attendees', 'body', 'busyStatus', 'categories', 'dtStamp', 'endTime', 'location', 'meetingStatus', 'reminder', 'responseType', 'sensitivity', 'startTime', 'subject'); foreach ($parentFields as $field) { if (!isset($exception->$field) && isset($this->_elements[$field])) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/EventException.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/EventException.php
Changed
@@ -27,6 +27,9 @@ protected $_dateTimeFormat = "Ymd\THis\Z"; protected $_properties = array( + 'AirSyncBase' => array( + 'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody') + ), 'Calendar' => array( 'allDayEvent' => array('type' => 'number'), 'appointmentReplyTime' => array('type' => 'datetime'),
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/IContent.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/IContent.php
Changed
@@ -24,6 +24,5 @@ interface Syncroton_Model_IContent { - }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/ISyncState.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/ISyncState.php
Changed
@@ -21,6 +21,5 @@ interface Syncroton_Model_ISyncState { - }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/SyncCollection.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/SyncCollection.php
Changed
@@ -273,6 +273,11 @@ // optional if (isset($bodyPreference->TruncationSize)) { $this->_elements['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize; + } + + // optional + if (isset($bodyPreference->Preview)) { + $this->_elements['options']['bodyPreferences'][$type]['preview'] = (int) $bodyPreference->Preview; } } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Model/SyncState.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Model/SyncState.php
Changed
@@ -15,18 +15,7 @@ * @package Model */ -class Syncroton_Model_SyncState implements Syncroton_Model_ISyncState +class Syncroton_Model_SyncState extends Syncroton_Model_AEntry implements Syncroton_Model_ISyncState { - public function __construct(array $_data = array()) - { - $this->setFromArray($_data); - } - - public function setFromArray(array $_data) - { - foreach($_data as $key => $value) { - $this->$key = $value; - } - } }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Server.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Server.php
Changed
@@ -148,12 +148,12 @@ if ($this->_logger instanceof Zend_Log) $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed"); + header("HTTP/1.1 449 Retry after sending a PROVISION command"); + if (version_compare($device->acsversion, '14.0', '>=')) { $response = $sepn->domDocument; } else { // pre 14.0 method - header("HTTP/1.1 449 Retry after sending a PROVISION command"); - return; } @@ -184,7 +184,19 @@ $outputStream = fopen("php://temp", 'r+'); $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); - $encoder->encode($response); + + try { + $encoder->encode($response); + } catch (Syncroton_Wbxml_Exception $swe) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe); + $this->_logger->err(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML()); + } + + header("HTTP/1.1 500 Internal server error"); + + return; + } if ($requestParameters['acceptMultipart'] == true) { $parts = $command->getParts();
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Wbxml/Abstract.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Wbxml/Abstract.php
Changed
@@ -151,26 +151,12 @@ protected function _getOpaque($_length) { - $string = ''; - - // it might happen that not complete data is read from stream. - // loop until all data is read or EOF - while ($_length) { - $chunk = fread($this->_stream, $_length); - - if ($chunk === false) { - throw new Syncroton_Wbxml_Exception("failed reading opaque data"); - } - - if ($len = strlen($chunk)) { - $string .= $chunk; - $_length -= $len; - } - else if (feof($this->_stream)) { - break; - } + $string = fread($this->_stream, $_length); + + if($string === false) { + throw new Syncroton_Wbxml_Exception("failed reading opaque data"); } - + return $string; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Wbxml/Decoder.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Wbxml/Decoder.php
Changed
@@ -129,8 +129,6 @@ case Syncroton_Wbxml_Abstract::OPAQUE: $length = $this->_getMultibyteUInt(); if($length > 0) { - // @TODO: handle big data with streams - // E.g. in SendMail command "opaqued" <Mime> contains full email body $opaque = $this->_getOpaque($length); try { // let see if we can decode it. maybe the opaque data is wbxml encoded content
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
Changed
@@ -49,5 +49,34 @@ 'AllowStorageCard' => 0x1b, 'AllowCamera' => 0x1c, 'RequireDeviceEncryption' => 0x1d, + 'AllowUnsignedApplications' => 0x1e, + 'AllowUnsignedInstallationPackages' => 0x1f, + 'MinDevicePasswordComplexCharacters' => 0x20, + 'AllowWiFi' => 0x21, + 'AllowTextMessaging' => 0x22, + 'AllowPOPIMAPEmail' => 0x23, + 'AllowBluetooth' => 0x24, + 'AllowIrDA' => 0x25, + 'RequireManualSyncWhenRoaming' => 0x26, + 'AllowDesktopSync' => 0x27, + 'MaxCalendarAgeFilter' => 0x28, + 'AllowHTMLEmail' => 0x29, + 'MaxEmailAgeFilter' => 0x2a, + 'MaxEmailBodyTruncationSize' => 0x2b, + 'MaxEmailHTMLBodyTruncationSize' => 0x2c, + 'RequireSignedSMIMEMessages' => 0x2d, + 'RequireEncryptedSMIMEMessages' => 0x2e, + 'RequireSignedSMIMEAlgorithm' => 0x2F, + 'RequireEncryptionSMIMEAlgorithm' => 0x30, + 'AllowSMIMEEncryptionAlgorithmNegotiation' => 0x31, + 'AllowSMIMESoftCerts' => 0x32, + 'AllowBrowser' => 0x33, + 'AllowConsumerEmail' => 0x34, + 'AllowRemoteDesktop' => 0x35, + 'AllowInternetSharing' => 0x36, + 'UnapprovedInROMApplicationList' => 0x37, + 'ApplicationName' => 0x38, + 'ApprovedApplicationList' => 0x39, + 'Hash' => 0x3a, ); -} \ No newline at end of file +}
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
Changed
@@ -54,8 +54,8 @@ 'Rtf' => 0x21, 'OrdinalDate' => 0x22, 'SubOrdinalDate' => 0x23, - 'CalendarType' => 0x23, - 'IsLeapMonth' => 0x23, - 'FirstDayOfWeek' => 0x23 + 'CalendarType' => 0x24, + 'IsLeapMonth' => 0x25, + 'FirstDayOfWeek' => 0x26, ); } \ No newline at end of file
View file
kolab-syncroton-2.1.0.tar.gz/lib/ext/tnef_decoder.php -> kolab-syncroton-2.2.0.tar.gz/lib/ext/tnef_decoder.php
Changed
@@ -243,16 +243,16 @@ /* Store any interesting attributes. */ switch ($attr_name) { case self::MAPI_ATTACH_LONG_FILENAME: + $value = str_replace("\0", '', $value); /* Used in preference to AFILENAME value. */ $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value); - $attachment_data[0]['name'] = str_replace("\0", '', $attachment_data[0]['name']); break; case self::MAPI_ATTACH_MIME_TAG: + $value = str_replace("\0", '', $value); /* Is this ever set, and what is format? */ - $attachment_data[0]['type'] = preg_replace('/^(.*)\/.*/', '\1', $value); + $attachment_data[0]['type'] = preg_replace('/^(.*)\/.*/', '\1', $value); $attachment_data[0]['subtype'] = preg_replace('/.*\/(.*)$/', '\1', $value); - $attachment_data[0]['subtype'] = str_replace("\0", '', $attachment_data[0]['subtype']); break; } } @@ -295,9 +295,10 @@ break; case self::AFILENAME: + $value = $this->_getx($data, $this->_geti($data, 32)); + $value = str_replace("\0", '', $value); /* Strip path. */ - $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $this->_getx($data, $this->_geti($data, 32))); - $attachment_data[0]['name'] = str_replace("\0", '', $attachment_data[0]['name']); + $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value); /* Checksum */ $this->_geti($data, 16);
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync.php
Changed
@@ -43,7 +43,7 @@ public $user; const CHARSET = 'UTF-8'; - const VERSION = "2.1.0"; + const VERSION = "2.2.0"; /** @@ -75,8 +75,8 @@ // WARNING: We can use only plugins that are prepared for this // e.g. are not using output or rcmail objects or // doesn't throw errors when using them - $plugins = (array)$this->config->get('activesync_plugins', array('kolab_auth', 'kolab_folders')); - $required = array('libkolab', 'kolab_folders'); + $plugins = (array)$this->config->get('activesync_plugins', array('kolab_auth')); + $required = array('libkolab'); // Initialize/load plugins $this->plugins = kolab_sync_plugin_api::get_instance(); @@ -172,16 +172,34 @@ */ public function authenticate($username, $password) { - $auth = $this->plugins->exec_hook('authenticate', array( - 'host' => $this->select_host($username), - 'user' => $username, - 'pass' => $password, - 'valid' => true, - )); + // use shared cache for kolab_auth plugin result (username canonification) + $cache = $this->get_cache_shared('activesync_auth'); + $cache_key = md5($username . '::' . $password); + + if (!$cache || !($auth = $cache->get($cache_key))) { + $auth = $this->plugins->exec_hook('authenticate', array( + 'host' => $this->select_host($username), + 'user' => $username, + 'pass' => $password, + 'valid' => true, + )); + + if ($auth['valid'] && $cache) { + $cache->set($cache_key, array( + 'user' => $auth['user'], + 'host' => $auth['host'], + 'valid' => $auth['valid'], + )); + } + } + else { + $auth['pass'] = $password; + } // Authenticate - get Roundcube user ID if ($auth['valid'] && !$auth['abort'] - && ($userid = $this->login($auth['user'], $auth['pass'], $auth['host']))) { + && ($userid = $this->login($auth['user'], $auth['pass'], $auth['host'])) + ) { return $userid; } @@ -350,6 +368,11 @@ } $this->config->set('log_dir', $log_dir); + + // re-set PHP error logging + if (($this->config->get('debug_level') & 1) && $this->config->get('log_driver') != 'syslog') { + ini_set('error_log', $log_dir . '/errors'); + } } @@ -360,6 +383,9 @@ { parent::shutdown(); + // cache garbage collector + $this->gc_run(); + // write performance stats to logs/console if ($this->config->get('devel_mode')) { if (function_exists('memory_get_usage'))
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_backend.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_backend.php
Changed
@@ -171,9 +171,10 @@ } // Activesync folder identifier (serverId) - $folder_id = self::folder_id($folder, $typedata[$folder]); + $folder_type = $typedata[$folder]; + $folder_id = self::folder_id($folder, $folder_type); - $folders_list[$folder_id] = $this->folder_data($folder, $typedata[$folder]); + $folders_list[$folder_id] = $this->folder_data($folder, $folder_type); } return $folders_list; @@ -371,6 +372,12 @@ // Fill local cache $this->devices_list(); + // Some devices create dummy devices with name "validate" (#1109) + // This device entry is used in two initial requests, but later + // the device registers a real name. We can remove this dummy entry + // on new device creation + $this->device_delete('validate'); + // Old Kolab_ZPush device parameters // MODE: -1 | 0 | 1 (not set | flatmode | foldermode) // TYPE: device type string @@ -392,30 +399,33 @@ $this->root_meta['DEVICE'][$id] = $device; // Subscribe to default folders - $foldertypes = $this->storage->get_metadata('*', array(kolab_storage::CTYPE_KEY, kolab_storage::CTYPE_KEY_PRIVATE)); - $types = array( - 'mail.drafts', - 'mail.wastebasket', - 'mail.sentitems', - 'mail.outbox', - 'event.default', - 'contact.default', - 'task.default', - 'event', - 'contact', - 'task' - ); - - $foldertypes = array_map(array('kolab_storage', 'folder_select_metadata'), $foldertypes); - $foldertypes = array_intersect($foldertypes, $types); - - // get default folders - foreach ($foldertypes as $folder => $type) { - // only personal folders - if ($this->storage->folder_namespace($folder) == 'personal') { - $this->folder_set($folder, $id, 1); + $foldertypes = kolab_storage::folders_typedata(); + + if (!empty($foldertypes)) { + $types = array( + 'mail.drafts', + 'mail.wastebasket', + 'mail.sentitems', + 'mail.outbox', + 'event.default', + 'contact.default', + 'task.default', + 'event', + 'contact', + 'task' + ); + + $foldertypes = array_intersect($foldertypes, $types); + + // get default folders + foreach ($foldertypes as $folder => $type) { + // only personal folders + if ($this->storage->folder_namespace($folder) == 'personal') { + $this->folder_set($folder, $id, 1); + } } } + // INBOX always exists $this->folder_set('INBOX', $id, 1); } @@ -423,7 +433,14 @@ return $result; } - + /** + * Device update. + * + * @param array $device Device data + * @param string $id Device ID + * + * @return bool True on success, False on failure + */ public function device_update($device, $id) { $devices_list = $this->devices_list(); @@ -593,7 +610,7 @@ $name = array_pop($items); // Folder UID - $folder_id = $this->folder_id($folder); + $folder_id = $this->folder_id($folder, $type); // Folder type $type = self::type_kolab2activesync($type);
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_backend_common.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_backend_common.php
Changed
@@ -56,6 +56,13 @@ */ protected $db; + /** + * Internal cache (in-memory) + * + * @var array + */ + protected $cache = array(); + /** * Constructor
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_backend_content.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_backend_content.php
Changed
@@ -44,7 +44,11 @@ $result = $this->db->query('UPDATE ' . $this->table_name . ' SET is_deleted = 1 WHERE id = ?', array($id)); - return (bool) $this->db->affected_rows($result); + if ($result = (bool) $this->db->affected_rows($result)) { + unset($this->cache['content_folderstate']); + } + + return $result; } /** @@ -84,6 +88,13 @@ { $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId; $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId; + $cachekey = $deviceId . ':' . $folderId; + + // in Sync request we call this function twice in case when + // folder state changed - use cache to skip at least one SELECT query + if (isset($this->cache['content_folderstate'][$cachekey])) { + return $this->cache['content_folderstate'][$cachekey]; + } $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId); $where[] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folderId); @@ -96,7 +107,7 @@ $result[] = $state['contentid']; } - return $result; + return $this->cache['content_folderstate'][$cachekey] = $result; } /** @@ -109,6 +120,9 @@ { $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId; $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId; + $cachekey = $deviceId . ':' . $folderId; + + unset($this->cache['content_folderstate'][$cache_key]); $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId); $where[] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folderId);
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_backend_state.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_backend_state.php
Changed
@@ -150,15 +150,13 @@ { $device_id = $deviceid instanceof Syncroton_Model_IDevice ? $deviceid->id : $deviceid; $folder_id = $folderid instanceof Syncroton_Model_IFolder ? $folderid->id : $folderid; + $states = array(); - // get sync data for current request and check if it's the last one - // by fetching synckey+1 - $states = array(); - $keys = array($sync_key, $sync_key + 1); - + // get sync data + // we'll get all records, thanks to this we'll be able to + // skip _deleteOtherStates() call below (one DELETE query less) $where['device_id'] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where['type'] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); - $where['counter'] = $this->db->quote_identifier('counter') . ' IN (' . $this->db->array2list($keys, 'int') . ')'; $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); @@ -166,19 +164,21 @@ $states[$row['counter']] = $this->get_object($row); } + // last state not found if (empty($states) || empty($states[$sync_key])) { return false; } $state = $states[$sync_key]; + $next = max(array_keys($states)); $where = array(); $where['device_id'] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where['folder_id'] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); $where['is_deleted'] = $this->db->quote_identifier('is_deleted') . ' = 1'; - // found more recent synckey => the last sync repsonse got not received by the client - if (!empty($states[$sync_key + 1])) { + // found more recent synckey => the last sync response got not received by the client + if ($next > $sync_key) { $where['synckey'] = $this->db->quote_identifier('creation_synckey') . ' = ' . $this->db->quote($state->counter); // undelete entries marked as deleted in syncroton_content table $this->db->query('UPDATE syncroton_content SET is_deleted = 0 WHERE ' . implode(' AND ', $where)); @@ -195,7 +195,9 @@ } // remove all other synckeys - $this->_deleteOtherStates($state); + if (count($states) > 1) { + $this->_deleteOtherStates($state); + } return $state; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_data.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_data.php
Changed
@@ -85,12 +85,32 @@ protected $folders = array(); /** + * Internal cache for IMAP folders list + * + * @var array + */ + protected $imap_folders = array(); + + /** * Timezone * * @var string */ protected $timezone; + /** + * List of device types with multiple folders support + * + * @var array + */ + protected $ext_devices = array( + 'iphone', + 'ipad', + 'thundertine', + 'windowsphone', + 'playbook', + ); + const RESULT_OBJECT = 0; const RESULT_UID = 1; const RESULT_COUNT = 2; @@ -186,9 +206,9 @@ $list = array(); // device supports multiple folders ? - if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) { + if (in_array(strtolower($this->device->devicetype), $this->ext_devices)) { // get the folders the user has access to - $list = $this->backend->folders_list($this->device->deviceid, $this->modelName); + $list = $this->listFolders(); } else if ($default = $this->getDefaultFolder()) { $list = array($default['serverId'] => $default); @@ -208,12 +228,25 @@ } /** + * Retrieve folders which were modified since last sync + * + * @param DateTime $startTimeStamp + * @param DateTime $endTimeStamp + * + * @return array List of folders + */ + public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp) + { + return array(); + } + + /** * Returns default folder for current class type. */ protected function getDefaultFolder() { // Check if there's any folder configured for sync - $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName); + $folders = $this->listFolders(); if (empty($folders)) { return $folders; @@ -331,6 +364,47 @@ } /** + * Empty folder (remove all entries and optionally subfolders) + * + * @param string $folderId Folder identifier + * @param array $options Options + */ + public function emptyFolderContents($folderid, $options) + { + $folders = $this->extractFolders($folderid); + + foreach ($folders as $folderid) { + $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); + + if ($foldername === null) { + continue; + } + + $folder = $this->getFolderObject($foldername); + + // Remove all entries + $folder->delete_all(); + + // Remove subfolders + if (!empty($options['deleteSubFolders'])) { + $list = $this->listFolders($folderid); + foreach ($list as $folderid => $folder) { + $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); + + if ($foldername === null) { + continue; + } + + $folder = $this->getFolderObject($foldername); + + // Remove all entries + $folder->delete_all(); + } + } + } + } + + /** * Moves object into another location (folder) * * @param string $srcFolderId Source folder identifier @@ -444,7 +518,7 @@ protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID) { if ($folderid == $this->defaultRootFolder) { - $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName); + $folders = $this->listFolders(); if (!is_array($folders)) { throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); @@ -644,22 +718,7 @@ */ protected function getObject($folderid, $entryid, &$folder = null) { - if ($folderid instanceof Syncroton_Model_IFolder) { - $folderid = $folderid->serverId; - } - - if ($folderid == $this->defaultRootFolder) { - $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName); - - if (!is_array($folders)) { - return null; - } - - $folders = array_keys($folders); - } - else { - $folders = array($folderid); - } + $folders = $this->extractFolders($folderid); foreach ($folders as $folderid) { $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); @@ -728,6 +787,69 @@ $folder = $this->getFolderObject($object['_mailbox']); return $folder && $folder->delete($entryid); } + + // object doesn't exist, confirm deletion + return true; + } + + /** + * Returns internal folder IDs + * + * @param string $folderid Folder identifier + * + * @return array List of folder identifiers + */ + protected function extractFolders($folderid) + { + if ($folderid instanceof Syncroton_Model_IFolder) { + $folderid = $folderid->serverId; + } + + if ($folderid == $this->defaultRootFolder) { + $folders = $this->listFolders(); + + if (!is_array($folders)) { + return null; + } + + $folders = array_keys($folders); + } + else { + $folders = array($folderid); + } + + return $folders; + } + + /** + * List of all IMAP folders (or subtree) + * + * @param string $parentid Parent folder identifier + * + * @return array List of folder identifiers + */ + protected function listFolders($parentid = null) + { + if (empty($this->imap_folders)) { + $this->imap_folders = $this->backend->folders_list($this->device->deviceid, $this->modelName); + } + + if ($parentid === null) { + return $this->imap_folders; + } + + $folders = array(); + $parents = array($parentid); + + foreach ($this->imap_folders as $folder_id => $folder) { + if ($folder['parentId'] && in_array($folder['parentId'], $parents)) { + $folders[$folder_id] = $folder; + $parents[] = $folder_id; + } + } + + + return $folders; } /** @@ -1043,10 +1165,10 @@ /** * Convert Kolab event/task recurrence into ActiveSync */ - protected function recurrence_from_kolab($data, $type = 'Event') + protected function recurrence_from_kolab($collection, $data, &$result, $type = 'Event') { if (empty($data['recurrence'])) { - return null; + return; } $recurrence = array(); @@ -1114,7 +1236,7 @@ $recurrence['interval'] = $r['INTERVAL'] ? $r['INTERVAL'] : 1; if (!empty($r['UNTIL'])) { - $recurrence['until'] = $this->date_from_kolab($r['UNTIL']); + $recurrence['until'] = self::date_from_kolab($r['UNTIL']); } else if (!empty($r['COUNT'])) { $recurrence['occurrences'] = $r['COUNT']; @@ -1122,19 +1244,25 @@ $class = 'Syncroton_Model_' . $type . 'Recurrence'; - return new $class($recurrence); + $result['recurrence'] = new $class($recurrence); + + // Tasks do not support exceptions + if ($type == 'Event') { + $result['exceptions'] = $this->exceptions_from_kolab($collection, $data, $result); + } } /** * Convert ActiveSync event/task recurrence into Kolab */ - protected function recurrence_to_kolab($recurrence, $timezone = null) + protected function recurrence_to_kolab($data, $folderid, $timezone = null) { - if (!($recurrence instanceof Syncroton_Model_EventRecurrence) || !isset($recurrence->type)) { + if (!($data->recurrence instanceof Syncroton_Model_EventRecurrence) || !isset($data->recurrence->type)) { return null; } - $type = $recurrence->type; + $recurrence = $data->recurrence; + $type = $recurrence->type; switch ($type) { case self::RECUR_TYPE_DAILY: @@ -1187,72 +1315,95 @@ $rrule['COUNT'] = $recurrence->occurrences; } + // recurrence exceptions (not supported by Tasks) + if ($data instanceof Syncroton_Model_Event) { + $this->exceptions_to_kolab($data, $rrule, $folderid, $timezone); + } + return $rrule; } /** * Convert Kolab event recurrence exceptions into ActiveSync */ - protected function exceptions_from_kolab($data, $start_date) + protected function exceptions_from_kolab($collection, $data, $result) { - if (empty($data['recurrence'])) { + if (empty($data['recurrence']['EXCEPTIONS']) && empty($data['recurrence']['EXDATE'])) { return null; } - $rex = (array) $data['recurrence']['EXDATE']; - $exceptions = array(); + $ex_list = array(); - foreach ($rex as $ex_date) { - if (!($ex_date instanceof DateTime)) { + // exceptions (modified occurences) + foreach ((array)$data['recurrence']['EXCEPTIONS'] as $exception) { + $exception['_mailbox'] = $data['_mailbox']; + $ex = $this->getEntry($collection, $exception, true); + + $ex['exceptionStartTime'] = clone $ex['startTime']; + + // remove fields not supported by Syncroton_Model_EventException + unset($ex['uID']); + + // @TODO: 'thisandfuture=true' is not supported in Activesync + // we'd need to slit the event into two separate events + + $ex_list[] = new Syncroton_Model_EventException($ex); + } + + // exdate (deleted occurences) + foreach ((array)$data['recurrence']['EXDATE'] as $exception) { + if (!($exception instanceof DateTime)) { continue; } - $start = clone $ex_date; - $end = clone $ex_date; - - $start->setTime(0, 0, 0); - $end->setTime(0, 0, 0); - $end->modify('+1 day'); + // set event start time to exception date + // that can't be any time, tested with Android + $hour = $data['_start']->format('H'); + $minute = $data['_start']->format('i'); + $second = $data['_start']->format('s'); + $exception->setTime($hour, $minute, $second); $ex = array( - 'exceptionStartTime' => $start_date, - 'startTime' => self::date_from_kolab($start), - 'endTime' => self::date_from_kolab($end), 'deleted' => 1, + 'exceptionStartTime' => self::date_from_kolab($exception), ); - if ($data['allday']) { - $ex['allDayEvent'] = 1; - } - - $exceptions[] = new Syncroton_Model_EventException($ex); + $ex_list[] = new Syncroton_Model_EventException($ex); } - return $exceptions; + return $ex_list; } /** * Convert ActiveSync event recurrence exceptions into Kolab */ - protected function exceptions_to_kolab($exceptions, $timezone = null) + protected function exceptions_to_kolab($data, &$rrule, $folderid, $timezone = null) { - $exdates = array(); + $rrule['EXDATE'] = array(); + $rrule['EXCEPTIONS'] = array(); + // handle exceptions from recurrence - if (!empty($exceptions)) { - foreach ($exceptions as $exception) { - if ($exception->deleted && $exception->startTime) { - $date = clone $exception->startTime; - $date->setTimezone($timezone); + if (!empty($data->exceptions)) { + foreach ($data->exceptions as $exception) { + if ($exception->deleted) { + $date = clone $exception->exceptionStartTime; + if ($timezone) { + $date->setTimezone($timezone); + } $date->setTime(0, 0, 0); - $exdates[] = $date; + $rrule['EXDATE'][] = $date; } - else { - // @TODO: handle modification exceptions (that doesn't delete) ? + else if (!$exception->deleted) { + $ex = $this->toKolab($exception, $folderid, null, $timezone); + + if ($data->allDayEvent) { + $ex['allday'] = 1; + } + + $rrule['EXCEPTIONS'][] = $ex; } } } - - return !empty($exdates) ? $exdates : null; } /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_data_calendar.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_data_calendar.php
Changed
@@ -173,8 +173,11 @@ * * @param Syncroton_Model_SyncCollection $collection Collection data * @param string $serverId Local entry identifier + * @param boolean $as_array Return entry as array + * + * @return array|Syncroton_Model_Event|array Event object */ - public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId) + public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) { $event = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); $config = $this->getFolderConfig($event['_mailbox']); @@ -217,6 +220,11 @@ } } + // set this date for use in exceptions handling + if ($name == 'start') { + $event['_start'] = $date; + } + $value = self::date_from_kolab($date); } @@ -247,6 +255,9 @@ $result['reminder'] = $minutes; } + $result['categories'] = array(); + $result['attendees'] = array(); + // Categories, Roundcube Calendar plugin supports only one category at a time if (!empty($event['categories'])) { $result['categories'] = (array) $event['categories']; @@ -272,8 +283,6 @@ // Attendees if (!empty($event['attendees'])) { - $result['attendees'] = array(); - foreach ($event['attendees'] as $idx => $attendee) { $att = array(); @@ -294,26 +303,15 @@ $result['attendees'][] = new Syncroton_Model_EventAttendee($att); } -/* - // set own status - if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($event->attendee)) !== null - && ($busyType = array_search($ownAttendee->status, $this->_busyStatusMapping)) !== false - ) { - $result['BusyStatus'] = $busyType; - } -*/ } // Event meeting status $result['meetingStatus'] = intval(!empty($result['attendees'])); - // Recurrence - $result['recurrence'] = $this->recurrence_from_kolab($event); - - // Recurrence exceptions - $result['exceptions'] = $this->exceptions_from_kolab($event, $result['startTime']); + // Recurrence (and exceptions) + $this->recurrence_from_kolab($collection, $event, $result); - return new Syncroton_Model_Event($result); + return $as_array ? $result : new Syncroton_Model_Event($result); } /** @@ -322,19 +320,21 @@ * @param Syncroton_Model_IEntry $data Contact to convert * @param string $folderid Folder identifier * @param array $entry Existing entry + * @param DateTimeZone $timezone Timezone of the event * * @return array */ - public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null) + public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null, $timezone = null) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - $event = !empty($entry) ? $entry : array(); - $config = $this->getFolderConfig($foldername); + $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); + $event = !empty($entry) ? $entry : array(); + $config = $this->getFolderConfig($foldername); + $is_exception = $data instanceof Syncroton_Model_EventException; $event['allday'] = 0; // Timezone - if (isset($data->timezone)) { + if (!$timezone && isset($data->timezone)) { $tzc = kolab_sync_timezone_converter::getInstance(); $expected = kolab_format::$timezone->getName(); @@ -356,6 +356,12 @@ // Calendar namespace fields foreach ($this->mapping as $key => $name) { + // skip UID field, unsupported in event exceptions + // we need to do this here, because the next line (data getter) will throw an exception + if ($is_exception && $key == 'uID') { + continue; + } + $value = $data->$key; switch ($name) { @@ -432,14 +438,16 @@ } // Organizer - $name = $data->organizerName; - $email = $data->organizerEmail; - if ($name || $email) { - $event['attendees'][] = array( - 'role' => 'ORGANIZER', - 'name' => $name, - 'email' => $email, - ); + if (!$is_exception) { + $name = $data->organizerName; + $email = $data->organizerEmail; + if ($name || $email) { + $event['attendees'][] = array( + 'role' => 'ORGANIZER', + 'name' => $name, + 'email' => $email, + ); + } } // Attendees @@ -463,12 +471,9 @@ } } - // recurrence - $event['recurrence'] = $this->recurrence_to_kolab($data->recurrence, $timezone); - - // recurrence exceptions - if ($exdate = $this->exceptions_to_kolab($data->exceptions, $timezone)) { - $event['recurrence']['EXDATE'] = $exdate; + // recurrence (and exceptions) + if (!$is_exception) { + $event['recurrence'] = $this->recurrence_to_kolab($data, $folderid, $timezone); } return $event;
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_data_email.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_data_email.php
Changed
@@ -103,6 +103,9 @@ parent::__construct($device, $syncTimeStamp); $this->storage = rcube::get_instance()->get_storage(); + + // Outlook 2013 support multi-folder + $this->ext_devices[] = 'windowsoutlook15'; } /** @@ -141,14 +144,9 @@ $addresses = rcube_mime::decode_address_list($headers->$name, null, true, $headers->charset); foreach ($addresses as $idx => $part) { - $name = $part['name']; - $mailto = $part['mailto']; - $string = $part['string']; - - // @TODO: convert to utf8? - // @FIXME: set name + address or address only? - //rcube_utils::idn_to_utf8(); - $addresses[$idx] = format_email_recipient($mailto, $name); + // @FIXME: set name + address or address only? + $addresses[$idx] = format_email_recipient($part['mailto'], $part['name']); + $addresses[$idx] = rcube_charset::clean($addresses[$idx]); } $value = implode(',', $addresses); @@ -301,7 +299,7 @@ // original body type // @TODO: get this value from getMessageBody() - $result['nativeBodyType'] = $message->has_html_part(false) ? 2 : 1; + $result['nativeBodyType'] = $message->has_html_part() ? 2 : 1; // Message class // @TODO: add messageClass suffix for encrypted messages @@ -418,14 +416,14 @@ */ public function getAllFolders() { - $list = $this->backend->folders_list($this->device->deviceid, $this->modelName); + $list = $this->listFolders(); if (!is_array($list)) { throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR); } // device doesn't support multiple folders - if (!in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) { + if (!in_array(strtolower($this->device->devicetype), $this->ext_devices)) { // We'll return max. one folder of supported type $result = array(); $types = $this->folder_types; @@ -459,7 +457,7 @@ */ protected function extractFolders($folder_id) { - $list = $this->backend->folders_list($this->device->deviceid, $this->modelName); + $list = $this->listFolders(); $result = array(); if (!is_array($list)) { @@ -467,7 +465,7 @@ } // device supports multiple folders? - if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) { + if (in_array(strtolower($this->device->devicetype), $this->ext_devices)) { if ($list[$folder_id]) { $result[] = $folder_id; } @@ -1004,7 +1002,7 @@ // @TODO: DeepTraversal if (empty($folders)) { - $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName); + $folders = $this->listFolders(); if (is_array($folders)) { $folders = array_keys($folders);
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_data_tasks.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_data_tasks.php
Changed
@@ -102,8 +102,11 @@ * * @param Syncroton_Model_SyncCollection $collection Collection data * @param string $serverId Local entry identifier + * @param boolean $as_array Return entry as an array + * + * @return array|Syncroton_Model_Task|array Task object */ - public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId) + public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) { $task = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); $config = $this->getFolderConfig($task['_mailbox']); @@ -149,9 +152,9 @@ } // Recurrence - $result['recurrence'] = $this->recurrence_from_kolab($task, 'Task'); + $this->recurrence_from_kolab($collection, $task, $result, 'Task'); - return new Syncroton_Model_Task($result); + return $as_array ? $result : new Syncroton_Model_Task($result); } /** @@ -203,7 +206,7 @@ } // recurrence - $task['recurrence'] = $this->recurrence_to_kolab($data->recurrence); + $task['recurrence'] = $this->recurrence_to_kolab($data, $folderid, null); return $task; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_message.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_message.php
Changed
@@ -159,10 +159,9 @@ { $rcube = rcube::get_instance(); $headers = $this->headers; + $mailto = $headers['To']; - $mailto = $headers['To']; - - $headers['User-Agent'] .= sprintf('%s v.%.1f', $rcube->app_name, kolab_sync::VERSION); + $headers['User-Agent'] .= $rcube->app_name . ' ' . kolab_sync::VERSION; if ($agent = $rcube->config->get('useragent')) { $headers['User-Agent'] .= '/' . $agent; } @@ -170,8 +169,9 @@ if (empty($headers['From'])) { $headers['From'] = $this->get_identity(); } + if (empty($headers['Message-ID'])) { - $headers['Message-ID'] = $this->gen_message_id(); + $headers['Message-ID'] = $rcube->gen_message_id(); } // remove empty headers @@ -275,6 +275,7 @@ return $sent; } + /** * MIME message parser * @@ -333,6 +334,9 @@ $this->body = $message; } + /** + * Normalize (fix) header names + */ protected function normalize_header_name($name) { $headers_map = array( @@ -411,30 +415,6 @@ } } - /** - * Unique Message-ID generator. - * - * @return string Message-ID - */ - protected function gen_message_id() - { - $user = kolab_sync::get_instance()->user; - $local_part = md5(uniqid('rcmail'.mt_rand(),true)); - $domain_part = $user->get_username('domain'); - - // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) - if (!preg_match('/\.[a-z]+$/i', $domain_part)) { - foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { - $host = preg_replace('/:[0-9]+$/', '', $host); - if ($host && preg_match('/\.[a-z]+$/i', $host)) { - $domain_part = $host; - } - } - } - - return sprintf('<%s@%s>', $local_part, $domain_part); - } - protected function save_content_type($ctype, $params = array()) { $this->ctype = $ctype;
View file
kolab-syncroton-2.1.0.tar.gz/lib/kolab_sync_timezone_converter.php -> kolab-syncroton-2.2.0.tar.gz/lib/kolab_sync_timezone_converter.php
Changed
@@ -80,11 +80,10 @@ * first entry to the returned array. * * @param string|array $_offsets - * @param string $_expectedTimezone Expected timezone * * @return array */ - public function getListOfTimezones($_offsets, $_expectedTimezone = null) + public function getListOfTimezones($_offsets) { if (is_string($_offsets) && isset($this->_knownTimezones[$_offsets])) { $timezones = $this->_knownTimezones[$_offsets]; @@ -100,7 +99,6 @@ } $this->_setDefaultStartDateIfEmpty($_offsets); - // don't use __METHOD__ ":" is not allowed as cache identifier $cacheId = $this->_getCacheId('timezones', $_offsets); $timezones = $this->_loadFromCache($cacheId); @@ -109,13 +107,7 @@ foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { $timezone = new DateTimeZone($timezoneIdentifier); if (false !== ($matchingTransition = $this->_checkTimezone($timezone, $_offsets))) { - if ($timezoneIdentifier === $_expectedTimezone) { - $timezones = array($timezoneIdentifier => $matchingTransition['abbr']); - break; - } - else { - $timezones[$timezoneIdentifier] = $matchingTransition['abbr']; - } + $timezones[$timezoneIdentifier] = $matchingTransition['abbr']; } } $this->_saveInCache($timezones, $cacheId); @@ -143,13 +135,13 @@ */ public function getTimezone($_offsets, $_expectedTimezone = null) { - $timezones = $this->getListOfTimezones($_offsets, $_expectedTimezone); + $timezones = $this->getListOfTimezones($_offsets); if ($_expectedTimezone && isset($timezones[$_expectedTimezone])) { return $_expectedTimezone; } else { - return current($timezones); + return key($timezones); } } @@ -536,12 +528,11 @@ */ protected function _checkTimezone(DateTimeZone $timezone, $offsets) { -// $this->_log(__METHOD__, __LINE__, 'Checking for matches with timezone: ' . $timezone->getName()); list($standardTransition, $daylightTransition) = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate['year']); if ($this->_checkTransition($standardTransition, $daylightTransition, $offsets)) { - //$this->_log(__METHOD__, __LINE__, 'Matching timezone ' . $timezone->getName(), 7); - //$this->_log(__METHOD__, __LINE__, 'Matching daylight transition ' . print_r($daylightTransition, 1), 7); - //$this->_log(__METHOD__, __LINE__, 'Matching standard transition ' . print_r($standardTransition, 1), 7); +// echo 'Matching timezone ' . $timezone->getName(); +// echo 'Matching daylight transition ' . print_r($daylightTransition, 1); +// echo 'Matching standard transition ' . print_r($standardTransition, 1); return $standardTransition; } @@ -620,17 +611,10 @@ { if ($this->cache === null) { $rcube = rcube::get_instance(); - - if ($cache = $rcube->config->get('activesync_cache')) { - $ttl = $rcube->config->get('activesync_cache_lifetime', '10d'); - $this->cache = $rcube->get_cache('ACTIVESYNC', $cache, $ttl); - } - else { - $this->cache = false; - } + $cache = $rcube->get_cache_shared('activesync'); + $this->cache = $cache ? $cache : false; } return $this->cache; } - }
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_auth/config.inc.php.dist -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/config.inc.php.dist
Changed
@@ -2,6 +2,17 @@ // The id of the LDAP address book (which refers to the $rcmail_config['ldap_public']) // or complete addressbook definition array. +// -------------------------------------------------------------------- +// Note: Multi-domain (hosted) installations can resolve domain aliases +// by adding following settings in kolab_auth_addressbook spec.: +// +// 'domain_base_dn' => 'cn=kolab,cn=config', +// 'domain_filter' => '(&(objectclass=domainrelatedobject)(associateddomain=%s))', +// 'domain_name_attr' => 'associateddomain', +// +// With this %dc variable in base_dn and groups/base_dn will be +// replaced with DN string of resolved domain +//--------------------------------------------------------------------- $rcmail_config['kolab_auth_addressbook'] = ''; // This will overwrite defined filter @@ -14,8 +25,9 @@ // If the value array contains more than one field, first non-empty will be used // Note: These aren't LDAP attributes, but field names in config // Note: If there's more than one email address, as many identities will be created -$rcmail_config['kolab_auth_name'] = array('name', 'cn'); -$rcmail_config['kolab_auth_email'] = array('email'); +$rcmail_config['kolab_auth_name'] = array('name', 'cn'); +$rcmail_config['kolab_auth_email'] = array('email'); +$rcmail_config['kolab_auth_organization'] = array('organization'); // Login and password of the admin user. Enables "Login As" feature. $rcmail_config['kolab_auth_admin_login'] = '';
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_auth/kolab_auth.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/kolab_auth.php
Changed
@@ -12,7 +12,7 @@ * @version @package_version@ * @author Aleksander Machniak <machniak@kolabsys.com> * - * Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com> + * Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -30,7 +30,7 @@ class kolab_auth extends rcube_plugin { - private $ldap; + static $ldap; private $data = array(); public function init() @@ -61,12 +61,12 @@ $rcmail->config->set('imap_debug', true); $rcmail->config->set('ldap_debug', true); $rcmail->config->set('smtp_debug', true); - } } - public function startup($args) { + public function startup($args) + { // Arguments are task / action, not interested if (!empty($_SESSION['user_roledns'])) { $this->load_user_role_plugins_and_settings($_SESSION['user_roledns']); @@ -75,7 +75,8 @@ return $args; } - public function load_user_role_plugins_and_settings($role_dns) { + public function load_user_role_plugins_and_settings($role_dns) + { $rcmail = rcube::get_instance(); $this->load_config(); @@ -86,6 +87,8 @@ // Array( // '<role_dn>' => Array('plugin1', 'plugin2'), // ); + // + // NOTE that <role_dn> may in fact be something like: 'cn=role,%dc' $role_plugins = $rcmail->config->get('kolab_auth_role_plugins'); @@ -100,9 +103,29 @@ // ), // ), // ); + // + // NOTE that <role_dn> may in fact be something like: 'cn=role,%dc' $role_settings = $rcmail->config->get('kolab_auth_role_settings'); + $ldap = self::ldap(); + if (!$ldap || !$ldap->ready) { + $args['abort'] = true; + return $args; + } + + if (!empty($role_plugins)) { + foreach ($role_plugins as $role_dn => $plugins) { + $role_plugins[$ldap->parse_vars($role_dn)] = $plugins; + } + } + + if (!empty($role_settings)) { + foreach ($role_settings as $role_dn => $settings) { + $role_settings[$ldap->parse_vars($role_dn)] = $settings; + } + } + foreach ($role_dns as $role_dn) { if (isset($role_plugins[$role_dn]) && is_array($role_plugins[$role_dn])) { foreach ($role_plugins[$role_dn] as $plugin) { @@ -151,7 +174,8 @@ } } - public function write_log($args) { + public function write_log($args) + { $rcmail = rcube::get_instance(); if (!$rcmail->config->get('kolab_auth_auditlog', false)) { @@ -208,7 +232,19 @@ if (!empty($this->data['user_email'])) { // addresses list is supported if (array_key_exists('email_list', $args)) { - $args['email_list'] = array_unique($this->data['user_email']); + $email_list = array_unique($this->data['user_email']); + + // add organization to the list + if (!empty($this->data['user_organization'])) { + foreach ($email_list as $idx => $email) { + $email_list[$idx] = array( + 'organization' => $this->data['user_organization'], + 'email' => $email, + ); + } + } + + $args['email_list'] = $email_list; } else { $args['user_email'] = $this->data['user_email'][0]; @@ -256,22 +292,8 @@ */ public function authenticate($args) { - $this->load_config(); - - if (!$this->init_ldap()) { - $args['abort'] = true; - return $args; - } - - $rcmail = rcube::get_instance(); - $admin_login = $rcmail->config->get('kolab_auth_admin_login'); - $admin_pass = $rcmail->config->get('kolab_auth_admin_password'); - $login_attr = $rcmail->config->get('kolab_auth_login'); - $name_attr = $rcmail->config->get('kolab_auth_name'); - $email_attr = $rcmail->config->get('kolab_auth_email'); - // get username and host - $host = rcube_utils::parse_host($args['host']); + $host = $args['host']; $user = $args['user']; $pass = $args['pass']; $loginas = trim(rcube_utils::get_input_value('_loginas', rcube_utils::INPUT_POST)); @@ -281,15 +303,28 @@ return $args; } + $ldap = self::ldap(); + if (!$ldap || !$ldap->ready) { + $args['abort'] = true; + return $args; + } + // Find user record in LDAP - $record = $this->get_user_record($user, $host); + $record = $ldap->get_user_record($user, $host); if (empty($record)) { $args['abort'] = true; return $args; } - $role_attr = $rcmail->config->get('kolab_auth_role'); + $rcmail = rcube::get_instance(); + $admin_login = $rcmail->config->get('kolab_auth_admin_login'); + $admin_pass = $rcmail->config->get('kolab_auth_admin_password'); + $login_attr = $rcmail->config->get('kolab_auth_login'); + $name_attr = $rcmail->config->get('kolab_auth_name'); + $email_attr = $rcmail->config->get('kolab_auth_email'); + $org_attr = $rcmail->config->get('kolab_auth_organization'); + $role_attr = $rcmail->config->get('kolab_auth_role'); if (!empty($role_attr) && !empty($record[$role_attr])) { $_SESSION['user_roledns'] = (array)($record[$role_attr]); @@ -298,40 +333,32 @@ // Login As... if (!empty($loginas) && $admin_login) { // Authenticate to LDAP - $dn = $this->ldap->dn_decode($record['ID']); - $result = $this->ldap->bind($dn, $pass); + $result = $ldap->bind($record['dn'], $pass); if (!$result) { + $args['abort'] = true; return $args; } // check if the original user has/belongs to administrative role/group - $isadmin = false; - $group = $rcmail->config->get('kolab_auth_group'); - $role_attr = $rcmail->config->get('kolab_auth_role'); - $role_dn = $rcmail->config->get('kolab_auth_role_value'); + $isadmin = false; + $group = $rcmail->config->get('kolab_auth_group'); + $role_dn = $rcmail->config->get('kolab_auth_role_value'); // check role attribute if (!empty($role_attr) && !empty($role_dn) && !empty($record[$role_attr])) { - $role_dn = $this->parse_vars($role_dn, $user, $host); - foreach ((array)$record[$role_attr] as $role) { - if ($role == $role_dn) { - $isadmin = true; - break; - } + $role_dn = $ldap->parse_vars($role_dn, $user, $host); + if (in_array($role_dn, (array)$record[$role_attr])) { + $isadmin = true; } } // check group if (!$isadmin && !empty($group)) { - $groups = $this->ldap->get_record_groups($record['ID']); - foreach ($groups as $g) { - if ($group == $this->ldap->dn_decode($g)) { - $isadmin = true; - break; - } + $groups = $ldap->get_user_groups($record['dn'], $user, $host); + if (in_array($group, $groups)) { + $isadmin = true; } - } // Save original user login for log (see below) @@ -346,7 +373,7 @@ // user has the privilage, get "login as" user credentials if ($isadmin) { - $record = $this->get_user_record($loginas, $host); + $record = $ldap->get_user_record($loginas, $host); } if (empty($record)) { @@ -362,8 +389,9 @@ $_SESSION['kolab_auth_password'] = $rcmail->encrypt($admin_pass); } - // Store UID in session for use by other plugins + // Store UID and DN of logged user in session for use by other plugins $_SESSION['kolab_uid'] = is_array($record['uid']) ? $record['uid'][0] : $record['uid']; + $_SESSION['kolab_dn'] = $record['dn']; // Set user login if ($login_attr) { @@ -388,6 +416,14 @@ $this->data['user_email'] = array_merge((array)$this->data['user_email'], (array)$email); } } + // Organization name for identity (first log in) + foreach ((array)$org_attr as $field) { + $organization = is_array($record[$field]) ? $record[$field][0] : $record[$field]; + if (!empty($organization)) { + $this->data['user_organization'] = $organization; + break; + } + } // Log "Login As" usage if (!empty($origname)) { @@ -435,14 +471,24 @@ /** * Initializes LDAP object and connects to LDAP server */ - private function init_ldap() + public static function ldap() { - if ($this->ldap) { - return $this->ldap->ready; + if (self::$ldap) { + return self::$ldap; } $rcmail = rcube::get_instance(); + // $this->load_config(); + // we're in static method, load config manually + $fpath = $rcmail->plugins->dir . '/kolab_auth/config.inc.php'; + if (is_file($fpath) && !$rcmail->config->load_from_file($fpath)) { + rcube::raise_error(array( + 'code' => 527, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Failed to load config from $fpath"), true, false); + } + $addressbook = $rcmail->config->get('kolab_auth_addressbook'); if (!is_array($addressbook)) { @@ -451,81 +497,13 @@ } if (empty($addressbook)) { - return false; + return null; } - $this->ldap = new kolab_auth_ldap_backend( - $addressbook, - $rcmail->config->get('ldap_debug'), - $rcmail->config->mail_domain($_SESSION['imap_host']) - ); - - return $this->ldap->ready; - } - - /** - * Fetches user data from LDAP addressbook - */ - private function get_user_record($user, $host) - { - $rcmail = rcube::get_instance(); - $filter = $rcmail->config->get('kolab_auth_filter'); + require_once __DIR__ . '/kolab_auth_ldap.php'; - $filter = $this->parse_vars($filter, $user, $host); - - // reset old result - $this->ldap->reset(); - - // get record - $this->ldap->set_filter($filter); - $results = $this->ldap->list_records(); - - if (count($results->records) == 1) { - return $results->records[0]; - } - } + self::$ldap = new kolab_auth_ldap($addressbook); - /** - * Prepares filter query for LDAP search - */ - private function parse_vars($str, $user, $host) - { - $rcmail = rcube::get_instance(); - $domain = $rcmail->config->get('username_domain'); - - if (!empty($domain) && strpos($user, '@') === false) { - if (is_array($domain) && isset($domain[$host])) { - $user .= '@'.rcube_utils::parse_host($domain[$host], $host); - } - else if (is_string($domain)) { - $user .= '@'.rcube_utils::parse_host($domain, $host); - } - } - - // replace variables in filter - list($u, $d) = explode('@', $user); - $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string - $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u); - - return strtr($str, $replaces); - } -} - -/** - * Wrapper class for rcube_ldap addressbook - */ -class kolab_auth_ldap_backend extends rcube_ldap -{ - function __construct($p, $debug=false, $mail_domain=null) - { - parent::__construct($p, $debug, $mail_domain); - $this->fieldmap['uid'] = 'uid'; - } - - function set_filter($filter) - { - if ($filter) { - $this->prop['filter'] = $filter; - } + return self::$ldap; } }
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/kolab_auth_ldap.php
Added
@@ -0,0 +1,485 @@ +<?php + +/** + * Kolab Authentication + * + * @version @package_version@ + * @author Aleksander Machniak <machniak@kolabsys.com> + * + * Copyright (C) 2011-2013, Kolab Systems AG <contact@kolabsys.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * Wrapper class for rcube_ldap_generic + */ +class kolab_auth_ldap extends rcube_ldap_generic +{ + private $icache = array(); + + + function __construct($p) + { + $rcmail = rcube::get_instance(); + + $this->debug = (bool) $rcmail->config->get('ldap_debug'); + $this->fieldmap = $p['fieldmap']; + $this->fieldmap['uid'] = 'uid'; + + $p['attributes'] = array_values($this->fieldmap); + + // Connect to the server (with bind) + parent::__construct($p); + $this->_connect(); + + $rcmail->add_shutdown_function(array($this, 'close')); + } + + /** + * Establish a connection to the LDAP server + */ + private function _connect() + { + $rcube = rcube::get_instance(); + + // try to connect + bind for every host configured + // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable + // see http://www.php.net/manual/en/function.ldap-connect.php + foreach ((array)$this->config['hosts'] as $host) { + // skip host if connection failed + if (!$this->connect($host)) { + continue; + } + + $bind_pass = $this->config['bind_pass']; + $bind_user = $this->config['bind_user']; + $bind_dn = $this->config['bind_dn']; + + if (empty($bind_pass)) { + $this->ready = true; + } + else { + if (!empty($bind_dn)) { + $this->ready = $this->bind($bind_dn, $bind_pass); + } + else if (!empty($this->config['auth_cid'])) { + $this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_user); + } + else { + $this->ready = $this->sasl_bind($bind_user, $bind_pass); + } + } + + // connection established, we're done here + if ($this->ready) { + break; + } + + } // end foreach hosts + + if (!is_resource($this->conn)) { + rcube::raise_error(array('code' => 100, 'type' => 'ldap', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not connect to any LDAP server, last tried $host"), true); + + $this->ready = false; + } + + return $this->ready; + } + + /** + * Fetches user data from LDAP addressbook + */ + function get_user_record($user, $host) + { + $rcmail = rcube::get_instance(); + $filter = $rcmail->config->get('kolab_auth_filter'); + $filter = $this->parse_vars($filter, $user, $host); + $base_dn = $this->parse_vars($this->config['base_dn'], $user, $host); + $scope = $this->config['scope']; + + // @TODO: print error if filter is empty + + // get record + if ($result = parent::search($base_dn, $filter, $scope, $this->attributes)) { + if ($result->count() == 1) { + $entries = $result->entries(true); + $dn = key($entries); + $entry = array_pop($entries); + $entry = $this->field_mapping($dn, $entry); + + return $entry; + } + } + } + + /** + * Fetches user data from LDAP addressbook + */ + function get_user_groups($dn, $user, $host) + { + if (empty($dn) || empty($this->config['groups'])) { + return array(); + } + + $base_dn = $this->parse_vars($this->config['groups']['base_dn'], $user, $host); + $name_attr = $this->config['groups']['name_attr'] ? $this->config['groups']['name_attr'] : 'cn'; + $member_attr = $this->get_group_member_attr(); + $filter = "(member=$dn)(uniqueMember=$dn)"; + + if ($member_attr != 'member' && $member_attr != 'uniqueMember') + $filter .= "($member_attr=$dn)"; + $filter = strtr("(|$filter)", array("\\" => "\\\\")); + + $result = parent::search($base_dn, $filter, 'sub', array('dn', $name_attr)); + + if (!$result) { + return array(); + } + + $groups = array(); + foreach ($result as $entry) { + $entry = rcube_ldap_generic::normalize_entry($entry); + if (!$entry['dn']) { + $entry['dn'] = $result->get_dn(); + } + $groups[$entry['dn']] = $entry[$name_attr]; + } + + return $groups; + } + + /** + * Get a specific LDAP record + * + * @param string DN + * + * @return array Record data + */ + function get_record($dn) + { + if (!$this->ready) { + return; + } + + if ($rec = $this->get_entry($dn)) { + $rec = rcube_ldap_generic::normalize_entry($rec); + $rec = $this->field_mapping($dn, $rec); + } + + return $rec; + } + + /** + * Replace LDAP record data items + * + * @param string $dn DN + * @param array $entry LDAP entry + * + * return bool True on success, False on failure + */ + function replace($dn, $entry) + { + // fields mapping + foreach ($this->fieldmap as $field => $attr) { + if (array_key_exists($field, $entry)) { + $entry[$attr] = $entry[$field]; + unset($entry[$field]); + } + } + + return $this->mod_replace($dn, $entry); + } + + /** + * Search records (simplified version of rcube_ldap::search) + * + * @param mixed $fields The field name of array of field names to search in + * @param mixed $value Search value (or array of values when $fields is array) + * @param int $mode Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * @param boolean $select True if results are requested, False if count only + * @param array $required List of fields that cannot be empty + * @param int $limit Number of records + * + * @return array List or false on error + */ + function search($fields, $value, $mode=1, $required = array(), $limit = 0) + { + $mode = intval($mode); + + // use AND operator for advanced searches + $filter = is_array($value) ? '(&' : '(|'; + + // set wildcards + $wp = $ws = ''; + if (!empty($this->config['fuzzy_search']) && $mode != 1) { + $ws = '*'; + if (!$mode) { + $wp = '*'; + } + } + + foreach ((array)$fields as $idx => $field) { + $val = is_array($value) ? $value[$idx] : $value; + if ($attrs = (array) $this->fieldmap[$field]) { + if (count($attrs) > 1) + $filter .= '(|'; + foreach ($attrs as $f) + $filter .= "($f=$wp" . rcube_ldap_generic::quote_string($val) . "$ws)"; + if (count($attrs) > 1) + $filter .= ')'; + } + } + $filter .= ')'; + + // add required (non empty) fields filter + $req_filter = ''; + + foreach ((array)$required as $field) { + if (in_array($field, (array)$fields)) // required field is already in search filter + continue; + if ($attrs = (array) $this->fieldmap[$field]) { + if (count($attrs) > 1) + $req_filter .= '(|'; + foreach ($attrs as $f) + $req_filter .= "($f=*)"; + if (count($attrs) > 1) + $req_filter .= ')'; + } + } + + if (!empty($req_filter)) { + $filter = '(&' . $req_filter . $filter . ')'; + } + + // avoid double-wildcard if $value is empty + $filter = preg_replace('/\*+/', '*', $filter); + + // add general filter to query + if (!empty($this->config['filter'])) { + $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->config['filter']) . ')' . $filter . ')'; + } + + $base_dn = $this->parse_vars($this->config['base_dn']); + $scope = $this->config['scope']; + $attrs = array_values($this->fieldmap); + $list = array(); + + if ($result = parent::search($base_dn, $filter, $scope, $attrs)) { + $i = 0; + foreach ($result as $entry) { + if ($limit && $limit <= $i) { + break; + } + $dn = $result->get_dn(); + $list[$dn] = $this->field_mapping($dn, $entry); + $i++; + } + } + + return $list; + } + + /** + * Set filter used in search() + */ + function set_filter($filter) + { + $this->config['filter'] = $filter; + } + + /** + * Maps LDAP attributes to defined fields + */ + protected function field_mapping($dn, $entry) + { + $entry['dn'] = $dn; + + // fields mapping + foreach ($this->fieldmap as $field => $attr) { + if (isset($entry[$attr])) { + $entry[$field] = $entry[$attr]; + } + } + + return $entry; + } + + /** + * Detects group member attribute name + */ + private function get_group_member_attr($object_classes = array()) + { + if (empty($object_classes)) { + $object_classes = $this->config['groups']['object_classes']; + } + if (!empty($object_classes)) { + foreach ((array)$object_classes as $oc) { + switch (strtolower($oc)) { + case 'group': + case 'groupofnames': + case 'kolabgroupofnames': + $member_attr = 'member'; + break; + + case 'groupofuniquenames': + case 'kolabgroupofuniquenames': + $member_attr = 'uniqueMember'; + break; + } + } + } + + if (!empty($member_attr)) { + return $member_attr; + } + + if (!empty($this->config['groups']['member_attr'])) { + return $this->config['groups']['member_attr']; + } + + return 'member'; + } + + /** + * Prepares filter query for LDAP search + */ + function parse_vars($str, $user = null, $host = null) + { + // When authenticating user $user is always set + // if not set it means we use this LDAP object for other + // purposes, e.g. kolab_delegation, then username with + // correct domain is in a session + if (!$user) { + $user = $_SESSION['username']; + } + else if (isset($this->icache[$user])) { + list($user, $dc) = $this->icache[$user]; + } + else { + $orig_user = $user; + $rcmail = rcube::get_instance(); + + // get default domain + if ($username_domain = $rcmail->config->get('username_domain')) { + if ($host && is_array($username_domain) && isset($username_domain[$host])) { + $domain = rcube_utils::parse_host($username_domain[$host], $host); + } + else if (is_string($username_domain)) { + $domain = rcube_utils::parse_host($username_domain, $host); + } + } + + // realmed username (with domain) + if (strpos($user, '@')) { + list($usr, $dom) = explode('@', $user); + + // unrealm domain, user login can contain a domain alias + if ($dom != $domain && ($dc = $this->find_domain($dom))) { + // @FIXME: we should replace domain in $user, I suppose + } + } + else if ($domain) { + $user .= '@' . $domain; + } + + $this->icache[$orig_user] = array($user, $dc); + } + + // replace variables in filter + list($u, $d) = explode('@', $user); + + // hierarchal domain string + if (empty($dc)) { + $dc = 'dc=' . strtr($d, array('.' => ',dc=')); + } + + $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u); + + return strtr($str, $replaces); + } + + /** + * Find root domain for specified domain + * + * @param string $domain Domain name + * + * @return string Domain DN string + */ + function find_domain($domain) + { + if (empty($domain) || empty($this->config['domain_base_dn']) || empty($this->config['domain_filter'])) { + return null; + } + + $base_dn = $this->config['domain_base_dn']; + $filter = $this->config['domain_filter']; + $name_attr = $this->config['domain_name_attribute']; + + if (empty($name_attr)) { + $name_attr = 'associateddomain'; + } + + $filter = str_replace('%s', rcube_ldap_generic::quote_string($domain), $filter); + $result = parent::search($base_dn, $filter, 'sub', array($name_attr, 'inetdomainbasedn')); + + if (!$result) { + return null; + } + + $entries = $result->entries(true); + $entry_dn = key($entries); + $entry = $entries[$entry_dn]; + + if (is_array($entry)) { + if (!empty($entry['inetdomainbasedn'])) { + return $entry['inetdomainbasedn']; + } + + $domain = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + + return $domain ? 'dc=' . implode(',dc=', explode('.', $domain)) : null; + } + } + + /** + * HTML-safe DN string encoding + * + * @param string $str DN string + * + * @return string Encoded HTML identifier string + */ + static function dn_encode($str) + { + return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); + } + + /** + * Decodes DN string encoded with _dn_encode() + * + * @param string $str Encoded HTML identifier string + * + * @return string DN string + */ + static function dn_decode($str) + { + $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT); + return base64_decode($str); + } +}
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/localization/es_ES.inc
Added
@@ -0,0 +1,5 @@ +<?php + +$labels['loginas'] = 'Login As'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/localization/et_EE.inc
Added
@@ -0,0 +1,5 @@ +<?php + +$labels['loginas'] = 'Login As'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/localization/fr_FR.inc
Added
@@ -0,0 +1,5 @@ +<?php + +$labels['loginas'] = 'Se connecter en tant que'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/localization/ja_JP.inc
Added
@@ -0,0 +1,5 @@ +<?php + +$labels['loginas'] = 'Login As'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/localization/nl_NL.inc
Added
@@ -0,0 +1,5 @@ +<?php + +$labels['loginas'] = 'Log in als'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/localization/ru_RU.inc
Added
@@ -0,0 +1,5 @@ +<?php + +$labels['loginas'] = 'Войти как'; + +?>
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_auth/package.xml -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_auth/package.xml
Changed
@@ -18,10 +18,10 @@ <email>machniak@kolabsys.com</email> <active>yes</active> </lead> - <date>2012-10-08</date> + <date>2013-06-25</date> <version> - <release>0.4</release> - <api>0.1</api> + <release>0.7</release> + <api>0.2</api> </version> <stability> <release>stable</release> @@ -35,6 +35,10 @@ <tasks:replace from="@name@" to="name" type="package-info"/> <tasks:replace from="@package_version@" to="version" type="package-info"/> </file> + <file name="kolab_auth_ldap.php" role="php"> + <tasks:replace from="@name@" to="name" type="package-info"/> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> <file name="config.inc.php.dist" role="data"></file> <file name="LICENSE" role="data"></file>
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_folders/config.inc.php.dist -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/config.inc.php.dist
Changed
@@ -18,6 +18,10 @@ $rcmail_config['kolab_folders_note_default'] = ''; // Default Journal folder $rcmail_config['kolab_folders_journal_default'] = ''; +// Default Files folder +$rcmail_config['kolab_folders_file_default'] = ''; +// Default FreeBusy folder +$rcmail_config['kolab_folders_freebusy_default'] = ''; // INBOX folder $rcmail_config['kolab_folders_mail_inbox'] = '';
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_folders/kolab_folders.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/kolab_folders.php
Changed
@@ -26,7 +26,7 @@ { public $task = '?(?!login).*'; - public $types = array('mail', 'event', 'journal', 'task', 'note', 'contact', 'configuration'); + public $types = array('mail', 'event', 'journal', 'task', 'note', 'contact', 'configuration', 'file', 'freebusy'); public $mail_types = array('inbox', 'drafts', 'sentitems', 'outbox', 'wastebasket', 'junkemail'); private $rc; @@ -54,6 +54,9 @@ $this->add_hook('folder_delete', array($this, 'folder_save')); $this->add_hook('folder_rename', array($this, 'folder_save')); $this->add_hook('folders_list', array($this, 'folders_list')); + + // Special folders setting + $this->add_hook('preferences_save', array($this, 'prefs_save')); } /** @@ -100,25 +103,25 @@ return $args; } - $table = $args['table']; - $storage = $this->rc->get_storage(); - // get folders types - $folderdata = $storage->get_metadata('*', kolab_storage::CTYPE_KEY); + $folderdata = kolab_storage::folders_typedata(); if (!is_array($folderdata)) { return $args; } + $table = $args['table']; + // Add type-based style for table rows // See kolab_folders::folder_class_name() for ($i=1, $cnt=$table->size(); $i<=$cnt; $i++) { $attrib = $table->get_row_attribs($i); $folder = $attrib['foldername']; // UTF7-IMAP - $type = !empty($folderdata[$folder]) ? $folderdata[$folder][kolab_storage::CTYPE_KEY] : null; + $type = $folderdata[$folder]; - if (!$type) + if (!$type) { $type = 'mail'; + } $class_name = self::folder_class_name($type); @@ -178,7 +181,7 @@ // Don't allow changing type of shared folder, according to ACL if (strlen($mbox)) { $options = $storage->folder_info($mbox); - if ($options['namespace'] != 'personal' && !in_array('a', $options['rights'])) { + if ($options['namespace'] != 'personal' && !in_array('a', (array)$options['rights'])) { if (in_array($ctype, $this->types)) { $value = $this->gettext('foldertype'.$ctype); } @@ -315,6 +318,83 @@ } /** + * Handler for user preferences save (preferences_save hook) + * + * @param array $args Hash array with hook parameters + * + * @return array Hash array with modified hook parameters + */ + public function prefs_save($args) + { + if ($args['section'] != 'folders') { + return $args; + } + + // Load configuration + $this->load_config(); + + // Check that configuration is not disabled + $dont_override = (array) $this->rc->config->get('dont_override', array()); + + // special handling for 'default_folders' + if (in_array('default_folders', $dont_override)) { + return $args; + } + + // map config option name to kolab folder type annotation + $opts = array( + 'drafts_mbox' => 'mail.drafts', + 'sent_mbox' => 'mail.sentitems', + 'junk_mbox' => 'mail.junkemail', + 'trash_mbox' => 'mail.wastebasket', + ); + + // check if any of special folders has been changed + foreach ($opts as $opt_name => $type) { + $new = $args['prefs'][$opt_name]; + $old = $this->rc->config->get($opt_name); + if ($new === $old) { + unset($opts[$opt_name]); + } + } + + if (empty($opts)) { + return $args; + } + + $folderdata = kolab_storage::folders_typedata(); + + if (!is_array($folderdata)) { + return $args; + } + + foreach ($opts as $opt_name => $type) { + $foldername = $args['prefs'][$opt_name]; + if (strlen($foldername)) { + + // get all folders of specified type + $folders = array_intersect($folderdata, array($type)); + + // folder already annotated with specified type + if (!empty($folders[$foldername])) { + continue; + } + + // set type to the new folder + $this->set_folder_type($foldername, $type); + + // unset old folder(s) type annotation + list($maintype, $subtype) = explode('.', $type); + foreach (array_keys($folders) as $folder) { + $this->set_folder_type($folder, $maintype); + } + } + } + + return $args; + } + + /** * Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2 * * @return boolean @@ -337,14 +417,7 @@ */ function get_folder_type($folder) { - $storage = $this->rc->get_storage(); - $folderdata = $storage->get_metadata($folder, array(kolab_storage::CTYPE_KEY_PRIVATE, kolab_storage::CTYPE_KEY)); - - if (!($ctype = $folderdata[$folder][kolab_storage::CTYPE_KEY_PRIVATE])) { - $ctype = $folderdata[$folder][kolab_storage::CTYPE_KEY]; - } - - return explode('.', $ctype); + return explode('.', (string)kolab_storage::folder_type($folder)); } /** @@ -355,7 +428,7 @@ * * @return boolean True on success */ - function set_folder_type($folder, $type='mail') + function set_folder_type($folder, $type = 'mail') { return kolab_storage::set_folder_type($folder, $type); } @@ -369,45 +442,16 @@ */ function get_default_folder($type) { - $storage = $this->rc->get_storage(); - $folderdata = $storage->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE, kolab_storage::CTYPE_KEY)); + $folderdata = kolab_storage::folders_typedata(); if (!is_array($folderdata)) { return null; } - $type .= '.default'; - $namespace = $storage->get_namespace(); - // get all folders of specified type - $folderdata = array_map(array($this, 'folder_select_metadata'), $folderdata); - $folderdata = array_intersect($folderdata, array($type)); - - foreach ($folderdata as $folder => $data) { - // check if folder is in personal namespace - foreach (array('shared', 'other') as $nskey) { - if (!empty($namespace[$nskey])) { - foreach ($namespace[$nskey] as $ns) { - if ($ns[0] && substr($folder, 0, strlen($ns[0])) == $ns[0]) { - continue 3; - } - } - } - } - - // There can be only one default folder of specified type - return $folder; - } - - return null; - } + $folderdata = array_intersect($folderdata, array($type.'.default')); - /** - * Callback for array_map to select the correct annotation value - */ - private function folder_select_metadata($types) - { - return $types[kolab_storage::CTYPE_KEY_PRIVATE] ?: $types[kolab_storage::CTYPE_KEY]; + return key($folderdata); } /** @@ -437,26 +481,12 @@ $storage = $this->rc->get_storage(); $namespace = $storage->get_namespace(); $defaults = array(); - $need_update = false; - - if (!is_array($folderdata)) { - $folderdata = $storage->get_metadata('*', kolab_storage::CTYPE_KEY); - - if (!is_array($folderdata)) { - return; - } - - // "Flattenize" metadata array to become a name->type hash - $folderdata = array_map('implode', $folderdata); - } + $prefix = ''; // Find personal namespace prefix if (is_array($namespace['personal']) && count($namespace['personal']) == 1) { $prefix = $namespace['personal'][0][0]; } - else { - $prefix = ''; - } $this->load_config(); @@ -477,46 +507,35 @@ } } - // find default folders - foreach ($defaults as $type => $foldername) { - // folder exists, do nothing - if (!empty($folderdata[$foldername])) { - continue; - } + if (empty($defaults)) { + return; + } - // special case, need to set type only - if ($foldername == 'INBOX' || $type == 'mail.inbox') { - $this->set_folder_type($foldername, 'mail.inbox'); - continue; - } + if ($folderdata === null) { + $folderdata = kolab_storage::folders_typedata(); + } + + if (!is_array($folderdata)) { + return; + } + // find default folders + foreach ($defaults as $type => $foldername) { // get all folders of specified type - $folders = array_intersect($folderdata, array($type)); - unset($folders[0]); - - // find folders in personal namespace - foreach ($folders as $folder) { - if ($folder) { - foreach (array('shared', 'other') as $nskey) { - if (!empty($namespace[$nskey])) { - foreach ($namespace[$nskey] as $ns) { - if ($ns[0] && substr($folder, 0, strlen($ns[0])) == $ns[0]) { - continue 3; - } - } - } - } - } + $_folders = array_intersect($folderdata, array($type)); - // got folder in personal namespace - continue 2; + // default folder found + if (!empty($_folders)) { + continue; } list($type1, $type2) = explode('.', $type); + $exists = !empty($folderdata[$foldername]) || $foldername == 'INBOX'; // create folder - if ($type1 != 'mail' || !$storage->folder_exists($foldername)) { + if (!$exists && !$storage->folder_exists($foldername)) { $storage->create_folder($foldername, $type1 == 'mail'); + $storage->subscribe($foldername); } // set type
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_folders/localization/en_US.inc -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/en_US.inc
Changed
@@ -10,6 +10,8 @@ $labels['foldertypenote'] = 'Notes'; $labels['foldertypecontact'] = 'Contacts'; $labels['foldertypeconfiguration'] = 'Configuration'; +$labels['foldertypefile'] = 'Files'; +$labels['foldertypefreebusy'] = 'Free-Busy'; $labels['default'] = 'Default'; $labels['inbox'] = 'Inbox';
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/es_ES.inc
Added
@@ -0,0 +1,26 @@ +<?php + +$labels = array(); + +$labels['folderctype'] = 'Content type'; +$labels['foldertypemail'] = 'Mail'; +$labels['foldertypeevent'] = 'Calendar'; // Events? +$labels['foldertypejournal'] = 'Journal'; +$labels['foldertypetask'] = 'Tareas'; +$labels['foldertypenote'] = 'Notas'; +$labels['foldertypecontact'] = 'Contactos'; +$labels['foldertypeconfiguration'] = 'Configuración'; +$labels['foldertypefile'] = 'Files'; +$labels['foldertypefreebusy'] = 'Free-Busy'; + +$labels['default'] = 'Default'; +$labels['inbox'] = 'Inbox'; +$labels['drafts'] = 'Drafts'; +$labels['sentitems'] = 'Sent'; +$labels['outbox'] = 'Outbox'; +$labels['wastebasket'] = 'Trash'; +$labels['junkemail'] = 'Junk'; + +$messages['defaultfolderexists'] = 'There is already default folder of specified type'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/et_EE.inc
Added
@@ -0,0 +1,26 @@ +<?php + +$labels = array(); + +$labels['folderctype'] = 'Content type'; +$labels['foldertypemail'] = 'Mail'; +$labels['foldertypeevent'] = 'Calendar'; // Events? +$labels['foldertypejournal'] = 'Journal'; +$labels['foldertypetask'] = 'Tasks'; +$labels['foldertypenote'] = 'Notes'; +$labels['foldertypecontact'] = 'Contacts'; +$labels['foldertypeconfiguration'] = 'Configuration'; +$labels['foldertypefile'] = 'Files'; +$labels['foldertypefreebusy'] = 'Free-Busy'; + +$labels['default'] = 'Default'; +$labels['inbox'] = 'Inbox'; +$labels['drafts'] = 'Drafts'; +$labels['sentitems'] = 'Sent'; +$labels['outbox'] = 'Outbox'; +$labels['wastebasket'] = 'Trash'; +$labels['junkemail'] = 'Junk'; + +$messages['defaultfolderexists'] = 'There is already default folder of specified type'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/fr_FR.inc
Added
@@ -0,0 +1,26 @@ +<?php + +$labels = array(); + +$labels['folderctype'] = 'Type de contenu'; +$labels['foldertypemail'] = 'Courriel'; +$labels['foldertypeevent'] = 'Calendrier'; // Events? +$labels['foldertypejournal'] = 'Journal'; +$labels['foldertypetask'] = 'Tâches'; +$labels['foldertypenote'] = 'Notes'; +$labels['foldertypecontact'] = 'Contacts'; +$labels['foldertypeconfiguration'] = 'Configuration'; +$labels['foldertypefile'] = 'Fichiers'; +$labels['foldertypefreebusy'] = 'Disponible/Occupé'; + +$labels['default'] = 'Par Défaut'; +$labels['inbox'] = 'Courrier entrant'; +$labels['drafts'] = 'Brouillons'; +$labels['sentitems'] = 'Envoyés'; +$labels['outbox'] = 'Courrier sortant'; +$labels['wastebasket'] = 'Corbeille'; +$labels['junkemail'] = 'Indésirables'; + +$messages['defaultfolderexists'] = 'Il existe déjà un répertoire par défaut pour le type spécifié'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/ja_JP.inc
Added
@@ -0,0 +1,26 @@ +<?php + +$labels = array(); + +$labels['folderctype'] = 'Content type'; +$labels['foldertypemail'] = 'Mail'; +$labels['foldertypeevent'] = 'Calendar'; // Events? +$labels['foldertypejournal'] = 'Journal'; +$labels['foldertypetask'] = 'Tasks'; +$labels['foldertypenote'] = 'Notes'; +$labels['foldertypecontact'] = 'Contacts'; +$labels['foldertypeconfiguration'] = '設定'; +$labels['foldertypefile'] = 'Files'; +$labels['foldertypefreebusy'] = 'Free-Busy'; + +$labels['default'] = 'Default'; +$labels['inbox'] = 'Inbox'; +$labels['drafts'] = 'Drafts'; +$labels['sentitems'] = 'Sent'; +$labels['outbox'] = 'Outbox'; +$labels['wastebasket'] = 'Trash'; +$labels['junkemail'] = 'Junk'; + +$messages['defaultfolderexists'] = 'There is already default folder of specified type'; + +?>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/nl_NL.inc
Added
@@ -0,0 +1,26 @@ +<?php + +$labels = array(); + +$labels['folderctype'] = 'Inhoudstype'; +$labels['foldertypemail'] = 'Mail'; +$labels['foldertypeevent'] = 'Agenda'; // Events? +$labels['foldertypejournal'] = 'Dagboek'; +$labels['foldertypetask'] = 'Taken'; +$labels['foldertypenote'] = 'Notities'; +$labels['foldertypecontact'] = 'Adresboek'; +$labels['foldertypeconfiguration'] = 'Configuratie'; +$labels['foldertypefile'] = 'Bestanden'; +$labels['foldertypefreebusy'] = 'Free/Busy'; + +$labels['default'] = 'Standaard'; +$labels['inbox'] = 'Inbox'; +$labels['drafts'] = 'Concepten'; +$labels['sentitems'] = 'Verzonden'; +$labels['outbox'] = 'Te versturen'; +$labels['wastebasket'] = 'Prullenbak'; +$labels['junkemail'] = 'Ongewenst'; + +$messages['defaultfolderexists'] = 'Er is reeds een standaard map voor dit type inhoud'; + +?>
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_folders/localization/pl_PL.inc -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/pl_PL.inc
Changed
@@ -9,6 +9,8 @@ $labels['foldertypenote'] = 'Notatki'; $labels['foldertypecontact'] = 'Kontakty'; $labels['foldertypeconfiguration'] = 'Konfiguracja'; +$labels['foldertypefile'] = 'Pliki'; +$labels['foldertypefreebusy'] = 'Free-Busy'; $labels['default'] = 'Domyślny'; $labels['inbox'] = 'Odebrane'; $labels['drafts'] = 'Szkice';
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/localization/ru_RU.inc
Added
@@ -0,0 +1,26 @@ +<?php + +$labels = array(); + +$labels['folderctype'] = 'Тип ящика'; +$labels['foldertypemail'] = 'Почта'; +$labels['foldertypeevent'] = 'Календарь'; // Events? +$labels['foldertypejournal'] = 'Журнал'; +$labels['foldertypetask'] = 'Задачи'; +$labels['foldertypenote'] = 'Заметки'; +$labels['foldertypecontact'] = 'Контакты'; +$labels['foldertypeconfiguration'] = 'Настройки'; +$labels['foldertypefile'] = 'Файлы'; +$labels['foldertypefreebusy'] = 'Занят/Свободен'; + +$labels['default'] = 'По умолчанию'; +$labels['inbox'] = 'Входящие'; +$labels['drafts'] = 'Черновики'; +$labels['sentitems'] = 'Отправленные'; +$labels['outbox'] = 'Исходящие'; +$labels['wastebasket'] = 'Корзина'; +$labels['junkemail'] = 'Спам'; + +$messages['defaultfolderexists'] = 'Уже назначен ящик по умолчанию для указанного типа'; + +?>
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/kolab_folders/package.xml -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/kolab_folders/package.xml
Changed
@@ -21,9 +21,9 @@ <email>machniak@kolabsys.com</email> <active>yes</active> </lead> - <date>2012-05-14</date> + <date>2012-10.25</date> <version> - <release>2.0</release> + <release>2.1</release> <api>2.0</api> </version> <stability>
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/LICENSE
Added
@@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>.
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/README -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/README
Changed
@@ -15,29 +15,14 @@ * PEAR: HTTP/Request2 * PEAR: Net/URL2 -* Optional for old format support: - Horde Kolab_Format package and all of its dependencies - which are at least Horde_(Browser,DOM,NLS,String,Utils) - INSTALLATION ------------ To use local cache you need to create a dedicated table in Roundcube's database. -To do so, execute the SQL commands in SQL/<yourdatabase>.sql +To do so, execute the SQL commands in SQL/<yourdatabase>.initial.sql CONFIGURATION ------------- -The following options can be configured in Roundcube's main config file -or a local config file (config.inc.php) located in the plugin folder. - -// Enable caching of Kolab objects in local database -$rcmail_config['kolab_cache'] = true; - -// Optional override of the URL to read and trigger Free/Busy information of Kolab users -// Defaults to https://<imap-server->/freebusy -$rcmail_config['kolab_freebusy_server'] = 'https://<some-host>/<freebusy-path>'; - -// Set this option to disable SSL certificate checks when triggering Free/Busy (enabled by default) -$rcmail_config['kolab_ssl_verify_peer'] = false; - +Rename config.inc.php.dist to config.inc.php in the plugin folder. +For available configuration options see config.inc.php.dist file.
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/SQL/mysql
Added
+(directory)
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/SQL/mysql.initial.sql
Added
@@ -0,0 +1,29 @@ +/** + * libkolab database schema + * + * @version @package_version@ + * @author Thomas Bruederli + * @licence GNU AGPL + **/ + +DROP TABLE IF EXISTS `kolab_cache`; + +CREATE TABLE `kolab_cache` ( + `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL, + `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `filename` varchar(255) DEFAULT NULL, + PRIMARY KEY(`resource`,`type`,`msguid`), + INDEX `resource_filename` (`resource`, `filename`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013041900');
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/SQL/mysql/2013011000.sql
Added
@@ -0,0 +1,1 @@ +-- empty \ No newline at end of file
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/SQL/mysql/2013041900.sql
Added
@@ -0,0 +1,3 @@ +DELETE FROM `kolab_cache` WHERE `type` = 'file'; +ALTER TABLE `kolab_cache` ADD `filename` varchar(255) DEFAULT NULL; +ALTER TABLE `kolab_cache` ADD INDEX `resource_filename` (`resource`, `filename`);
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/SQL/postgres.initial.sql
Added
@@ -0,0 +1,31 @@ +/** + * libkolab database schema + * + * @version @package_version@ + * @author Sidlyarenko Sergey + * @licence GNU AGPL + **/ + +DROP TABLE IF EXISTS kolab_cache; + +CREATE TABLE kolab_cache ( + resource character varying(255) NOT NULL, + type character varying(32) NOT NULL, + msguid NUMERIC(20) NOT NULL, + uid character varying(128) NOT NULL, + created timestamp without time zone DEFAULT NULL, + changed timestamp without time zone DEFAULT NULL, + data text NOT NULL, + xml text NOT NULL, + dtstart timestamp without time zone, + dtend timestamp without time zone, + tags character varying(255) NOT NULL, + words text NOT NULL, + filename character varying(255) DEFAULT NULL, + PRIMARY KEY(resource, type, msguid) +); + +CREATE INDEX kolab_cache_resource_filename_idx ON kolab_cache (resource, filename); + + +INSERT INTO system (name, value) VALUES ('libkolab-version', '2013041900');
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/UPGRADING
Added
@@ -0,0 +1,9 @@ +UPGRADING instructions +====================== + +To update database schema please run in Roundcube bin/ directory: + +updatedb.sh --package=libkolab --version=<version> --dir=../plugins/libkolab/SQL + +[*] Replace <version> with Roundcube version e.g. 0.7.3 +[*] Roundcube should be upgraded before plugin upgrades
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/bin/modcache.sh -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/bin/modcache.sh
Changed
@@ -81,7 +81,7 @@ if (!$db->is_connected() || $db->is_error()) die("No DB connection\n"); - $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','distribution-list','event','task','configuration'); + $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','distribution-list','event','task','configuration','file'); $folder_types_db = array_map(array($db, 'quote'), $folder_types); if ($opts['all']) { @@ -106,7 +106,7 @@ $rcmail->plugins->load_plugin('libkolab'); if (authenticate($opts)) { - $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','event','task','configuration'); + $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','event','task','configuration','file'); foreach ($folder_types as $type) { // sync every folder of the given type foreach (kolab_storage::get_folders($type) as $folder) {
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/config.inc.php.dist -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/config.inc.php.dist
Changed
@@ -1,9 +1,26 @@ <?php - /* Configuration for libkolab */ - $rcmail_config['kolab_cache'] = true; +/* Configuration for libkolab */ - $rcmail_config['kolab_freebusy_server'] = 'https://' . $_SESSION['imap_host'] . '/freebusy'; - $rcmail_config['kolab_ssl_verify_peer'] = true; +// Enable caching of Kolab objects in local database +$rcmail_config['kolab_cache'] = true; + +// Specify format version to write Kolab objects (must be a string value!) +$rcmail_config['kolab_format_version'] = '3.0'; + +// Optional override of the URL to read and trigger Free/Busy information of Kolab users +// Defaults to https://<imap-server->/freebusy +$rcmail_config['kolab_freebusy_server'] = 'https://<some-host>/<freebusy-path>'; + +// Set this option to disable SSL certificate checks when triggering Free/Busy (enabled by default) +$rcmail_config['kolab_ssl_verify_peer'] = false; + +// Enables listing of only subscribed folders. This e.g. will limit +// folders in calendar view or available addressbooks +$rcmail_config['kolab_use_subscriptions'] = false; + +// Enables the use of displayname folder annotations as introduced in KEP:? +// for displaying resource folder names (experimental!) +$rcmail_config['kolab_custom_display_names'] = false; ?>
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_date_recurrence.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_date_recurrence.php
Changed
@@ -3,7 +3,8 @@ /** * Recurrence computation class for xcal-based Kolab format objects * - * Uitility class to compute instances of recurring events. + * Utility class to compute instances of recurring events. + * It requires the libcalendaring PHP module to be installed and loaded. * * @version @package_version@ * @author Thomas Bruederli <bruederli@kolabsys.com> @@ -25,14 +26,12 @@ */ class kolab_date_recurrence { - private $engine; - private $object; - private $next; - private $duration; - private $tz_offset = 0; - private $dst_start = 0; - private $allday = false; - private $hour = 0; + private /* EventCal */ $engine; + private /* kolab_format_xcal */ $object; + private /* DateTime */ $start; + private /* DateTime */ $next; + private /* cDateTime */ $cnext; + private /* DateInterval */ $duration; /** * Default constructor @@ -41,27 +40,17 @@ */ function __construct($object) { + $data = $object->to_array(); + $this->object = $object; - $this->next = new Horde_Date($object['start'], kolab_format::$timezone->getName()); + $this->engine = $object->to_libcal(); + $this->start = $this->next = $data['start']; + $this->cnext = kolab_format::get_datetime($this->next); - if (is_object($object['start']) && is_object($object['end'])) - $this->duration = $object['start']->diff($object['end']); + if (is_object($data['start']) && is_object($data['end'])) + $this->duration = $data['start']->diff($data['end']); else - $this->duration = new DateInterval('PT' . ($object['end'] - $object['start']) . 'S'); - - // use (copied) Horde classes to compute recurring instances - // TODO: replace with something that has less than 6'000 lines of code - $this->engine = new Horde_Date_Recurrence($this->next); - $this->engine->fromRRule20($this->to_rrule($object['recurrence'])); // TODO: get that string directly from libkolabxml - - foreach ((array)$object['recurrence']['EXDATE'] as $exdate) - $this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j')); - - $now = new DateTime('now', kolab_format::$timezone); - $this->tz_offset = $object['allday'] ? $now->getOffset() - date('Z') : 0; - $this->dst_start = $this->next->format('I'); - $this->allday = $object['allday']; - $this->hour = $this->next->hour; + $this->duration = new DateInterval('PT' . ($data['end'] - $data['start']) . 'S'); } /** @@ -73,20 +62,14 @@ public function next_start($timestamp = false) { $time = false; - if ($this->next && ($next = $this->engine->nextActiveRecurrence(array('year' => $this->next->year, 'month' => $this->next->month, 'mday' => $this->next->mday + 1, 'hour' => $this->next->hour, 'min' => $this->next->min, 'sec' => $this->next->sec)))) { - if ($this->allday) { - $next->hour = $this->hour; # fix time for all-day events - $next->min = 0; - } - if ($timestamp) { - # consider difference in daylight saving between base event and recurring instance - $dst_diff = ($this->dst_start - $next->format('I')) * 3600; - $time = $next->timestamp() - $this->tz_offset - $dst_diff; - } - else { - $time = $next->toDateTime(); + + if ($this->engine && $this->next) { + if (($cnext = new cDateTime($this->engine->getNextOccurence($this->cnext))) && $cnext->isValid()) { + $next = kolab_format::php_datetime($cnext); + $time = $timestamp ? $next->format('U') : $next; + $this->cnext = $cnext; + $this->next = $next; } - $this->next = $next; } return $time; @@ -103,7 +86,7 @@ $next_end = clone $next_start; $next_end->add($this->duration); - $next = $this->object; + $next = $this->object->to_array(); $next['recurrence_id'] = $next_start->format('Y-m-d'); $next['start'] = $next_start; $next['end'] = $next_end; @@ -123,49 +106,11 @@ */ public function end($limit = 'now +1 year') { - if ($this->object['recurrence']['UNTIL']) - return $this->object['recurrence']['UNTIL']->format('U'); - - $limit_time = strtotime($limit); - while ($next_start = $this->next_start(true)) { - if ($next_start > $limit_time) - break; - } - - if ($this->next) { - $next_end = $this->next->toDateTime(); - $next_end->add($this->duration); - return $next_end->format('U'); + $limit_dt = new DateTime($limit); + if ($this->engine && ($cend = $this->engine->getLastOccurrence()) && ($end_dt = kolab_format::php_datetime(new cDateTime($cend))) && $end_dt < $limit_dt) { + return $end_dt->format('U'); } return false; } - - /** - * Convert the internal structured data into a vcalendar RRULE 2.0 string - */ - private function to_rrule($recurrence) - { - if (is_string($recurrence)) - return $recurrence; - - $rrule = ''; - foreach ((array)$recurrence as $k => $val) { - $k = strtoupper($k); - switch ($k) { - case 'UNTIL': - $val = $val->format('Ymd\THis'); - break; - case 'EXDATE': - foreach ((array)$val as $i => $ex) - $val[$i] = $ex->format('Ymd\THis'); - $val = join(',', (array)$val); - break; - } - $rrule .= $k . '=' . $val . ';'; - } - - return $rrule; - } - }
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format.php
Changed
@@ -30,40 +30,62 @@ public static $timezone; public /*abstract*/ $CTYPE; + public /*abstract*/ $CTYPEv2; + protected /*abstract*/ $objclass; protected /*abstract*/ $read_func; protected /*abstract*/ $write_func; protected $obj; protected $data; protected $xmldata; + protected $xmlobject; protected $loaded = false; + protected $version = '3.0'; - const VERSION = '3.0'; const KTYPE_PREFIX = 'application/x-vnd.kolab.'; + const PRODUCT_ID = 'Roundcube-libkolab-0.9'; /** - * Factory method to instantiate a kolab_format object of the given type + * Factory method to instantiate a kolab_format object of the given type and version * * @param string Object type to instantiate + * @param float Format version * @param string Cached xml data to initialize with * @return object kolab_format */ - public static function factory($type, $xmldata = null) + public static function factory($type, $version = '3.0', $xmldata = null) { if (!isset(self::$timezone)) self::$timezone = new DateTimeZone('UTC'); + if (!self::supports($version)) + return PEAR::raiseError("No support for Kolab format version " . $version); + $type = preg_replace('/configuration\.[a-z.]+$/', 'configuration', $type); $suffix = preg_replace('/[^a-z]+/', '', $type); $classname = 'kolab_format_' . $suffix; if (class_exists($classname)) - return new $classname($xmldata); + return new $classname($xmldata, $version); return PEAR::raiseError("Failed to load Kolab Format wrapper for type " . $type); } /** + * Determine support for the given format version + * + * @param float Format version to check + * @return boolean True if supported, False otherwise + */ + public static function supports($version) + { + if ($version == '2.0') + return class_exists('kolabobject'); + // default is version 3 + return class_exists('kolabformat'); + } + + /** * Convert the given date/time value into a cDateTime object * * @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object @@ -184,6 +206,23 @@ return preg_replace('/dictionary.[a-z.]+$/', 'dictionary', substr($x_kolab_type, strlen(self::KTYPE_PREFIX))); } + + /** + * Default constructor of all kolab_format_* objects + */ + public function __construct($xmldata = null, $version = null) + { + $this->obj = new $this->objclass; + $this->xmldata = $xmldata; + + if ($version) + $this->version = $version; + + // use libkolab module if available + if (class_exists('kolabobject')) + $this->xmlobject = new XMLObject(); + } + /** * Check for format errors after calling kolabformat::write*() * @@ -211,7 +250,7 @@ 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "kolabformat write $log: " . kolabformat::errorMessage(), + 'message' => "kolabformat $log: " . kolabformat::errorMessage(), ), true); } @@ -226,7 +265,12 @@ { // get generated UID if (!$this->data['uid']) { - $this->data['uid'] = kolabformat::getSerializedUID(); + if ($this->xmlobject) { + $this->data['uid'] = $this->xmlobject->getSerializedUID(); + } + if (empty($this->data['uid'])) { + $this->data['uid'] = kolabformat::getSerializedUID(); + } $this->obj->setUid($this->data['uid']); } } @@ -246,6 +290,39 @@ } /** + * Get constant value for libkolab's version parameter + * + * @param float Version value to convert + * @return int Constant value of either kolabobject::KolabV2 or kolabobject::KolabV3 or false if kolabobject module isn't available + */ + protected function libversion($v = null) + { + if (class_exists('kolabobject')) { + $version = $v ?: $this->version; + if ($version <= '2.0') + return kolabobject::KolabV2; + else + return kolabobject::KolabV3; + } + + return false; + } + + /** + * Determine the correct libkolab(xml) wrapper function for the given call + * depending on the available PHP modules + */ + protected function libfunc($func) + { + if (is_array($func) || strpos($func, '::')) + return $func; + else if (class_exists('kolabobject')) + return array($this->xmlobject, $func); + else + return 'kolabformat::' . $func; + } + + /** * Direct getter for object properties */ public function __get($var) @@ -257,22 +334,39 @@ * Load Kolab object data from the given XML block * * @param string XML data + * @return boolean True on success, False on failure */ public function load($xml) { - $this->obj = call_user_func($this->read_func, $xml, false); + $read_func = $this->libfunc($this->read_func); + + if (is_array($read_func)) + $r = call_user_func($read_func, $xml, $this->libversion()); + else + $r = call_user_func($read_func, $xml, false); + + if (is_resource($r)) + $this->obj = new $this->objclass($r); + else if (is_a($r, $this->objclass)) + $this->obj = $r; + $this->loaded = !$this->format_errors(); } /** * Write object data to XML format * + * @param float Format version to write * @return string XML data */ - public function write() + public function write($version = null) { $this->init(); - $this->xmldata = call_user_func($this->write_func, $this->obj); + $write_func = $this->libfunc($this->write_func); + if (is_array($write_func)) + $this->xmldata = call_user_func($write_func, $this->obj, $this->libversion($version), self::PRODUCT_ID); + else + $this->xmldata = call_user_func($write_func, $this->obj, self::PRODUCT_ID); if (!$this->format_errors()) $this->update_uid(); @@ -287,26 +381,82 @@ * * @param array Object data as hash array */ - abstract public function set(&$object); + public function set(&$object) + { + $this->init(); - /** - * - */ - abstract public function is_valid(); + if (!empty($object['uid'])) + $this->obj->setUid($object['uid']); + + // set some automatic values if missing + if (method_exists($this->obj, 'setCreated') && !$this->obj->created()) { + if (empty($object['created'])) + $object['created'] = new DateTime('now', self::$timezone); + $this->obj->setCreated(self::get_datetime($object['created'])); + } + + $object['changed'] = new DateTime('now', self::$timezone); + $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + + // Save custom properties of the given object + if (!empty($object['x-custom'])) { + $vcustom = new vectorcs; + foreach ($object['x-custom'] as $cp) { + if (is_array($cp)) + $vcustom->push(new CustomProperty($cp[0], $cp[1])); + } + $this->obj->setCustomProperties($vcustom); + } + } /** * Convert the Kolab object into a hash array data structure * + * @param array Additional data for merge + * * @return array Kolab object data as hash array */ - abstract public function to_array(); + public function to_array($data = array()) + { + $this->init(); + + // read object properties into local data object + $object = array( + 'uid' => $this->obj->uid(), + 'changed' => self::php_datetime($this->obj->lastModified()), + ); + + // not all container support the created property + if (method_exists($this->obj, 'created')) { + $object['created'] = self::php_datetime($this->obj->created()); + } + + // read custom properties + $vcustom = $this->obj->customProperties(); + for ($i=0; $i < $vcustom->size(); $i++) { + $cp = $vcustom->get($i); + $object['x-custom'][] = array($cp->identifier, $cp->value); + } + + // merge with additional data, e.g. attachments from the message + if ($data) { + foreach ($data as $idx => $value) { + if (is_array($value)) { + $object[$idx] = array_merge((array)$object[$idx], $value); + } + else { + $object[$idx] = $value; + } + } + } + + return $object; + } /** - * Load object data from Kolab2 format - * - * @param array Hash array with object properties (produced by Horde Kolab_Format classes) + * Object validation method to be implemented by derived classes */ - abstract public function fromkolab2($object); + abstract public function is_valid(); /** * Callback for kolab_storage_cache to get object specific tags to cache
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_configuration.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_configuration.php
Changed
@@ -25,9 +25,11 @@ class kolab_format_configuration extends kolab_format { public $CTYPE = 'application/x-vnd.kolab.configuration'; + public $CTYPEv2 = 'application/x-vnd.kolab.configuration'; - protected $read_func = 'kolabformat::readConfiguration'; - protected $write_func = 'kolabformat::writeConfiguration'; + protected $objclass = 'Configuration'; + protected $read_func = 'readConfiguration'; + protected $write_func = 'writeConfiguration'; private $type_map = array( 'dictionary' => Configuration::TypeDictionary, @@ -35,12 +37,6 @@ ); - function __construct($xmldata = null) - { - $this->obj = new Configuration; - $this->xmldata = $xmldata; - } - /** * Set properties to the kolabformat object * @@ -48,7 +44,8 @@ */ public function set(&$object) { - $this->init(); + // set common object properties + parent::set($object); // read type-specific properties switch ($object['type']) { @@ -67,14 +64,8 @@ return false; } - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - if (!empty($object['created'])) - $this->obj->setCreated(self::get_datetime($object['created'])); - // adjust content-type string - $this->CTYPE = 'application/x-vnd.kolab.configuration.' . $object['type']; + $this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; // cache this data $this->data = $object; @@ -92,24 +83,22 @@ /** * Convert the Configuration object into a hash array data structure * + * @param array Additional data for merge + * * @return array Config object data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); + // read common object props into local data object + $object = parent::to_array($data); + $type_map = array_flip($this->type_map); - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - 'type' => $type_map[$this->obj->type()], - ); + $object['type'] = $type_map[$this->obj->type()]; // read type-specific properties switch ($object['type']) { @@ -126,26 +115,13 @@ // adjust content-type string if ($object['type']) - $this->CTYPE = 'application/x-vnd.kolab.configuration.' . $object['type']; + $this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; $this->data = $object; return $this->data; } /** - * Load data from old Kolab2 format - */ - public function fromkolab2($record) - { - $object = array( - 'uid' => $record['uid'], - 'changed' => $record['last-modification-date'], - ); - - $this->data = $object + $record; - } - - /** * Callback for kolab_storage_cache to get object specific tags to cache * * @return array List of tags to save in cache
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_contact.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_contact.php
Changed
@@ -25,11 +25,13 @@ class kolab_format_contact extends kolab_format { public $CTYPE = 'application/vcard+xml'; + public $CTYPEv2 = 'application/x-vnd.kolab.contact'; - protected $read_func = 'kolabformat::readContact'; - protected $write_func = 'kolabformat::writeContact'; + protected $objclass = 'Contact'; + protected $read_func = 'readContact'; + protected $write_func = 'writeContact'; - public static $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'email'); + public static $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'email:address'); public $phonetypes = array( 'home' => Telephone::Home, @@ -45,6 +47,12 @@ 'other' => Telephone::Textphone, ); + public $emailtypes = array( + 'home' => Email::Home, + 'work' => Email::Work, + 'other' => Email::NoType, + ); + public $addresstypes = array( 'home' => Address::Home, 'work' => Address::Work, @@ -63,53 +71,13 @@ 'children' => Related::Child, ); - // old Kolab 2 format field map - private $kolab2_fieldmap = array( - // kolab => roundcube - 'full-name' => 'name', - 'given-name' => 'firstname', - 'middle-names' => 'middlename', - 'last-name' => 'surname', - 'prefix' => 'prefix', - 'suffix' => 'suffix', - 'nick-name' => 'nickname', - 'organization' => 'organization', - 'department' => 'department', - 'job-title' => 'jobtitle', - 'birthday' => 'birthday', - 'anniversary' => 'anniversary', - 'phone' => 'phone', - 'im-address' => 'im', - 'web-page' => 'website', - 'profession' => 'profession', - 'manager-name' => 'manager', - 'assistant' => 'assistant', - 'spouse-name' => 'spouse', - 'children' => 'children', - 'body' => 'notes', - 'pgp-publickey' => 'pgppublickey', - 'free-busy-url' => 'freebusyurl', - 'picture' => 'photo', - ); - private $kolab2_phonetypes = array( - 'home1' => 'home', - 'business1' => 'work', - 'business2' => 'work', - 'businessfax' => 'workfax', - ); - private $kolab2_addresstypes = array( - 'business' => 'work' - ); - private $kolab2_gender = array(0 => 'male', 1 => 'female'); - /** * Default constructor */ - function __construct($xmldata = null) + function __construct($xmldata = null, $version = 3.0) { - $this->obj = new Contact; - $this->xmldata = $xmldata; + parent::__construct($xmldata, $version); // complete phone types $this->phonetypes['homefax'] |= Telephone::Home; @@ -123,20 +91,8 @@ */ public function set(&$object) { - $this->init(); - - // set some automatic values if missing - if (false && !$this->obj->created()) { - if (!empty($object['created'])) - $object['created'] = new DateTime('now', self::$timezone); - $this->obj->setCreated(self::get_datetime($object['created'])); - } - - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + // set common object properties + parent::set($object); // do the hard work of setting object values $nc = new NameComponents; @@ -147,6 +103,7 @@ $nc->setSuffixes(self::array2vector($object['suffix'])); $this->obj->setNameComponents($nc); $this->obj->setName($object['name']); + $this->obj->setCategories(self::array2vector($object['categories'])); if (isset($object['nickname'])) $this->obj->setNickNames(self::array2vector($object['nickname'])); @@ -174,10 +131,21 @@ } $org->setRelateds($rels); - // email, im, url - $this->obj->setEmailAddresses(self::array2vector($object['email'])); + // im, email, url $this->obj->setIMaddresses(self::array2vector($object['im'])); + if (class_exists('vectoremail')) { + $vemails = new vectoremail; + foreach ((array)$object['email'] as $email) { + $type = $this->emailtypes[$email['type']]; + $vemails->push(new Email($email['address'], intval($type))); + } + } + else { + $vemails = self::array2vector(array_map(function($v){ return $v['address']; }, $object['email'])); + } + $this->obj->setEmailAddresses($vemails); + $vurls = new vectorurl; foreach ((array)$object['website'] as $url) { $type = $url['type'] == 'blog' ? Url::Blog : Url::NoType; @@ -287,6 +255,8 @@ // TODO: handle language, gpslocation, etc. + // set type property for proper caching + $object['_type'] = 'contact'; // cache this data $this->data = $object; @@ -304,22 +274,20 @@ /** * Convert the Contact object into a hash array data structure * + * @param array Additional data for merge + * * @return array Contact data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); + // read common object props into local data object + $object = parent::to_array($data); - // read object properties into local data object - $object = array( - 'uid' => $this->obj->uid(), - 'name' => $this->obj->name(), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); + $object['name'] = $this->obj->name(); $nc = $this->obj->nameComponents(); $object['surname'] = join(' ', self::vector2array($nc->surnames())); @@ -329,6 +297,7 @@ $object['suffix'] = join(' ', self::vector2array($nc->suffixes())); $object['nickname'] = join(' ', self::vector2array($this->obj->nickNames())); $object['profession'] = join(' ', self::vector2array($this->obj->titles())); + $object['categories'] = self::vector2array($this->obj->categories()); // organisation related properties (affiliation) $orgs = $this->obj->affiliations(); @@ -340,8 +309,19 @@ $this->read_relateds($org->relateds(), $object); } - $object['email'] = self::vector2array($this->obj->emailAddresses()); - $object['im'] = self::vector2array($this->obj->imAddresses()); + $object['im'] = self::vector2array($this->obj->imAddresses()); + + $emails = $this->obj->emailAddresses(); + if ($emails instanceof vectoremail) { + $emailtypes = array_flip($this->emailtypes); + for ($i=0; $i < $emails->size(); $i++) { + $email = $emails->get($i); + $object['email'][] = array('address' => $email->address(), 'type' => $emailtypes[$email->types()]); + } + } + else { + $object['email'] = self::vector2array($emails); + } $urls = $this->obj->urls(); for ($i=0; $i < $urls->size(); $i++) { @@ -378,6 +358,8 @@ if ($this->obj->photoMimetype()) $object['photo'] = $this->obj->photo(); + else if ($this->xmlobject && ($photo_name = $this->xmlobject->pictureAttachmentName())) + $object['photo'] = $photo_name; // relateds -> spouse, children $this->read_relateds($this->obj->relateds(), $object); @@ -404,8 +386,19 @@ public function get_words() { $data = ''; - foreach (self::$fulltext_cols as $col) { - $val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col]; + foreach (self::$fulltext_cols as $colname) { + list($col, $field) = explode(':', $colname); + + if ($field) { + $a = array(); + foreach ((array)$this->data[$col] as $attr) + $a[] = $attr[$field]; + $val = join(' ', $a); + } + else { + $val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col]; + } + if (strlen($val)) $data .= $val . ' '; } @@ -414,58 +407,6 @@ } /** - * Load data from old Kolab2 format - * - * @param array Hash array with object properties - */ - public function fromkolab2($record) - { - $object = array( - 'uid' => $record['uid'], - 'email' => array(), - 'phone' => array(), - ); - - foreach ($this->kolab2_fieldmap as $kolab => $rcube) { - if (is_array($record[$kolab]) || strlen($record[$kolab])) - $object[$rcube] = $record[$kolab]; - } - - if (isset($record['gender'])) - $object['gender'] = $this->kolab2_gender[$record['gender']]; - - foreach ((array)$record['email'] as $i => $email) - $object['email'][] = $email['smtp-address']; - - if (!$record['email'] && $record['emails']) - $object['email'] = preg_split('/,\s*/', $record['emails']); - - if (is_array($record['address'])) { - foreach ($record['address'] as $i => $adr) { - $object['address'][] = array( - 'type' => $this->kolab2_addresstypes[$adr['type']] ? $this->kolab2_addresstypes[$adr['type']] : $adr['type'], - 'street' => $adr['street'], - 'locality' => $adr['locality'], - 'code' => $adr['postal-code'], - 'region' => $adr['region'], - 'country' => $adr['country'], - ); - } - } - - // office location goes into an address block - if ($record['office-location']) - $object['address'][] = array('type' => 'office', 'locality' => $record['office-location']); - - // merge initials into nickname - if ($record['initials']) - $object['nickname'] = trim($object['nickname'] . ', ' . $record['initials'], ', '); - - // remove empty fields - $this->data = array_filter($object); - } - - /** * Helper method to copy contents of an Address vector to the contact data object */ private function read_addresses($addresses, &$object, $type = null)
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_distributionlist.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_distributionlist.php
Changed
@@ -25,17 +25,13 @@ class kolab_format_distributionlist extends kolab_format { public $CTYPE = 'application/vcard+xml'; + public $CTYPEv2 = 'application/x-vnd.kolab.distribution-list'; - protected $read_func = 'kolabformat::readDistlist'; - protected $write_func = 'kolabformat::writeDistlist'; + protected $objclass = 'DistList'; + protected $read_func = 'readDistlist'; + protected $write_func = 'writeDistlist'; - function __construct($xmldata = null) - { - $this->obj = new DistList; - $this->xmldata = $xmldata; - } - /** * Set properties to the kolabformat object * @@ -43,18 +39,11 @@ */ public function set(&$object) { - $this->init(); - - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + // set common object properties + parent::set($object); $this->obj->setName($object['name']); - $seen = array(); $members = new vectorcontactref; foreach ((array)$object['member'] as $member) { if ($member['uid']) @@ -66,7 +55,6 @@ $m->setName($member['name']); $members->push($m); - $seen[$member['email']]++; } $this->obj->setMembers($members); @@ -85,45 +73,23 @@ } /** - * Load data from old Kolab2 format - */ - public function fromkolab2($record) - { - $object = array( - 'uid' => $record['uid'], - 'changed' => $record['last-modification-date'], - 'name' => $record['last-name'], - 'member' => array(), - ); - - foreach ((array)$record['member'] as $member) { - $object['member'][] = array( - 'email' => $member['smtp-address'], - 'name' => $member['display-name'], - 'uid' => $member['uid'], - ); - } - - $this->data = $object; - } - - /** * Convert the Distlist object into a hash array data structure * + * @param array Additional data for merge + * * @return array Distribution list data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); + // read common object props into local data object + $object = parent::to_array($data); - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'changed' => self::php_datetime($this->obj->lastModified()), + // add object properties + $object += array( 'name' => $this->obj->name(), 'member' => array(), '_type' => 'distribution-list', @@ -132,11 +98,11 @@ $members = $this->obj->members(); for ($i=0; $i < $members->size(); $i++) { $member = $members->get($i); -# if ($member->type() == ContactReference::UidReference && ($uid = $member->uid())) +// if ($member->type() == ContactReference::UidReference && ($uid = $member->uid())) $object['member'][] = array( - 'uid' => $member->uid(), + 'uid' => $member->uid(), 'email' => $member->email(), - 'name' => $member->name(), + 'name' => $member->name(), ); }
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_event.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_event.php
Changed
@@ -24,31 +24,47 @@ class kolab_format_event extends kolab_format_xcal { - protected $read_func = 'kolabformat::readEvent'; - protected $write_func = 'kolabformat::writeEvent'; - - private $kolab2_rolemap = array( - 'required' => 'REQ-PARTICIPANT', - 'optional' => 'OPT-PARTICIPANT', - 'resource' => 'CHAIR', - ); - private $kolab2_statusmap = array( - 'none' => 'NEEDS-ACTION', - 'tentative' => 'TENTATIVE', - 'accepted' => 'CONFIRMED', - 'accepted' => 'ACCEPTED', - 'declined' => 'DECLINED', - ); - private $kolab2_monthmap = array('', 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'); + public $CTYPEv2 = 'application/x-vnd.kolab.event'; + protected $objclass = 'Event'; + protected $read_func = 'readEvent'; + protected $write_func = 'writeEvent'; /** * Default constructor */ - function __construct($xmldata = null) + function __construct($data = null, $version = 3.0) { - $this->obj = new Event; - $this->xmldata = $xmldata; + parent::__construct(is_string($data) ? $data : null, $version); + + // got an Event object as argument + if (is_object($data) && is_a($data, $this->objclass)) { + $this->obj = $data; + $this->loaded = true; + } + } + + /** + * Clones into an instance of libcalendaring's extended EventCal class + * + * @return mixed EventCal object or false on failure + */ + public function to_libcal() + { + static $error_logged = false; + + if (class_exists('kolabcalendaring')) { + return new EventCal($this->obj); + } + else if (!$error_logged) { + $error_logged = true; + rcube::raise_error(array( + 'code' => 900, 'type' => 'php', + 'message' => "required kolabcalendaring module not found" + ), true); + } + + return false; } /** @@ -58,8 +74,6 @@ */ public function set(&$object) { - $this->init(); - // set common xcal properties parent::set($object); @@ -75,17 +89,17 @@ $status = kolabformat::StatusCancelled; $this->obj->setStatus($status); - // save attachments - $vattach = new vectorattachment; - foreach ((array)$object['_attachments'] as $cid => $attr) { - if (empty($attr)) - continue; - $attach = new Attachment; - $attach->setLabel((string)$attr['name']); - $attach->setUri('cid:' . $cid, $attr['mimetype']); - $vattach->push($attach); + // save recurrence exceptions + if ($object['recurrence']['EXCEPTIONS']) { + $vexceptions = new vectorevent; + foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) { + $exevent = new kolab_format_event; + $exevent->set($this->compact_exception($exception, $object)); // only save differing values + $exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), (bool)$exception['thisandfuture']); + $vexceptions->push($exevent->obj); + } + $this->obj->setExceptions($vexceptions); } - $this->obj->setAttachments($vattach); // cache this data $this->data = $object; @@ -103,18 +117,18 @@ /** * Convert the Event object into a hash array data structure * + * @param array Additional data for merge + * * @return array Event data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); - // read common xcal props - $object = parent::to_array(); + $object = parent::to_array($data); // read object properties $object += array( @@ -135,28 +149,25 @@ if ($status == kolabformat::StatusTentative) $object['free_busy'] = 'tentative'; else if ($status == kolabformat::StatusCancelled) - $objec['cancelled'] = true; - - // handle attachments - $vattach = $this->obj->attachments(); - for ($i=0; $i < $vattach->size(); $i++) { - $attach = $vattach->get($i); - - // skip cid: attachments which are mime message parts handled by kolab_storage_folder - if (substr($attach->uri(), 0, 4) != 'cid') { - $name = $attach->label(); - $data = $attach->data(); - $object['_attachments'][$name] = array( - 'name' => $name, - 'mimetype' => $attach->mimetype(), - 'size' => strlen($data), - 'content' => $data, - ); + $object['cancelled'] = true; + + // read exception event objects + if (($exceptions = $this->obj->exceptions()) && is_object($exceptions) && $exceptions->size()) { + for ($i=0; $i < $exceptions->size(); $i++) { + if (($exobj = $exceptions->get($i))) { + $exception = new kolab_format_event($exobj); + if ($exception->is_valid()) { + $object['recurrence']['EXCEPTIONS'][] = $this->expand_exception($exception->to_array(), $object); + } + } } } + // this is an exception object + else if ($this->obj->recurrenceID()->isValid()) { + $object['thisandfuture'] = $this->obj->thisAndFuture(); + } - $this->data = $object; - return $this->data; + return $this->data = $object; } /** @@ -180,124 +191,32 @@ } /** - * Load data from old Kolab2 format + * Remove some attributes from the exception container */ - public function fromkolab2($rec) + private function compact_exception($exception, $master) { - if (PEAR::isError($rec)) - return; - - $start_time = date('H:i:s', $rec['start-date']); - $allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date'])); - - // in Roundcube all-day events go from 12:00 to 13:00 - if ($allday) { - $now = new DateTime('now', self::$timezone); - $gmt_offset = $now->getOffset(); + $forbidden = array('recurrence','organizer','attendees','sequence'); - $rec['start-date'] += 12 * 3600; - $rec['end-date'] -= 11 * 3600; - $rec['end-date'] -= $gmt_offset - date('Z', $rec['end-date']); // shift times from server's timezone to user's timezone - $rec['start-date'] -= $gmt_offset - date('Z', $rec['start-date']); // because generated with mktime() in Horde_Kolab_Format_Date::decodeDate() - // sanity check - if ($rec['end-date'] <= $rec['start-date']) - $rec['end-date'] += 86400; + foreach ($forbidden as $prop) { + if (array_key_exists($prop, $exception)) { + unset($exception[$prop]); } + } - // convert alarm time into internal format - if ($rec['alarm']) { - $alarm_value = $rec['alarm']; - $alarm_unit = 'M'; - if ($rec['alarm'] % 1440 == 0) { - $alarm_value /= 1440; - $alarm_unit = 'D'; - } - else if ($rec['alarm'] % 60 == 0) { - $alarm_value /= 60; - $alarm_unit = 'H'; - } - $alarm_value *= -1; - } - - // convert recurrence rules into internal pseudo-vcalendar format - if ($recurrence = $rec['recurrence']) { - $rrule = array( - 'FREQ' => strtoupper($recurrence['cycle']), - 'INTERVAL' => intval($recurrence['interval']), - ); - - if ($recurrence['range-type'] == 'number') - $rrule['COUNT'] = intval($recurrence['range']); - else if ($recurrence['range-type'] == 'date') - $rrule['UNTIL'] = date_create('@'.$recurrence['range']); - - if ($recurrence['day']) { - $byday = array(); - $prefix = ($rrule['FREQ'] == 'MONTHLY' || $rrule['FREQ'] == 'YEARLY') ? intval($recurrence['daynumber'] ? $recurrence['daynumber'] : 1) : ''; - foreach ($recurrence['day'] as $day) - $byday[] = $prefix . substr(strtoupper($day), 0, 2); - $rrule['BYDAY'] = join(',', $byday); - } - if ($recurrence['daynumber']) { - if ($recurrence['type'] == 'monthday' || $recurrence['type'] == 'daynumber') - $rrule['BYMONTHDAY'] = $recurrence['daynumber']; - else if ($recurrence['type'] == 'yearday') - $rrule['BYYEARDAY'] = $recurrence['daynumber']; - } - if ($recurrence['month']) { - $monthmap = array_flip($this->kolab2_monthmap); - $rrule['BYMONTH'] = strtolower($monthmap[$recurrence['month']]); - } - - if ($recurrence['exclusion']) { - foreach ((array)$recurrence['exclusion'] as $excl) - $rrule['EXDATE'][] = date_create($excl . date(' H:i:s', $rec['start-date'])); // use time of event start - } - } - - $attendees = array(); - if ($rec['organizer']) { - $attendees[] = array( - 'role' => 'ORGANIZER', - 'name' => $rec['organizer']['display-name'], - 'email' => $rec['organizer']['smtp-address'], - 'status' => 'ACCEPTED', - ); - $_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' '; - } - - foreach ((array)$rec['attendee'] as $attendee) { - $attendees[] = array( - 'role' => $this->kolab2_rolemap[$attendee['role']], - 'name' => $attendee['display-name'], - 'email' => $attendee['smtp-address'], - 'status' => $this->kolab2_statusmap[$attendee['status']], - 'rsvp' => $attendee['request-response'], - ); - $_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' '; - } + return $exception; + } - $this->data = array( - 'uid' => $rec['uid'], - 'title' => $rec['summary'], - 'location' => $rec['location'], - 'description' => $rec['body'], - 'start' => new DateTime('@'.$rec['start-date']), - 'end' => new DateTime('@'.$rec['end-date']), - 'allday' => $allday, - 'recurrence' => $rrule, - 'alarms' => $alarm_value . $alarm_unit, - 'categories' => explode(',', $rec['categories']), - 'attachments' => $attachments, - 'attendees' => $attendees, - 'free_busy' => $rec['show-time-as'], - 'priority' => $rec['priority'], - 'sensitivity' => $rec['sensitivity'], - 'changed' => $rec['last-modification-date'], - ); + /** + * Copy attributes not specified by the exception from the master event + */ + private function expand_exception($exception, $master) + { + foreach ($master as $prop => $value) { + if (empty($exception[$prop]) && !empty($value)) + $exception[$prop] = $value; + } - // assign current timezone to event start/end - $this->data['start']->setTimezone(self::$timezone); - $this->data['end']->setTimezone(self::$timezone); + return $exception; } + }
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_file.php
Added
@@ -0,0 +1,156 @@ +<?php + +/** + * Kolab File model class + * + * @version @package_version@ + * @author Thomas Bruederli <bruederli@kolabsys.com> + * @author Aleksander Machniak <machniak@kolabsys.com> + * + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +class kolab_format_file extends kolab_format +{ + public $CTYPE = 'application/x-vnd.kolab.file'; + + protected $objclass = 'File'; + protected $read_func = 'kolabformat::readKolabFile'; + protected $write_func = 'kolabformat::writeKolabFile'; + + protected $sensitivity_map = array( + 'public' => kolabformat::ClassPublic, + 'private' => kolabformat::ClassPrivate, + 'confidential' => kolabformat::ClassConfidential, + ); + + /** + * Set properties to the kolabformat object + * + * @param array Object data as hash array + */ + public function set(&$object) + { + // set common object properties + parent::set($object); + + $this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]); + $this->obj->setCategories(self::array2vector($object['categories'])); + + if (isset($object['notes'])) { + $this->obj->setNote($object['notes']); + } + + // Add file attachment + if (!empty($object['_attachments'])) { + $cid = key($object['_attachments']); + $attach_attr = $object['_attachments'][$cid]; + $attach = new Attachment; + + $attach->setLabel((string)$attach_attr['name']); + $attach->setUri('cid:' . $cid, $attach_attr['mimetype']); + $this->obj->setFile($attach); + + // make sure size is set, so object saved in cache contains this info + if (!isset($attach_attr['size'])) { + $size = 0; + + if (!empty($attach_attr['content'])) { + if (is_resource($attach_attr['content'])) { + $stat = fstat($attach_attr['content']); + $size = $stat ? $stat['size'] : 0; + } + else { + $size = strlen($attach_attr['content']); + } + } + else if (isset($attach_attr['path'])) { + $size = @filesize($attach_attr['path']); + } + + $object['_attachments'][$cid]['size'] = $size; + } + } + + // cache this data + $this->data = $object; + unset($this->data['_formatobj']); + } + + /** + * Check if object's data validity + */ + public function is_valid() + { + return $this->data || (is_object($this->obj) && $this->obj->isValid()); + } + + /** + * Convert the Configuration object into a hash array data structure + * + * @param array Additional data for merge + * + * @return array Config object data as hash array + */ + public function to_array($data = array()) + { + // return cached result + if (!empty($this->data)) { + return $this->data; + } + + // read common object props into local data object + $object = parent::to_array($data); + + $sensitivity_map = array_flip($this->sensitivity_map); + + // read object properties + $object += array( + 'sensitivity' => $sensitivity_map[$this->obj->classification()], + 'categories' => self::vector2array($this->obj->categories()), + 'notes' => $this->obj->note(), + ); + + return $this->data = $object; + } + + /** + * Callback for kolab_storage_cache to get object specific tags to cache + * + * @return array List of tags to save in cache + */ + public function get_tags() + { + $tags = array(); + + foreach ((array)$this->data['categories'] as $cat) { + $tags[] = rcube_utils::normalize_string($cat); + } + + // Add file mimetype to tags + if (!empty($this->data['_attachments'])) { + reset($this->data['_attachments']); + $key = key($this->data['_attachments']); + $attachment = $this->data['_attachments'][$key]; + + if ($attachment['mimetype']) { + $tags[] = $attachment['mimetype']; + } + } + + return $tags; + } +}
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_journal.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_journal.php
Changed
@@ -25,17 +25,13 @@ class kolab_format_journal extends kolab_format { public $CTYPE = 'application/calendar+xml'; + public $CTYPEv2 = 'application/x-vnd.kolab.journal'; - protected $read_func = 'kolabformat::readJournal'; - protected $write_func = 'kolabformat::writeJournal'; + protected $objclass = 'Journal'; + protected $read_func = 'readJournal'; + protected $write_func = 'writeJournal'; - function __construct($xmldata = null) - { - $this->obj = new Journal; - $this->xmldata = $xmldata; - } - /** * Set properties to the kolabformat object * @@ -43,14 +39,8 @@ */ public function set(&$object) { - $this->init(); - - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + // set common object properties + parent::set($object); // TODO: set object propeties @@ -68,40 +58,20 @@ } /** - * Load data from old Kolab2 format - */ - public function fromkolab2($record) - { - $object = array( - 'uid' => $record['uid'], - 'changed' => $record['last-modification-date'], - ); - - // TODO: implement this - - $this->data = $object; - } - - /** * Convert the Configuration object into a hash array data structure * + * @param array Additional data for merge + * * @return array Config object data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); - - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); - + // read common object props into local data object + $object = parent::to_array($data); // TODO: read object properties
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_note.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_note.php
Changed
@@ -25,17 +25,13 @@ class kolab_format_note extends kolab_format { public $CTYPE = 'application/x-vnd.kolab.note'; + public $CTYPEv2 = 'application/x-vnd.kolab.note'; - protected $read_func = 'kolabformat::readNote'; - protected $write_func = 'kolabformat::writeNote'; + protected $objclass = 'Note'; + protected $read_func = 'readNote'; + protected $write_func = 'writeNote'; - function __construct($xmldata = null) - { - $this->obj = new Note; - $this->xmldata = $xmldata; - } - /** * Set properties to the kolabformat object * @@ -43,14 +39,8 @@ */ public function set(&$object) { - $this->init(); - - // set some automatic values if missing - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + // set common object properties + parent::set($object); // TODO: set object propeties @@ -68,39 +58,20 @@ } /** - * Load data from old Kolab2 format - */ - public function fromkolab2($record) - { - $object = array( - 'uid' => $record['uid'], - 'changed' => $record['last-modification-date'], - ); - - - $this->data = $object; - } - - /** * Convert the Configuration object into a hash array data structure * + * @param array Additional data for merge + * * @return array Config object data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); - - // read object properties - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); - + // read common object props into local data object + $object = parent::to_array($data); // TODO: read object properties
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_task.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_task.php
Changed
@@ -24,15 +24,12 @@ class kolab_format_task extends kolab_format_xcal { - protected $read_func = 'kolabformat::readTodo'; - protected $write_func = 'kolabformat::writeTodo'; + public $CTYPEv2 = 'application/x-vnd.kolab.task'; + protected $objclass = 'Todo'; + protected $read_func = 'readTodo'; + protected $write_func = 'writeTodo'; - function __construct($xmldata = null) - { - $this->obj = new Todo; - $this->xmldata = $xmldata; - } /** * Set properties to the kolabformat object @@ -41,8 +38,6 @@ */ public function set(&$object) { - $this->init(); - // set common xcal properties parent::set($object); @@ -74,18 +69,18 @@ /** * Convert the Configuration object into a hash array data structure * + * @param array Additional data for merge + * * @return array Config object data as hash array */ - public function to_array() + public function to_array($data = array()) { // return cached result if (!empty($this->data)) return $this->data; - $this->init(); - // read common xcal props - $object = parent::to_array(); + $object = parent::to_array($data); $object['complete'] = intval($this->obj->percentComplete()); @@ -105,21 +100,6 @@ } /** - * Load data from old Kolab2 format - */ - public function fromkolab2($record) - { - $object = array( - 'uid' => $record['uid'], - 'changed' => $record['last-modification-date'], - ); - - // TODO: implement this - - $this->data = $object; - } - - /** * Callback for kolab_storage_cache to get object specific tags to cache * * @return array List of tags to save in cache
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_xcal.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_format_xcal.php
Changed
@@ -43,6 +43,14 @@ 'CHAIR' => kolabformat::Chair, ); + protected $cutype_map = array( + 'INDIVIDUAL' => kolabformat::CutypeIndividual, + 'GROUP' => kolabformat::CutypeGroup, + 'ROOM' => kolabformat::CutypeRoom, + 'RESOURCE' => kolabformat::CutypeResource, + 'UNKNOWN' => kolabformat::CutypeUnknown, + ); + protected $rrule_type_map = array( 'MINUTELY' => RecurrenceRule::Minutely, 'HOURLY' => RecurrenceRule::Hourly, @@ -88,21 +96,25 @@ /** * Convert common xcard properties into a hash array data structure * + * @param array Additional data for merge + * * @return array Object data as hash array */ - public function to_array() + public function to_array($data = array()) { + // read common object props + $object = parent::to_array($data); + $status_map = array_flip($this->status_map); $sensitivity_map = array_flip($this->sensitivity_map); - $object = array( - 'uid' => $this->obj->uid(), - 'created' => self::php_datetime($this->obj->created()), - 'changed' => self::php_datetime($this->obj->lastModified()), + $object += array( + 'sequence' => intval($this->obj->sequence()), 'title' => $this->obj->summary(), 'location' => $this->obj->location(), 'description' => $this->obj->description(), - 'status' => $this->status_map[$this->obj->status()], + 'url' => $this->obj->url(), + 'status' => $status_map[$this->obj->status()], 'sensitivity' => $sensitivity_map[$this->obj->classification()], 'priority' => $this->obj->priority(), 'categories' => self::vector2array($this->obj->categories()), @@ -110,7 +122,7 @@ ); // read organizer and attendees - if ($organizer = $this->obj->organizer()) { + if (($organizer = $this->obj->organizer()) && ($organizer->email() || $organizer->name())) { $object['organizer'] = array( 'email' => $organizer->email(), 'name' => $organizer->name(), @@ -118,18 +130,22 @@ } $role_map = array_flip($this->role_map); + $cutype_map = array_flip($this->cutype_map); $part_status_map = array_flip($this->part_status_map); $attvec = $this->obj->attendees(); for ($i=0; $i < $attvec->size(); $i++) { $attendee = $attvec->get($i); $cr = $attendee->contact(); - $object['attendees'][] = array( - 'role' => $role_map[$attendee->role()], - 'status' => $part_status_map[$attendee->partStat()], - 'rsvp' => $attendee->rsvp(), - 'email' => $cr->email(), - 'name' => $cr->name(), - ); + if ($cr->email() != $object['organizer']['email']) { + $object['attendees'][] = array( + 'role' => $role_map[$attendee->role()], + 'cutype' => $cutype_map[$attendee->cutype()], + 'status' => $part_status_map[$attendee->partStat()], + 'rsvp' => $attendee->rsvp(), + 'email' => $cr->email(), + 'name' => $cr->name(), + ); + } } // read recurrence rule @@ -167,9 +183,9 @@ $object['recurrence']['BYMONTH'] = join(',', self::vector2array($bymonth)); } - if ($exceptions = $this->obj->exceptionDates()) { - for ($i=0; $i < $exceptions->size(); $i++) { - if ($exdate = self::php_datetime($exceptions->get($i))) + if ($exdates = $this->obj->exceptionDates()) { + for ($i=0; $i < $exdates->size(); $i++) { + if ($exdate = self::php_datetime($exdates->get($i))) $object['recurrence']['EXDATE'][] = $exdate; } } @@ -202,6 +218,27 @@ } } + // handle attachments + $vattach = $this->obj->attachments(); + for ($i=0; $i < $vattach->size(); $i++) { + $attach = $vattach->get($i); + + // skip cid: attachments which are mime message parts handled by kolab_storage_folder + if (substr($attach->uri(), 0, 4) != 'cid:' && $attach->label()) { + $name = $attach->label(); + $content = $attach->data(); + $object['_attachments'][$name] = array( + 'name' => $name, + 'mimetype' => $attach->mimetype(), + 'size' => strlen($content), + 'content' => $content, + ); + } + else if (substr($attach->uri(), 0, 4) == 'http') { + $object['links'][] = $attach->uri(); + } + } + return $object; } @@ -213,21 +250,17 @@ */ public function set(&$object) { - // set some automatic values if missing - if (!$this->obj->created()) { - if (!empty($object['created'])) - $object['created'] = new DateTime('now', self::$timezone); - $this->obj->setCreated(self::get_datetime($object['created'])); - } + $this->init(); - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); + $is_new = !$this->obj->uid(); - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + // set common object properties + parent::set($object); - // increment sequence - $this->obj->setSequence($this->obj->sequence()+1); + // increment sequence on updates + if (empty($object['sequence'])) + $object['sequence'] = !$is_new ? $this->obj->sequence()+1 : 0; + $this->obj->setSequence($object['sequence']); $this->obj->setSummary($object['title']); $this->obj->setLocation($object['location']); @@ -235,6 +268,7 @@ $this->obj->setPriority($object['priority']); $this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]); $this->obj->setCategories(self::array2vector($object['categories'])); + $this->obj->setUrl(strval($object['url'])); // process event attendees $attendees = new vectorattendee; @@ -242,14 +276,15 @@ if ($attendee['role'] == 'ORGANIZER') { $object['organizer'] = $attendee; } - else { + else if ($attendee['email'] != $object['organizer']['email']) { $cr = new ContactReference(ContactReference::EmailReference, $attendee['email']); $cr->setName($attendee['name']); $att = new Attendee; $att->setContact($cr); - $att->setPartStat($this->status_map[$attendee['status']]); + $att->setPartStat($this->part_status_map[$attendee['status']]); $att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required); + $att->setCutype($this->cutype_map[$attendee['cutype']] ? $this->cutype_map[$attendee['cutype']] : kolabformat::CutypeIndividual); $att->setRSVP((bool)$attendee['rsvp']); if ($att->isValid()) { @@ -273,8 +308,10 @@ } // save recurrence rule + $rr = new RecurrenceRule; + $rr->setFrequency(RecurrenceRule::FreqNone); + if ($object['recurrence']) { - $rr = new RecurrenceRule; $rr->setFrequency($this->rrule_type_map[$object['recurrence']['FREQ']]); if ($object['recurrence']['INTERVAL']) @@ -314,8 +351,6 @@ $rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true)); if ($rr->isValid()) { - $this->obj->setRecurrenceRule($rr); - // add exception dates (only if recurrence rule is valid) $exdates = new vectordatetime; foreach ((array)$object['recurrence']['EXDATE'] as $exdate) @@ -331,6 +366,8 @@ } } + $this->obj->setRecurrenceRule($rr); + // save alarm $valarms = new vectoralarm; if ($object['alarms']) { @@ -363,6 +400,25 @@ $valarms->push($alarm); } $this->obj->setAlarms($valarms); + + // save attachments + $vattach = new vectorattachment; + foreach ((array)$object['_attachments'] as $cid => $attr) { + if (empty($attr)) + continue; + $attach = new Attachment; + $attach->setLabel((string)$attr['name']); + $attach->setUri('cid:' . $cid, $attr['mimetype']); + $vattach->push($attach); + } + + foreach ((array)$object['links'] as $link) { + $attach = new Attachment; + $attach->setUri($link, 'unknown'); + $vattach->push($attach); + } + + $this->obj->setAttachments($vattach); } /**
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_storage.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_storage.php
Changed
@@ -5,6 +5,7 @@ * * @version @package_version@ * @author Thomas Bruederli <bruederli@kolabsys.com> + * @author Aleksander Machniak <machniak@kolabsys.com> * * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> * @@ -26,18 +27,37 @@ { const CTYPE_KEY = '/shared/vendor/kolab/folder-type'; const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type'; - const COLOR_KEY_SHARED = '/shared/vendor/kolab/color'; + const COLOR_KEY_SHARED = '/shared/vendor/kolab/color'; const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color'; - const SERVERSIDE_SUBSCRIPTION = 0; - const CLIENTSIDE_SUBSCRIPTION = 1; + const NAME_KEY_SHARED = '/shared/vendor/kolab/displayname'; + const NAME_KEY_PRIVATE = '/private/vendor/kolab/displayname'; + public static $version = '3.0'; public static $last_error; private static $ready = false; + private static $subscriptions; + private static $states; private static $config; - private static $cache; private static $imap; + // Default folder names + private static $default_folders = array( + 'event' => 'Calendar', + 'contact' => 'Contacts', + 'task' => 'Tasks', + 'note' => 'Notes', + 'file' => 'Files', + 'configuration' => 'Configuration', + 'journal' => 'Journal', + 'mail.inbox' => 'INBOX', + 'mail.drafts' => 'Drafts', + 'mail.sentitems' => 'Sent', + 'mail.wastebasket' => 'Trash', + 'mail.outbox' => 'Outbox', + 'mail.junkemail' => 'Junk', + ); + /** * Setup the environment needed by the libs @@ -49,6 +69,7 @@ $rcmail = rcube::get_instance(); self::$config = $rcmail->config; + self::$version = strval($rcmail->config->get('kolab_format_version', self::$version)); self::$imap = $rcmail->get_storage(); self::$ready = class_exists('kolabformat') && (self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2')); @@ -61,6 +82,18 @@ )); self::$imap->set_pagesize(9999); } + else if (!class_exists('kolabformat')) { + rcube::raise_error(array( + 'code' => 900, 'type' => 'php', + 'message' => "required kolabformat module not found" + ), true); + } + else { + rcube::raise_error(array( + 'code' => 900, 'type' => 'php', + 'message' => "IMAP server doesn't support METADATA or ANNOTATEMORE" + ), true); + } return self::$ready; } @@ -78,7 +111,7 @@ $folders = $folderdata = array(); if (self::setup()) { - foreach ((array)self::list_folders('', '*', $type, false, $folderdata) as $foldername) { + foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]); } } @@ -86,6 +119,23 @@ return $folders; } + /** + * Getter for the storage folder for the given type + * + * @param string Data type to list folders for (contact,distribution-list,event,task,note) + * @return object kolab_storage_folder The folder object + */ + public static function get_default_folder($type) + { + if (self::setup()) { + foreach ((array)self::list_folders('', '*', $type . '.default', false, $folderdata) as $foldername) { + return new kolab_storage_folder($foldername, $folderdata[$foldername]); + } + } + + return null; + } + /** * Getter for a specific storage folder @@ -178,13 +228,14 @@ /** * Creates IMAP folder * - * @param string $name Folder name (UTF7-IMAP) - * @param string $type Folder type - * @param bool $subscribed Sets folder subscription + * @param string $name Folder name (UTF7-IMAP) + * @param string $type Folder type + * @param bool $subscribed Sets folder subscription + * @param bool $active Sets folder state (client-side subscription) * * @return bool True on success, false on failure */ - public static function folder_create($name, $type = null, $subscribed = false) + public static function folder_create($name, $type = null, $subscribed = false, $active = false) { self::setup(); @@ -197,6 +248,10 @@ if (!$saved) { self::$imap->delete_folder($name); } + // activate folder + else if ($active) { + self::set_state($name, true); + } } } @@ -208,6 +263,7 @@ return false; } + /** * Renames IMAP folder * @@ -233,10 +289,12 @@ * Does additional checks for permissions and folder name restrictions * * @param array Hash array with folder properties and metadata - * - name: Folder name - * - oldname: Old folder name when changed - * - parent: Parent folder to create the new one in - * - type: Folder type to create + * - name: Folder name + * - oldname: Old folder name when changed + * - parent: Parent folder to create the new one in + * - type: Folder type to create + * - subscribed: Subscribed flag (IMAP subscription) + * - active: Activation flag (client-side subscription) * @return mixed New folder name or False on failure */ public static function folder_update(&$prop) @@ -266,7 +324,7 @@ else { // these characters are problematic e.g. when used in LIST/LSUB foreach (array($delimiter, '%', '*') as $char) { - if (strpos($folder, $delimiter) !== false) { + if (strpos($folder, $char) !== false) { self::$last_error = 'forbiddencharacter'; return false; } @@ -305,21 +363,11 @@ } // create new folder else { - $result = self::folder_create($folder, $prop['type'], $prop['subscribed'] === self::SERVERSIDE_SUBSCRIPTION); + $result = self::folder_create($folder, $prop['type'], $prop['subscribed'], $prop['active']); } - // save color in METADATA - // TODO: also save 'showalarams' and other properties here - - if ($result && $prop['color']) { - $meta_saved = false; - $ns = self::$imap->folder_namespace($folder); - if ($ns == 'personal') // save in shared namespace for personal folders - $meta_saved = self::$imap->set_metadata($folder, array(self::COLOR_KEY_SHARED => $prop['color'])); - if (!$meta_saved) // try in private namespace - $meta_saved = self::$imap->set_metadata($folder, array(self::COLOR_KEY_PRIVATE => $prop['color'])); - if ($meta_saved) - unset($prop['color']); // unsetting will prevent fallback to local user prefs + if ($result) { + self::set_folder_props($folder, $prop); } return $result ? $folder : false; @@ -339,6 +387,14 @@ { self::setup(); + // find custom display name in folder METADATA + if (self::$config->get('kolab_custom_display_names', true)) { + $metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED)); + if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) { + return $name; + } + } + $found = false; $namespace = self::$imap->get_namespace(); @@ -470,33 +526,30 @@ } } - $names[$name] = rcube_charset::convert($name, 'UTF7-IMAP'); + $names[$name] = self::object_name($name); } // Make sure parent folder is listed (might be skipped e.g. if it's namespace root) if ($p_len && !isset($names[$parent])) { - $names[$parent] = rcube_charset::convert($parent, 'UTF7-IMAP'); + $names[$parent] = self::object_name($parent); } // Sort folders list asort($names, SORT_LOCALE_STRING); - $folders = array_keys($names); - $names = array(); - // Build SELECT field of parent folder $attrs['is_escaped'] = true; $select = new html_select($attrs); $select->add('---', ''); - foreach ($folders as $name) { - $imap_name = $name; - $name = $origname = self::object_name($name); + $listnames = array(); + foreach (array_keys($names) as $imap_name) { + $name = $origname = $names[$imap_name]; // find folder prefix to truncate - for ($i = count($names)-1; $i >= 0; $i--) { - if (strpos($name, $names[$i].' » ') === 0) { - $length = strlen($names[$i].' » '); + for ($i = count($listnames)-1; $i >= 0; $i--) { + if (strpos($name, $listnames[$i].' » ') === 0) { + $length = strlen($listnames[$i].' » '); $prefix = substr($name, 0, $length); $count = count(explode(' » ', $prefix)); $name = str_repeat(' ', $count-1) . '» ' . substr($name, $length); @@ -504,7 +557,7 @@ } } - $names[] = $origname; + $listnames[] = $origname; $select->add($name, $imap_name); } @@ -518,17 +571,22 @@ * @param string Optional root folder * @param string Optional name pattern * @param string Data type to list folders for (contact,distribution-list,event,task,note,mail) - * @param string Enable to return subscribed folders only + * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) * @param array Will be filled with folder-types data * * @return array List of folders */ - public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = false, &$folderdata = array()) + public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = null, &$folderdata = array()) { if (!self::setup()) { return null; } + // use IMAP subscriptions + if ($subscribed === null && self::$config->get('kolab_use_subscriptions')) { + $subscribed = true; + } + if (!$filter) { // Get ALL folders list, standard way if ($subscribed) { @@ -540,19 +598,17 @@ } $prefix = $root . $mbox; + $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/'; // get folders types - $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); + $folderdata = self::folders_typedata($prefix); if (!is_array($folderdata)) { return array(); } - $folderdata = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); - $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/'; - // In some conditions we can skip LIST command (?) - if ($subscribed == false && $filter != 'mail' && $prefix == '*') { + if (!$subscribed && $filter != 'mail' && $prefix == '*') { foreach ($folderdata as $folder => $type) { if (!preg_match($regexp, $type)) { unset($folderdata[$folder]); @@ -591,11 +647,71 @@ /** + * Sort the given list of kolab folders by namespace/name + * + * @param array List of kolab_storage_folder objects + * @return array Sorted list of folders + */ + public static function sort_folders($folders) + { + $nsnames = array('personal' => array(), 'shared' => array(), 'other' => array()); + foreach ($folders as $folder) { + $folders[$folder->name] = $folder; + $ns = $folder->get_namespace(); + $nsnames[$ns][$folder->name] = strtolower(html_entity_decode(self::object_name($folder->name, $ns), ENT_COMPAT, RCUBE_CHARSET)); // decode » + } + + $names = array(); + foreach ($nsnames as $ns => $dummy) { + asort($nsnames[$ns], SORT_LOCALE_STRING); + $names += $nsnames[$ns]; + } + + $out = array(); + foreach ($names as $utf7name => $name) { + $out[] = $folders[$utf7name]; + } + + return $out; + } + + + /** + * Returns folder types indexed by folder name + * + * @param string $prefix Folder prefix (Default '*' for all folders) + * + * @return array|bool List of folders, False on failure + */ + public static function folders_typedata($prefix = '*') + { + if (!self::setup()) { + return false; + } + + $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); + + if (!is_array($folderdata)) { + return false; + } + + return array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); + } + + + /** * Callback for array_map to select the correct annotation value */ - static function folder_select_metadata($types) + public static function folder_select_metadata($types) { - return $types[self::CTYPE_KEY_PRIVATE] ?: $types[self::CTYPE_KEY]; + if (!empty($types[self::CTYPE_KEY_PRIVATE])) { + return $types[self::CTYPE_KEY_PRIVATE]; + } + else if (!empty($types[self::CTYPE_KEY])) { + list($ctype, $suffix) = explode('.', $types[self::CTYPE_KEY]); + return $ctype; + } + return null; } @@ -606,7 +722,7 @@ * * @return string Folder type */ - static function folder_type($folder) + public static function folder_type($folder) { self::setup(); @@ -623,6 +739,7 @@ return 'mail'; } + /** * Sets folder content-type. * @@ -631,8 +748,10 @@ * * @return boolean True on success */ - static function set_folder_type($folder, $type='mail') + public static function set_folder_type($folder, $type='mail') { + self::setup(); + list($ctype, $subtype) = explode('.', $type); $success = self::$imap->set_metadata($folder, array(self::CTYPE_KEY => $ctype, self::CTYPE_KEY_PRIVATE => $subtype ? $type : null)); @@ -642,4 +761,259 @@ return $success; } + + + /** + * Check subscription status of this folder + * + * @param string $folder Folder name + * + * @return boolean True if subscribed, false if not + */ + public static function folder_is_subscribed($folder) + { + if (self::$subscriptions === null) { + self::setup(); + self::$subscriptions = self::$imap->list_folders_subscribed(); + } + + return in_array($folder, self::$subscriptions); + } + + + /** + * Change subscription status of this folder + * + * @param string $folder Folder name + * + * @return True on success, false on error + */ + public static function folder_subscribe($folder) + { + self::setup(); + + if (self::$imap->subscribe($folder)) { + self::$subscriptions === null; + return true; + } + + return false; + } + + + /** + * Change subscription status of this folder + * + * @param string $folder Folder name + * + * @return True on success, false on error + */ + public static function folder_unsubscribe($folder) + { + self::setup(); + + if (self::$imap->unsubscribe($folder)) { + self::$subscriptions === null; + return true; + } + + return false; + } + + + /** + * Check activation status of this folder + * + * @param string $folder Folder name + * + * @return boolean True if active, false if not + */ + public static function folder_is_active($folder) + { + $active_folders = self::get_states(); + + return in_array($folder, $active_folders); + } + + + /** + * Change activation status of this folder + * + * @param string $folder Folder name + * + * @return True on success, false on error + */ + public static function folder_activate($folder) + { + return self::set_state($folder, true); + } + + + /** + * Change activation status of this folder + * + * @param string $folder Folder name + * + * @return True on success, false on error + */ + public static function folder_deactivate($folder) + { + return self::set_state($folder, false); + } + + + /** + * Return list of active folders + */ + private static function get_states() + { + if (self::$states !== null) { + return self::$states; + } + + $rcube = rcube::get_instance(); + $folders = $rcube->config->get('kolab_active_folders'); + + if ($folders !== null) { + self::$states = !empty($folders) ? explode('**', $folders) : array(); + } + // for backward-compatibility copy server-side subscriptions to activation states + else { + self::setup(); + if (self::$subscriptions === null) { + self::$subscriptions = self::$imap->list_folders_subscribed(); + } + self::$states = self::$subscriptions; + $folders = implode(self::$states, '**'); + $rcube->user->save_prefs(array('kolab_active_folders' => $folders)); + } + + return self::$states; + } + + + /** + * Update list of active folders + */ + private static function set_state($folder, $state) + { + self::get_states(); + + // update in-memory list + $idx = array_search($folder, self::$states); + if ($state && $idx === false) { + self::$states[] = $folder; + } + else if (!$state && $idx !== false) { + unset(self::$states[$idx]); + } + + // update user preferences + $folders = implode(self::$states, '**'); + $rcube = rcube::get_instance(); + return $rcube->user->save_prefs(array('kolab_active_folders' => $folders)); + } + + /** + * Creates default folder of specified type + * To be run when none of subscribed folders (of specified type) is found + * + * @param string $type Folder type + * @param string $props Folder properties (color, etc) + * + * @return string Folder name + */ + public static function create_default_folder($type, $props = array()) + { + if (!self::setup()) { + return; + } + + $folders = self::$imap->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE)); + + // from kolab_folders config + $folder_type = strpos($type, '.') ? str_replace('.', '_', $type) : $type . '_default'; + $default_name = self::$config->get('kolab_folders_' . $folder_type); + $folder_type = str_replace('_', '.', $folder_type); + + // check if we have any folder in personal namespace + // folder(s) may exist but not subscribed + foreach ($folders as $f => $data) { + if (strpos($data[self::CTYPE_KEY_PRIVATE], $type) === 0) { + $folder = $f; + break; + } + } + + if (!$folder) { + if (!$default_name) { + $default_name = self::$default_folders[$type]; + } + + if (!$default_name) { + return; + } + + $folder = rcube_charset::convert($default_name, RCUBE_CHARSET, 'UTF7-IMAP'); + $prefix = self::$imap->get_namespace('prefix'); + + // add personal namespace prefix if needed + if ($prefix && strpos($folder, $prefix) !== 0 && $folder != 'INBOX') { + $folder = $prefix . $folder; + } + + if (!self::$imap->folder_exists($folder)) { + if (!self::$imap->folder_create($folder)) { + return; + } + } + + self::set_folder_type($folder, $folder_type); + } + + self::folder_subscribe($folder); + + if ($props['active']) { + self::set_state($folder, true); + } + + if (!empty($props)) { + self::set_folder_props($folder, $props); + } + + return $folder; + } + + /** + * Sets folder metadata properties + * + * @param string $folder Folder name + * @param array $prop Folder properties + */ + public static function set_folder_props($folder, &$prop) + { + if (!self::setup()) { + return; + } + + // TODO: also save 'showalarams' and other properties here + $ns = self::$imap->folder_namespace($folder); + $supported = array( + 'color' => array(self::COLOR_KEY_SHARED, self::COLOR_KEY_PRIVATE), + 'displayname' => array(self::NAME_KEY_SHARED, self::NAME_KEY_PRIVATE), + ); + + foreach ($supported as $key => $metakeys) { + if (array_key_exists($key, $prop)) { + $meta_saved = false; + if ($ns == 'personal') // save in shared namespace for personal folders + $meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key])); + if (!$meta_saved) // try in private namespace + $meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key])); + if ($meta_saved) + unset($prop[$key]); // unsetting will prevent fallback to local user prefs + } + } + } + }
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_storage_cache.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_storage_cache.php
Changed
@@ -35,8 +35,13 @@ private $synched = false; private $synclock = false; private $ready = false; - private $max_sql_packet = 1046576; // 1 MB - 2000 bytes - private $binary_cols = array('photo','pgppublickey','pkcs7publickey'); + private $max_sql_packet; + private $max_sync_lock_time = 600; + private $binary_items = array( + 'photo' => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i', + 'pgppublickey' => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></photo>|i', + 'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></photo>|i', + ); /** @@ -52,9 +57,6 @@ if ($this->enabled) { // remove sync-lock on script termination $rcmail->add_shutdown_function(array($this, '_sync_unlock')); - - // read max_allowed_packet from mysql config - $this->max_sql_packet = min($this->db->get_variable('max_allowed_packet', 1048500), 4*1024*1024) - 2000; // mysql limit or max 4 MB } if ($storage_folder) @@ -92,7 +94,7 @@ return; // increase time limit - @set_time_limit(500); + @set_time_limit($this->max_sync_lock_time); // lock synchronization for this folder or wait if locked $this->_sync_lock(); @@ -157,7 +159,7 @@ { // delegate to another cache instance if ($foldername && $foldername != $this->folder->name) { - return kolab_storage::get_folder($foldername)->cache->get($msguid, $object); + return kolab_storage::get_folder($foldername)->cache->get($msguid, $type); } // load object if not in memory @@ -238,8 +240,8 @@ $result = $this->db->query( "INSERT INTO kolab_cache ". - " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words)". - " VALUES (?, ?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ?, ?, ?)", + " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words, filename)". + " VALUES (?, ?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ?, ?, ?, ?)", $this->resource_uri, $objtype, $msguid, @@ -250,7 +252,8 @@ $sql_data['dtstart'], $sql_data['dtend'], $sql_data['tags'], - $sql_data['words'] + $sql_data['words'], + $sql_data['filename'] ); if (!$this->db->affected_rows($result)) { @@ -274,12 +277,12 @@ * @param string Entry's Object UID * @param string Target IMAP folder to move it to */ - public function move($msguid, $objuid, $target_folder) + public function move($msguid, $uid, $target_folder) { $target = kolab_storage::get_folder($target_folder); // resolve new message UID in target folder - if ($new_msguid = $target->cache->uid2msguid($objuid)) { + if ($new_msguid = $target->cache->uid2msguid($uid)) { $this->db->query( "UPDATE kolab_cache SET resource=?, msguid=? ". "WHERE resource=? AND msguid=? AND type<>?", @@ -363,6 +366,15 @@ // TODO: post-filter result according to query } + // We don't want to cache big results in-memory, however + // if we select only one object here, there's a big chance we will need it later + if (!$uids && count($result) == 1) { + if ($msguid = $result[0]['_msguid']) { + $this->uid2msg[$result[0]['uid']] = $msguid; + $this->objects[$msguid] = $result[0]; + } + } + return $result; } @@ -407,7 +419,17 @@ { $sql_where = ''; foreach ($query as $param) { - if ($param[1] == '=' && is_array($param[2])) { + if (is_array($param[0])) { + $subq = array(); + foreach ($param[0] as $q) { + $subq[] = preg_replace('/^\s*AND\s+/i', '', $this->_sql_where(array($q))); + } + if (!empty($subq)) { + $sql_where .= ' AND (' . implode($param[1] == 'OR' ? ' OR ' : ' AND ', $subq) . ')'; + } + continue; + } + else if ($param[1] == '=' && is_array($param[2])) { $qvalue = '(' . join(',', array_map(array($this->db, 'quote'), $param[2])) . ')'; $param[1] = 'IN'; } @@ -506,9 +528,8 @@ */ private function _serialize($object) { - $bincols = array_flip($this->binary_cols); $sql_data = array('changed' => null, 'dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => ''); - $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; + $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; // set type specific values if ($objtype == 'event') { @@ -517,8 +538,8 @@ $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']); // extend date range for recurring events - if ($object['recurrence']) { - $recurrence = new kolab_date_recurrence($object); + if ($object['recurrence'] && $object['_formatobj']) { + $recurrence = new kolab_date_recurrence($object['_formatobj']); $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year')); } } @@ -528,26 +549,33 @@ if ($object['due']) $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['due']) ? $object['due']->format('U') : $object['due']); } + else if ($objtype == 'file') { + if (!empty($object['_attachments'])) { + reset($object['_attachments']); + $sql_data['filename'] = $object['_attachments'][key($object['_attachments'])]['name']; + } + } if ($object['changed']) { $sql_data['changed'] = date('Y-m-d H:i:s', is_object($object['changed']) ? $object['changed']->format('U') : $object['changed']); } if ($object['_formatobj']) { - $sql_data['xml'] = preg_replace('!(</?[a-z0-9:-]+>)[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write()); - $sql_data['tags'] = ' ' . join(' ', $object['_formatobj']->get_tags()) . ' '; // pad with spaces for strict/prefix search + $sql_data['xml'] = preg_replace('!(</?[a-z0-9:-]+>)[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write(3.0)); + $sql_data['tags'] = ' ' . join(' ', $object['_formatobj']->get_tags()) . ' '; // pad with spaces for strict/prefix search $sql_data['words'] = ' ' . join(' ', $object['_formatobj']->get_words()) . ' '; } // extract object data $data = array(); foreach ($object as $key => $val) { + // skip empty properties if ($val === "" || $val === null) { - // skip empty properties continue; } - if (isset($bincols[$key])) { - $data[$key] = base64_encode($val); + // mark binary data to be extracted from xml on unserialize() + if (isset($this->binary_items[$key])) { + $data[$key] = true; } else if ($key[0] != '_') { $data[$key] = $val; @@ -573,16 +601,18 @@ $object = unserialize($sql_arr['data']); // decode binary properties - foreach ($this->binary_cols as $key) { - if (!empty($object[$key])) - $object[$key] = base64_decode($object[$key]); + foreach ($this->binary_items as $key => $regexp) { + if (!empty($object[$key]) && preg_match($regexp, $sql_arr['xml'], $m)) { + $object[$key] = base64_decode($m[1]); + } } // add meta data $object['_type'] = $sql_arr['type']; $object['_msguid'] = $sql_arr['msguid']; $object['_mailbox'] = $this->folder->name; - $object['_formatobj'] = kolab_format::factory($sql_arr['type'], $sql_arr['xml']); + $object['_size'] = strlen($sql_arr['xml']); + $object['_formatobj'] = kolab_format::factory($sql_arr['type'], 3.0, $sql_arr['xml']); return $object; } @@ -615,14 +645,15 @@ $this->db->quote($sql_data['dtend']), $this->db->quote($sql_data['tags']), $this->db->quote($sql_data['words']), + $this->db->quote($sql_data['filename']), ); $line = '(' . join(',', $values) . ')'; } - if ($buffer && (!$msguid || (strlen($buffer) + strlen($line) > $this->max_sql_packet))) { + if ($buffer && (!$msguid || (strlen($buffer) + strlen($line) > $this->max_sql_packet()))) { $result = $this->db->query( "INSERT INTO kolab_cache ". - " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words)". + " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words, filename)". " VALUES $buffer" ); if (!$this->db->affected_rows($result)) { @@ -639,6 +670,20 @@ } /** + * Returns max_allowed_packet from mysql config + */ + private function max_sql_packet() + { + if (!$this->max_sql_packet) { + // mysql limit or max 4 MB + $value = $this->db->get_variable('max_allowed_packet', 1048500); + $this->max_sql_packet = min($value, 4*1024*1024) - 2000; + } + + return $this->max_sql_packet; + } + + /** * Check lock record for this folder and wait if locked or set lock */ private function _sync_lock() @@ -646,12 +691,9 @@ if (!$this->ready) return; - $sql_arr = $this->db->fetch_assoc($this->db->query( - "SELECT msguid AS locked, ".$this->db->unixtimestamp('created')." AS created FROM kolab_cache ". - "WHERE resource=? AND type=?", - $this->resource_uri, - 'lock' - )); + $sql_query = "SELECT msguid AS locked, ".$this->db->unixtimestamp('created')." AS created FROM kolab_cache ". + "WHERE resource=? AND type=?"; + $sql_arr = $this->db->fetch_assoc($this->db->query($sql_query, $this->resource_uri, 'lock')); // abort if database is not set-up if ($this->db->is_error()) { @@ -661,27 +703,25 @@ $this->synclock = true; + // wait if locked (expire locks after 10 minutes) + while ($sql_arr && intval($sql_arr['locked']) > 0 && $sql_arr['created'] + $this->max_sync_lock_time > time()) { + usleep(500000); + $sql_arr = $this->db->fetch_assoc($this->db->query($sql_query, $this->resource_uri, 'lock')); + } + // create lock record if not exists if (!$sql_arr) { $this->db->query( - "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml)". - " VALUES (?, ?, 1, ?, '', '', '')", + "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml, tags, words)". + " VALUES (?, ?, 1, " . $this->db->now() . ", '', '', '', '', '')", $this->resource_uri, - 'lock', - date('Y-m-d H:i:s') + 'lock' ); } - // wait if locked (expire locks after 10 minutes) - else if (intval($sql_arr['locked']) > 0 && (time() - $sql_arr['created']) < 600) { - usleep(500000); - return $this->_sync_lock(); - } - // set lock else { $this->db->query( - "UPDATE kolab_cache SET msguid=1, created=? ". - "WHERE resource=? AND type=?", - date('Y-m-d H:i:s'), + "UPDATE kolab_cache SET msguid = 1, created = " . $this->db->now() . + " WHERE resource = ? AND type = ?", $this->resource_uri, 'lock' ); @@ -697,8 +737,7 @@ return; $this->db->query( - "UPDATE kolab_cache SET msguid=0 ". - "WHERE resource=? AND type=?", + "UPDATE kolab_cache SET msguid = 0 WHERE resource = ? AND type = ?", $this->resource_uri, 'lock' ); @@ -717,7 +756,8 @@ { if (!isset($this->uid2msg[$uid])) { // use IMAP SEARCH to get the right message - $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid); + $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . + 'HEADER SUBJECT ' . rcube_imap_generic::escape($uid)); $results = $index->get(); $this->uid2msg[$uid] = $results[0]; }
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/lib/kolab_storage_folder.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/lib/kolab_storage_folder.php
Changed
@@ -5,6 +5,7 @@ * * @version @package_version@ * @author Thomas Bruederli <bruederli@kolabsys.com> + * @author Aleksander Machniak <machniak@kolabsys.com> * * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> * @@ -48,11 +49,12 @@ public $cache; private $type_annotation; + private $namespace; private $imap; private $info; + private $idata; private $owner; private $resource_uri; - private $uid2msg = array(); /** @@ -98,6 +100,16 @@ return $this->info; } + /** + * Make IMAP folder data available for this folder + */ + public function get_imap_data() + { + if (!isset($this->idata)) + $this->idata = $this->imap->folder_data($this->name); + + return $this->idata; + } /** * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION) @@ -148,7 +160,6 @@ break; default: - $owner = ''; list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']); if (strpos($user, '@') === false) { $domain = strstr($rcmail->get_user_name(), '@'); @@ -170,7 +181,9 @@ */ public function get_namespace() { - return $this->imap->folder_namespace($this->name); + if (!isset($this->namespace)) + $this->namespace = $this->imap->folder_namespace($this->name); + return $this->namespace; } @@ -191,6 +204,35 @@ /** + * Get the display name value of this folder + * + * @return string Folder name + */ + public function get_name() + { + return kolab_storage::object_name($this->name, $this->namespace); + } + + + /** + * Get the color value stored in metadata + * + * @param string Default color value to return if not set + * @return mixed Color value from IMAP metadata or $default is not set + */ + public function get_color($default = null) + { + // color is defined in folder METADATA + $metadata = $this->get_metadata(array(kolab_storage::COLOR_KEY_PRIVATE, kolab_storage::COLOR_KEY_SHARED)); + if (($color = $metadata[kolab_storage::COLOR_KEY_PRIVATE]) || ($color = $metadata[kolab_storage::COLOR_KEY_SHARED])) { + return $color; + } + + return $default; + } + + + /** * Compose a unique resource URI for this IMAP folder */ public function get_resource_uri() @@ -204,7 +246,7 @@ if (is_array($nsdata[0]) && strlen($nsdata[0][0]) && strpos($this->name, $nsdata[0][0]) === 0) { $subpath = substr($this->name, strlen($nsdata[0][0])); if ($ns == 'other') { - list($user, $suffix) = explode($nsdata[0][1], $subpath); + list($user, $suffix) = explode($nsdata[0][1], $subpath, 2); $subpath = $suffix; } } @@ -218,46 +260,47 @@ } /** - * Check subscription status of this folder + * Check activation status of this folder * - * @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION) - * @return boolean True if subscribed, false if not + * @return boolean True if enabled, false if not */ - public function is_subscribed($type = 0) + public function is_active() { - static $subscribed; // local cache - - if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) { - if (!$subscribed) - $subscribed = $this->imap->list_folders_subscribed(); + return kolab_storage::folder_is_active($this->name); + } - return in_array($this->name, $subscribed); - } - else if (kolab_storage::CLIENTSIDE_SUBSCRIPTION) { - // TODO: implement this - return true; - } + /** + * Change activation status of this folder + * + * @param boolean The desired subscription status: true = active, false = not active + * + * @return True on success, false on error + */ + public function activate($active) + { + return $active ? kolab_storage::folder_activate($this->name) : kolab_storage::folder_deactivate($this->name); + } - return false; + /** + * Check subscription status of this folder + * + * @return boolean True if subscribed, false if not + */ + public function is_subscribed() + { + return kolab_storage::folder_is_subscribed($this->name); } /** * Change subscription status of this folder * * @param boolean The desired subscription status: true = subscribed, false = not subscribed - * @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION) + * * @return True on success, false on error */ - public function subscribe($subscribed, $type = 0) + public function subscribe($subscribed) { - if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) { - return $subscribed ? $this->imap->subscribe($this->name) : $this->imap->unsubscribe($this->name); - } - else { - // TODO: implement this - } - - return false; + return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name); } @@ -369,17 +412,22 @@ /** * Getter for a single Kolab object, identified by its UID * - * @param string Object UID + * @param string $uid Object UID + * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration) + * Defaults to folder type + * * @return array The Kolab object represented as hash array */ - public function get_object($uid) + public function get_object($uid, $type = null) { // synchronize caches $this->cache->synchronize(); $msguid = $this->cache->uid2msguid($uid); - if ($msguid && ($object = $this->cache->get($msguid))) + + if ($msguid && ($object = $this->cache->get($msguid, $type))) { return $object; + } return false; } @@ -389,17 +437,21 @@ * Fetch a Kolab object attachment which is stored in a separate part * of the mail MIME message that represents the Kolab record. * - * @param string Object's UID - * @param string The attachment's mime number - * @param string IMAP folder where message is stored; - * If set, that also implies that the given UID is an IMAP UID + * @param string Object's UID + * @param string The attachment's mime number + * @param string IMAP folder where message is stored; + * If set, that also implies that the given UID is an IMAP UID + * @param bool True to print the part content + * @param resource File pointer to save the message part + * @param boolean Disables charset conversion + * * @return mixed The attachment content as binary string */ - public function get_attachment($uid, $part, $mailbox = null) + public function get_attachment($uid, $part, $mailbox = null, $print = false, $fp = null, $skip_charset_conv = false) { if ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid))) { $this->imap->set_folder($mailbox ? $mailbox : $this->name); - return $this->imap->get_message_part($msguid, $part); + return $this->imap->get_message_part($msguid, $part, null, $print, $fp, $skip_charset_conv); } return null; @@ -423,12 +475,40 @@ $this->imap->set_folder($folder); $headers = $this->imap->get_message_headers($msguid); + $message = null; // Message doesn't exist? if (empty($headers)) { return false; } + // extract the X-Kolab-Type header from the XML attachment part if missing + if (empty($headers->others['x-kolab-type'])) { + $message = new rcube_message($msguid); + foreach ((array)$message->attachments as $part) { + if (strpos($part->mimetype, kolab_format::KTYPE_PREFIX) === 0) { + $headers->others['x-kolab-type'] = $part->mimetype; + break; + } + } + } + // fix buggy messages stating the X-Kolab-Type header twice + else if (is_array($headers->others['x-kolab-type'])) { + $headers->others['x-kolab-type'] = reset($headers->others['x-kolab-type']); + } + + // no object type header found: abort + if (empty($headers->others['x-kolab-type'])) { + rcube::raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, + 'line' => __LINE__, + 'message' => "No X-Kolab-Type information found in message $msguid ($this->name).", + ), true); + return false; + } + $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']); $content_type = kolab_format::KTYPE_PREFIX . $object_type; @@ -436,7 +516,7 @@ if ($type != '*' && $object_type != $type) return false; - $message = new rcube_message($msguid); + if (!$message) $message = new rcube_message($msguid); $attachments = array(); // get XML part @@ -445,12 +525,23 @@ $xml = $part->body ? $part->body : $message->get_part_content($part->mime_id); } else if ($part->filename || $part->content_id) { - $key = $part->content_id ? trim($part->content_id, '<>') : $part->filename; + $key = $part->content_id ? trim($part->content_id, '<>') : $part->filename; + $size = null; + + // Use Content-Disposition 'size' as for the Kolab Format spec. + if (isset($part->d_parameters['size'])) { + $size = $part->d_parameters['size']; + } + // we can trust part size only if it's not encoded + else if ($part->encoding == 'binary' || $part->encoding == '7bit' || $part->encoding == '8bit') { + $size = $part->size; + } + $attachments[$key] = array( - 'id' => $part->mime_id, - 'name' => $part->filename, + 'id' => $part->mime_id, + 'name' => $part->filename, 'mimetype' => $part->mimetype, - 'size' => $part->size, + 'size' => $size, ); } } @@ -466,46 +557,33 @@ return false; } - $format = kolab_format::factory($object_type); - - if (is_a($format, 'PEAR_Error')) - return false; - // check kolab format version - $mime_version = $headers->others['x-kolab-mime-version']; - if (empty($mime_version)) { + $format_version = $headers->others['x-kolab-mime-version']; + if (empty($format_version)) { list($xmltype, $subtype) = explode('.', $object_type); $xmlhead = substr($xml, 0, 512); // detect old Kolab 2.0 format if (strpos($xmlhead, '<' . $xmltype) !== false && strpos($xmlhead, 'xmlns=') === false) - $mime_version = 2.0; + $format_version = '2.0'; else - $mime_version = 3.0; // assume 3.0 + $format_version = '3.0'; // assume 3.0 } - if ($mime_version <= 2.0) { - // read Kolab 2.0 format - $handler = class_exists('Horde_Kolab_Format') ? Horde_Kolab_Format::factory('XML', $xmltype, array('subtype' => $subtype)) : null; - if (!is_object($handler) || is_a($handler, 'PEAR_Error')) { - return false; - } + // get Kolab format handler for the given type + $format = kolab_format::factory($object_type, $format_version); - // XML-to-array - $object = $handler->load($xml); - $format->fromkolab2($object); - } - else { - // load Kolab 3 format using libkolabxml - $format->load($xml); - } + if (is_a($format, 'PEAR_Error')) + return false; + + // load Kolab object from XML part + $format->load($xml); if ($format->is_valid()) { - $object = $format->to_array(); - $object['_type'] = $object_type; - $object['_msguid'] = $msguid; - $object['_mailbox'] = $this->name; - $object['_attachments'] = array_merge((array)$object['_attachments'], $attachments); + $object = $format->to_array(array('_attachments' => $attachments)); + $object['_type'] = $object_type; + $object['_msguid'] = $msguid; + $object['_mailbox'] = $this->name; $object['_formatobj'] = $format; return $object; @@ -527,7 +605,6 @@ return false; } - /** * Save an object in this folder. * @@ -552,17 +629,52 @@ unset($object['_attachments'][$key]); } // load photo.attachment from old Kolab2 format to be directly embedded in xcard block - else if ($key == 'photo.attachment' && !isset($object['photo']) && !$object['_attachments'][$key]['content'] && $att['id']) { - $object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']); + else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) { + if (!isset($object['photo'])) + $object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']); unset($object['_attachments'][$key]); } } } - // generate unique keys (used as content-id) for attachments + // save contact photo to attachment for Kolab2 format + if (kolab_storage::$version == '2.0' && $object['photo']) { + $attkey = 'kolab-picture.png'; // this file name is hard-coded in libkolab/kolabformatV2/contact.cpp + $object['_attachments'][$attkey] = array( + 'mimetype'=> rcube_mime::image_content_type($object['photo']), + 'content' => preg_match('![^a-z0-9/=+-]!i', $object['photo']) ? $object['photo'] : base64_decode($object['photo']), + ); + } + + // process attachments if (is_array($object['_attachments'])) { $numatt = count($object['_attachments']); foreach ($object['_attachments'] as $key => $attachment) { + // FIXME: kolab_storage and Roundcube attachment hooks use different fields! + if (empty($attachment['content']) && !empty($attachment['data'])) + $attachment['content'] = $attachment['data']; + + // make sure size is set, so object saved in cache contains this info + if (!isset($attachment['size'])) { + if (!empty($attachment['content'])) { + if (is_resource($attachment['content'])) { + // this need to be a seekable resource, otherwise + // fstat() failes and we're unable to determine size + // here nor in rcube_imap_generic before IMAP APPEND + $stat = fstat($attachment['content']); + $attachment['size'] = $stat ? $stat['size'] : 0; + } + else { + $attachment['size'] = strlen($attachment['content']); + } + } + else if (!empty($attachment['path'])) { + $attachment['size'] = filesize($attachment['path']); + } + $object['_attachments'][$key] = $attachment; + } + + // generate unique keys (used as content-id) for attachments if (is_numeric($key) && $key < $numatt) { // derrive content-id from attachment file name $ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null; @@ -576,29 +688,109 @@ } } - if ($raw_msg = $this->build_message($object, $type)) { - $result = $this->imap->save_message($this->name, $raw_msg, '', false); + // save recurrence exceptions as individual objects due to lack of support in Kolab v2 format + if (kolab_storage::$version == '2.0' && $object['recurrence']['EXCEPTIONS']) { + $this->save_recurrence_exceptions($object, $type); + } + + // check IMAP BINARY extension support for 'file' objects + // allow configuration to workaround bug in Cyrus < 2.4.17 + $rcmail = rcube::get_instance(); + $binary = $type == 'file' && !$rcmail->config->get('kolab_binary_disable') && $this->imap->get_capability('BINARY'); + + // generate and save object message + if ($raw_msg = $this->build_message($object, $type, $binary, $body_file)) { + // resolve old msguid before saving + if ($uid && empty($object['_msguid']) && ($msguid = $this->cache->uid2msguid($uid))) { + $object['_msguid'] = $msguid; + $object['_mailbox'] = $this->name; + } + + $result = $this->imap->save_message($this->name, $raw_msg, null, false, null, null, $binary); // delete old message if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) { $this->imap->delete_message($object['_msguid'], $object['_mailbox']); $this->cache->set($object['_msguid'], false, $object['_mailbox']); } - else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) { - $this->imap->delete_message($msguid, $this->name); - $this->cache->set($object['_msguid'], false); - } // update cache with new UID if ($result) { $object['_msguid'] = $result; $this->cache->insert($result, $object); + + // remove temp file + if ($body_file) { + @unlink($body_file); + } } } - + return $result; } + /** + * Save recurrence exceptions as individual objects. + * The Kolab v2 format doesn't allow us to save fully embedded exception objects. + * + * @param array Hash array with event properties + * @param string Object type + */ + private function save_recurrence_exceptions(&$object, $type = null) + { + if ($object['recurrence']['EXCEPTIONS']) { + $exdates = array(); + foreach ((array)$object['recurrence']['EXDATE'] as $exdate) { + $key = is_a($exdate, 'DateTime') ? $exdate->format('Y-m-d') : strval($exdate); + $exdates[$key] = 1; + } + + // save every exception as individual object + foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) { + $exception['uid'] = self::recurrence_exception_uid($object['uid'], $exception['start']->format('Ymd')); + $exception['sequence'] = $object['sequence'] + 1; + + if ($exception['thisandfuture']) { + $exception['recurrence'] = $object['recurrence']; + + // adjust the recurrence duration of the exception + if ($object['recurrence']['COUNT']) { + $recurrence = new kolab_date_recurrence($object['_formatobj']); + if ($end = $recurrence->end()) { + unset($exception['recurrence']['COUNT']); + $exception['recurrence']['UNTIL'] = new DateTime('@'.$end); + } + } + + // set UNTIL date if we have a thisandfuture exception + $untildate = clone $exception['start']; + $untildate->sub(new DateInterval('P1D')); + $object['recurrence']['UNTIL'] = $untildate; + unset($object['recurrence']['COUNT']); + } + else { + if (!$exdates[$exception['start']->format('Y-m-d')]) + $object['recurrence']['EXDATE'][] = clone $exception['start']; + unset($exception['recurrence']); + } + + unset($exception['recurrence']['EXCEPTIONS'], $exception['_formatobj'], $exception['_msguid']); + $this->save($exception, $type, $exception['uid']); + } + + unset($object['recurrence']['EXCEPTIONS']); + } + } + + /** + * Generate an object UID with the given recurrence-ID in a way that it is + * unique (the original UID is not a substring) but still recoverable. + */ + private static function recurrence_exception_uid($uid, $recurrence_id) + { + $offset = -2; + return substr($uid, 0, $offset) . '-' . $recurrence_id . '-' . substr($uid, $offset); + } /** * Delete the specified object from this folder. @@ -666,7 +858,7 @@ public function move($uid, $target_folder) { if ($msguid = $this->cache->uid2msguid($uid)) { - if ($success = $this->imap->move_message($msguid, $target_folder, $this->name)) { + if ($this->imap->move_message($msguid, $target_folder, $this->name)) { $this->cache->move($msguid, $uid, $target_folder); return true; } @@ -685,8 +877,16 @@ /** * Creates source of the configuration object message + * + * @param array $object The array that holds the data of the object. + * @param string $type The type of the kolab object. + * @param bool $binary Enables use of binary encoding of attachment(s) + * @param string $body_file Reference to filename of message body + * + * @return mixed Message as string or array with two elements + * (one for message file path, second for message headers) */ - private function build_message(&$object, $type) + private function build_message(&$object, $type, $binary, &$body_file) { // load old object to preserve data we don't understand/process if (is_object($object['_formatobj'])) @@ -696,43 +896,68 @@ // create new kolab_format instance if (!$format) - $format = kolab_format::factory($type); + $format = kolab_format::factory($type, kolab_storage::$version); if (PEAR::isError($format)) return false; $format->set($object); - $xml = $format->write(); + $xml = $format->write(kolab_storage::$version); $object['uid'] = $format->uid; // read UID from format $object['_formatobj'] = $format; - if (!$format->is_valid() || empty($object['uid'])) { + if (empty($xml) || !$format->is_valid() || empty($object['uid'])) { return false; } - $mime = new Mail_mime("\r\n"); - $rcmail = rcube::get_instance(); - $headers = array(); - $part_id = 1; + $mime = new Mail_mime("\r\n"); + $rcmail = rcube::get_instance(); + $headers = array(); + $files = array(); + $part_id = 1; + $encoding = $binary ? 'binary' : 'base64'; - if ($ident = $rcmail->user->get_identity()) { - $headers['From'] = $ident['email']; - $headers['To'] = $ident['email']; + if ($user_email = $rcmail->get_user_email()) { + $headers['From'] = $user_email; + $headers['To'] = $user_email; } $headers['Date'] = date('r'); $headers['X-Kolab-Type'] = kolab_format::KTYPE_PREFIX . $type; - $headers['X-Kolab-Mime-Version'] = kolab_format::VERSION; + $headers['X-Kolab-Mime-Version'] = kolab_storage::$version; $headers['Subject'] = $object['uid']; // $headers['Message-ID'] = $rcmail->gen_message_id(); $headers['User-Agent'] = $rcmail->config->get('useragent'); + // Check if we have enough memory to handle the message in it + // It's faster than using files, so we'll do this if we only can + if (!empty($object['_attachments']) && ($mem_limit = parse_bytes(ini_get('memory_limit'))) > 0) { + $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB + + foreach ($object['_attachments'] as $id => $attachment) { + $memory += $attachment['size']; + } + + // 1.33 is for base64, we need at least 4x more memory than the message size + if ($memory * ($binary ? 1 : 1.33) * 4 > $mem_limit) { + $marker = '%%%~~~' . md5(microtime(true) . $memory) . '~~~%%%'; + $is_file = true; + $temp_dir = unslashify($rcmail->config->get('temp_dir')); + $mime->setParam('delay_file_io', true); + } + } + $mime->headers($headers); - $mime->setTXTBody('This is a Kolab Groupware object. ' - . 'To view this object you will need an email client that understands the Kolab Groupware format. ' + $mime->setTXTBody("This is a Kolab Groupware object. " + . "To view this object you will need an email client that understands the Kolab Groupware format. " . "For a list of such email clients please visit http://www.kolab.org/\n\n"); + $ctype = kolab_storage::$version == '2.0' ? $format->CTYPEv2 : $format->CTYPE; + // Convert new lines to \r\n, to wrokaround "NO Message contains bare newlines" + // when APPENDing from temp file + $xml = preg_replace('/\r?\n/', "\r\n", $xml); + $mime->addAttachment($xml, // file - $format->CTYPE, // content-type + $ctype, // content-type 'kolab.xml', // filename false, // is_file '8bit', // encoding @@ -742,29 +967,92 @@ $part_id++; // save object attachments as separate parts - // TODO: optimize memory consumption by using tempfiles for transfer foreach ((array)$object['_attachments'] as $key => $att) { if (empty($att['content']) && !empty($att['id'])) { + // @TODO: use IMAP CATENATE to skip attachment fetch+push operation $msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid']; - $att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox']); + if ($is_file) { + $att['path'] = tempnam($temp_dir, 'rcmAttmnt'); + if (($fp = fopen($att['path'], 'w')) && $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, $fp, true)) { + fclose($fp); + } + else { + return false; + } + } + else { + $att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, null, true); + } } $headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $key . '>', RCUBE_CHARSET, 'quoted-printable')); $name = !empty($att['name']) ? $att['name'] : $key; - if (!empty($att['content'])) { - $mime->addAttachment($att['content'], $att['mimetype'], $name, false, 'base64', 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); - $part_id++; + // To store binary files we can use faster method + // without writting full message content to a temporary file but + // directly to IMAP, see rcube_imap_generic::append(). + // I.e. use file handles where possible + if (!empty($att['path'])) { + if ($is_file && $binary) { + $files[] = fopen($att['path'], 'r'); + $mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); + } + else { + $mime->addAttachment($att['path'], $att['mimetype'], $name, true, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); + } + } + else { + if (is_resource($att['content']) && $is_file && $binary) { + $files[] = $att['content']; + $mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); + } + else { + if (is_resource($att['content'])) { + @rewind($att['content']); + $att['content'] = stream_get_contents($att['content']); + } + $mime->addAttachment($att['content'], $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); + } + } + + $object['_attachments'][$key]['id'] = ++$part_id; + } + + if (!$is_file || !empty($files)) { + $message = $mime->getMessage(); + } + + // parse message and build message array with + // attachment file pointers in place of file markers + if (!empty($files)) { + $message = explode($marker, $message); + $tmp = array(); + + foreach ($message as $msg_part) { + $tmp[] = $msg_part; + if ($file = array_shift($files)) { + $tmp[] = $file; + } } - else if (!empty($att['path'])) { - $mime->addAttachment($att['path'], $att['mimetype'], $name, true, 'base64', 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); - $part_id++; + $message = $tmp; + } + // write complete message body into temp file + else if ($is_file) { + // use common temp dir + $body_file = tempnam($temp_dir, 'rcmMsg'); + + if (PEAR::isError($mime_result = $mime->saveMessageBody($body_file))) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$mime_result->getMessage()), + true, false); + return false; } - $object['_attachments'][$key]['id'] = $part_id; + $message = array(trim($mime->txtHeaders()) . "\r\n\r\n", fopen($body_file, 'r')); } - return $mime->getMessage(); + return $message; } @@ -784,7 +1072,11 @@ case 'event': if ($this->get_namespace() == 'personal') { $result = $this->trigger_url( - sprintf('%s/trigger/%s/%s.pfb', kolab_storage::get_freebusy_server(), $owner, $this->imap->mod_folder($this->name)), + sprintf('%s/trigger/%s/%s.pfb', + kolab_storage::get_freebusy_server(), + urlencode($owner), + urlencode($this->imap->mod_folder($this->name)) + ), $this->imap->options['user'], $this->imap->options['password'] );
View file
kolab-syncroton-2.1.0.tar.gz/lib/plugins/libkolab/libkolab.php -> kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/libkolab.php
Changed
@@ -46,20 +46,9 @@ kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT')); } catch (Exception $e) { - raise_error($e, true); + rcube::raise_error($e, true); kolab_format::$timezone = new DateTimeZone('GMT'); } - - // load (old) dependencies if available - if (@include_once('Horde/Util.php')) { - include_once 'Horde/Kolab/Format.php'; - include_once 'Horde/Kolab/Format/XML.php'; - include_once 'Horde/Kolab/Format/XML/contact.php'; - include_once 'Horde/Kolab/Format/XML/event.php'; - include_once 'Horde_Kolab_Format_XML_configuration.php'; - - String::setDefaultCharset('UTF-8'); - } } /** @@ -70,6 +59,4 @@ $p['fetch_headers'] = trim($p['fetch_headers'] .' X-KOLAB-TYPE X-KOLAB-MIME-VERSION'); return $p; } - - }
View file
kolab-syncroton-2.2.0.tar.gz/lib/plugins/libkolab/package.xml
Added
@@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.0 + http://pear.php.net/dtd/package-2.0.xsd"> + <name>libkolab</name> + <uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri> + <summary>Kolab core library</summary> + <description>Plugin to setup a basic environment for the interaction with a Kolab server.</description> + <lead> + <name>Thomas Bruederli</name> + <user>bruederli</user> + <email>bruederli@kolabsys.com</email> + <active>yes</active> + </lead> + <developer> + <name>Alensader Machniak</name> + <user>machniak</user> + <email>machniak@kolabsys.com</email> + <active>yes</active> + </developer> + <date>2013-04-19</date> + <version> + <release>0.9</release> + <api>0.9</api> + </version> + <stability> + <release>stable</release> + <api>stable</api> + </stability> + <license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3</license> + <notes>-</notes> + <contents> + <dir baseinstalldir="/" name="/"> + <file name="libkolab.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_configuration.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_contact.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_distributionlist.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_event.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_file.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_journal.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_note.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_task.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_format_xcal.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_storage.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_storage_cache.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_storage_folder.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file name="lib/kolab_date_recurrence.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + + <file name="bin/modcache.php" role="php"></file> + + <file name="config.inc.php.dist" role="data"></file> + <file name="LICENSE" role="data"></file> + <file name="README" role="data"></file> + <file name="UPGRADING" role="data"></file> + </dir> + <!-- / --> + </contents> + <dependencies> + <required> + <php> + <min>5.3.1</min> + </php> + <pearinstaller> + <min>1.7.0</min> + </pearinstaller> + </required> + </dependencies> + <phprelease/> +</package>
View file
kolab-syncroton-2.1.0.tar.gz/tests/data.php -> kolab-syncroton-2.2.0.tar.gz/tests/data.php
Changed
@@ -22,7 +22,7 @@ $event = new Syncroton_Model_Event($xml->ApplicationData); $data = new kolab_sync_data_test; - $result = $data->recurrence_to_kolab($event->recurrence); + $result = $data->recurrence_to_kolab($event); $this->assertEquals('DAILY', $result['FREQ']); $this->assertEquals(1, $result['INTERVAL']); @@ -42,7 +42,7 @@ $xml = new SimpleXMLElement($xml); $event = new Syncroton_Model_Event($xml->ApplicationData); - $result = $data->recurrence_to_kolab($event->recurrence); + $result = $data->recurrence_to_kolab($event, null); $this->assertEquals('WEEKLY', $result['FREQ']); $this->assertEquals(1, $result['INTERVAL']); @@ -59,16 +59,16 @@ { } - public function recurrence_to_kolab($data, $timezone = null) + public function recurrence_to_kolab($data) { - return parent::recurrence_to_kolab($data, $timezone); + return parent::recurrence_to_kolab($data, null); } - function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null) + function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null, $timezone = null) { } - function getEntry(Syncroton_Model_SyncCollection $collection, $serverId) + function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) { } }
View file
kolab-syncroton-2.1.0.tar.gz/tests/phpunit.xml -> kolab-syncroton-2.2.0.tar.gz/tests/phpunit.xml
Changed
@@ -7,6 +7,7 @@ <file>data.php</file> <file>data_tasks.php</file> <file>message.php</file> + <file>timezone_converter.php</file> </testsuite> </testsuites> </phpunit>
View file
kolab-syncroton-2.2.0.tar.gz/tests/timezone_converter.php
Added
@@ -0,0 +1,28 @@ +<?php + +class timezone_converter extends PHPUnit_Framework_TestCase +{ + function setUp() + { + } + + + function test_list_timezones() + { + $converter = timezone_converter_test::getInstance(); + + $input = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAEAAAAAAAAAxP///w=='; + $output = $converter->getListOfTimezones($input, 'UTC'); + + $this->assertTrue(is_array($output)); + } +} + +class timezone_converter_test extends kolab_sync_timezone_converter +{ + // disable cache + function getCache() + { + return null; + } +}
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 2.1.0-1 +Version: 2.2.0-1 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Paul Klos <kolab@klos2day.nl> Homepage: http://www.kolab.org/ @@ -12,5 +12,5 @@ Package-List: kolab-syncroton deb utils extra Files: - 00000000000000000000000000000000 0 kolab-syncroton_2.1.0.tar.gz + 00000000000000000000000000000000 0 kolab-syncroton-2.2.0.tar.gz 00000000000000000000000000000000 0 debian.tar.gz
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.