From e667e168b4325e6613d71be26b1f1f208802ffeb Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 15 Jul 2009 19:31:25 +0000 Subject: [PATCH] - merged SyncML-1.2 branch with trunk: svn merge ^/trunk/phpgwapi@27377 ^/branches/SyncML-1.2/phpgwapi . --- phpgwapi/inc/class.contenthistory.inc.php | 4 +- phpgwapi/inc/horde/Horde/Date.php | 769 +++++++++ phpgwapi/inc/horde/Horde/RPC/syncml.php | 4 +- phpgwapi/inc/horde/Horde/String.php | 437 +++-- phpgwapi/inc/horde/Horde/SyncML.php | 369 +++-- phpgwapi/inc/horde/Horde/SyncML/Command.php | 180 ++- .../inc/horde/Horde/SyncML/Command/Alert.php | 699 ++++---- .../inc/horde/Horde/SyncML/Command/Final.php | 31 +- .../inc/horde/Horde/SyncML/Command/Get.php | 85 +- .../inc/horde/Horde/SyncML/Command/Map.php | 130 +- .../inc/horde/Horde/SyncML/Command/Put.php | 231 ++- .../horde/Horde/SyncML/Command/Replace.php | 35 +- .../horde/Horde/SyncML/Command/Results.php | 307 +--- .../inc/horde/Horde/SyncML/Command/Status.php | 173 +- .../inc/horde/Horde/SyncML/Command/Sync.php | 340 ++-- .../horde/Horde/SyncML/Command/Sync/Add.php | 33 +- .../Command/Sync/ContentSyncElement.php | 203 ++- .../Horde/SyncML/Command/Sync/Delete.php | 32 +- .../Horde/SyncML/Command/Sync/Replace.php | 42 +- .../Horde/SyncML/Command/Sync/SyncElement.php | 184 ++- .../SyncML/Command/Sync/SyncElementItem.php | 82 +- phpgwapi/inc/horde/Horde/SyncML/State.php | 300 +++- phpgwapi/inc/horde/Horde/SyncML/State_egw.php | 727 +++++---- phpgwapi/inc/horde/Horde/SyncML/Sync.php | 578 +++++-- .../SyncML/Sync/RefreshFromClientSync.php | 17 +- .../SyncML/Sync/RefreshFromServerSync.php | 190 ++- .../inc/horde/Horde/SyncML/Sync/SlowSync.php | 334 ++-- .../horde/Horde/SyncML/Sync/TwoWaySync.php | 445 ++++-- phpgwapi/inc/horde/Horde/iCalendar.php | 1410 +++++++++-------- phpgwapi/inc/horde/Horde/iCalendar/valarm.php | 10 +- phpgwapi/inc/horde/Horde/iCalendar/vcard.php | 46 +- phpgwapi/inc/horde/Horde/iCalendar/vevent.php | 48 +- .../inc/horde/Horde/iCalendar/vfreebusy.php | 285 +++- .../inc/horde/Horde/iCalendar/vjournal.php | 10 +- phpgwapi/inc/horde/Horde/iCalendar/vnote.php | 18 +- .../inc/horde/Horde/iCalendar/vtimezone.php | 144 +- phpgwapi/inc/horde/Horde/iCalendar/vtodo.php | 10 +- phpgwapi/inc/horde/XML/WBXML.php | 46 +- .../inc/horde/XML/WBXML/ContentHandler.php | 39 +- phpgwapi/inc/horde/XML/WBXML/DTD.php | 14 +- phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php | 37 +- .../inc/horde/XML/WBXML/DTD/SyncMLDevInf.php | 35 +- .../inc/horde/XML/WBXML/DTD/SyncMLMetInf.php | 32 +- phpgwapi/inc/horde/XML/WBXML/DTDManager.php | 76 +- phpgwapi/inc/horde/XML/WBXML/Decoder.php | 110 +- phpgwapi/inc/horde/XML/WBXML/Encoder.php | 56 +- phpgwapi/inc/horde/config/conf.php | 2 +- phpgwapi/inc/horde/lib/core.php | 31 +- 48 files changed, 5959 insertions(+), 3461 deletions(-) create mode 100644 phpgwapi/inc/horde/Horde/Date.php diff --git a/phpgwapi/inc/class.contenthistory.inc.php b/phpgwapi/inc/class.contenthistory.inc.php index c01a878a91..2c0ad1f5f7 100644 --- a/phpgwapi/inc/class.contenthistory.inc.php +++ b/phpgwapi/inc/class.contenthistory.inc.php @@ -39,7 +39,7 @@ class contenthistory * * @param string $_appName the appname example: infolog_notes * @param int $_id the internal egwapp content id - * @return bool + * @return boolean */ function expireMapping($_appName, $_id) { @@ -163,7 +163,7 @@ class contenthistory // now update the time stamp $newData = array ( 'sync_changedby' => $GLOBALS['egw_info']['user']['account_id'], - $_action == 'modify' ? 'sync_modified' : 'sync_deleted' => $_ts , + $_action == 'delete' ? 'sync_deleted' : 'sync_modified' => $this->db->to_timestamp($_ts), ); $this->db->update(self::TABLE, $newData, $where,__LINE__,__FILE__); break; diff --git a/phpgwapi/inc/horde/Horde/Date.php b/phpgwapi/inc/horde/Horde/Date.php new file mode 100644 index 0000000000..496a2ca328 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/Date.php @@ -0,0 +1,769 @@ +_supportedSpecs .= 'bBpxX'; + } + + if (is_array($date) || is_object($date)) { + 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 (is_array($date) && isset($date['day']) && + is_numeric($date['day'])) { + $this->mday = (int)$date['day']; + } + // 'minute' key also from Horde_Form_datetime + if (is_array($date) && isset($date['minute'])) { + $this->min = $date['minute']; + } + } elseif (!is_null($date)) { + // Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'. + if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})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]; + } else { + // 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']; + } + } + } + } + + /** + * @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 day of the year (1-366) that corresponds to the + * first day of the given week. + * + * TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php + * + * @param integer $week The week of the year to find the first day of. + * @param integer $year The year to calculate for. + * + * @return integer The day of the year of the first day of the given week. + */ + function firstDayOfWeek($week, $year) + { + $jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1)); + $start = $jan1->dayOfWeek(); + if ($start > HORDE_DATE_THURSDAY) { + $start -= 7; + } + return (($week * 7) - (7 + $start)) + 1; + } + + /** + * @static + */ + function daysInMonth($month, $year) + { + if ($month == 2) { + if (Horde_Date::isLeapYear($year)) { + return 29; + } else { + return 28; + } + } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) { + return 30; + } else { + return 31; + } + } + + /** + * Return the day of the week (0 = Sunday, 6 = Saturday) of this + * object's date. + * + * @return integer The day of the week. + */ + 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. + */ + function dayOfYear() + { + $monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334); + $dayOfYear = $this->mday + $monthTotals[$this->month - 1]; + if (Horde_Date::isLeapYear($this->year) && $this->month > 2) { + ++$dayOfYear; + } + + return $dayOfYear; + } + + /** + * Returns the week of the month. + * + * @since Horde 3.2 + * + * @return integer The week number. + */ + 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. + */ + function weekOfYear() + { + return $this->format('W'); + } + + /** + * Return the number of weeks in the given year (52 or 53). + * + * @static + * + * @param integer $year The year to count the number of weeks in. + * + * @return integer $numWeeks The number of weeks in $year. + */ + function weeksInYear($year) + { + // Find the last Thursday of the year. + $day = 31; + $date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0)); + while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) { + --$date->mday; + } + return $date->weekOfYear(); + } + + /** + * Set 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). + */ + function setNthWeekday($weekday, $nth = 1) + { + if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) { + return false; + } + + $this->mday = 1; + $first = $this->dayOfWeek(); + if ($weekday < $first) { + $this->mday = 8 + $weekday - $first; + } else { + $this->mday = $weekday - $first + 1; + } + $this->mday += 7 * $nth - 7; + + $this->correct(); + + return true; + } + + function dump($prefix = '') + { + echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "
\n"; + } + + /** + * Is the date currently represented by this object a valid date? + * + * @return boolean Validity, counting leap years, etc. + */ + function isValid() + { + if ($this->year < 0 || $this->year > 9999) { + return false; + } + return checkdate($this->month, $this->mday, $this->year); + } + + /** + * Correct any over- or underflows in any of the date's members. + * + * @param integer $mask We may not want to correct some overflows. + */ + function correct($mask = HORDE_DATE_MASK_ALLPARTS) + { + if ($mask & HORDE_DATE_MASK_SECOND) { + while ($this->sec < 0) { + --$this->min; + $this->sec += 60; + } + while ($this->sec > 59) { + ++$this->min; + $this->sec -= 60; + } + } + + if ($mask & HORDE_DATE_MASK_MINUTE) { + while ($this->min < 0) { + --$this->hour; + $this->min += 60; + } + while ($this->min > 59) { + ++$this->hour; + $this->min -= 60; + } + } + + if ($mask & HORDE_DATE_MASK_HOUR) { + while ($this->hour < 0) { + --$this->mday; + $this->hour += 24; + } + while ($this->hour > 23) { + ++$this->mday; + $this->hour -= 24; + } + } + + if ($mask & HORDE_DATE_MASK_MONTH) { + while ($this->month > 12) { + ++$this->year; + $this->month -= 12; + } + while ($this->month < 1) { + --$this->year; + $this->month += 12; + } + } + + if ($mask & HORDE_DATE_MASK_DAY) { + while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) { + $this->mday -= Horde_Date::daysInMonth($this->month, $this->year); + ++$this->month; + $this->correct(HORDE_DATE_MASK_MONTH); + } + while ($this->mday < 1) { + --$this->month; + $this->correct(HORDE_DATE_MASK_MONTH); + $this->mday += Horde_Date::daysInMonth($this->month, $this->year); + } + } + } + + /** + * Compare this date to another date object to see which one is + * greater (later). Assumes that the dates are in the same + * timezone. + * + * @param mixed $date The date to compare to. + * + * @return integer == 0 if the dates are equal + * >= 1 if this date is greater (later) + * <= -1 if the other date is greater (later) + */ + function compareDate($date) + { + if (!is_a($date, 'Horde_Date')) { + $date = new Horde_Date($date); + } + + if ($this->year != $date->year) { + return $this->year - $date->year; + } + if ($this->month != $date->month) { + return $this->month - $date->month; + } + + return $this->mday - $date->mday; + } + + /** + * Compare 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 $date The date to compare to. + * + * @return integer == 0 if the dates are equal + * >= 1 if this date is greater (later) + * <= -1 if the other date is greater (later) + */ + function compareTime($date) + { + if (!is_a($date, 'Horde_Date')) { + $date = new Horde_Date($date); + } + + if ($this->hour != $date->hour) { + return $this->hour - $date->hour; + } + if ($this->min != $date->min) { + return $this->min - $date->min; + } + + return $this->sec - $date->sec; + } + + /** + * Compare 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 $date The date to compare to. + * + * @return integer == 0 if the dates are equal + * >= 1 if this date is greater (later) + * <= -1 if the other date is greater (later) + */ + function compareDateTime($date) + { + if (!is_a($date, 'Horde_Date')) { + $date = new Horde_Date($date); + } + + if ($diff = $this->compareDate($date)) { + return $diff; + } + + return $this->compareTime($date); + } + + /** + * Get 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. + */ + function tzOffset($colon = true) + { + $secs = $this->format('Z'); + + if ($secs < 0) { + $sign = '-'; + $secs = -$secs; + } else { + $sign = '+'; + } + $colon = $colon ? ':' : ''; + $mins = intval(($secs + 30) / 60); + return sprintf('%s%02d%s%02d', + $sign, $mins / 60, $colon, $mins % 60); + } + + /** + * Return the unix timestamp representation of this date. + * + * @return integer A unix timestamp. + */ + function timestamp() + { + if (class_exists('DateTime')) { + return $this->format('U'); + } else { + return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year); + } + } + + /** + * Return the unix timestamp representation of this date, 12:00am. + * + * @return integer A unix timestamp. + */ + function datestamp() + { + if (class_exists('DateTime')) { + $dt = new DateTime(); + $dt->setDate($this->year, $this->month, $this->mday); + $dt->setTime(0, 0, 0); + return $dt->format('U'); + } else { + return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year); + } + } + + /** + * Format time using the specifiers available in date() or in the DateTime + * class' format() method. + * + * @since Horde 3.3 + * + * @param string $format + * + * @return string Formatted time. + */ + function format($format) + { + if (class_exists('DateTime')) { + $dt = new DateTime(); + $dt->setDate($this->year, $this->month, $this->mday); + $dt->setTime($this->hour, $this->min, $this->sec); + return $dt->format($format); + } else { + return date($format, $this->timestamp()); + } + } + + /** + * Format time in ISO-8601 format. Works correctly since Horde 3.2. + * + * @return string Date and time in ISO-8601 format. + */ + function iso8601DateTime() + { + return $this->rfc3339DateTime() . $this->tzOffset(); + } + + /** + * Format time in RFC 2822 format. + * + * @return string Date and time in RFC 2822 format. + */ + function rfc2822DateTime() + { + return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false); + } + + /** + * Format time in RFC 3339 format. + * + * @since Horde 3.1 + * + * @return string Date and time in RFC 3339 format. The seconds part has + * been added with Horde 3.2. + */ + function rfc3339DateTime() + { + return $this->format('Y-m-d\TH:i:s'); + } + + /** + * Format time to standard 'ctime' format. + * + * @return string Date and time. + */ + function cTime() + { + return $this->format('D M j H:i:s Y'); + } + + /** + * Format date and time using strftime() format. + * + * @since Horde 3.1 + * + * @return string strftime() formatted date and time. + */ + function strftime($format) + { + if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) { + return strftime($format, $this->timestamp()); + } else { + return $this->_strftime($format); + } + } + + /** + * Format date and time using a limited set of the strftime() format. + * + * @return string strftime() formatted date and time. + */ + function _strftime($format) + { + if (preg_match('/%[bBpxX]/', $format)) { + require_once 'Horde/NLS.php'; + } + + 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(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))', + '$this->_strftime(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(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(NLS::getLangInfo(D_FMT))', + '$this->_strftime(NLS::getLangInfo(T_FMT))', + 'substr(sprintf(\'%04d\', $this->year), -2)', + (int)$this->year, + '%'), + $format); + } + + /** + * mktime() implementation that supports dates outside of 1970-2038, + * from http://phplens.com/phpeverywhere/adodb_date_library. + * + * @TODO remove in Horde 4 + * + * This does NOT work with pre-1970 daylight saving times. + * + * @static + */ + function _mktime($hr, $min, $sec, $mon = false, $day = false, + $year = false, $is_dst = false, $is_gmt = false) + { + if ($mon === false) { + return $is_gmt + ? @gmmktime($hr, $min, $sec) + : @mktime($hr, $min, $sec); + } + + if ($year > 1901 && $year < 2038 && + ($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) { + return $is_gmt + ? @gmmktime($hr, $min, $sec, $mon, $day, $year) + : @mktime($hr, $min, $sec, $mon, $day, $year); + } + + $gmt_different = $is_gmt + ? 0 + : (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0)); + + $mon = intval($mon); + $day = intval($day); + $year = intval($year); + + if ($mon > 12) { + $y = floor($mon / 12); + $year += $y; + $mon -= $y * 12; + } elseif ($mon < 1) { + $y = ceil((1 - $mon) / 12); + $year -= $y; + $mon += $y * 12; + } + + $_day_power = 86400; + $_hour_power = 3600; + $_min_power = 60; + + $_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + $_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + + $_total_date = 0; + if ($year >= 1970) { + for ($a = 1970; $a <= $year; $a++) { + $leaf = Horde_Date::isLeapYear($a); + if ($leaf == true) { + $loop_table = $_month_table_leaf; + $_add_date = 366; + } else { + $loop_table = $_month_table_normal; + $_add_date = 365; + } + if ($a < $year) { + $_total_date += $_add_date; + } else { + for ($b = 1; $b < $mon; $b++) { + $_total_date += $loop_table[$b]; + } + } + } + + return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different; + } + + for ($a = 1969 ; $a >= $year; $a--) { + $leaf = Horde_Date::isLeapYear($a); + if ($leaf == true) { + $loop_table = $_month_table_leaf; + $_add_date = 366; + } else { + $loop_table = $_month_table_normal; + $_add_date = 365; + } + if ($a > $year) { + $_total_date += $_add_date; + } else { + for ($b = 12; $b > $mon; $b--) { + $_total_date += $loop_table[$b]; + } + } + } + + $_total_date += $loop_table[$mon] - $day; + $_day_time = $hr * $_hour_power + $min * $_min_power + $sec; + $_day_time = $_day_power - $_day_time; + $ret = -($_total_date * $_day_power + $_day_time - $gmt_different); + if ($ret < -12220185600) { + // If earlier than 5 Oct 1582 - gregorian correction. + return $ret + 10 * 86400; + } elseif ($ret < -12219321600) { + // If in limbo, reset to 15 Oct 1582. + return -12219321600; + } else { + return $ret; + } + } + +} diff --git a/phpgwapi/inc/horde/Horde/RPC/syncml.php b/phpgwapi/inc/horde/Horde/RPC/syncml.php index f276e2f6b2..93350b03da 100644 --- a/phpgwapi/inc/horde/Horde/RPC/syncml.php +++ b/phpgwapi/inc/horde/Horde/RPC/syncml.php @@ -113,8 +113,8 @@ class Horde_RPC_syncml extends Horde_RPC { * that came up for later debugging. */ $errorLogging = ob_get_clean(); if (!empty($errorLogging)) { - #Horde::logMessage('SyncML: caught output=' . - # $errorLogging, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML: caught output=' . + $errorLogging, __FILE__, __LINE__, PEAR_LOG_DEBUG); } return $response; diff --git a/phpgwapi/inc/horde/Horde/String.php b/phpgwapi/inc/horde/Horde/String.php index 7e4ef98878..1ff4021c21 100644 --- a/phpgwapi/inc/horde/Horde/String.php +++ b/phpgwapi/inc/horde/Horde/String.php @@ -1,16 +1,14 @@ + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. @@ -21,18 +19,40 @@ $GLOBALS['_HORDE_STRING_CHARSET'] = 'iso-8859-1'; */ class String { + /** + * Caches the result of extension_loaded() calls. + * + * @param string $ext The extension name. + * + * @return boolean Is the extension loaded? + * + * @see Util::extensionExists() + */ + function extensionExists($ext) + { + static $cache = array(); + + if (!isset($cache[$ext])) { + $cache[$ext] = extension_loaded($ext); + } + + return $cache[$ext]; + } + /** * Sets a default charset that the String:: methods will use if none is - * explicitely specified. + * explicitly specified. * * @param string $charset The charset to use as the default one. */ function setDefaultCharset($charset) { $GLOBALS['_HORDE_STRING_CHARSET'] = $charset; - if (Util::extensionExists('mbstring') && + if (String::extensionExists('mbstring') && function_exists('mb_regex_encoding')) { - @mb_regex_encoding($charset); + $old_error = error_reporting(0); + mb_regex_encoding(String::_mbstringCharset($charset)); + error_reporting($old_error); } } @@ -44,44 +64,53 @@ class String { * The original string is returned if conversion failed or none * of the extensions were available. * - * @param mixed $input The data to be converted. If $input is an an - * array, the array's values get converted - * recursively. - * @param string $from The string's current charset. - * @param string $to The charset to convert the string to. If not - * specified, the global variable - * $_HORDE_STRING_CHARSET will be used. - * @param bool $recursion Internally used. + * @param mixed $input The data to be converted. If $input is an an array, + * the array's values get converted recursively. + * @param string $from The string's current charset. + * @param string $to The charset to convert the string to. If not + * specified, the global variable + * $_HORDE_STRING_CHARSET will be used. * - * @return string The converted string. + * @return mixed The converted input data. */ - function convertCharset($input, $from, $to = null, $recursion = false) + function convertCharset($input, $from, $to = null) { + /* Don't bother converting numbers. */ + if (is_numeric($input)) { + return $input; + } + /* Get the user's default character set if none passed in. */ if (is_null($to)) { $to = $GLOBALS['_HORDE_STRING_CHARSET']; } /* If the from and to character sets are identical, return now. */ - if (!$recursion) { - $from = String::lower($from); - $to = String::lower($to); - } + $from = String::lower($from); + $to = String::lower($to); if ($from == $to) { return $input; } if (is_array($input)) { $tmp = array(); - foreach ($input as $key => $val) { - $tmp[String::convertCharset($key, $from, $to, true)] = String::convertCharset($val, $from, $to, true); + while (list($key, $val) = each($input)) { + $tmp[String::_convertCharset($key, $from, $to)] = String::convertCharset($val, $from, $to); } return $tmp; } if (is_object($input)) { + // PEAR_Error objects are almost guaranteed to contain recursion, + // which will cause a segfault in PHP. We should never reach + // this line, but add a check and a log message to help the devs + // track down and fix this issue. + if (is_a($input, 'PEAR_Error')) { + Horde::logMessage('Called convertCharset() on a PEAR_Error object. ' . print_r($input, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return ''; + } $vars = get_object_vars($input); - foreach ($vars as $key => $val) { - $input->$key = String::convertCharset($val, $from, $to, true); + while (list($key, $val) = each($vars)) { + $input->$key = String::convertCharset($val, $from, $to); } return $input; } @@ -90,43 +119,62 @@ class String { return $input; } - $output = false; + return String::_convertCharset($input, $from, $to); + } - /* Use utf8_[en|de]code() if possible. */ + /** + * Internal function used to do charset conversion. + * + * @access private + * + * @param string $input See String::convertCharset(). + * @param string $from See String::convertCharset(). + * @param string $to See String::convertCharset(). + * + * @return string The converted string. + */ + function _convertCharset($input, $from, $to) + { + $output = ''; $from_check = (($from == 'iso-8859-1') || ($from == 'us-ascii')); - if ($from_check && ($to == 'utf-8')) { - return utf8_encode($input); - } - $to_check = (($to == 'iso-8859-1') || ($to == 'us-ascii')); - if (($from == 'utf-8') && $to_check) { - return utf8_decode($input); + + /* Use utf8_[en|de]code() if possible and if the string isn't too + * large (less than 16 MB = 16 * 1024 * 1024 = 16777216 bytes) - these + * functions use more memory. */ + if (strlen($input) < 16777216 || !(String::extensionExists('iconv') || String::extensionExists('mbstring'))) { + if ($from_check && ($to == 'utf-8')) { + return utf8_encode($input); + } + + if (($from == 'utf-8') && $to_check) { + return utf8_decode($input); + } } /* First try iconv with transliteration. */ - if ($from != 'utf7-imap' && - $to != 'utf7-imap' && - Util::extensionExists('iconv')) { - ini_set('track_errors', 1); - /* We need to tack an extra character temporarily because - * of a bug in iconv() if the last character is not a 7 - * bit ASCII character. */ + if (($from != 'utf7-imap') && + ($to != 'utf7-imap') && + String::extensionExists('iconv')) { + /* We need to tack an extra character temporarily because of a bug + * in iconv() if the last character is not a 7 bit ASCII + * character. */ + $oldTrackErrors = ini_set('track_errors', 1); + unset($php_errormsg); $output = @iconv($from, $to . '//TRANSLIT', $input . 'x'); - if (isset($php_errormsg)) { - $output = false; - } else { - $output = String::substr($output, 0, -1, $to); - } - ini_restore('track_errors'); + $output = (isset($php_errormsg)) ? false : String::substr($output, 0, -1, $to); + ini_set('track_errors', $oldTrackErrors); } /* Next try mbstring. */ - if (!$output && Util::extensionExists('mbstring')) { - $output = @mb_convert_encoding($input, $to, $from); + if (!$output && String::extensionExists('mbstring')) { + $old_error = error_reporting(0); + $output = mb_convert_encoding($input, $to, String::_mbstringCharset($from)); + error_reporting($old_error); } /* At last try imap_utf7_[en|de]code if appropriate. */ - if (!$output && Util::extensionExists('imap')) { + if (!$output && String::extensionExists('imap')) { if ($from_check && ($to == 'utf7-imap')) { return @imap_utf7_encode($input); } @@ -135,7 +183,7 @@ class String { } } - return !$output ? $input : $output; + return (!$output) ? $input : $output; } /** @@ -155,12 +203,14 @@ class String { if ($locale) { /* The existence of mb_strtolower() depends on the platform. */ - if (Util::extensionExists('mbstring') && + if (String::extensionExists('mbstring') && function_exists('mb_strtolower')) { if (is_null($charset)) { $charset = $GLOBALS['_HORDE_STRING_CHARSET']; } - $ret = @mb_strtolower($string, $charset); + $old_error = error_reporting(0); + $ret = mb_strtolower($string, String::_mbstringCharset($charset)); + error_reporting($old_error); if (!empty($ret)) { return $ret; } @@ -173,7 +223,7 @@ class String { } if (!isset($lowers[$string])) { $language = setlocale(LC_CTYPE, 0); - setlocale(LC_CTYPE, 'en_US'); + setlocale(LC_CTYPE, 'C'); $lowers[$string] = strtolower($string); setlocale(LC_CTYPE, $language); } @@ -203,7 +253,9 @@ class String { if (is_null($charset)) { $charset = $GLOBALS['_HORDE_STRING_CHARSET']; } - $ret = @mb_strtoupper($string, $charset); + $old_error = error_reporting(0); + $ret = mb_strtoupper($string, String::_mbstringCharset($charset)); + error_reporting($old_error); if (!empty($ret)) { return $ret; } @@ -216,7 +268,7 @@ class String { } if (!isset($uppers[$string])) { $language = setlocale(LC_CTYPE, 0); - setlocale(LC_CTYPE, 'en_US'); + setlocale(LC_CTYPE, 'C'); $uppers[$string] = strtoupper($string); setlocale(LC_CTYPE, $language); } @@ -251,31 +303,34 @@ class String { /** * Returns part of a string. * - * @param string $string The string to be converted. - * @param int $start The part's start position, zero based. - * @param int $length The part's length. - * @param string $charset The charset to use when calculating the part's - * position and length, defaults to current charset. + * @param string $string The string to be converted. + * @param integer $start The part's start position, zero based. + * @param integer $length The part's length. + * @param string $charset The charset to use when calculating the part's + * position and length, defaults to current + * charset. * * @return string The string's part. */ function substr($string, $start, $length = null, $charset = null) { - if (Util::extensionExists('mbstring')) { + if (is_null($length)) { + $length = String::length($string, $charset) - $start; + } + if ($length == 0) { + return ''; + } + if (String::extensionExists('mbstring')) { if (is_null($charset)) { $charset = $GLOBALS['_HORDE_STRING_CHARSET']; } - if (is_null($length)) { - $length = String::length($string, $charset); - } - $ret = @mb_substr($string, $start, $length, $charset); + $old_error = error_reporting(0); + $ret = mb_substr($string, $start, $length, String::_mbstringCharset($charset)); + error_reporting($old_error); if (!empty($ret)) { return $ret; } } - if (is_null($length)) { - $length = String::length($string); - } return substr($string, $start, $length); } @@ -290,11 +345,17 @@ class String { */ function length($string, $charset = null) { - if (Util::extensionExists('mbstring')) { - if (is_null($charset)) { - $charset = $GLOBALS['_HORDE_STRING_CHARSET']; - } - $ret = @mb_strlen($string, $charset); + if (is_null($charset)) { + $charset = $GLOBALS['_HORDE_STRING_CHARSET']; + } + $charset = String::lower($charset); + if ($charset == 'utf-8' || $charset == 'utf8') { + return strlen(utf8_decode($string)); + } + if (String::extensionExists('mbstring')) { + $old_error = error_reporting(0); + $ret = mb_strlen($string, String::_mbstringCharset($charset)); + error_reporting($old_error); if (!empty($ret)) { return $ret; } @@ -308,22 +369,24 @@ class String { * * @param string $haystack The string to search through. * @param string $needle The string to search for. - * @param int $offset Allows to specify which character in haystack + * @param integer $offset Allows to specify which character in haystack * to start searching. * @param string $charset The charset to use when searching for the * $needle string. * - * @return int The position of first occurrence. + * @return integer The position of first occurrence. */ function pos($haystack, $needle, $offset = 0, $charset = null) { - if (Util::extensionExists('mbstring')) { + if (String::extensionExists('mbstring')) { if (is_null($charset)) { $charset = $GLOBALS['_HORDE_STRING_CHARSET']; } - ini_set('track_errors', 1); - $ret = @mb_strpos($haystack, $needle, $offset, $charset); - ini_restore('track_errors'); + $track_errors = ini_set('track_errors', 1); + $old_error = error_reporting(0); + $ret = mb_strpos($haystack, $needle, $offset, String::_mbstringCharset($charset)); + error_reporting($old_error); + ini_set('track_errors', $track_errors); if (!isset($php_errormsg)) { return $ret; } @@ -337,7 +400,7 @@ class String { * This method behaves exactly like str_pad but is multibyte safe. * * @param string $input The string to be padded. - * @param int $length The length of the resulting string. + * @param integer $length The length of the resulting string. * @param string $pad The string to pad the input string with. Must * be in the same charset like the input string. * @param const $type The padding type. One of STR_PAD_LEFT, @@ -388,22 +451,89 @@ class String { /** * Wraps the text of a message. * - * @todo Make multibyte-save. + * @since Horde 3.2 * - * @access public + * @param string $string String containing the text to wrap. + * @param integer $width Wrap the string at this number of + * characters. + * @param string $break Character(s) to use when breaking lines. + * @param boolean $cut Whether to cut inside words if a line + * can't be wrapped. + * @param string $charset Character set to use when breaking lines. + * @param boolean $line_folding Whether to apply line folding rules per + * RFC 822 or similar. The correct break + * characters including leading whitespace + * have to be specified too. * - * @param string $text String containing the text to wrap. - * @param optional integer $length Wrap $text at this number of - * characters. - * @param optional string $break_char Character(s) to use when breaking - * lines. - * @param optional string $charset Character set to use when breaking - * lines. - * @param optional boolean $quote Ignore lines that are wrapped with - * the '>' character (RFC 2646)? If - * true, we don't remove any padding - * whitespace at the end of the - * string. + * @return string String containing the wrapped text. + */ + function wordwrap($string, $width = 75, $break = "\n", $cut = false, + $charset = null, $line_folding = false) + { + /* Get the user's default character set if none passed in. */ + if (is_null($charset)) { + $charset = $GLOBALS['_HORDE_STRING_CHARSET']; + } + $charset = String::_mbstringCharset($charset); + $string = String::convertCharset($string, $charset, 'utf-8'); + $wrapped = ''; + + while (String::length($string, 'utf-8') > $width) { + $line = String::substr($string, 0, $width, 'utf-8'); + $string = String::substr($string, String::length($line, 'utf-8'), null, 'utf-8'); + // Make sure didn't cut a word, unless we want hard breaks anyway. + if (!$cut && preg_match('/^(.+?)(\s|\r?\n)/u', $string, $match)) { + $line .= $match[1]; + $string = String::substr($string, String::length($match[1], 'utf-8'), null, 'utf-8'); + } + // Wrap at existing line breaks. + if (preg_match('/^(.*?)(\r?\n)(.*)$/u', $line, $match)) { + $wrapped .= $match[1] . $match[2]; + $string = $match[3] . $string; + continue; + } + // Wrap at the last colon or semicolon followed by a whitespace if + // doing line folding. + if ($line_folding && + preg_match('/^(.*?)(;|:)(\s+.*)$/u', $line, $match)) { + $wrapped .= $match[1] . $match[2] . $break; + $string = $match[3] . $string; + continue; + } + // Wrap at the last whitespace of $line. + if ($line_folding) { + $sub = '(.+[^\s])'; + } else { + $sub = '(.*)'; + } + if (preg_match('/^' . $sub . '(\s+)(.*)$/u', $line, $match)) { + $wrapped .= $match[1] . $break; + $string = ($line_folding ? $match[2] : '') . $match[3] . $string; + continue; + } + // Hard wrap if necessary. + if ($cut) { + $wrapped .= String::substr($line, 0, $width, 'utf-8') . $break; + $string = String::substr($line, $width, null, 'utf-8') . $string; + continue; + } + $wrapped .= $line; + } + + return String::convertCharset($wrapped . $string, 'utf-8', $charset); + } + + /** + * Wraps the text of a message. + * + * @param string $text String containing the text to wrap. + * @param integer $length Wrap $text at this number of characters. + * @param string $break_char Character(s) to use when breaking lines. + * @param string $charset Character set to use when breaking lines. + * @param boolean $quote Ignore lines that are wrapped with the '>' + * character (RFC 2646)? If true, we don't + * remove any padding whitespace at the end of + * the string. * * @return string String containing the wrapped text. */ @@ -422,7 +552,7 @@ class String { if ($input != '-- ') { $input = rtrim($input); } - $line = wordwrap($input, $length, $break_char); + $line = String::wordwrap($input, $length, $break_char, false, $charset); } $paragraphs[] = $line; @@ -432,9 +562,8 @@ class String { } /** - * Returns true if the every character in the parameter is an - * alphabetic character. This method doesn't work with any charset - * other than the current charset yet. + * Returns true if the every character in the parameter is an alphabetic + * character. * * @param $string The string to test. * @param $charset The charset to use when testing the string. @@ -443,26 +572,30 @@ class String { */ function isAlpha($string, $charset = null) { - if (Util::extensionExists('mbstring')) { - $old_charset = mb_regex_encoding(); - if ($charset != $old_charset) { - @mb_regex_encoding($charset); - } - $alpha = !mb_ereg_match('[^[:alpha:]]', $string); - if ($charset != $old_charset) { - @mb_regex_encoding($old_charset); - } - return $alpha; + if (!String::extensionExists('mbstring')) { + return ctype_alpha($string); } - return ctype_alpha($string); + $charset = String::_mbstringCharset($charset); + $old_charset = mb_regex_encoding(); + $old_error = error_reporting(0); + + if ($charset != $old_charset) { + mb_regex_encoding($charset); + } + $alpha = !mb_ereg_match('[^[:alpha:]]', $string); + if ($charset != $old_charset) { + mb_regex_encoding($old_charset); + } + + error_reporting($old_error); + + return $alpha; } /** - * Returns true if every character in the parameter is a lowercase - * letter in the current locale. - * - * @access public + * Returns true if ever character in the parameter is a lowercase letter in + * the current locale. * * @param $string The string to test. * @param $charset The charset to use when testing the string. @@ -476,10 +609,8 @@ class String { } /** - * Returns true if every character in the parameter is an - * uppercase letter in the current locale. - * - * @access public + * Returns true if every character in the parameter is an uppercase letter + * in the current locale. * * @param string $string The string to test. * @param string $charset The charset to use when testing the string. @@ -493,63 +624,59 @@ class String { } /** - * Performs a regex match search on the text provided. Will correctly - * handle text with multibyte characters if the mbstring extensions and - * the mbregex functions are available. Will use the preg_match() - * function if possible or if the mbregex ereg function is not available. + * Performs a multibyte safe regex match search on the text provided. * - * @access public * @since Horde 3.1 * * @param string $text The text to search. - * @param array $regex The regular expressions to use. These - * expressions should conform to ereg() rules - - * extended perl rules are NOT supported. - * Additionally, do NOT add perl regex delimiters - * (e.g. '/' or '|') to the beginning/end. + * @param array $regex The regular expressions to use, without perl + * regex delimiters (e.g. '/' or '|'). * @param string $charset The character set of the text. * * @return array The matches array from the first regex that matches. */ function regexMatch($text, $regex, $charset = null) { - static $mbregex; - if (!isset($mbregex)) { - $mbregex = function_exists('mb_ereg'); - } - - $use_mb = false; - - if ($mbregex && !is_null($charset) && - (String::lower($charset) != 'utf-8')) { - $old_charset = mb_regex_encoding(); - if ($charset != $old_charset) { - @mb_regex_encoding($charset); - } else { - unset($old_charset); - } - $use_mb = true; + if (!empty($charset)) { + $regex = String::convertCharset($regex, $charset, 'utf-8'); + $text = String::convertCharset($text, $charset, 'utf-8'); } $matches = array(); - foreach ($regex as $val) { - if ($use_mb) { - if (mb_ereg($val, $text, $matches)) { - break; - } - } else { - if (preg_match('/' . $val . '/u', $text, $matches)) { - break; - } + if (preg_match('/' . $val . '/u', $text, $matches)) { + break; } } - - if (isset($old_charset)) { - @mb_regex_encoding($old_charset); + + if (!empty($charset)) { + $matches = String::convertCharset($matches, 'utf-8', $charset); } return $matches; } + /** + * Workaround charsets that don't work with mbstring functions. + * + * @access private + * + * @param string $charset The original charset. + * + * @return string The charset to use with mbstring functions. + */ + function _mbstringCharset($charset) + { + /* mbstring functions do not handle the 'ks_c_5601-1987' & + * 'ks_c_5601-1989' charsets. However, these charsets are used, for + * example, by various versions of Outlook to send Korean characters. + * Use UHC (CP949) encoding instead. See, e.g., + * http://lists.w3.org/Archives/Public/ietf-charsets/2001AprJun/0030.html */ + if (in_array(String::lower($charset), array('ks_c_5601-1987', 'ks_c_5601-1989'))) { + $charset = 'UHC'; + } + + return $charset; + } + } diff --git a/phpgwapi/inc/horde/Horde/SyncML.php b/phpgwapi/inc/horde/Horde/SyncML.php index 50bd3935bd..8bdef4f2cd 100644 --- a/phpgwapi/inc/horde/Horde/SyncML.php +++ b/phpgwapi/inc/horde/Horde/SyncML.php @@ -1,32 +1,32 @@ + * @author Karsten Fourmont + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command.php'; include_once 'Horde/SyncML/Command/Status.php'; include_once 'Horde/SyncML/Command/Alert.php'; include_once 'Horde/SyncML/Command/Final.php'; include_once 'Horde/SyncML/Command/Sync.php'; include_once 'Horde/SyncML/Sync.php'; +include_once 'Horde/SyncML/Command/Sync/SyncElementItem.php'; -/** - * The Horde_SyncML_SyncHdr and Horde_SyncML_SyncBody classes provides - * a SyncHdr and SyncBody in SyncML Representation Protocol, version - * 1.1 5.2.2 and 5.2.3. Most of the work is passed on to - * Horde_SyncML_Command_Alert and Horde_SyncML_Command_Sync. - * - * $Horde: framework/SyncML/SyncML.php,v 1.21 2004/07/21 19:26:36 karsten Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @author Karsten Fourmont - * - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_ContentHandler { /** @@ -144,41 +144,52 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { var $_credType; + var $_maxMsgSize; + function getStateFromSession($sourceURI, $locName, $sessionID) { // Remove any existing session since we'll be contructing a // custom session id. + session_regenerate_id(); session_destroy(); // we need to (re-)load the eGW session-handler, as session_destroy unloads custom session-handlers - if (function_exists('init_session_handler')) - { - init_session_handler(); + if (function_exists('init_session_handler')) { + init_session_handler(); } - // Reload the Horde SessionHandler if necessary. + + // Reload the Horde SessionHandler if necessary. Horde::setupSessionHandler(); // It would seem multisync does not send the user name once it // has been authorized. Make sure we have a valid session id. if(!empty($_GET['syncml_sessionid'])) { session_id($_GET['syncml_sessionid']); - Horde::logMessage('SyncML['. session_id() .']: reusing existing session', __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML['. session_id() .']: reusing existing session', + __FILE__, __LINE__, PEAR_LOG_DEBUG); } else { #session_id('syncml' . preg_replace('/[^a-zA-Z0-9]/', '', $sourceURI . $sessionID)); session_id('syncml-' . md5(uniqid(rand(), true))); - Horde::logMessage('SyncML['. session_id() .']: starting new session for '.$this->_locName, __FILE__, __LINE__, PEAR_LOG_INFO); + Horde::logMessage('SyncML['. session_id() .']: starting new session for ' + . $this->_locName, __FILE__, __LINE__, PEAR_LOG_INFO); } + @session_start(); if (!isset($_SESSION['SyncML.state'])) { // Create a new state if one does not already exist. - Horde::logMessage('SyncML['. session_id() .']: create new session state variable', __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML['. session_id() .']: create new session state variable for ' + . $sourceURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); -# LK $_SESSION['SyncML.state'] = new Horde_SyncML_State($sourceURI, $locName, $sessionID); $_SESSION['SyncML.state'] = new EGW_SyncML_State($sourceURI, $locName, $sessionID); - } - #if($_SESSION['SyncML.state']->_isAuthorized) - # Horde::logMessage('SyncML['. session_id() .']: is session authorized', __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + + if($_SESSION['SyncML.state']->isAuthorized()) { + Horde::logMessage('SyncML['. session_id() .']: session is authorized', + __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + #Horde::logMessage('SyncML['. session_id() . "]:\n" . print_r($_SESSION['SyncML.state'], true), __FILE__, __LINE__, PEAR_LOG_DEBUG); return $_SESSION['SyncML.state']; } @@ -216,18 +227,26 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { #Horde::logMessage('SymcML: SyncHdr done. Try to load state from session.', __FILE__, __LINE__, PEAR_LOG_DEBUG); $state = $this->getStateFromSession($this->_sourceURI, $this->_locName, $this->_sessionID); - Horde::logMessage('SyncML['. session_id() .']: package '.$this->_msgID.' +++++++++++++++++++++ started', __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML['. session_id() .']: package ' + . $this->_msgID.' +++++++++++++++++++++ started', + __FILE__, __LINE__, PEAR_LOG_DEBUG); $state->setVersion($this->_version); $state->setMsgID($this->_msgID); $state->setTargetURI($this->_targetURI); $state->setWBXML(is_a($this->_output, 'XML_WBXML_Encoder')); - if(isset($this->_credData) && isset($this->_locName) && !$state->isAuthorized()) - { + + if(isset($this->_credData) + && isset($this->_locName) + && !$state->isAuthorized()) { $state->setPassword($this->_credData); $state->setLocName($this->_locName); } + if (isset($this->_maxMsgSize)) { + $state->setMaxMsgSize($this->_maxMsgSize); + } + #$str = 'authorized=' . $state->isAuthorized(); #$str .= ' version=' . $state->getVersion(); #$str .= ' msgid=' . $state->getMsgID(); @@ -246,11 +265,17 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { case 3: if ($element == 'VerProto') { // - if (trim($this->_chars) == 'SyncML/1.1') { - $this->_version = 1; - } else { - $this->_version = 0; - } + switch (strtolower(trim($this->_chars))) { + case 'syncml/1.2': + $this->_version = 2; + break; + case 'syncml/1.1': + $this->_version = 1; + break; + default: + $this->_version = 0; + break; + } } elseif ($element == 'SessionID') { // $this->_sessionID = trim($this->_chars); @@ -271,8 +296,7 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { $tmp = explode(':', $this->_credData, 2); // set only if not set by LocName already - if(!isset($this->_locName)) - { + if(!isset($this->_locName)) { $this->_locName = $tmp[0]; } $this->_credData = $tmp[1]; @@ -282,26 +306,34 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { break; case 4: - if ($element == 'LocURI') { - if ($this->_isSource) { - // - $this->_sourceURI = trim($this->_chars); - } else { - // - $this->_targetURI = trim($this->_chars); - } - } elseif ($element == 'LocName') { - if ($this->_isSource) { - // - $this->_locName = trim($this->_chars); - } - } elseif ($element == 'Data') { - // - if ($this->_isCred) { - $this->_credData = trim($this->_chars); - } - } - break; + switch ($element) { + case 'LocURI': + if ($this->_isSource) { + // + $this->_sourceURI = trim($this->_chars); + } else { + // + $this->_targetURI = trim($this->_chars); + } + break; + case 'LocName': + if ($this->_isSource) { + // + $this->_locName = trim($this->_chars); + } + break; + case 'Data': + // + if ($this->_isCred) { + $this->_credData = trim($this->_chars); + } + break; + case 'MaxMsgSize': + // + $this->_maxMsgSize = trim($this->_chars); + break; + } + break; case 5: if ($this->_isCred) { @@ -323,19 +355,31 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { { $attrs = array(); - $state = $_SESSION['SyncML.state']; + $state = &$_SESSION['SyncML.state']; $uri = $state->getURI(); $uriMeta = $state->getURIMeta(); $output->startElement($uri, 'SyncHdr', $attrs); $output->startElement($uri, 'VerDTD', $attrs); - $chars = ($this->_version == 1) ? '1.1' : '1.0'; + if ($this->_version == 2) { + $chars = '1.2'; + } elseif ($this->_version == 1) { + $chars = '1.1'; + } else { + $chars = '1.0'; + } $output->characters($chars); $output->endElement($uri, 'VerDTD'); $output->startElement($uri, 'VerProto', $attrs); - $chars = ($this->_version == 1) ? 'SyncML/1.1' : 'SyncML/1.0'; + if ($this->_version == 2) { + $chars = 'SyncML/1.2'; + } elseif ($this->_version == 1) { + $chars = 'SyncML/1.1'; + } else { + $chars = 'SyncML/1.0'; + } $output->characters($chars); $output->endElement($uri, 'VerProto'); @@ -359,37 +403,40 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { $output->endElement($uri, 'LocURI'); $output->endElement($uri, 'Source'); - if(session_id() != '' && !strpos($this->_targetURI,'syncml_sessionid')) { - $output->startElement($uri, 'RespURI', $attrs); + if (session_id() != '' && !strpos($this->_targetURI,'syncml_sessionid')) { + $output->startElement($uri, 'RespURI', $attrs); - // some clients don't send the whole URL as targetURI - if (strpos($this->_targetURI,$_SERVER['PHP_SELF']) === false) { - $output->characters($this->_targetURI . $_SERVER['PHP_SELF'] . '?syncml_sessionid=' . session_id()); - } else { - $output->characters($this->_targetURI . '?syncml_sessionid=' . session_id()); + // some clients don't send the whole URL as targetURI + if (strpos($this->_targetURI,$_SERVER['PHP_SELF']) === false) { + $output->characters($this->_targetURI . $_SERVER['PHP_SELF'] . '?syncml_sessionid=' . session_id()); + } else { + $output->characters($this->_targetURI . '?syncml_sessionid=' . session_id()); + } + $output->endElement($uri, 'RespURI'); } - $output->endElement($uri, 'RespURI'); - } - /* + $output->startElement($uri, 'Meta', $attrs); - // Dummy Max MsqSize, this is just put in to make the packet - // work, it is not a real value. + if (!($maxMsgSize = $state->getMaxMsgSizeClient())) { + // Dummy MaxMsqSize, this is just put in to make the packet + // work, it is not our real value. + $maxMsgSize = 50000; + } $output->startElement($uriMeta, 'MaxMsgSize', $attrs); - $chars = '50000'; - $output->characters($chars); + $output->characters($maxMsgSize); $output->endElement($uriMeta, 'MaxMsgSize'); - // Dummy MaxObjSize, this is just put in to make the packet - // work, it is not a real value. - $output->startElement($uriMeta, 'MaxObjSize', $attrs); - $chars = '4000000'; - $output->characters($chars); - $output->endElement($uriMeta, 'MaxObjSize'); + #// Dummy MaxObjSize, this is just put in to make the packet + #// work, it is not our real value. + #if ($this->_version > 0) { + # // Don't send this to old devices + # $output->startElement($uriMeta, 'MaxObjSize', $attrs); + # $output->characters('4000000'); + # $output->endElement($uriMeta, 'MaxObjSize'); + #} $output->endElement($uri, 'Meta'); - */ $output->endElement($uri, 'SyncHdr'); } @@ -448,26 +495,27 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { function startElement($uri, $element, $attrs) { parent::startElement($uri, $element, $attrs); + $state = &$_SESSION['SyncML.state']; switch ($this->_xmlStack) { case 2: - $state = & $_SESSION['SyncML.state']; - - $this->_actionCommands = false; // so far, we have not seen commands that require action from our side $state->_sendFinal = false; // $this->_output->startElement($uri, $element, $attrs); - if($state->getLocName()) - { - // Right our status about the header. - $status = new Horde_SyncML_Command_Status(($state->isAuthorized()) ? - RESPONSE_AUTHENTICATION_ACCEPTED : RESPONSE_INVALID_CREDENTIALS, 'SyncHdr'); - } - else - { + if($state->getLocName()) { + if($state->isAuthConfirmed()) { + // Right our status about the header + $status = new Horde_SyncML_Command_Status(($state->isAuthorized()) ? + RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS, 'SyncHdr'); + } else { + // Right our status about the header. + $status = new Horde_SyncML_Command_Status(($state->isAuthorized()) ? + RESPONSE_AUTHENTICATION_ACCEPTED : RESPONSE_INVALID_CREDENTIALS, 'SyncHdr'); + } + } else { // Request credentials if not sent so far $status = new Horde_SyncML_Command_Status(RESPONSE_MISSING_CREDENTIALS, 'SyncHdr'); } @@ -475,6 +523,7 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { $status->setSourceRef($state->getSourceURI()); $status->setTargetRef($state->getTargetURI()); $status->setCmdRef(0); + $state->clearNumberOfElements(); /*$str = 'authorized=' . $state->isAuthorized(); $str .= ' version=' . $state->getVersion(); @@ -483,11 +532,12 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { $str .= ' target=' . $state->getTargetURI(); */ $this->_currentCmdID = $status->output($this->_currentCmdID, $this->_output); + if ($state->isAuthorized()) { + $state->AuthConfirmed(); + } break; case 3: - $state = & $_SESSION['SyncML.state']; - // <[Command]> #Horde::logMessage('SyncML['. session_id() ."]: found command $element ", __FILE__, __LINE__, PEAR_LOG_DEBUG); $this->_currentCommand = Horde_SyncML_Command::factory($element); @@ -497,7 +547,7 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { // We've got to do something! This can't be the last // packet. $this->_actionCommands = true; - Horde::logMessage('SyncML['. session_id() ."]: found action commands <$element> " . $this->_actionCommands, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML['. session_id() ."]: found action commands <$element> ", __FILE__, __LINE__, PEAR_LOG_DEBUG); } switch($element) @@ -516,23 +566,29 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { } } - function endElement($uri, $element) { + function endElement($uri, $element) + { + $state = &$_SESSION['SyncML.state']; + switch ($this->_xmlStack) { case 2: // - $state = & $_SESSION['SyncML.state']; Horde::logMessage('SyncML['. session_id() .']: package ----------------------- done', __FILE__, __LINE__, PEAR_LOG_DEBUG); - if($state->getSyncStatus() == CLIENT_SYNC_FINNISHED && $state->getAlert222Received() == true) { - $state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED); + if ($state->getAlert222Received() == true) { + // the Funambol specialty + if ($state->getSyncStatus() == CLIENT_SYNC_FINNISHED) { + $state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED); + } $state->setAlert222Received(false); } + // send the sync reply // we do still have some data to send OR // we should reply to the Sync command - if($state->getSyncStatus() >= CLIENT_SYNC_ACKNOWLEDGED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED) { + if($state->getSyncStatus() > CLIENT_SYNC_FINNISHED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED) { $sync = new Horde_SyncML_Command_Sync(); $this->_currentCmdID = $sync->syncToClient($this->_currentCmdID, $this->_output); } @@ -546,13 +602,13 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { $this->_output->endElement($uri, $element); - Horde::logMessage('SyncML['. session_id() .']: syncStatus ' . $state->getSyncStatus() .'actionCommands: '.$this->_actionCommands, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML['. session_id() .']: syncStatus = '. $state->getSyncStatus() .', actionCommands = '. + ($this->_actionCommands ? 'True' : 'False'), __FILE__, __LINE__, PEAR_LOG_DEBUG); if (!$this->_actionCommands && $state->getSyncStatus() == SERVER_SYNC_FINNISHED) { // this packet did not contain any real actions, just status and map. // This means, we're through! The session can be closed and // the Anchors saved for the next Sync - $state = & $_SESSION['SyncML.state']; Horde::logMessage('SyncML['. session_id() .']: sync' . session_id() . ' completed successfully!', __FILE__, __LINE__, PEAR_LOG_INFO); $state->writeSyncSummary(); $log = $state->getLog(); @@ -560,6 +616,9 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { foreach($log as $k => $v) { $s .= " $k=$v"; } + if (strlen(trim($s)) == 0) { + $s = ' Both parties were already in sync'; + } Horde::logMessage('SyncML['. session_id() .']: summary:' . $s, __FILE__, __LINE__, PEAR_LOG_INFO); # Horde::logMessage('SyncML['. session_id() .']: destroying sync session '.session_id(), __FILE__, __LINE__, PEAR_LOG_INFO); # // session can be closed here! @@ -571,7 +630,6 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { // this packet did not contain any real actions, just status and map. // This means, we're through! The session can be closed and // the Anchors saved for the next Sync - $state = & $_SESSION['SyncML.state']; Horde::logMessage('SyncML['. session_id() .']: sync' . session_id() . ' completed successfully!', __FILE__, __LINE__, PEAR_LOG_INFO); $state->writeSyncSummary(); $log = $state->getLog(); @@ -579,6 +637,9 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { foreach($log as $k => $v) { $s .= " $k=$v"; } + if (strlen(trim($s)) == 0) { + $s = ' Both parties were already in sync'; + } Horde::logMessage('SyncML['. session_id() .']: summary:' . $s, __FILE__, __LINE__, PEAR_LOG_INFO); Horde::logMessage('SyncML['. session_id() .']: destroying sync session '.session_id(), __FILE__, __LINE__, PEAR_LOG_INFO); @@ -586,75 +647,53 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { session_unset(); session_destroy(); } - if($state->getSyncStatus() == CLIENT_SYNC_FINNISHED) { - $state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED); - Horde::logMessage('SyncML['. session_id() .']: syncStatus(client sync acknowledged) '.$state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - } - break; case 3: // - $state = & $_SESSION['SyncML.state']; - - // this should be moved to case 2: - if($element == 'Final') - { - // make sure that we request devinfo, if we not have them already - -/* if(!$state->getClientDeviceInfo()) - { - $attrs = array(); - $this->_output->startElement($state->getURI(), 'Get', $attrs); - $this->_output->startElement($state->getURI(), 'CmdID', $attrs); - $this->_output->characters($this->_currentCmdID); - $this->_currentCmdID++; - $this->_output->endElement($state->getURI(), 'CmdID'); - - $this->_output->startElement($state->getURI(), 'Meta', $attrs); - $this->_output->startElement($state->getURIMeta(), 'Type', $attrs); - if(is_a($this->_output, 'XML_WBXML_Encoder')) - $this->_output->characters('application/vnd.syncml-devinf+wbxml'); - else - $this->_output->characters('application/vnd.syncml-devinf+xml'); - $this->_output->endElement($state->getURIMeta(), 'Type'); - $this->_output->endElement($state->getURI(), 'Meta'); - - $this->_output->startElement($state->getURI(), 'Item', $attrs); - $this->_output->startElement($state->getURI(), 'Target', $attrs); - $this->_output->startElement($state->getURI(), 'LocURI', $attrs); - $this->_output->characters(($state->getVersion() == 0) ? './devinf10' : './devinf11'); - $this->_output->endElement($state->getURI(), 'LocURI'); - $this->_output->endElement($state->getURI(), 'Target'); - $this->_output->endElement($state->getURI(), 'Item'); - - $this->_output->endElement($state->getURI(), 'Get'); - } */ - } $this->_currentCommand->endElement($uri, $element); switch($element) { - case 'Final': - if($state->getSyncStatus() == CLIENT_SYNC_STARTED) { - $state->setSyncStatus(CLIENT_SYNC_FINNISHED); - Horde::logMessage('SyncML['. session_id() .']: syncStatus(client sync finnished) ' . $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - } + case 'Final': + $this->_actionCommands = false; + $deviceInfo = $state->getClientDeviceInfo(); - if($state->getSyncStatus() == SERVER_SYNC_FINNISHED) { - $state->setSyncStatus(SERVER_SYNC_ACKNOWLEDGED); - Horde::logMessage('SyncML['. session_id() .']: syncStatus(server sync acknowledged) ' . $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - } + if ($state->getSyncStatus() == CLIENT_SYNC_STARTED) { + if (strtolower($deviceInfo['manufacturer']) == 'funambol' + && isset($deviceInfo['softwareVersion'])) { + $swversion = $deviceInfo['softwareVersion']; + if ($swversion < 1.0) { + // e.g. Mozilla plugin uses this range + $swversion = $swversion * 10; + } + if($swversion < 7.0) { + // We wait for a ALERT_NEXT_MESSAGE from Funambol clients + Horde::logMessage('SyncML['. session_id() + . "]: Special treatment for Funambol version $swversion activated", + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setSyncStatus(CLIENT_SYNC_FINNISHED); + } else { + $state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED); + } + } else { + $state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED); + } + } - $this->_clientSentFinal = true; - #Horde::logMessage('SyncML['. session_id() .']: Sync _syncTag = '. $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_INFO); + if ($state->getSyncStatus() == SERVER_SYNC_FINNISHED) { + $state->setSyncStatus(SERVER_SYNC_ACKNOWLEDGED); + } - break; + $this->_clientSentFinal = true; + Horde::logMessage('SyncML['. session_id() .']: syncStatus(server sync acknowledged) ' + . $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + break; - default: - $this->_currentCmdID = $this->_currentCommand->output($this->_currentCmdID, $this->_output); - break; - } + default: + $this->_currentCmdID = $this->_currentCommand->output($this->_currentCmdID, $this->_output); + break; + } unset($this->_currentCommand); break; diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command.php b/phpgwapi/inc/horde/Horde/SyncML/Command.php index 97a2236109..505a32e12c 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command.php @@ -1,80 +1,166 @@ + * commands. * - * Copyright 2003-2004 Anthony Mills + * A SyncML command is a protocol primitive. Each SyncML command specifies to + * a recipient an individual operation that is to be performed. * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * The SyncML_Command objects are hooked into the XML parser of the + * SyncML_ContentHandler class and are reponsible for parsing a single command + * inside the SyncBody section of a SyncML message. All actions that must be + * executed for a single SyncML command are handled by these objects, by means + * of the handleCommand() method. * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * + * Using the PEAR Log class (which need to be installed!) + * + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @author Jan Schneider + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ +include_once 'Horde/SyncML/State_egw.php'; + class Horde_SyncML_Command { + /** + * Name of the command, like 'Put'. + * + * Must be overwritten by a sub class. + * + * @var string + */ + var $_cmdName; + + /** + * The command ID (). + * + * @var integer + */ var $_cmdID; - var $_xmlStack; + /** + * Stack for holding the XML elements during creation of the object from + * the XML event flow. + * + * @var array + */ + var $_stack = array(); - var $_chars; + /** + * Buffer for the parsed character data. + * + * @var string + */ + var $_chars = ''; - function &factory($command, $params = null) + /** + * Start element handler for the XML parser, delegated from + * SyncML_ContentHandler::startElement(). + * + * @param string $uri The namespace URI of the element. + * @param string $element The element tag name. + * @param array $attrs A hash with the element's attributes. + */ + function startElement($uri, $element, $attrs) { - include_once 'Horde/SyncML/Command/' . $command . '.php'; - $class = 'Horde_SyncML_Command_' . $command; - if (class_exists($class)) { - return $cmd = new $class($params); - } else { - Horde::logMessage('SyncML: Class definition of ' . $class . ' not found.', __FILE__, __LINE__, PEAR_LOG_ERR); - require_once 'PEAR.php'; - return PEAR::raiseError('Class definition of ' . $class . ' not found.'); - } - } - - function output($currentCmdID, $output) - { - } - - function startElement($uri, $localName, $attrs) - { - $this->_xmlStack++; + $this->_stack[] = $element; } + /** + * End element handler for the XML parser, delegated from + * SyncML_ContentHandler::endElement(). + * + * @param string $uri The namespace URI of the element. + * @param string $element The element tag name. + */ function endElement($uri, $element) { - switch ($this->_xmlStack) { - case 2: - if ($element == 'CmdID') { - $this->_cmdID = intval(trim($this->_chars)); - } - break; + if (count($this->_stack) == 2 && + $element == 'CmdID') { + $this->_cmdID = intval(trim($this->_chars)); } - if (isset($this->_chars)) { - unset($this->_chars); + if (strlen($this->_chars)) { + $this->_chars = ''; } - $this->_xmlStack--; + array_pop($this->_stack); } + /** + * Character data handler for the XML parser, delegated from + * SyncML_ContentHandler::characters(). + * + * @param string $str The data string. + */ function characters($str) { - $tempValue = trim($str); - - if(empty($tempValue)) return; - if (isset($this->_chars)) { - $this->_chars = $this->_chars . $str; + $this->_chars .= $str; } else { $this->_chars = $str; } } + /** + * Returns the command name this instance is reponsible for. + * + * @return string The command name this object is handling. + */ + function getCommandName() + { + return $this->_cmdName; + } + + /** + * This method is supposed to implement the actual business logic of the + * command once the XML parsing is complete. + * + * @abstract + */ + function output($currentCmdID, &$output) + { + } + + /** + * Attempts to return a concrete Horde_SyncML_Command instance based on + * $command. + * + * @param string $command The type of the concrete + * SyncML_Comment subclass to + * return. + * @param $params Optional Parameter. + * + * @return SyncML_Command The newly created concrete SyncML_Command + * instance, or false on error. + */ + function &factory($command, $params = null) + { + $command = basename($command); + $class = 'Horde_SyncML_Command_' . $command; + + if (!class_exists($class)) { + include_once 'Horde/SyncML/Command/' . $command . '.php'; + } + if (class_exists($class)) { + $cmd = new $class($params); + } else { + $msg = 'SyncML: Class definition of ' . $class . ' not found.'; + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); + require_once 'PEAR.php'; + $cmd = PEAR::raiseError($msg); + } + + return $cmd; + } + } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php index 642211165d..9d30115b4e 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php @@ -1,56 +1,87 @@ + * Using the PEAR Log class (which need to be installed!) * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ +include_once 'Horde/SyncML/State_egw.php'; +include_once 'Horde/SyncML/Command.php'; + class Horde_SyncML_Command_Alert extends Horde_SyncML_Command { /** - * @var integer $_alert + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Alert'; + + /** + * The alert type. Should be one of the ALERT_* constants. + * + * @var integer */ var $_alert; /** - * @var string $_sourceURI + * Source database of the Alert command. + * + * @var string */ var $_sourceLocURI; /** - * @var string $_targetURI + * Target database of the Alert command. + * + * @var string */ var $_targetLocURI; /** - * @var string $_metaAnchorNext + * Optional parameter for the Target database. + * + * @var string + */ + var $_targetLocURIParameters; + + /** + * The current time this synchronization happens, from the + * element. + * + * @var string */ var $_metaAnchorNext; /** - * @var integer $_metaAnchorLast + * The last time when synchronization happened, from the + * element. + * + * @var integer */ var $_metaAnchorLast; /** - * Use in xml tag. + * The filter expression the client provided + * (e.g. the time range for calendar synchronization) + * + * @var string */ - var $_isInSource; + var $_filterExpression = ''; + /** * Creates a new instance of Alert. @@ -64,6 +95,8 @@ class Horde_SyncML_Command_Alert extends Horde_SyncML_Command { function output($currentCmdID, &$output) { + global $registry; + $attrs = array(); $state = &$_SESSION['SyncML.state']; @@ -76,323 +109,389 @@ class Horde_SyncML_Command_Alert extends Horde_SyncML_Command { return $currentCmdID; } + $type = $this->_targetLocURI; + $clientAnchorNext = $this->_metaAnchorNext; - if($this->_alert < ALERT_RESULT_ALERT) { + if ($this->_alert == ALERT_TWO_WAY || + $this->_alert == ALERT_ONE_WAY_FROM_CLIENT || + $this->_alert == ALERT_ONE_WAY_FROM_SERVER) { + // Check if we have information about previous sync. + $info = $state->getSyncSummary($this->_targetLocURI); + if (is_a($info, 'DataTreeObject')) { + $x = $info->get('ClientAnchor'); + $clientlast = $x[$type]; + $x = $info->get('ServerAnchor'); + $serverAnchorLast = $x[$type]; + } elseif (is_array($info)) { + $clientlast = $info['ClientAnchor']; + $serverAnchorLast = $info['ServerAnchor']; + } else { + $clientlast = false; + $serverAnchorLast = 0; + } + $state->setServerAnchorLast($type, $serverAnchorLast); - $type = $this->_targetLocURI; + if ($clientlast !== false){ + // Info about previous successful sync sessions found. + Horde::logMessage('SyncML: Previous sync found for target ' . $type + . '; client timestamp: ' . $clientlast, + __FILE__, __LINE__, PEAR_LOG_DEBUG); - // Store client's Next Anchor in State. After successful sync - // this is then written to persistence for negotiation of - // further syncs. - $state->setClientAnchorNext($type, $this->_metaAnchorNext); + // Check if anchor sent from client matches our own stored + // data. + if ($clientlast == $this->_metaAnchorLast) { + // Last sync anchors matche, TwoWaySync will do. + $anchormatch = true; + Horde::logMessage('SyncML: Anchor timestamps match, TwoWaySync possible. Syncing data since ' + . date('Y-m-d H:i:s', $serverAnchorLast), + __FILE__, __LINE__, PEAR_LOG_DEBUG); + } else { + // Server and client have different anchors, enforce + // SlowSync/RefreshSync + Horde::logMessage('SyncML: Client requested sync with anchor timestamp ' + . $this->_metaAnchorLast + . ' but server has recorded timestamp ' + . $clientlast . '. Enforcing SlowSync', + __FILE__, __LINE__, PEAR_LOG_INFO); + $anchormatch = false; + $clientlast = 0; + } + } else { + // No info about previous sync, use SlowSync or RefreshSync. + Horde::logMessage('SyncML: No info about previous syncs found for device ' . + $state->getSourceURI() . ' and target ' . $type, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $clientlast = 0; + $serverAnchorLast = 0; + $anchormatch = false; + } + } else { + // SlowSync requested, no anchor check required. + $anchormatch = true; + } - $info = $state->getSyncSummary($this->_targetLocURI); - #Horde::logMessage("SyncML: Anchor match, TwoWaySync sinceee " . $clientlast, __FILE__, __LINE__, PEAR_LOG_DEBUG); - if (is_a($info, 'DataTreeObject')) { - $x = $info->get('ClientAnchor'); - $clientlast = $x[$type]; - $x = $info->get('ServerAnchor'); - $state->setServerAnchorLast($type, $x[$type]); - } elseif (is_array($info)) { - $clientlast = $info['ClientAnchor']; - $state->setServerAnchorLast($type, $info['ServerAnchor']); - } else { - $clientlast = false; - $state->setServerAnchorLast($type, 0); - } - - Horde::logMessage('SyncML: checking anchor targetLocURI: clientlast: ' . $clientlast .' / '. $this->_metaAnchorLast, __FILE__, __LINE__, PEAR_LOG_DEBUG); - - // Set Server Anchor for this sync to current time. - $state->setServerAnchorNext($type,time()); - if ($clientlast !== false && $clientlast == $this->_metaAnchorLast) { - // Last Sync Anchor matches, TwoWaySync will do. - $code = RESPONSE_OK; - Horde::logMessage("SyncML: Anchor match, TwoWaySync since " . $clientlast, __FILE__, __LINE__, PEAR_LOG_DEBUG); - } else { - Horde::logMessage("SyncML: Anchor mismatch, enforcing SlowSync clientlast $clientlast serverlast ".$this->_metaAnchorLast, __FILE__, __LINE__, PEAR_LOG_DEBUG); - // Mismatch, enforce slow sync. 508=RESPONSE_REFRESH_REQUIRED 201=ALERT_SLOW_SYNC - $this->_alert = 201; - $code = 508; - // create new synctype - $sync = &Horde_SyncML_Sync::factory($this->_alert); - $sync->_targetLocURI = $this->_targetLocURI; - $sync->_sourceLocURI = $this->_sourceLocURI; - if(isset($this->_targetLocURIParameters)) - $sync->_targetLocURIParameters = $this->_targetLocURIParameters; - $state->setSync($this->_targetLocURI, $sync); - // PH : no longer delete entire content_map entries for client before SlowSync, - // use content_map to verify if content is mapped correctly - //$state->removeAllUID($this->_targetLocURI); - } + // Determine sync type and status response code. + Horde::logMessage("SyncML: Alert " . $this->_alert, __FILE__, __LINE__, PEAR_LOG_DEBUG); + switch ($this->_alert) { + case ALERT_NEXT_MESSAGE: + $state->setAlert222Received(true); + case ALERT_RESULT_ALERT: + case ALERT_NO_END_OF_DATA: + // Nothing to do on our side + $status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert'); + $status->setCmdRef($this->_cmdID); + if ($this->_sourceLocURI != null) { + $status->setSourceRef($this->_sourceLocURI); + } + if ($this->_targetLocURI != null) { + $status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI)); + } + if ($this->_alert == ALERT_NEXT_MESSAGE) { + if ($this->_sourceLocURI != null) { + $status->setItemSourceLocURI($this->_sourceLocURI); + } + if ($this->_targetLocURI != null) { + $status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI); + } + } + $currentCmdID = $status->output($currentCmdID, $output); + return $currentCmdID; + case ALERT_TWO_WAY: + if ($anchormatch) { + $synctype = ALERT_TWO_WAY; + $response = RESPONSE_OK; + } else { + $synctype = ALERT_SLOW_SYNC; + $response = RESPONSE_REFRESH_REQUIRED; + } + break; - $status = new Horde_SyncML_Command_Status($code, 'Alert'); - $status->setCmdRef($this->_cmdID); - if ($this->_sourceLocURI != null) { - $status->setSourceRef($this->_sourceLocURI); - } - if ($this->_targetLocURI != null) { - $status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI)); - } + case ALERT_SLOW_SYNC: + $synctype = ALERT_SLOW_SYNC; + $response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED; + break; - // Mirror Next Anchor from client back to client. - if (isset($this->_metaAnchorNext)) { - $status->setItemDataAnchorNext($this->_metaAnchorNext); - } + case ALERT_ONE_WAY_FROM_CLIENT: + if ($anchormatch) { + $synctype = ALERT_ONE_WAY_FROM_CLIENT; + $response = RESPONSE_OK; + } else { + $synctype = ALERT_REFRESH_FROM_CLIENT; + $response = RESPONSE_REFRESH_REQUIRED; + } + break; - // Mirror Last Anchor from client back to client. - if (isset($this->_metaAnchorLast)) { - $status->setItemDataAnchorLast($this->_metaAnchorLast); - } + case ALERT_REFRESH_FROM_CLIENT: + $synctype = ALERT_REFRESH_FROM_CLIENT; + $response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED; - $currentCmdID = $status->output($currentCmdID, $output); + // We will erase the current server content, + // then we can add the client's contents. - if ($state->isAuthorized()) { - $output->startElement($state->getURI(), 'Alert', $attrs); + $hordeType = $state->getHordeType($this->_targetLocURI); - $output->startElement($state->getURI(), 'CmdID', $attrs); - $chars = $currentCmdID; - $output->characters($chars); - $output->endElement($state->getURI(), 'CmdID'); + $state->setTargetURI($this->_targetLocURI); + $deletes = $state->getClientItems(); + if (is_array($deletes)) { + foreach ($deletes as $delete) { + $registry->call($hordeType . '/delete', array($delete)); + } + Horde::logMessage("SyncML: RefreshFromClient " . count($deletes) . " entries deleted for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + $anchormatch = false; + break; - $output->startElement($state->getURI(), 'Data', $attrs); - $chars = $this->_alert; - $output->characters($chars); - $output->endElement($state->getURI(), 'Data'); + case ALERT_ONE_WAY_FROM_SERVER: + if ($anchormatch) { + $synctype = ALERT_ONE_WAY_FROM_SERVER; + $response = RESPONSE_OK; + } else { + $synctype = ALERT_REFRESH_FROM_SERVER; + $response = RESPONSE_REFRESH_REQUIRED; + } + break; - $output->startElement($state->getURI(), 'Item', $attrs); + case ALERT_REFRESH_FROM_SERVER: + $synctype = ALERT_REFRESH_FROM_SERVER; + $response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED; + break; - if ($this->_sourceLocURI != null) { - $output->startElement($state->getURI(), 'Target', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $chars = $this->_sourceLocURI; - $output->characters($chars); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Target'); - } - - if ($this->_targetLocURI != null) { - $output->startElement($state->getURI(), 'Source', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $chars = (isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI); - $output->characters($chars); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Source'); - } + case ALERT_RESUME: + // @TODO: Suspend and Resume is not supported yet + $synctype = ALERT_SLOW_SYNC; + $response = RESPONSE_REFRESH_REQUIRED; + break; - $output->startElement($state->getURI(), 'Meta', $attrs); + default: + // We can't handle this one + Horde::logMessage('SyncML: Unknown sync type ' . $this->_alert, + __FILE__, __LINE__, PEAR_LOG_ERR); + $status = new Horde_SyncML_Command_Status(RESPONSE_BAD_REQUEST, 'Alert'); + $status->setCmdRef($this->_cmdID); + if ($this->_sourceLocURI != null) { + $status->setSourceRef($this->_sourceLocURI); + } + if ($this->_targetLocURI != null) { + $status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI)); + } + $currentCmdID = $status->output($currentCmdID, $output); + return $currentCmdID; + } - $output->startElement($state->getURIMeta(), 'Anchor', $attrs); + // Store client's Next Anchor in State and + // set server's Next Anchor. After successful sync + // this is then written to persistence for negotiation of + // further syncs. + $state->setClientAnchorNext($type, $this->_metaAnchorNext); + $serverAnchorNext = time(); + $state->setServerAnchorNext($type, $serverAnchorNext); - $output->startElement($state->getURIMeta(), 'Last', $attrs); - $chars = $state->getServerAnchorLast($type); - $output->characters($chars); - $output->endElement($state->getURIMeta(), 'Last'); + // Now set interval to retrieve server changes from, defined by + // ServerAnchor [Last,Next] + if ($synctype != ALERT_TWO_WAY && + $synctype != ALERT_ONE_WAY_FROM_CLIENT && + $synctype != ALERT_ONE_WAY_FROM_SERVER) { + $serverAnchorLast = 0; + #if (!$anchormatch) { + // Erase existing map: + $state->removeAllUID($this->_targetLocURI); + #} + } + // Now create the actual SyncML_Sync object, if it doesn't exist yet. + $sync = &$state->getSync($this->_targetLocURI); + if (!$sync) { + Horde::logMessage('SyncML: Creating SyncML_Sync object for target ' + . $this->_targetLocURI . '; sync type ' . $synctype, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $sync = &Horde_SyncML_Sync::factory($synctype); + $state->clearConflictItems($this->_targetLocURI); + } + $sync->setTargetLocURI($this->_targetLocURI); + $sync->setSourceLocURI($this->_sourceLocURI); + $sync->setLocName($state->getLocName()); // We need it for conflict handling + $sync->setsyncType($synctype); + $sync->setFilterExpression($this->_filterExpression); + $state->setSync($this->_targetLocURI, $sync); - $output->startElement($state->getURIMeta(), 'Next', $attrs); - $chars = $state->getServerAnchorNext($type); - $output->characters($chars); - $output->endElement($state->getURIMeta(), 'Next'); + $status = new Horde_SyncML_Command_Status($response, 'Alert'); + $status->setCmdRef($this->_cmdID); + if ($this->_sourceLocURI != null) { + $status->setSourceRef($this->_sourceLocURI); + } + if ($this->_targetLocURI != null) { + $status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI)); + } - $output->endElement($state->getURIMeta(), 'Anchor'); - $output->endElement($state->getURI(), 'Meta'); - $output->endElement($state->getURI(), 'Item'); - $output->endElement($state->getURI(), 'Alert'); + // Mirror Next Anchor from client back to client. + if (isset($this->_metaAnchorNext)) { + $status->setItemDataAnchorNext($this->_metaAnchorNext); + } - // still needed? lars - $state->_sendFinal = true; - - $currentCmdID++; + // Mirror Last Anchor from client back to client. + if (isset($this->_metaAnchorLast)) { + $status->setItemDataAnchorLast($this->_metaAnchorLast); + } - if($state->_devinfoRequested == false && - $this->_sourceLocURI != null && - is_a($state->getPreferedContentTypeClient($this->_sourceLocURI), 'PEAR_Error')) { - - $output->startElement($state->getURI(), 'Get', $attrs); + $currentCmdID = $status->output($currentCmdID, $output); - $output->startElement($state->getURI(), 'CmdID', $attrs); - $output->characters($currentCmdID); - $currentCmdID++; - $output->endElement($state->getURI(), 'CmdID'); - - $output->startElement($state->getURI(), 'Meta', $attrs); - $output->startElement($state->getURIMeta(), 'Type', $attrs); - if(is_a($output, 'XML_WBXML_Encoder')) { - $output->characters('application/vnd.syncml-devinf+wbxml'); - } else { - $output->characters('application/vnd.syncml-devinf+xml'); - } - $output->endElement($state->getURIMeta(), 'Type'); - $output->endElement($state->getURI(), 'Meta'); - - $output->startElement($state->getURI(), 'Item', $attrs); - $output->startElement($state->getURI(), 'Target', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $output->characters(($state->getVersion() == 0) ? './devinf10' : './devinf11'); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Target'); - $output->endElement($state->getURI(), 'Item'); - - $output->endElement($state->getURI(), 'Get'); - - $state->_devinfoRequested = true; - } - } + $output->startElement($state->getURI(), 'Alert', $attrs); - } elseif ($this->_alert == ALERT_NEXT_MESSAGE) { - $status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert'); - $status->setCmdRef($this->_cmdID); - if ($this->_targetLocURI != null) { - $status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI)); - } - if ($this->_sourceLocURI != null) { - $status->setSourceRef($this->_sourceLocURI); - } - $status->setItemSourceLocURI($this->_sourceLocURI); - $status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI); - $currentCmdID = $status->output($currentCmdID, $output); + $output->startElement($state->getURI(), 'CmdID', $attrs); + $chars = $currentCmdID; + $output->characters($chars); + $output->endElement($state->getURI(), 'CmdID'); - $state->setAlert222Received(true); + $output->startElement($state->getURI(), 'Data', $attrs); + $chars = $synctype; + $output->characters($chars); + $output->endElement($state->getURI(), 'Data'); - } else { - $status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert'); - $status->setCmdRef($this->_cmdID); - if ($this->_sourceLocURI != null) { - $status->setSourceRef($this->_sourceLocURI); - } - if ($this->_targetLocURI != null) { - $status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI)); - } + $output->startElement($state->getURI(), 'Item', $attrs); - $currentCmdID = $status->output($currentCmdID, $output); - } + if ($this->_sourceLocURI != null) { + $output->startElement($state->getURI(), 'Target', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = $this->_sourceLocURI; + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Target'); + } + if ($this->_targetLocURI != null) { + $output->startElement($state->getURI(), 'Source', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = (isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI); + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Source'); + } + + $output->startElement($state->getURI(), 'Meta', $attrs); + + $output->startElement($state->getURIMeta(), 'Anchor', $attrs); + + $output->startElement($state->getURIMeta(), 'Last', $attrs); + $chars = $state->getServerAnchorLast($type); + $output->characters($chars); + $output->endElement($state->getURIMeta(), 'Last'); + + $output->startElement($state->getURIMeta(), 'Next', $attrs); + $chars = $state->getServerAnchorNext($type); + $output->characters($chars); + $output->endElement($state->getURIMeta(), 'Next'); + + $output->endElement($state->getURIMeta(), 'Anchor'); + $output->endElement($state->getURI(), 'Meta'); + $output->endElement($state->getURI(), 'Item'); + $output->endElement($state->getURI(), 'Alert'); + + // Final packet of this message + $state->_sendFinal = true; + + $currentCmdID++; + + if ($state->_devinfoRequested == false && + $this->_sourceLocURI != null && + is_a($state->getPreferedContentTypeClient($this->_sourceLocURI), 'PEAR_Error')) { + + Horde::logMessage("SyncML: PreferedContentTypeClient missing, sending ", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $output->startElement($state->getURI(), 'Get', $attrs); + + $output->startElement($state->getURI(), 'CmdID', $attrs); + $output->characters($currentCmdID); + $currentCmdID++; + $output->endElement($state->getURI(), 'CmdID'); + + $output->startElement($state->getURI(), 'Meta', $attrs); + $output->startElement($state->getURIMeta(), 'Type', $attrs); + if (is_a($output, 'XML_WBXML_Encoder')) { + $output->characters('application/vnd.syncml-devinf+wbxml'); + } else { + $output->characters('application/vnd.syncml-devinf+xml'); + } + $output->endElement($state->getURIMeta(), 'Type'); + $output->endElement($state->getURI(), 'Meta'); + + $output->startElement($state->getURI(), 'Item', $attrs); + $output->startElement($state->getURI(), 'Target', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + if ($state->getVersion() == 2) { + $output->characters('./devinf12'); + } elseif ($state->getVersion() == 1) { + $output->characters('./devinf11'); + } else { + $output->characters('./devinf10'); + } + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Target'); + $output->endElement($state->getURI(), 'Item'); + + $output->endElement($state->getURI(), 'Get'); + + $state->_devinfoRequested = true; + } return $currentCmdID; } /** - * Setter for property sourceURI. + * End element handler for the XML parser, delegated from + * SyncML_ContentHandler::endElement(). * - * @param string $sourceURI New value of property sourceURI. + * @param string $uri The namespace URI of the element. + * @param string $element The element tag name. */ - function setSourceLocURI($sourceURI) + function endElement($uri, $element) { - $this->_sourceLocURI = $sourceURI; - } - - function getTargetLocURI() - { - return $this->_targetURI; - } - - /** - * Setter for property targetURI. - * - * @param string $targetURI New value of property targetURI. - */ - // is this function still used??? - function setTargetURI($targetURI) - { - $this->_targetLocURI = $targetURI; - } - - /** - * Setter for property targetURI. - * - * @param string $targetURI New value of property targetURI. - */ - function setTargetLocURI($targetURI) - { - $this->_targetLocURI = $targetURI; - } - - function startElement($uri, $element, $attrs) - { - parent::startElement($uri, $element, $attrs); - - switch ($this->_xmlStack) { - case 3: - if ($element == 'Target') { - $this->_isInSource = false; - } else { - $this->_isInSource = true; + switch (count($this->_stack)) { + case 2: + if ($element == 'Data') { + $this->_alert = intval(trim($this->_chars)); } break; + + case 4: + if ($element == 'LocURI') { + switch ($this->_stack[2]) { + case 'Source': + $this->_sourceLocURI = trim($this->_chars); + break; + case 'Target': + $targetLocURIData = explode('?/',trim($this->_chars)); + + $this->_targetLocURI = $targetLocURIData[0]; + + if (isset($targetLocURIData[1])) { + $this->_targetLocURIParameters = $targetLocURIData[1]; + } + break; + } + } + break; + + case 5: + switch ($element) { + case 'Next': + $this->_metaAnchorNext = trim($this->_chars); + break; + case 'Last': + $this->_metaAnchorLast = trim($this->_chars); + break; + } + break; + + case 7: + if ($element == 'Data' + && $this->_stack[2] == 'Target' + && $this->_stack[3] == 'Filter' + && $this->_stack[4] == 'Record' + && $this->_stack[5] == 'Item') { + $this->_filterExpression = trim($this->_chars); + } + break; } - } - - function endElement($uri, $element) - { - switch ($this->_xmlStack) { - case 1: - $state = & $_SESSION['SyncML.state']; - $sync = $state->getSync($this->_targetLocURI); - - if (!$sync && $this->_alert < ALERT_RESULT_ALERT) { - Horde::logMessage('SyncML: create new sync for ' . $this->_targetLocURI . ' ' . $this->_alert, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $sync = &Horde_SyncML_Sync::factory($this->_alert); - - $sync->_targetLocURI = $this->_targetLocURI; - $sync->_sourceLocURI = $this->_sourceLocURI; - if(isset($this->_targetLocURIParameters)) { - $sync->_targetLocURIParameters = $this->_targetLocURIParameters; - } - - $state->setSync($this->_targetLocURI, $sync); - - if($this->_alert == ALERT_SLOW_SYNC || $this->_alert == ALERT_REFRESH_FROM_SERVER) { - $state->removeAllUID($this->_targetLocURI); - } - } - - break; - - case 2: - if ($element == 'Data') { - $this->_alert = intval(trim($this->_chars)); - } - - break; - - case 4: - if ($element == 'LocURI') { - if ($this->_isInSource) { - $this->_sourceLocURI = trim($this->_chars); - } else { - $targetLocURIData = explode('?/',trim($this->_chars)); - - $this->_targetLocURI = $targetLocURIData[0]; - - if(isset($targetLocURIData[1])) { - $this->_targetLocURIParameters = $targetLocURIData[1]; - } - } - } - - break; - - case 5: - if ($element == 'Next') { - $this->_metaAnchorNext = trim($this->_chars); - } else if ($element == 'Last') { - $this->_metaAnchorLast = trim($this->_chars); - } - - break; - } - - parent::endElement($uri, $element); - } - - function getAlert() - { - return $this->_alert; - } - - function setAlert($alert) - { - $this->_alert = $alert; + parent::endElement($uri, $element); } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php index cf62408905..0066137665 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php @@ -1,24 +1,31 @@ + * Using the PEAR Log class (which need to be installed!) * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ +include_once 'Horde/SyncML/Command.php'; + class Horde_SyncML_Command_Final extends Horde_SyncML_Command { + /** + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Final'; + function output($currentCmdID, &$output) { $state = $_SESSION['SyncML.state']; diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php index 82199ab933..96538e401a 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php @@ -1,32 +1,37 @@ + * @author Karsten Fourmont + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/State.php'; include_once 'Horde/SyncML/Command.php'; include_once 'Horde/SyncML/Command/Results.php'; -/** - * The Horde_SyncML_Command_Get class. - * - * $Horde: framework/SyncML/SyncML/Command/Get.php,v 1.14 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @author Karsten Fourmont - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Get extends Horde_SyncML_Command { function output($currentCmdID, &$output) { $state = $_SESSION['SyncML.state']; - $ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11'; + if ($state->getVersion() == 2) { + $ref = './devinf12'; + } elseif ($state->getVersion() == 1) { + $ref = './devinf11'; + } else { + $ref = './devinf10'; + } $status = new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Get'); $status->setCmdRef($this->_cmdID); @@ -74,7 +79,13 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command { $output->startElement($state->getURIDevInf() , 'DevInf', $attrs); $output->startElement($state->getURIDevInf() , 'VerDTD', $attrs); - $output->characters(($state->getVersion() == 0) ? '1.0' : '1.1'); + if ($state->getVersion() == 2) { + $output->characters('1.2'); + } elseif($state->getVersion() == 1) { + $output->characters('1.1'); + } else { + $output->characters('1.0'); + } $output->endElement($state->getURIDevInf() , 'VerDTD', $attrs); $output->startElement($state->getURIDevInf() , 'Man', $attrs); $output->characters('www.egroupware.org'); @@ -85,12 +96,22 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command { $output->startElement($state->getURIDevInf() , 'DevTyp', $attrs); $output->characters('server'); $output->endElement($state->getURIDevInf() , 'DevTyp', $attrs); + $output->startElement($state->getURIDevInf() , 'UTC', $attrs); + $output->endElement($state->getURIDevInf() , 'UTC', $attrs); + $output->startElement($state->getURIDevInf() , 'SupportNumberOfChanges', $attrs); + $output->endElement($state->getURIDevInf() , 'SupportNumberOfChanges', $attrs); + $output->startElement($state->getURIDevInf() , 'SupportLargeObjs', $attrs); + $output->endElement($state->getURIDevInf() , 'SupportLargeObjs', $attrs); $this->_writeDataStore('./notes', 'text/x-vnote', '1.1', $output, array('text/plain' => '1.0')); - $this->_writeDataStore('./contacts', 'text/x-vcard', '2.1', $output); - $this->_writeDataStore('./tasks', 'text/x-vcalendar', '1.0', $output); - $this->_writeDataStore('./calendar', 'text/x-vcalendar', '1.0', $output); - $this->_writeDataStore('./caltasks', 'text/x-vcalendar', '1.0', $output); + $this->_writeDataStore('./contacts', 'text/vcard', '3.0', $output, + array('text/x-vcard' => '2.1')); + $this->_writeDataStore('./tasks', 'text/calendar', '2.0', $output, + array('text/x-vcalendar' => '1.0')); + $this->_writeDataStore('./calendar', 'text/calendar', '2.0', $output, + array('text/x-vcalendar' => '1.0')); + $this->_writeDataStore('./caltasks', 'text/calendar', '2.0', $output, + array('text/x-vcalendar' => '1.0')); $output->endElement($state->getURIDevInf() , 'DevInf', $attrs); $output->endElement($state->getURI(), 'Data'); @@ -111,7 +132,7 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command { * @param string $version: data for <(R|T)x-Pref><VerCT> * @param string &$output contenthandler that will received the output. * @param array $additionaltypes: array of additional types for Tx and Rx; - * format array('text/vcard' => '2.0') + * format array('text/vcard' => '3.0') */ function _writeDataStore($sourceref, $mimetype, $version, &$output, $additionaltypes = false) @@ -124,6 +145,9 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command { $output->startElement($state->getURIDevInf() , 'SourceRef', $attrs); $output->characters($sourceref); $output->endElement($state->getURIDevInf() , 'SourceRef', $attrs); + $output->startElement($state->getURIDevInf() , 'MaxGUIDSize', $attrs); + $output->characters(255); + $output->endElement($state->getURIDevInf() , 'MaxGUIDSize', $attrs); $output->startElement($state->getURIDevInf() , 'Rx-Pref', $attrs); $output->startElement($state->getURIDevInf() , 'CTType', $attrs); @@ -170,12 +194,13 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command { } $output->startElement($state->getURIDevInf() , 'SyncCap', $attrs); - $output->startElement($state->getURIDevInf() , 'SyncType', $attrs); - $output->characters('1'); - $output->endElement($state->getURIDevInf() , 'SyncType', $attrs); - $output->startElement($state->getURIDevInf() , 'SyncType', $attrs); - $output->characters('2'); - $output->endElement($state->getURIDevInf() , 'SyncType', $attrs); + // We support all sync Types from 1-6: two way, slow, refresh|update + // from client|server + for ($i = 1; $i <= 6; ++$i) { + $output->startElement($state->getURIDevInf(), 'SyncType', $attrs); + $output->characters($i); + $output->endElement($state->getURIDevInf(), 'SyncType', $attrs); + } $output->endElement($state->getURIDevInf() , 'SyncCap', $attrs); $output->endElement($state->getURIDevInf() , 'DataStore', $attrs); } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php index fcca7b266c..17dd7e44fa 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php @@ -1,43 +1,60 @@ - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * Using the PEAR Log class (which need to be installed!) * + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde * @author Karsten Fourmont - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ +include_once 'Horde/SyncML/State.php'; +include_once 'Horde/SyncML/Command.php'; + class Horde_SyncML_Command_Map extends Horde_SyncML_Command { /** - * @var string $_sourceURI + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Map'; + + /** + * Source database of the Map command. + * + * @var string */ var $_sourceLocURI; /** - * @var string $_targetURI + * Target database of the Map command. + * + * @var string */ var $_targetLocURI; /** - * Use in xml tag. + * Recipient map item specifier. + * + * @var string */ - var $_isInSource; - var $_mapTarget; + + /** + * Originator map item specifier. + * + * @var string + */ var $_mapSource; function output($currentCmdID, &$output) @@ -60,76 +77,25 @@ class Horde_SyncML_Command_Map extends Horde_SyncML_Command { return $currentCmdID; } - /** - * Setter for property sourceURI. - * - * @param string $sourceURI New value of property sourceURI. - */ - function setSourceLocURI($sourceURI) - { - $this->_sourceURI = $sourceURI; - } - - function getTargetLocURI() - { - return $this->_targetURI; - } - - /** - * Setter for property targetURI. - * - * @param string $targetURI New value of property targetURI. - */ - function setTargetURI($targetURI) - { - $this->_targetURI = $targetURI; - } - function startElement($uri, $element, $attrs) { parent::startElement($uri, $element, $attrs); - switch ($this->_xmlStack) { - case 2: - if ($element == 'Target') { - $this->_isInSource = false; - } - if ($element == 'Source') { - $this->_isInSource = true; - } - if ($element == 'MapItem') { - unset($this->_mapTarget); - unset($this->_mapSource); - } - break; - - case 3: - if ($element == 'Target') { - $this->_isInSource = false; - } - if ($element == 'Source') { - $this->_isInSource = true; - } - break; + if (count($this->_stack) == 2 && + $element == 'MapItem') { + unset($this->_mapTarget); + unset($this->_mapSource); } } function endElement($uri, $element) { - switch ($this->_xmlStack) { - case 1: - $state = $_SESSION['SyncML.state']; - $sync = $state->getSync($this->_targetLocURI); - if (!$sync) { - } - - $_SESSION['SyncML.state'] = $state; - break; + $state = &$_SESSION['SyncML.state']; + switch (count($this->_stack)) { case 2: if ($element == 'MapItem') { - $state = $_SESSION['SyncML.state']; $sync = $state->getSync($this->_targetLocURI); if (!$state->isAuthorized()) { Horde::logMessage('SyncML: Not Authorized in the middle of MapItem!', __FILE__, __LINE__, PEAR_LOG_ERR); @@ -149,26 +115,20 @@ class Horde_SyncML_Command_Map extends Horde_SyncML_Command { case 3: if ($element == 'LocURI') { - if ($this->_isInSource) { + if ($this->_stack[1] == 'Source') { $this->_sourceLocURI = trim($this->_chars); - } else { + } elseif ($this->_stack[1] == 'Target') { $targetLocURIData = explode('?/',trim($this->_chars)); - - $this->_targetLocURI = $targetLocURIData[0]; - - if(isset($targetLocURIData[1])) - { - $this->_targetLocURIParameters = $targetLocURIData[1]; - } + $this->_targetLocURI = $targetLocURIData[0]; } } break; case 4: if ($element == 'LocURI') { - if ($this->_isInSource) { + if ($this->_stack[2] == 'Source') { $this->_mapSource = trim($this->_chars); - } else { + } elseif ($this->_stack[2] == 'Target') { $this->_mapTarget = trim($this->_chars); } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php index 09f78e784f..58e1fb8a4f 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php @@ -1,23 +1,31 @@ + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/State.php'; include_once 'Horde/SyncML/Command.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Put.php,v 1.12 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { + /** + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Put'; + /** * @var string $_manufacturer */ @@ -36,6 +44,12 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { var $_oem; + /** + * @var array $_deviceInfo + */ + + var $_deviceInfo; + /** * @var string $_softwareVersion */ @@ -43,62 +57,63 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { var $_softwareVersion; function endElement($uri, $element) { - switch ($this->_xmlStack) { + switch (count($this->_stack)) { case 5: - switch($element) { + switch ($element) { case 'DataStore': - $this->_deviceInfo['dataStore'][$this->_sourceReference] = array( + $this->_deviceInfo['dataStore'][$this->_sourceReference] = array ( 'maxGUIDSize' => $this->_maxGUIDSize, 'rxPreference' => $this->_rxPreference, 'txPreference' => $this->_txPreference, 'syncCapabilities' => $this->_syncCapabilities, + 'properties' => $this->_properties, ); break; - + case 'DevID': $this->_deviceInfo['deviceID'] = trim($this->_chars); break; - + case 'DevTyp': $this->_deviceInfo['deviceType'] = trim($this->_chars); break; - + case 'FwV': $this->_deviceInfo['firmwareVersion'] = trim($this->_chars); break; - + case 'HwV': $this->_deviceInfo['hardwareVersion'] = trim($this->_chars); break; - + case 'Man': $this->_deviceInfo['manufacturer'] = trim($this->_chars); break; - + case 'Mod': $this->_deviceInfo['model'] = trim($this->_chars); break; - + case 'OEM': $this->_deviceInfo['oem'] = trim($this->_chars); break; - + case 'SwV': $this->_deviceInfo['softwareVersion'] = trim($this->_chars); break; - + case 'SupportLargeObjs': $this->_deviceInfo['supportLargeObjs'] = true; break; - + case 'SupportNumberOfChanges': $this->_deviceInfo['supportNumberOfChanges'] = true; break; - + case 'UTC': $this->_deviceInfo['UTC'] = true; break; - + case 'VerDTD': $this->_deviceInfo['DTDVersion'] = trim($this->_chars); break; @@ -109,18 +124,18 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { case 'MaxGUIDSize': $this->_maxGUIDSize = trim($this->_chars); break; - + case 'Rx-Pref': $this->_rxPreference = array( 'contentType' => $this->_contentType, 'contentVersion' => $this->_contentVersion, ); break; - + case 'SourceRef': $this->_sourceReference = trim($this->_chars); break; - + case 'Tx-Pref': $this->_txPreference = array( 'contentType' => $this->_contentType, @@ -129,7 +144,7 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { break; } break; - + case 7: switch($element) { case 'CTType': @@ -178,87 +193,165 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { } } break; - + case 'SyncType': $this->_syncCapabilities[] = trim($this->_chars); break; - + case 'VerCT': $this->_contentVersion = trim($this->_chars); break; + + case 'Property': + if (isset($this->_PropName)) { + $this->_properties[$this->_contentType][$this->_contentVersion][$this->_PropName] = array( + 'Size' => $this->_PropSize, + 'NoTruncate' => $this->_PropNoTruncate, + ); + } + break; } break; + + case 8: + switch($element) { + case 'PropName': + $this->_PropName = trim($this->_chars); + break; + + case 'Size': + $this->_PropSize = trim($this->_chars); + break; + + case 'NoTruncate': + $this->_PropNoTruncate = true; + break; + } + beak; } - + parent::endElement($uri, $element); } - + function finalizeDeviceInfo() { // get some more information about the device from out of band data $ua = $_SERVER['HTTP_USER_AGENT']; - if (preg_match("/^\s*Funambol (.*) (\d+\.\d+\.\d+)\s*$/i", $ua, $matches)) - { - if (!isset($this->_deviceInfo['manufacturer'])) - $this->_deviceInfo['manufacturer'] = 'Funambol'; - if (!isset($this->_deviceInfo['model'])) - $this->_deviceInfo['model'] = 'Funambol ' . trim($matches[1]); - if (!isset($this->_deviceInfo['softwareVersion'])) - $this->_deviceInfo['softwareVersion'] = $matches[2]; + if (preg_match("/^\s*Funambol (.*) [^\d]*(\d+\.?\d*)[\.|\d]*\s*$/i", $ua, $matches)) { + // Funambol uses the hardware Manufacturer we don't care about + $this->_deviceInfo['manufacturer'] = 'Funambol'; + $this->_deviceInfo['model'] = trim($matches[1]); + $this->_deviceInfo['softwareVersion'] = floatval($matches[2]); - if (!isset($this->_deviceInfo['deviceType'])) - { - switch (strtolower(trim($matches[1]))) - { - case 'outlook plug-in': - default: - $this->_deviceInfo['deviceType'] = 'workstation'; - break; + if (!isset($this->_deviceInfo['deviceType'])) { + switch (strtolower(trim($matches[1]))) { case 'pocket pc plug-in': $this->_deviceInfo['deviceType'] = 'windowsmobile'; break; + case 'outlook plug-in': + default: + $this->_deviceInfo['deviceType'] = 'workstation'; + break; } } } - $devid = $this->_deviceInfo['deviceID']; - switch (strtolower($devid)) - { + switch (strtolower($this->_deviceInfo['deviceID'])) { case 'fmz-thunderbird-plugin': - if (empty($this->_devinceInfo['manufacturer'])) + if (empty($this->_devinceInfo['manufacturer'])) { $this->_deviceInfo['manufacturer'] = 'Funambol'; - if (empty($this->_devinceInfo['model'])) + } + if (empty($this->_devinceInfo['model'])) { $this->_deviceInfo['model'] = 'ThunderBird'; - if (empty($this->_devinceInfo['softwareVersion'])) - $this->_deviceInfo['softwareVersion'] = '0.3'; + } + if (empty($this->_devinceInfo['softwareVersion'])) { + $this->_deviceInfo['softwareVersion'] = '3.0'; + } + break; + } + + if (preg_match('/Funambol.*/i', $this->_deviceInfo['manufacturer'])) { + $this->_deviceInfo['supportLargeObjs'] = true; + } + + switch (strtolower($this->_deviceInfo['manufacturer'])) { + case 'sonyericsson': + case 'sony ericsson': + if (strtolower($this->_deviceInfo['model']) == 'w890i') { + $this->_deviceInfo['supportLargeObjs'] = false; + } + break; + case 'synthesis ag': + foreach ($this->_deviceInfo['dataStore'] as &$ctype) { + $ctype['maxGUIDSize'] = 255; + } break; } } - + function output($currentCmdID, &$output ) { $state = &$_SESSION['SyncML.state']; - - $status = new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Put'); + + $status = new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), $this->_cmdName); $status->setCmdRef($this->_cmdID); - - $ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11'; - + + if ($state->getVersion() == 2) { + $ref = './devinf12'; + } elseif ($state->getVersion() == 1) { + $ref = './devinf11'; + } else { + $ref = './devinf10'; + } + $status->setSourceRef($ref); - + if($state->isAuthorized()) { $this->finalizeDeviceInfo(); + if(count((array)$this->_deviceInfo) > 0) { - $state->setClientDeviceInfo($this->_deviceInfo); + $devInfo = $state->getClientDeviceInfo(); + if (is_array($devInfo['dataStore']) + && $devInfo['softwareVersion'] == $this->_deviceInfo['softwareVersion']) { + // merge with existing information + $devInfo['dataStore'] = + array_merge($devInfo['dataStore'], + $this->_deviceInfo['dataStore']); + } else { + // new device + $devInfo = $this->_deviceInfo; + } + #Horde::logMessage("SyncML: Put DeviceInfo:\n" . print_r($this->_deviceInfo, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setClientDeviceInfo($devInfo); $state->writeClientDeviceInfo(); } } - + return $status->output($currentCmdID, $output); } function startElement($uri, $element, $attrs) { + #Horde::logMessage("SyncML: startElement[" . count($this->_stack) . "] $uri $element", __FILE__, __LINE__, PEAR_LOG_DEBUG); + switch (count($this->_stack)) { + case 4: + switch ($element) { + case 'DataStore': + $this->_properties = array(); + break; + } + break; + + case 6: + switch ($element) { + case 'Property': + unset($this->_PropName); + $this->_PropSize = -1; + $this->_PropNoTruncate = false; + break; + } + break; + } parent::startElement($uri, $element, $attrs); } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Replace.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Replace.php index 73bca5df63..010083bbf2 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Replace.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Replace.php @@ -1,22 +1,29 @@ + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Replace.php,v 1.7 2004/05/26 17:41:30 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Final extends Horde_SyncML_Command { + /** + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Replace'; + function output($currentCmdID, &$output) { return $currentCmdID; diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php index ca06ebd60c..2541666dc0 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php @@ -1,291 +1,36 @@ + * The SyncML_Command_Results class provides a SyncML implementation of the + * Results command as defined in SyncML Representation Protocol, version 1.1, + * section 5.5.12. * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * The Results command is used to return the results of a Search or Get + * command. Currently SyncML_Command_Results behaves the same as + * SyncML_Command_Put. The only results we get is the same DevInf as for the + * Put command. * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * + * Using the PEAR Log class (which need to be installed!) + * + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ -class Horde_SyncML_Command_Results extends Horde_SyncML_Command { +include_once 'Horde/SyncML/Command/Put.php'; - var $_cmdRef; - var $_type; - var $_data; - var $_locSourceURI; - var $_deviceInfo; - - function endElement($uri, $element) { - #Horde::logMessage('SyncML: put endelement ' . $element . ' chars ' . $this->_chars, __FILE__, __LINE__, PEAR_LOG_DEBUG); - - switch ($this->_xmlStack) { - case 5: - switch($element) { - case 'DataStore': - $this->_deviceInfo['dataStore'][$this->_sourceReference] = array ( - 'maxGUIDSize' => $this->_maxGUIDSize, - 'rxPreference' => $this->_rxPreference, - 'txPreference' => $this->_txPreference, - 'syncCapabilities' => $this->_syncCapabilities, - ); - break; - - case 'DevID': - $this->_deviceInfo['deviceID'] = trim($this->_chars); - break; - - case 'DevTyp': - $this->_deviceInfo['deviceType'] = trim($this->_chars); - break; - - case 'FwV': - $this->_deviceInfo['firmwareVersion'] = trim($this->_chars); - break; - - case 'HwV': - $this->_deviceInfo['hardwareVersion'] = trim($this->_chars); - break; - - case 'Man': - $this->_deviceInfo['manufacturer'] = trim($this->_chars); - break; - - case 'Mod': - $this->_deviceInfo['model'] = trim($this->_chars); - break; - - case 'OEM': - $this->_deviceInfo['oem'] = trim($this->_chars); - break; - - case 'SwV': - $this->_deviceInfo['softwareVersion'] = trim($this->_chars); - break; - - case 'SupportLargeObjs': - $this->_deviceInfo['supportLargeObjs'] = true; - break; - - case 'SupportNumberOfChanges': - $this->_deviceInfo['supportNumberOfChanges'] = true; - break; - - case 'UTC': - $this->_deviceInfo['UTC'] = true; - break; - - case 'VerDTD': - $this->_deviceInfo['DTDVersion'] = trim($this->_chars); - break; - } - break; +class Horde_SyncML_Command_Results extends Horde_SyncML_Command_Put { - case 6: - switch($element) { - case 'MaxGUIDSize': - $this->_maxGUIDSize = trim($this->_chars); - break; - - case 'Rx-Pref': - $this->_rxPreference = array ( - 'contentType' => $this->_contentType, - 'contentVersion' => $this->_contentVersion, - ); - break; - - case 'SourceRef': - $this->_sourceReference = trim($this->_chars); - break; - - case 'Tx-Pref': - $this->_txPreference = array( - 'contentType' => $this->_contentType, - 'contentVersion' => $this->_contentVersion, - ); - break; - } - break; - - case 7: - switch($element) { - case 'CTType': - $this->_contentType = trim($this->_chars); - break; - - case 'SyncType': - $this->_syncCapabilities[] = trim($this->_chars); - break; - - case 'VerCT': - $this->_contentVersion = trim($this->_chars); - break; - } - break; - } - - parent::endElement($uri, $element); - } + /** + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Results'; - function finalizeDeviceInfo() - { - // get some more information about the device from out of band data - - $ua = $_SERVER['HTTP_USER_AGENT']; - - if (preg_match("/^\s*Funambol (.*) (\d+\.\d+\.\d+)\s*$/i", $ua, $matches)) - { - if (!isset($this->_deviceInfo['manufacturer'])) - $this->_deviceInfo['manufacturer'] = 'Funambol'; - if (!isset($this->_deviceInfo['model'])) - $this->_deviceInfo['model'] = 'Funambol ' . trim($matches[1]); - if (!isset($this->_deviceInfo['softwareVersion'])) - $this->_deviceInfo['softwareVersion'] = $matches[2]; - - if (!isset($this->_deviceInfo['deviceType'])) - { - switch (strtolower(trim($matches[1]))) - { - case 'outlook plug-in': - default: - $this->_deviceInfo['deviceType'] = 'workstation'; - break; - case 'pocket pc plug-in': - $this->_deviceInfo['deviceType'] = 'windowsmobile'; - break; - } - } - } - - $devid = $this->_deviceInfo['deviceID']; - switch (strtolower($devid)) - { - case 'fmz-thunderbird-plugin': - if (empty($this->_devinceInfo['manufacturer'])) - $this->_deviceInfo['manufacturer'] = 'Funambol'; - if (empty($this->_devinceInfo['model'])) - $this->_deviceInfo['model'] = 'ThunderBird'; - if (empty($this->_devinceInfo['softwareVersion'])) - $this->_deviceInfo['softwareVersion'] = '0.3'; - break; - } - } - - function output($currentCmdID, &$output) { - if(!isset($this->_locSourceURI)) { - #Horde::logMessage('SyncML: BIG TODO!!!!!!!!!!!!!!!!!! parse reply', __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state = &$_SESSION['SyncML.state']; - - $status = new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Results'); - $status->setCmdRef($this->_cmdID); - - $ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11'; - - $status->setSourceRef($ref); - - if($state->isAuthorized()) { - $this->finalizeDeviceInfo(); - if(count((array)$this->_deviceInfo) > 0) { - $state->setClientDeviceInfo($this->_deviceInfo); - $state->writeClientDeviceInfo(); - } - } - - return $status->output($currentCmdID, $output); - } else { - #Horde::logMessage('SyncML: BIG TODO!!!!!!!!!!!!!!!!!! generate reponse', __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state = $_SESSION['SyncML.state']; - - $attrs = array(); - $output->startElement($state->getURI(), 'Results', $attrs); - - $output->startElement($state->getURI(), 'CmdID', $attrs); - $chars = $currentCmdID; - $output->characters($chars); - $output->endElement($state->getURI(), 'CmdID'); - - $output->startElement($state->getURI(), 'MsgRef', $attrs); - $chars = $state->getMsgID(); - $output->characters($chars); - $output->endElement($state->getURI(), 'MsgRef'); - - $output->startElement($state->getURI(), 'CmdRef', $attrs); - $chars = $this->_cmdRef; - $output->characters($chars); - $output->endElement($state->getURI(), 'CmdRef'); - - $output->startElement($state->getURI(), 'Meta', $attrs); - $output->startElement($state->getURIMeta(), 'Type', $attrs); - $output->characters($this->_type); - $output->endElement($state->getURIMeta(), 'Type'); - $output->endElement($state->getURI(), 'Meta'); - - $output->startElement($state->getURI(), 'Item', $attrs); - $output->startElement($state->getURI(), 'Source', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $chars = $this->_locSourceURI; - $output->characters($chars); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Source'); - - $output->startElement($state->getURI(), 'Data', $attrs); - - // Need to send this information as opaque data so the WBXML - // will understand it. - $output->opaque($this->_data); - - $output->endElement($state->getURI(), 'Data'); - $output->endElement($state->getURI(), 'Item'); - - $output->endElement($state->getURI(), 'Results'); - - $currentCmdID++; - - return $currentCmdID; - } - } - - /** - * Setter for property cmdRef. - * - * @param string $cmdRef New value of property cmdRef. - */ - function setCmdRef($cmdRef) { - $this->_cmdRef = $cmdRef; - } - - /** - * Setter for property Type. - * - * @param string $type New value of property type. - */ - function setType($type) { - $this->_type = $type; - } - - /** - * Setter for property data. - * - * @param string $data New value of property data. - */ - function setData($data) { - $this->_data = $data; - } - - /** - * Setter for property locSourceURI. - * - * @param string $locSourceURI New value of property locSourceURI. - */ - function setlocSourceURI($locSourceURI) { - $this->_locSourceURI = $locSourceURI; - } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Status.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Status.php index 2efc029223..32dfeff6ac 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Status.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Status.php @@ -1,37 +1,76 @@ + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/State.php'; include_once 'Horde/SyncML/Command.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Status.php,v 1.15 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Status extends Horde_SyncML_Command { + /** + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Status'; + + /** + * The Response code of the command sent to the client, that this + * Status response refers to. + * + * @var integer + */ var $_response; + /** + * The command ID (CmdID) of the command sent to the client, that this + * Status response refers to. + * + * @var integer + */ var $_cmdRef; /** - * Must be present. + * The command (Add, Replace, etc) sent to the client, that this Status + * response refers to. + * + * @var string */ var $_cmd; /** - * Must if not null (what does this mean?). + * The server ID of the sent object, that this Status response refers to. + * + * This element is optional. If specified, Status response refers to a + * single Item in the command sent to the client. It refers to all Items in + * the sent command otherwise. + * + * @var string */ var $_sourceRef; + + /** + * The client ID of the sent object, that this Status response refers to. + * + * This element is optional. If specified, Status response refers to a + * single Item in the command sent to the client. It refers to all Items in + * the sent command otherwise. + * + * @var string + */ var $_targetRef; var $_chalMetaFormat; @@ -48,6 +87,15 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command { var $_itemSourceLocURI; + var $_syncItems; + + /** + * Constructor. + * + * @param integer $response The response code. + * @param string $cmd The command sent to the client, + * that this Status response refers to. + */ function Horde_SyncML_Command_Status($response = null, $cmd = null) { if ($response != null) { @@ -61,12 +109,11 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command { function output($currentCmdID, &$output) { + $state = &$_SESSION['SyncML.state']; + $attrs = array(); - $state = $_SESSION['SyncML.state']; - if ($this->_cmd != null) { - $attrs = array(); $output->startElement($state->getURI(), 'Status', $attrs); $output->startElement($state->getURI(), 'CmdID', $attrs); @@ -174,61 +221,41 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command { $output->endElement($state->getURI(), 'Item'); } - if (isset($this->_itemTargetLocURI) && isset($this->_itemSourceLocURI)) { - $output->startElement($state->getURI(), 'Item', $attrs); + if (isset($this->_syncItems)) { + // Support multible items per command + foreach ($this->_syncItems as $locURI => &$syncItem) { + $output->startElement($state->getURI(), 'Item', $attrs); + $output->startElement($state->getURI(), 'Source', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $output->characters($locURI); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Source'); + $output->endElement($state->getURI(), 'Item'); + } + } elseif (isset($this->_itemTargetLocURI) || isset($this->_itemSourceLocURI)) { + $output->startElement($state->getURI(), 'Item', $attrs); - $output->startElement($state->getURI(), 'Target', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $output->characters($this->_itemTargetLocURI); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Target'); - - $output->startElement($state->getURI(), 'Source', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $output->characters($this->_itemSourceLocURI); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Source'); - - $output->endElement($state->getURI(), 'Item'); - } + if (isset($this->_itemTargetLocURI)) { + $output->startElement($state->getURI(), 'Target', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $output->characters($this->_itemTargetLocURI); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Target'); + } + if (isset($this->_itemSourceLocURI)) { + $output->startElement($state->getURI(), 'Source', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $output->characters($this->_itemSourceLocURI); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Source'); + } + $output->endElement($state->getURI(), 'Item'); + } $output->endElement($state->getURI(), 'Status'); $currentCmdID++; - - // moredata pending request them -/* if($this->_response == RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED) { - $output->startElement($state->getURI(), 'Alert', $attrs); - $output->startElement($state->getURI(), 'CmdID', $attrs); - $chars = $currentCmdID; - $output->characters($chars); - $output->endElement($state->getURI(), 'CmdID'); - - $output->startElement($state->getURI(), 'Data', $attrs); - $output->characters(ALERT_NEXT_MESSAGE); - $output->endElement($state->getURI(), 'Data'); - - if (isset($this->_itemTargetLocURI) && isset($this->_itemSourceLocURI)) { - $output->startElement($state->getURI(), 'Item', $attrs); - - $output->startElement($state->getURI(), 'Target', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $output->characters($this->_itemTargetLocURI); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Target'); - - $output->startElement($state->getURI(), 'Source', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $output->characters($this->_itemSourceLocURI); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Source'); - } - - $output->endElement($state->getURI(), 'Alert'); - - $currentCmdID++; - } */ } return $currentCmdID; @@ -323,4 +350,14 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command { { $this->_itemTargetLocURI = $itemTargetLocURI; } + + /** + * Setter for the the list of handled SyncItems + * + * @param array $syncItems The Items of the command + */ + function setSyncItems(&$syncItems) + { + $this->_syncItems = $syncItems; + } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php index c5ff34d483..eb79a36965 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php @@ -1,220 +1,258 @@ + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/State.php'; include_once 'Horde/SyncML/Command.php'; include_once 'Horde/SyncML/Command/Sync/SyncElement.php'; include_once 'Horde/SyncML/Sync/TwoWaySync.php'; include_once 'Horde/SyncML/Sync/SlowSync.php'; include_once 'Horde/SyncML/Sync/OneWayFromServerSync.php'; +include_once 'Horde/SyncML/Sync/OneWayFromClientSync.php'; include_once 'Horde/SyncML/Sync/RefreshFromServerSync.php'; +include_once 'Horde/SyncML/Sync/RefreshFromClientSync.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Sync.php,v 1.17 2004/07/03 15:21:14 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ -class Horde_SyncML_Command_Sync extends Horde_Syncml_Command { +class Horde_SyncML_Command_Sync extends Horde_SyncML_Command { - var $_isInSource; - var $_currentSyncElement; - var $_syncElements = array(); - - function output($currentCmdID, &$output) { - - $state = &$_SESSION['SyncML.state']; - - $attrs = array(); - - Horde::logMessage('SyncML: $this->_targetURI = ' . $this->_targetURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); - - $status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Sync'); - - // $status->setState($state); - - $status->setCmdRef($this->_cmdID); - - if ($this->_targetURI != null) { - $status->setTargetRef((isset($this->_targetURIParameters) ? $this->_targetURI.'?/'.$this->_targetURIParameters : $this->_targetURI)); - } - - if ($this->_sourceURI != null) { - $status->setSourceRef($this->_sourceURI); - } - - $currentCmdID = $status->output($currentCmdID, $output); - - if($sync = $state->getSync($this->_targetURI)) { - $currentCmdID = $sync->startSync($currentCmdID, $output); - - foreach ($this->_syncElements as $element) { - $currentCmdID = $sync->nextSyncCommand($currentCmdID, $element, $output); - } - } + /** + * Name of the command. + * + * @var string + */ + var $_cmdName = 'Sync'; + + /** + * Source database of the command. + * + * @var string + */ + var $_sourceURI; + + /** + * Target database of the command. + * + * @var string + */ + var $_targetURI; + + /** + * Optional parameter for the Target. + * + * @var string + */ + var $_targetURIParameters; + + /** + * SyncML_SyncElement object for the currently parsed sync command. + * + * @var SyncML_SyncElement + */ + var $_curItem; + + /** + * List of all SyncML_SyncElement objects that have parsed. + * + * @var array + */ + var $_syncElements = array(); + + function output($currentCmdID, &$output) + { + $state = &$_SESSION['SyncML.state']; + + Horde::logMessage('SyncML: $this->_targetURI = ' . $this->_targetURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Sync'); + + $status->setCmdRef($this->_cmdID); + + if ($this->_targetURI != null) { + $status->setTargetRef((isset($this->_targetURIParameters) ? $this->_targetURI.'?/'.$this->_targetURIParameters : $this->_targetURI)); + } + + if ($this->_sourceURI != null) { + $status->setSourceRef($this->_sourceURI); + } + + $currentCmdID = $status->output($currentCmdID, $output); + + if ($sync = &$state->getSync($this->_targetURI)) { + $currentCmdID = $sync->startSync($currentCmdID, $output); + + foreach ($this->_syncElements as $element) { + $currentCmdID = $sync->nextSyncCommand($currentCmdID, $element, $output); + } + } + + return $currentCmdID; + } - return $currentCmdID; - } - - function getTargetURI() { - return $this->_targetURI; - } function startElement($uri, $element, $attrs) { parent::startElement($uri, $element, $attrs); - switch ($this->_xmlStack) { + switch (count($this->_stack)) { case 2: - if ($element == 'Replace' || $element == 'Add' || $element == 'Delete') { - $this->_currentSyncElement = &Horde_SyncML_Command_Sync_SyncElement::factory($element); - // $this->_currentSyncElement->setVersion($this->_version); - // $this->_currentSyncElement->setCmdRef($this->_cmdID); - // $this->_currentSyncElement->setMsgID($this->_msgID); - } elseif ($element == 'Target') { - $this->_isInSource = false; - } else { - $this->_isInSource = true; + if ($element == 'Replace' || + $element == 'Add' || + $element == 'Delete') { + Horde::logMessage("SyncML: sync element $element found", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $this->_curItem = &Horde_SyncML_Command_Sync_SyncElement::factory($element); } break; } - - if (isset($this->_currentSyncElement)) { - $this->_currentSyncElement->startElement($uri, $element, $attrs); + if (isset($this->_curItem)) { + $this->_curItem->startElement($uri, $element, $attrs); } } - + + // We create a seperate Sync Element for the Sync Data sent // from the Server to the client as we want to process the // client sync information before. function syncToClient($currentCmdID, &$output) { - Horde::logMessage('SyncML: starting sync to client', __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML: starting sync to client', __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state = $_SESSION['SyncML.state']; - if($state->getSyncStatus() >= CLIENT_SYNC_ACKNOWLEDGED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED) - { - $deviceInfo = $state->getClientDeviceInfo(); - $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); - $targets = $state->getTargets(); - Horde::logMessage('SyncML: starting sync to client '.$targets[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); - $attrs = array(); + $state = &$_SESSION['SyncML.state']; + if($state->getSyncStatus() >= CLIENT_SYNC_FINNISHED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED) + { + $deviceInfo = $state->getClientDeviceInfo(); + $targets = $state->getTargets(); foreach($targets as $target) { - $sync = $state->getSync($target); - - // make sure that the state reflects what is currently being done - $state->_currentSourceURI = $sync->_sourceLocURI; - $state->_currentTargetURI = $sync->_targetLocURI; - - $output->startElement($state->getURI(), 'Sync', $attrs); - $output->startElement($state->getURI(), 'CmdID', $attrs); - $output->characters($currentCmdID); - $currentCmdID++; - $output->endElement($state->getURI(), 'CmdID'); - - $output->startElement($state->getURI(), 'Target', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - $chars = $sync->_sourceLocURI; - $output->characters($chars); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Target'); - - $output->startElement($state->getURI(), 'Source', $attrs); - $output->startElement($state->getURI(), 'LocURI', $attrs); - #$chars = $sync->_targetLocURI; - $chars = (isset($sync->_targetLocURIParameters) ? $sync->_targetLocURI.'?/'.$sync->_targetLocURIParameters : $sync->_targetLocURI); - $output->characters($chars); - $output->endElement($state->getURI(), 'LocURI'); - $output->endElement($state->getURI(), 'Source'); - - if(!$sync->_syncDataLoaded) - { - $numberOfItems = $sync->loadData(); - if($deviceInfo['supportNumberOfChanges']) + $sync = &$state->getSync($target); + Horde::logMessage('SyncML['. session_id() .']: sync alerttype '. $sync->_syncType .' found for target ' . $target, __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($sync->_syncType == ALERT_ONE_WAY_FROM_CLIENT || + $sync->_syncType == ALERT_REFRESH_FROM_CLIENT) { + + Horde::logMessage('SyncML['. session_id() .']: From client Sync, no sync of '. $target .' to client', __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->clearSync($target); + + } else if ($state->getSyncStatus() >= CLIENT_SYNC_ACKNOWLEDGED) { + + Horde::logMessage("SyncML: starting sync to client $target", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $attrs = array(); + + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + + $output->startElement($state->getURI(), 'Sync', $attrs); + $output->startElement($state->getURI(), 'CmdID', $attrs); + $output->characters($currentCmdID); + $currentCmdID++; + $output->endElement($state->getURI(), 'CmdID'); + + $output->startElement($state->getURI(), 'Target', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = $sync->_sourceLocURI; + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Target'); + + $output->startElement($state->getURI(), 'Source', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = (isset($sync->_targetLocURIParameters) ? $sync->_targetLocURI.'?/'.$sync->_targetLocURIParameters : $sync->_targetLocURI); + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Source'); + + if(!$sync->_syncDataLoaded) { - $output->startElement($state->getURI(), 'NumberOfChanged', $attrs); - $output->characters($numberOfItems); - $output->endElement($state->getURI(), 'NumberOfChanged'); + $numberOfItems = $sync->loadData(); + if($deviceInfo['supportNumberOfChanges']) + { + $output->startElement($state->getURI(), 'NumberOfChanges', $attrs); + $output->characters($numberOfItems); + $output->endElement($state->getURI(), 'NumberOfChanges'); + } } + + $currentCmdID = $sync->endSync($currentCmdID, $output); + + $output->endElement($state->getURI(), 'Sync'); + + if (isset($state->curSyncItem) || + $state->getNumberOfElements() === false) { + break; + } + } else { + Horde::logMessage("SyncML: Waiting for client ACKNOWLEDGE for $target", __FILE__, __LINE__, PEAR_LOG_DEBUG); } - - $currentCmdID = $sync->endSync($currentCmdID, $output); - - $output->endElement($state->getURI(), 'Sync'); - - break; } - + // no syncs left - if($state->getTargets() === FALSE) + if($state->getTargets() === FALSE && + !isset($state->curSyncItem)) { $state->setSyncStatus(SERVER_SYNC_FINNISHED); - - Horde::logMessage('SyncML: syncStatus(server_sync_finnished) '. $state->getSyncStatus, __FILE__, __LINE__, PEAR_LOG_DEBUG); - } + } + + Horde::logMessage('SyncML: syncStatus(syncToClient) = '. $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + } return $currentCmdID; + } function endElement($uri, $element) { - if (isset($this->_currentSyncElement)) { - $this->_currentSyncElement->endElement($uri, $element); + if (isset($this->_curItem)) { + $this->_curItem->endElement($uri, $element); } - switch ($this->_xmlStack) { + switch (count($this->_stack)) { case 2: - if ($element == 'Replace' || $element == 'Add' || $element == 'Delete') { - $this->_syncElements[] = $this->_currentSyncElement; - unset($this->_currentSyncElement); + if ($element == 'Replace' || + $element == 'Add' || + $element == 'Delete') { + $this->_syncElements[] = &$this->_curItem; + unset($this->_curItem); } break; case 3: - $state = & $_SESSION['SyncML.state']; - - if ($element == 'LocURI' && !isset($this->_currentSyncElement)) { - if ($this->_isInSource) { + if ($element == 'LocURI' && !isset($this->_curItem)) { + if ($this->_stack[1] == 'Source') { $this->_sourceURI = trim($this->_chars); - $state->_currentSourceURI = $this->_sourceURI; - } else { - $this->_targetURI = trim($this->_chars); - + } elseif ($this->_stack[1] == 'Target') { $targetURIData = explode('?/',trim($this->_chars)); - $this->_targetURI = $targetURIData[0]; - $state->_currentTargetURI = $this->_targetURI; - - if(isset($targetURIData[1])) - { - $this->_targetURIParameters = $targetURIData[1]; - $state->_currentTargetURIParameters = $this->_targetURIParameters; - } - + $this->_targetURI = $targetURIData[0]; + + if (isset($targetURIData[1])) { + $this->_targetURIParameters = $targetURIData[1]; + } } } break; } parent::endElement($uri, $element); - + } function characters($str) { - if (isset($this->_currentSyncElement)) { - $this->_currentSyncElement->characters($str); + if (isset($this->_curItem)) { + $this->_curItem->characters($str); } else { if (isset($this->_chars)) { - $this->_chars = $this->_chars . $str; + $this->_chars .= $str; } else { $this->_chars = $str; } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Add.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Add.php index 7b4bec7f43..0517faa76e 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Add.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Add.php @@ -1,20 +1,20 @@ + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command/Sync/SyncElement.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Sync/Add.php,v 1.10 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Sync_Add extends Horde_SyncML_Command_Sync_SyncElement { var $_status = RESPONSE_ITEM_ADDED; @@ -24,9 +24,10 @@ class Horde_SyncML_Command_Sync_Add extends Horde_SyncML_Command_Sync_SyncElemen $status = new Horde_SyncML_Command_Status($this->_status, 'Add'); $status->setCmdRef($this->_cmdID); - if (isset($this->_luid)) { - $status->setSourceRef($this->_luid); + if (!empty($this->_items)) { + $status->setSyncItems($this->_items); } + return $status->output($currentCmdID, $output); } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php index 01042f2dd9..4d6f7fac20 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php @@ -1,106 +1,44 @@ * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * Using the PEAR Log class (which need to be installed!) * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ -class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_Sync_SyncElement { +include_once 'Horde/SyncML/State.php'; +include_once 'Horde/SyncML/Command/Sync/SyncElementItem.php'; - /** - * The content: vcard data, etc. - */ - var $_content; - - /** - * Local to server: our Horde guid. - */ - var $_locURI; - - var $_targetURI; - var $_contentType; - - function setSourceURI($uri) - { - $this->_locURI = $uri; - } - - function getSourceURI() - { - return $this->_locURI; - } - - function setTargetURI($uri) - { - $this->_targetURI = $uri; - } - - function getTargetURI() - { - return $this->_targetURI; - } - - function setContentType($c) - { - $this->_contentType = $c; - } - - function setContentFormat($_format) - { - $this->_contentFormat = $_format; - } - - function getContentType() - { - return $this->_contentType; - } - - function getContent() - { - return $this->_content; - } - - function setContent($content) - { - $this->_content = $content; - } - - function endElement($uri, $element) - { - switch ($this->_xmlStack) { - case 2: - if ($element == 'Data') { - $this->_content = trim($this->_chars); - } - break; - } - - parent::endElement($uri, $element); - } +class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_Sync_SyncElementItem { function outputCommand($currentCmdID, &$output, $command) { $state = $_SESSION['SyncML.state']; + $maxMsgSize = $state->getMaxMsgSizeClient(); + $maxGUIDSize = $state->getMaxGUIDSizeClient(); + + if ($this->_moreData) { + $command = $this->_command; + } else { + $this->_command = $command; + } $attrs = array(); $output->startElement($state->getURI(), $command, $attrs); $output->startElement($state->getURI(), 'CmdID', $attrs); - $chars = $currentCmdID; - $output->characters($chars); + $output->characters($currentCmdID); $output->endElement($state->getURI(), 'CmdID'); +/* if (isset($this->_contentType)) { $output->startElement($state->getURI(), 'Meta', $attrs); $output->startElement($state->getURIMeta(), 'Type', $attrs); @@ -108,43 +46,100 @@ class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_ $output->endElement($state->getURIMeta(), 'Type'); $output->endElement($state->getURI(), 'Meta'); } +*/ + if (isset($this->_content) && !$this->_moreData) { + $this->_content = trim($this->_content); + $this->_contentSize = strlen($this->_content); + if (strtolower($this->_contentFormat) == 'b64') { + $this->_content = base64_encode($this->_content); + } + } else { + $this->_contentSize = 0; + } - if (isset($this->_content) - || isset($this->_locURI) || isset($this->targetURI)) { + // + if ($this->_contentSize || isset($this->_contentType) || isset($this->_contentFormat)) { + $output->startElement($state->getURI(), 'Meta', $attrs); + if (isset($this->_contentType)) { + $output->startElement($state->getURIMeta(), 'Type', $attrs); + $output->characters($this->_contentType); + $output->endElement($state->getURIMeta(), 'Type'); + } + if (isset($this->_contentFormat)) { + $output->startElement($state->getURIMeta(), 'Format', $attrs); + $output->characters($this->_contentFormat); + $output->endElement($state->getURIMeta(), 'Format'); + } + if ($this->_contentSize) { + $output->startElement($state->getURIMeta(), 'Size', $attrs); + $output->characters(($this->_contentSize)); + $output->endElement($state->getURIMeta(), 'Size'); + } + $output->endElement($state->getURI(), 'Meta'); + } + + if (isset($this->_content) || isset($this->_luid) || isset($this->_guid)) { $output->startElement($state->getURI(), 'Item', $attrs); - // send only when sending adds - if ($this->_locURI != null && (strtolower($command) == 'add')) { + + // + if (isset($this->_guid)) { $output->startElement($state->getURI(), 'Source', $attrs); $output->startElement($state->getURI(), 'LocURI', $attrs); - $chars = substr($this->_locURI,0,39); - $state->setUIDMapping($this->_locURI, $chars); + $chars = substr($this->_guid, 0, $maxGUIDSize); + $state->setUIDMapping($this->_guid, $chars); $output->characters($chars); $output->endElement($state->getURI(), 'LocURI'); $output->endElement($state->getURI(), 'Source'); } - - if(isset($this->_contentFormat)) { - $output->startElement($state->getURI(), 'Meta', $attrs); - $output->startElement($state->getURIMeta(), 'Format', $attrs); - $output->characters($this->_contentFormat); - $output->endElement($state->getURIMeta(), 'Format'); - $output->endElement($state->getURI(), 'Meta'); - } - if ($this->_targetURI != null) { + // + if (isset($this->_luid)) { $output->startElement($state->getURI(), 'Target', $attrs); $output->startElement($state->getURI(), 'LocURI', $attrs); - $chars = $this->_targetURI; - $output->characters($chars); + $output->characters($this->_luid); $output->endElement($state->getURI(), 'LocURI'); $output->endElement($state->getURI(), 'Target'); } + + + // if (isset($this->_content)) { $output->startElement($state->getURI(), 'Data', $attrs); #$chars = '_content.']]>'; - $chars = $this->_content; - $output->characters($chars); + $currentSize = $output->getOutputSize(); + Horde::logMessage("SyncML: $command: current = $currentSize, max = $maxMsgSize", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (!$maxMsgSize || + (($currentSize + MIN_MSG_LEFT + $this->_contentSize) <= $maxMsgSize)) { + $chars = $this->_content; + unset($this->_content); + $this->_moreData = false; + } else { + $sizeLeft = $maxMsgSize - $currentSize - MIN_MSG_LEFT; + if ($sizeLeft < 0) { + Horde::logMessage("SyncML: $command: split with $currentSize for $maxMsgSize, increase MIN_MSG_LEFT!", __FILE__, __LINE__, PEAR_LOG_WARNING); + $sizeLeft = 0; + } + // don't let us loose characters by trimming + while (($this->_contentSize > $sizeLeft) && + (strlen(trim(substr($this->_content, $sizeLeft - 1, 2))) < 2)) { + Horde::logMessage("SyncML: $command: split at $sizeLeft hit WS!", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $sizeLeft++; + } + $chars = substr($this->_content, 0, $sizeLeft); + $this->_content = substr($this->_content, $sizeLeft, $this->_contentSize - $sizeLeft); + Horde::logMessage("SyncML: $command: " + . $this->_contentSize . " split at $sizeLeft:\n" + . $chars, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $this->_moreData = true; + } + $output->characters($chars); $output->endElement($state->getURI(), 'Data'); + + // + if ($this->_moreData) { + $output->startElement($state->getURI(), 'MoreData', $attrs); + $output->endElement($state->getURI(), 'MoreData'); + } } $output->endElement($state->getURI(), 'Item'); } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php index 57132f981d..b64bde926a 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php @@ -1,20 +1,20 @@ + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command/Sync/SyncElement.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Sync/Delete.php,v 1.9 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Sync_Delete extends Horde_SyncML_Command_Sync_SyncElement { function output($currentCmdID, &$output) @@ -22,8 +22,8 @@ class Horde_SyncML_Command_Sync_Delete extends Horde_SyncML_Command_Sync_SyncEle $status = new Horde_SyncML_Command_Status($this->_status, 'Delete'); $status->setCmdRef($this->_cmdID); - if (isset($this->_luid)) { - $status->setSourceRef($this->_luid); + if (!empty($this->_items)) { + $status->setSyncItems($this->_items); } return $status->output($currentCmdID, $output); diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Replace.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Replace.php index 2168700d11..3e30982a7a 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Replace.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Replace.php @@ -1,32 +1,30 @@ + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command/Sync/SyncElement.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Sync/Replace.php,v 1.9 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Sync_Replace extends Horde_SyncML_Command_Sync_SyncElement { + function output($currentCmdID, &$output) { $status = new Horde_SyncML_Command_Status($this->_status, 'Replace'); $status->setCmdRef($this->_cmdID); - - if (isset($this->_luid)) { - $status->setSourceRef($this->_luid); - } - - #$status->setItemSourceLocURI($this->_sourceLocURI); - #$status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI); - + + if (!empty($this->_items)) { + $status->setSyncItems($this->_items); + } + return $status->output($currentCmdID, $output); } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElement.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElement.php index 9adef9aabf..acad907b0d 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElement.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElement.php @@ -1,37 +1,40 @@ + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Sync/SyncElement.php,v 1.11 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * Copyright 2005-2006 Lars Kneschke - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Sync_SyncElement extends Horde_SyncML_Command { var $_luid; var $_guid; - var $_isSource; - var $_content; + var $_contentSize; var $_contentType; + var $_contentFormat; var $_status = RESPONSE_OK; - var $_items; - + var $_curItem; + var $_items = array(); + var $_moreData = false; + var $_command = false; + function &factory($command, $params = null) { include_once 'Horde/SyncML/Command/Sync/SyncElementItem.php'; @include_once 'Horde/SyncML/Command/Sync/' . $command . '.php'; - + $class = 'Horde_SyncML_Command_Sync_' . $command; - + if (class_exists($class)) { #Horde::logMessage('SyncML: Class definition of ' . $class . ' found in SyncElement::factory.', __FILE__, __LINE__, PEAR_LOG_DEBUG); return $element = new $class($params); @@ -44,78 +47,122 @@ class Horde_SyncML_Command_Sync_SyncElement extends Horde_SyncML_Command { function startElement($uri, $element, $attrs) { parent::startElement($uri, $element, $attrs); - - switch ($this->_xmlStack) { - case 3: - if ($element == 'Source') { - $this->_isSource = true; + $state = &$_SESSION['SyncML.state']; + + switch (count($this->_stack)) { + case 1: + $this->_command = $element; + break; + case 2: + if ($element == 'Item') { + if (isset($state->curSyncItem)) { + // Copy from state in case of . + $this->_curItem = &$state->curSyncItem; + if (isset($this->_luid) && + ($this->_luid != $this->_curItem->_luid)) { + Horde::logMessage('SyncML: moreData mismatch for LocURI ' . + $this->_curItem->_luid . ' (' . $this->_luid . ')', __FILE__, __LINE__, PEAR_LOG_ERROR); + } else { + Horde::logMessage('SyncML: moreData item found for LocURI ' . $this->_curItem->_luid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + unset($state->curSyncItem); + } else { + $this->_curItem = new Horde_SyncML_Command_Sync_SyncElementItem(); + } + $this->_moreData = false; } break; } } - + function endElement($uri, $element) { + $state = &$_SESSION['SyncML.state']; $search = array('/ *\n/','/ *$/m'); $replace = array('',''); - - switch ($this->_xmlStack) { + + switch (count($this->_stack)) { case 1: + $this->_command = false; // Need to add sync elements to the Sync method? #error_log('total # of items: '.count($this->_items)); #error_log(print_r($this->_items[10], true)); break; case 2; if($element == 'Item') { - $item = new Horde_SyncML_Command_Sync_SyncElementItem(); - if($this->_luid) { - $item->setLocURI($this->_luid); - $item->setContent($this->_content); - $item->setContentType($this->_contentType); - + $this->_curItem->setLocURI($this->_luid); + $this->_curItem->setContentType($this->_contentType); + $this->_curItem->setContentFormat($this->_contentFormat); + $this->_curItem->setCommand($this->_command); + if($this->_contentSize) - $item->setContentType($this->_contentSize); - if($this->_moreData) - $item->setMoreData($this->_moreData); - - $this->_items[$this->_luid] = $item; + $this->_curItem->setContentSize($this->_contentSize); + if($this->_moreData) { + $state->curSyncItem = &$this->_curItem; + Horde::logMessage('SyncML: moreData item saved for LocURI ' . $this->_curItem->_luid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } else { + if (strtolower($this->_curItem->getContentFormat()) == 'b64') { + $content = $this->_curItem->getContent(); + $content = ($content ? base64_decode($content) : ''); + $this->_curItem->setContent($content); + #Horde::logMessage('SyncML: BASE64 encoded item for LocURI ' + # . $this->_curItem->_luid . ":\n $content", __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + $this->_items[$this->_luid] = $this->_curItem; + } } - - unset($this->_content); unset($this->_contentSize); unset($this->_luid); } break; case 3: - if ($element == 'Source') { - $this->_isSource = false; - } elseif ($element == 'Data') { - $this->_content = $this->_chars; - } elseif ($element == 'MoreData') { - $this->_moreData = TRUE; - } elseif ($element == 'Type') { - if(empty($this->_contentType)) - $this->_contentType = trim($this->_chars); + switch ($element) { + case 'Data': + $this->_curItem->_content .= $this->_chars; + break; + case 'MoreData': + $this->_moreData = true; + break; + case 'Type': + if(empty($this->_contentType)) { + $this->_contentType = trim($this->_chars); + } + break; + case 'Format': + $this->_contentFormat = strtolower(trim($this->_chars)); + break; + case 'Size': + $this->_contentSize = $this->_chars; + break; } break; - + case 4: - if ($element == 'LocURI' && $this->_isSource) { - $this->_luid = trim($this->_chars); - } elseif ($element == 'Type') { - $this->_contentType = trim($this->_chars); - } elseif ($element == 'Size') { - $this->_contentSize = trim($this->_chars); + switch ($element) { + case 'LocURI': + if ($this->_stack[2] == 'Source') { + $this->_luid = trim($this->_chars); + } + break; + case 'Type': + $this->_contentType = trim($this->_chars); + break; + case 'Format': + $this->_contentFormat = strtolower(trim($this->_chars)); + break; + case 'Size': + $this->_contentSize = trim($this->_chars); + break; } break; } - + parent::endElement($uri, $element); } - - function getSyncElementItems() { - return (array)$this->_items; - } + + function getSyncElementItems() { + return (array)$this->_items; + } function getLocURI() { @@ -149,14 +196,17 @@ class Horde_SyncML_Command_Sync_SyncElement extends Horde_SyncML_Command { function getContent() { - return $this->_content; + if ($this->_curItem) { + return $this->_curItem->getcontent(); + } + return false; } - function setContent($content) + function hasMoreData() { - $this->_content = $content; + return $this->_moreData; } - + function setStatus($_status) { $this->_status = $_status; diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElementItem.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElementItem.php index 0d87fe795f..6ef74146bb 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElementItem.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElementItem.php @@ -1,67 +1,95 @@ + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Command.php'; -/** - * $Horde: framework/SyncML/SyncML/Command/Sync/SyncElement.php,v 1.11 2004/07/02 19:24:44 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * Copyright 2005-2006 Lars Kneschke - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Command_Sync_SyncElementItem { var $_luid; var $_guid; - var $_content; + var $_content = ''; var $_contentSize; var $_contentType; + var $_contentFormat; + var $_command; var $_moreData = false; - + function getLocURI() { return $this->_luid; } - + function getGUID() { return $this->_guid; } - + function getContentType() { return $this->_contentType; } - + + function getContentFormat() { + return $this->_contentFormat; + } + function getContent() { return $this->_content; } - + + function getContentSize() { + if (isset($this->_contentSize)) { + return $this->_contentSize; + } + return false; + } + + function getCommand() { + return $this->_command; + } + function setLocURI($luid) { $this->_luid = $luid; } - + function setGUID($guid) { $this->_guid = $guid; } - - function setContent($content) { - $this->_content = $content; + + function setContent($_content) { + $this->_content = $_content; } function setContentSize($_size) { $this->_contentSize = $_size; } - function setContentType($_contentType) { - $this->_contentType = $_contentType; + function setContentType($_type) { + $this->_contentType = $_type; + } + + function setContentFormat($_format) { + $this->_contentFormat = $_format; } function setMoreData($_status) { $this->_moreData = $_status; } + + function hasMoreData() { + return $this->_moreData; + } + + function setCommand($_command) { + $this->_command = $_command; + } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/State.php b/phpgwapi/inc/horde/Horde/SyncML/State.php index 515771d6f3..fec43a3884 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/State.php +++ b/phpgwapi/inc/horde/Horde/SyncML/State.php @@ -1,4 +1,18 @@ + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ define('ALERT_DISPLAY', 100); @@ -21,6 +35,10 @@ define('ALERT_RESULT_ALERT', 221); define('ALERT_NEXT_MESSAGE', 222); define('ALERT_NO_END_OF_DATA', 223); +// Not (really) implemented. +define('ALERT_SUSPEND', 224); // New in SyncML 1.2 +define('ALERT_RESUME', 225); // New in SyncML 1.2 + define('MIME_SYNCML_XML', 'application/vnd.syncml+xml'); define('MIME_SYNCML_WBXML', 'application/vnd.syncml+wbxml'); @@ -107,7 +125,7 @@ define('RESPONSE_COMMAND_FAILED', 500); // define('RESPONSE_COMMAND_FAILED', 505); // define('RESPONSE_COMMAND_FAILED', 506); // define('RESPONSE_COMMAND_FAILED', 507); -// define('RESPONSE_COMMAND_FAILED', 508); +define('RESPONSE_REFRESH_REQUIRED', 508); // define('RESPONSE_COMMAND_FAILED', 509); // define('RESPONSE_COMMAND_FAILED', 510); // define('RESPONSE_COMMAND_FAILED', 511); @@ -117,12 +135,15 @@ define('RESPONSE_COMMAND_FAILED', 500); // define('RESPONSE_COMMAND_FAILED', 515); define('RESPONSE_ATOMIC_ROLL_BACK_FAILED', 516); -define('NAME_SPACE_URI_SYNCML', 'syncml:syncml1.0'); +define('NAME_SPACE_URI_SYNCML_1_0', 'syncml:syncml1.0'); define('NAME_SPACE_URI_SYNCML_1_1', 'syncml:syncml1.1'); -define('NAME_SPACE_URI_METINF', 'syncml:metinf'); +define('NAME_SPACE_URI_SYNCML_1_2', 'syncml:syncml1.2'); +define('NAME_SPACE_URI_METINF_1_0', 'syncml:metinf1.0'); define('NAME_SPACE_URI_METINF_1_1', 'syncml:metinf1.1'); -define('NAME_SPACE_URI_DEVINF', 'syncml:devinf'); +define('NAME_SPACE_URI_METINF_1_2', 'syncml:metinf1.2'); +define('NAME_SPACE_URI_DEVINF_1_0', 'syncml:devinf1.0'); define('NAME_SPACE_URI_DEVINF_1_1', 'syncml:devinf1.1'); +define('NAME_SPACE_URI_DEVINF_1_2', 'syncml:devinf1.2'); define('CLIENT_SYNC_STARTED', 1); define('CLIENT_SYNC_FINNISHED', 2); @@ -131,8 +152,18 @@ define('SERVER_SYNC_DATA_PENDING', 4); define('SERVER_SYNC_FINNISHED', 5); define('SERVER_SYNC_ACKNOWLEDGED', 6); +// conflict management +define('CONFLICT_CLIENT_WINNING', 0); +define('CONFLICT_SERVER_WINNING', 1); +define('CONFLICT_MERGE_DATA', 2); +define('CONFLICT_RESOLVED_WITH_DUPLICATE', 3); +define('CONFLICT_CLIENT_CHANGES_IGNORED', 4); +define('CONFLICT_CLIENT_REFRESH_ENFORCED', 5); + define('MAX_DATA', 19); -define('MAX_ENTRIES', 10); +define('MAX_ENTRIES', 10); // default +define('MAX_GUID_SIZE', 64); +define('MIN_MSG_LEFT', 200); // Overhead /** * The Horde_SyncML_State class provides a SyncML state object. @@ -148,6 +179,7 @@ define('MAX_ENTRIES', 10); * @version $Revision$ * @since Horde 3.0 * @package Horde_SyncML + * @modified Joerg Lehrke 2009/01/20, support all syn types */ class Horde_SyncML_State { @@ -157,6 +189,10 @@ class Horde_SyncML_State { var $_msgID; + var $_maxMsgSize; + + var $_maxGUIDSize; + var $_targetURI; var $_sourceURI; @@ -169,6 +205,8 @@ class Horde_SyncML_State { var $_isAuthorized; + var $_AuthConfirmed; + var $_uri; var $_uriMeta; @@ -181,7 +219,7 @@ class Horde_SyncML_State { var $_serverAnchorNext = array(); // written to db after successful sync - var $_clientDeviceInfo = array(); + var $_clientDeviceInfo; // array list of changed items, which need to be synced to the client var $_changedItems; @@ -192,8 +230,11 @@ class Horde_SyncML_State { // array list of added items, which need to be synced to the client var $_addedItems; - // bool flag that we need to more data - var $_syncStatus; + // array list of items, which need to be refreshed at the client + var $_conflictItems; + + // current session status + var $_syncStatus = 0; var $_log = array(); @@ -208,6 +249,22 @@ class Horde_SyncML_State { */ var $_uidMappings = array(); + /** + * Current sync element sent from client. + * + * Stored in state if one element is split into multiple message packets. + * + * @var SyncML_SyncElement + */ + var $curSyncItem; + + /** + * Number of sync elements sent to client within current message. + * + * @var _numberOfElements + */ + var $_numberOfElements; + /** * Creates a new instance of Horde_SyncML_State. */ @@ -220,7 +277,8 @@ class Horde_SyncML_State { $this->setPassword($password); } - $this->isAuthorized = false; + $this->_isAuthorized = false; + $this->_isAuthConfirmed = false; } /** @@ -234,7 +292,7 @@ class Horde_SyncML_State { * retrieve the real egw uid for a given send uid */ function getUIDMapping($_sentEgwUid) { - if(isset($this->_uidMappings[$_sentEgwUid])) { + if(strlen("$_sentEgwUid") && isset($this->_uidMappings[$_sentEgwUid])) { return $this->_uidMappings[$_sentEgwUid]; } @@ -311,6 +369,16 @@ class Horde_SyncML_State { return false; } + function &getConflictItems($_type) + { + if(isset($this->_conflictItems[$_type])) + { + return $this->_conflictItems[$_type]; + } + + return false; + } + function getMoreDataPending() { return $this->_moreDataPending; @@ -321,6 +389,14 @@ class Horde_SyncML_State { return $this->_msgID; } + function getMaxMsgSizeClient() + { + if (isset($this->_maxMsgSize)) { + return $this->_maxMsgSize; + } + return false; + } + function setWBXML($wbxml) { $this->_wbxml = $wbxml; @@ -341,6 +417,11 @@ class Horde_SyncML_State { $this->_addedItems[$_type] = $_addedItems; } + function pushAddedItem($_type, $_addedItems) + { + $this->_addedItems[$_type] = $_addedItems; + } + function setChangedItems($_type, $_changedItems) { $this->_changedItems[$_type] = $_changedItems; @@ -356,9 +437,14 @@ class Horde_SyncML_State { $this->_deletedItems[$_type] = $_deletedItems; } - function setMoreDataPending($_state) + function addConflictItem($_type, $_conflict) { - $this->_moreDataPending = $_state; + $this->_conflictItems[$_type][] = $_conflict; + } + + function clearConflictItems($_type) + { + $this->_conflictItems[$_type] = array(); } /** @@ -370,6 +456,15 @@ class Horde_SyncML_State { $this->_msgID = $msgID; } + /** + * Setter for property maxMsgSize. + * @param size New value of property maxMsgSize. + */ + function setMaxMsgSize($size) + { + $this->_maxMsgSize = $size; + } + /** * Setter for property locName. * @param locName New value of property locName. @@ -408,15 +503,20 @@ class Horde_SyncML_State { { $this->_version = $version; - if ($version == 0) { - $this->_uri = NAME_SPACE_URI_SYNCML; - $this->_uriMeta = NAME_SPACE_URI_METINF; - $this->_uriDevInf = NAME_SPACE_URI_DEVINF; - } else { + if ($version == 2) { + $this->_uri = NAME_SPACE_URI_SYNCML_1_2; + $this->_uriMeta = NAME_SPACE_URI_METINF_1_2; + $this->_uriDevInf = NAME_SPACE_URI_DEVINF_1_2; + } elseif ($version == 1) { $this->_uri = NAME_SPACE_URI_SYNCML_1_1; $this->_uriMeta = NAME_SPACE_URI_METINF_1_1; $this->_uriDevInf = NAME_SPACE_URI_DEVINF_1_1; - } + } else { + $this->_uri = NAME_SPACE_URI_SYNCML_1_0; + $this->_uriMeta = NAME_SPACE_URI_METINF_1_0; + $this->_uriDevInf = NAME_SPACE_URI_DEVINF_1_0; + } + } function setSessionID($sessionID) @@ -457,6 +557,16 @@ class Horde_SyncML_State { return $this->_isAuthorized; } + function isAuthConfirmed() + { + return $this->_AuthConfirmed; + } + + function AuthConfirmed() + { + $this->_AuthConfirmed = true; + } + function clearSync($target) { unset($this->_syncs[$target]); @@ -486,6 +596,9 @@ class Horde_SyncML_State { $targets[] = $target; } + // Make sure we keep the order + sort($targets); + return $targets; } @@ -502,6 +615,7 @@ class Horde_SyncML_State { return ''; } } + function getURIMeta() { return $this->_uriMeta; @@ -548,8 +662,9 @@ class Horde_SyncML_State { * this->_locName . $this->_sourceURI . $type . $locid so you can * have different syncs with different devices. If an entry * already exists, it is overwritten. + * Expired entries can be deleted at the next session start. */ - function setUID($type, $locid, $guid, $ts=0) + function setUID($type, $locid, $guid, $ts=0, $expired=0) { $dt = &$this->getDataTree(); @@ -558,6 +673,7 @@ class Horde_SyncML_State { $gid->set('type', $type); $gid->set('locid', $locid); $gid->set('ts', $ts); + $gid->set('expired', $expired); $r = $dt->add($gid); if (is_a($r, 'PEAR_Error')) { @@ -657,54 +773,41 @@ class Horde_SyncML_State { */ function adjustContentType($type, $target = null) { - $ctype; - if (is_array($type)) - { + if (is_array($type)) { $ctype = $type['ContentType']; $res = $type; - } - else - { + } else { $ctype = $type; $res = array(); $res['ContentType'] = $ctype; } - $deviceInfo = $this->getClientDeviceInfo(); + $deviceInfo = $this->getClientDeviceInfo(); + $manufacturer = isset($deviceInfo['manufacturer']) ? strtolower($deviceInfo['manufacturer']) : 'unknown'; + switch ($manufacturer) { + case 'funambol': + switch (strtolower($deviceInfo['model'])) { + case 'thunderbird': + case 'mozilla plugin': + $res['mayFragment'] = 1; + break; + default: + if (isset($deviceInfo['softwareVersion']) + && $deviceInfo['softwareVersion'] < 4.0) { + $res['mayFragment'] = 0; + } else { + $res['mayFragment'] = 1; + } + break; + } + break; + default: + $res['mayFragment'] = 1; + break; + } - if (isset($deviceInfo['manufacturer'])) - { - switch (strtolower($deviceInfo['manufacturer'])) - { - case 'funambol': - if (strtolower($deviceInfo['model']) == 'thunderbird') - { - $res['mayFragment'] = 1; - } - - if (isset($deviceInfo['softwareVersion']) - && $deviceInfo['softwareVersion']{0} == '3') - { - // anything beyond 6.0 supports fragmentation - $res['mayFragment'] = 0; - } - else - { - $res['mayFragment'] = 1; - } - break; - } - } - - if (!isset($res['mayFragment'])) - { - $res['mayFragment'] = 1; - } - - - // the funambol specific types need to be encoded in base 64 - switch(strtolower($ctype)) - { + // the funambol specific types need to be encoded in base64 + switch (strtolower($ctype)) { case 'text/x-s4j-sifc': case 'text/x-s4j-sife': case 'text/x-s4j-sift': @@ -712,17 +815,15 @@ class Horde_SyncML_State { $res['ContentFormat'] = 'b64'; break; } - return $res; } function getPreferedContentType($type) { $_type = str_replace('./','',$type); - switch(strtolower($_type)) - { + switch (strtolower($_type)) { case 'contacts': - return 'text/x-vcard'; + return 'text/vcard'; break; case 'notes': @@ -730,9 +831,10 @@ class Horde_SyncML_State { break; case 'calendar': + case 'events': case 'tasks': case 'caltasks': - return 'text/x-vcalendar'; + return 'text/calendar'; break; case 'sifcalendar': @@ -778,6 +880,7 @@ class Horde_SyncML_State { return 'tasks'; break; + case 'events': case 'calendar': return 'calendar'; break; @@ -815,7 +918,6 @@ class Horde_SyncML_State { } } - /** /** * Returns the preferred contenttype of the client for the given * sync data type (database). @@ -826,12 +928,33 @@ class Horde_SyncML_State { function getPreferedContentTypeClient($_sourceLocURI, $_targetLocURI = null) { $deviceInfo = $this->getClientDeviceInfo(); - if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'])) - { - return $this->adjustContentType($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'], $_targetLocURI); + if(isset($deviceInfo['dataStore'][$_sourceLocURI]['maxGUIDSize']['contentType'])) { + $this->_maxGUIDSize = $deviceInfo['dataStore'][$this->_sourceURI]['maxGUIDSize']['contentType']; } - Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI .' not found', __FILE__, __LINE__, PEAR_LOG_DEBUG); + if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'])) + { + $ctype = $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType']; + $cvers = $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentVersion']; + $cfrmt = $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentFormat']; + $cprops = $deviceInfo['dataStore'][$_sourceLocURI]['properties'][$ctype][$cvers]; + if (isset($deviceInfo['dataStore'][$_sourceLocURI]['maxGUIDSize'])) { + // get UID properties from maxGUIDSize + $cprops['UID']['Size'] = $deviceInfo['dataStore'][$_sourceLocURI]['maxGUIDSize']; + $cprops['UID']['NoTruncate'] = true; + } + $clientPrefs = array( + 'ContentType' => $ctype, + 'ContentFormat' => $cfrmt, + 'mayFragment' => 1, + 'Properties' => $cprops, + ); + #Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI . " clientPrefs:\n" + # . print_r($clientPrefs, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $this->adjustContentType($clientPrefs, $_targetLocURI); + } + + Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI . " not found:\n" . print_r($deviceInfo, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); if ($_targetLocURI != null) { @@ -841,6 +964,19 @@ class Horde_SyncML_State { return PEAR::raiseError(_('sourceLocURI not found')); } + /** + * Returns the MaxGUIDSize of the client + */ + + function getMaxGUIDSizeClient() { + $maxGUIDSize = MAX_GUID_SIZE; + + if (isset($this->_maxGUIDSize)) { + $maxGUIDSize = $this->_maxGUIDSize; + } + return $maxGUIDSize; + } + function setClientAnchorNext($type, $a) { $this->_clientAnchorNext[$type] = $a; @@ -900,6 +1036,11 @@ class Horde_SyncML_State { */ function getClientDeviceInfo() { + if (isset($this->_clientDeviceInfo) && is_array($this->_clientDeviceInfo)) { + // use cached information + return $this->_clientDeviceInfo; + } + $dt = &$this->getDataTree(); $id = $dt->getId($this->_locName . $this->_sourceURI . 'deviceInfo'); @@ -907,7 +1048,7 @@ class Horde_SyncML_State { return false; } - $info = $dt->getObjectById($id); + $info = $dt->getObjectById($id); return $info->get('ClientDeviceInfo'); } @@ -1070,4 +1211,23 @@ class Horde_SyncML_State { $this->_receivedAlert222 = (bool)$_status; } + function incNumberOfElements() { + $this->_numberOfElements++; + } + + function clearNumberOfElements() { + $this->_numberOfElements = 0; + } + + function getNumberOfElements() { + if (isset($this->_numberOfElements)) { + return $this->_numberOfElements; + } else { + return false; + } + } + + function maxNumberOfElements() { + unset($this->_numberOfElements); + } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/State_egw.php b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php index e1570c9409..0478bad56b 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/State_egw.php +++ b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php @@ -1,14 +1,18 @@ + * @author Joerg Lehrke * @version $Id$ */ - include_once dirname(__FILE__).'/State.php'; /** @@ -17,45 +21,42 @@ include_once dirname(__FILE__).'/State.php'; class EGW_SyncML_State extends Horde_SyncML_State { var $table_devinfo = 'egw_syncmldevinfo'; - + /* * store the mappings of egw uids to client uids */ var $uidMappings = array(); - - /** - * get the local content id from a syncid - * - * @param sting $_syncid id used in syncml - * @return int local egw content id - */ - function get_egwID($_syncid) - { + /** + * get the local content id from a syncid + * + * @param sting $_syncid id used in syncml + * @return int local egw content id + */ + function get_egwID($_syncid) { $syncIDParts = explode('-',$_syncid); array_shift($syncIDParts); $_id = implode ('', $syncIDParts); return $_id; - } - - /** - * when got a entry last added/modified/deleted - * - * @param $_syncid containing appName-contentid - * @param $_action string can be add, delete or modify - * @return string the last timestamp - */ - function getSyncTSforAction($_syncid, $_action) - { + } + + /** + * when got a entry last added/modified/deleted + * + * @param $_syncid containing appName-contentid + * @param $_action string can be add, delete or modify + * @return string the last timestamp + */ + function getSyncTSforAction($_syncid, $_action) { $syncIDParts = explode('-',$_syncid); $_appName = array_shift($syncIDParts); $_id = implode ('', $syncIDParts); - + $ts = $GLOBALS['egw']->contenthistory->getTSforAction($_appName, $_id, $_action); - + return $ts; - } - + } + /** * get the timestamp for action * @@ -63,57 +64,94 @@ class EGW_SyncML_State extends Horde_SyncML_State * * @param string$_appName the appname example: infolog_notes * @param string $_action can be modify, add or delete - * @param string $_ts timestamp where to start searching from + * @param string $_ts timestamp where to start searching from * @return array containing syncIDs with changes */ - function getHistory($_appName, $_action, $_ts) - { + function getHistory($_appName, $_action, $_ts) { $guidList = array (); $syncIdList = array (); $idList = $GLOBALS['egw']->contenthistory->getHistory($_appName, $_action, $_ts); foreach ($idList as $idItem) { - $syncIdList[] = $_appName . '-' . $idItem; + if ($idItem) // ignore inconsistent entries + { + $syncIdList[] = $_appName . '-' . $idItem; + } } - return $syncIdList; + return $syncIdList; } - - /** - * Returns the timestamp (if set) of the last change to the - * obj:guid, that was caused by the client. This is stored to - * avoid mirroring these changes back to the client. - */ - function getChangeTS($type, $guid) - { - $mapID = $this->_locName . $this->_sourceURI . $type; - - #Horde::logMessage('SyncML: getChangeTS for ' . $mapID .' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); - - if ($ts = $GLOBALS['egw']->db->select('egw_contentmap', 'map_timestamp', array( - 'map_id' => $mapID, - 'map_guid' => $guid, - ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn()) - { - #Horde::logMessage('SyncML: getChangeTS changets is ' . $GLOBALS['egw']->db->from_timestamp($ts), __FILE__, __LINE__, PEAR_LOG_DEBUG); - return $GLOBALS['egw']->db->from_timestamp($ts); - } - return false; - } /** - * Retrieves information about the clients device info if any. Returns - * false if no info found or a DateTreeObject with at least the - * following attributes: - * - * a array containing all available infos about the device - */ - function getClientDeviceInfo() - { - if(($deviceID = $GLOBALS['egw']->db->select('egw_syncmldeviceowner', 'owner_devid',array ( - 'owner_locname' => $this->_locName, - 'owner_deviceid' => $this->_sourceURI, - ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) + * Returns the timestamp (if set) of the last change to the + * obj:guid, that was caused by the client. This is stored to + * avoid mirroring these changes back to the client. + */ + function getChangeTS($type, $guid) { + $mapID = $this->_locName . $this->_sourceURI . $type; + + #Horde::logMessage('SyncML: getChangeTS for ' . $mapID + # . ' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + if ($ts = $GLOBALS['egw']->db->select('egw_contentmap', 'map_timestamp', + array( + 'map_id' => $mapID, + 'map_guid' => $guid, + ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn()) { + #Horde::logMessage('SyncML: getChangeTS changets is ' + # . $GLOBALS['egw']->db->from_timestamp($ts), + # __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $GLOBALS['egw']->db->from_timestamp($ts); + } + return false; + } + + /** + * Returns the exceptions for a GUID which the client knows of + */ + function getGUIDExceptions($type, $guid) { + $mapID = $this->_locName . $this->_sourceURI . $type; + + #Horde::logMessage('SyncML: getChangeTS for ' . $mapID + # . ' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $guid_exceptions = array(); + $where = array ('map_id' => $mapID,); + $where[] = "map_guid LIKE '$guid" . ":%'"; + + // Fetch all exceptions which the client knows of + foreach ($GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', $where, + __LINE__,__FILE__, false, '', 'syncml') as $row) { + $parts = preg_split('/:/', $row['map_guid']); + $Id = $parts[0]; + $extension = $parts[1]; + $guid_exceptions[$extension] = $row['map_guid']; + } + return $guid_exceptions; + } + + /** + * Retrieves information about the clients device info if any. Returns + * false if no info found or a DateTreeObject with at least the + * following attributes: + * + * a array containing all available infos about the device + */ + function getClientDeviceInfo() { + #Horde::logMessage("SyncML: getClientDeviceInfo " . $this->_locName + # . ", " . $this->_sourceURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (isset($this->_clientDeviceInfo) + && is_array($this->_clientDeviceInfo)) { + // use cached information + return $this->_clientDeviceInfo; + } + + if (($deviceID = $GLOBALS['egw']->db->select('egw_syncmldeviceowner', + 'owner_devid', + array ( + 'owner_locname' => $this->_locName, + 'owner_deviceid' => $this->_sourceURI, + ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) { $cols = array( 'dev_dtdversion', 'dev_numberofchanges', @@ -129,156 +167,156 @@ class EGW_SyncML_State extends Horde_SyncML_State 'dev_utc', ); + #Horde::logMessage("SyncML: getClientDeviceInfo $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $where = array( - 'dev_id' => $deviceID, + 'dev_id' => $deviceID, ); - if (($row = $GLOBALS['egw']->db->select('egw_syncmldevinfo', $cols, $where, __LINE__, __FILE__, false, '', 'syncml')->fetch())) - { - return array ( - 'DTDVersion' => $row['dev_dtdversion'], - 'supportNumberOfChanges'=> $row['dev_numberofchanges'], - 'supportLargeObjs' => $row['dev_largeobjs'], - 'UTC' => $row['dev_utc'], - 'softwareVersion' => $row['dev_swversion'], - 'hardwareVersion' => $row['dev_hwversion'], - 'firmwareVersion' => $row['dev_fwversion'], - 'oem' => $row['dev_oem'], - 'model' => $row['dev_model'], - 'manufacturer' => $row['dev_manufacturer'], - 'deviceType' => $row['dev_devicetype'], - 'dataStore' => unserialize($row['dev_datastore']), + if (($row = $GLOBALS['egw']->db->select('egw_syncmldevinfo', + $cols, $where, __LINE__, __FILE__, false, '', 'syncml')->fetch())) { + $deviceMaxEntries = 'maxEntries-' . $this->_sourceURI; + $deviceUIDExtension = 'uidExtension-' . $this->_sourceURI; + $deviceNonBlockingAllday = 'nonBlockingAllday-' . $this->_sourceURI; + $syncml_prefs = $GLOBALS['egw_info']['user']['preferences']['syncml']; + $this->_clientDeviceInfo = array ( + 'DTDVersion' => $row['dev_dtdversion'], + 'supportNumberOfChanges' => $row['dev_numberofchanges'], + 'supportLargeObjs' => $row['dev_largeobjs'], + 'UTC' => $row['dev_utc'], + 'softwareVersion' => $row['dev_swversion'], + 'hardwareVersion' => $row['dev_hwversion'], + 'firmwareVersion' => $row['dev_fwversion'], + 'oem' => $row['dev_oem'], + 'model' => $row['dev_model'], + 'manufacturer' => $row['dev_manufacturer'], + 'deviceType' => $row['dev_devicetype'], + 'maxMsgSize' => $this->_maxMsgSize, + 'maxEntries' => $syncml_prefs[$deviceMaxEntries], + 'uidExtension' => $syncml_prefs[$deviceUIDExtension], + 'nonBlockingAllday' => $syncml_prefs[$deviceNonBlockingAllday], + 'dataStore' => unserialize($row['dev_datastore']), ); + return $this->_clientDeviceInfo; } } - return false; } /** - * returns GUIDs of all client items - */ - function _getClientItems($type) - { - $mapID = $this->_locName . $this->_sourceURI . $type; + * returns GUIDs of all client items + */ + function getClientItems() { + $mapID = $this->_locName . $this->_sourceURI . $this->_targetURI; - $guids = array(); - foreach($GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', array( - 'map_id' => $mapID, - 'map_expired' => 0, - ), __LINE__, __FILE__, false, '', 'syncml') as $row) - { - $guids[] = $row['map_guid']; - } - return $guids ? $guids : false; + $guids = array(); + foreach($GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', array( + 'map_id' => $mapID, + 'map_expired' => false, + ), __LINE__, __FILE__, false, '', 'syncml') as $row) { + $guids[] = $row['map_guid']; + } + return $guids ? $guids : false; } - /** - * Retrieves the Horde server guid (like - * kronolith:0d1b415fc124d3427722e95f0e926b75) for a given client - * locid. Returns false if no such id is stored yet. - * - * Opposite of getLocId which returns the locid for a given guid. - */ - function getGlobalUID($type, $locid) - { - $mapID = $this->_locName . $this->_sourceURI . $type; + /** + * Retrieves the Horde server guid (like + * kronolith:0d1b415fc124d3427722e95f0e926b75) for a given client + * locid. Returns false if no such id is stored yet. + * + * Opposite of getLocId which returns the locid for a given guid. + */ + function getGlobalUID($type, $locid) { + $mapID = $this->_locName . $this->_sourceURI . $type; - #Horde::logMessage('SyncML: search GlobalUID for ' . $mapID .' / '.$locid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + #Horde::logMessage('SyncML: search GlobalUID for ' . $mapID .' / '.$locid, __FILE__, __LINE__, PEAR_LOG_DEBUG); - return $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', array( - 'map_id' => $mapID, - 'map_locuid' => $locid, - 'map_expired' => 0, - ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn(); - } + return $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', + array( + 'map_id' => $mapID, + 'map_locuid' => $locid, + 'map_expired' => false, + ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn(); + } - /** - * Converts a EGW GUID (like - * kronolith:0d1b415fc124d3427722e95f0e926b75) to a client ID as - * used by the sync client (like 12) returns false if no such id - * is stored yet. - */ - function getLocID($type, $guid) - { - $mapID = $this->_locName . $this->_sourceURI . $type; + /** + * Converts a EGW GUID (like + * kronolith:0d1b415fc124d3427722e95f0e926b75) to a client ID as + * used by the sync client (like 12) returns false if no such id + * is stored yet. + */ + function getLocID($type, $guid) { + $mapID = $this->_locName . $this->_sourceURI . $type; - Horde::logMessage('SyncML: search LocID for ' . $mapID .' / '.$guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage('SyncML: search LocID for ' . $mapID . ' / ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); - if (($locuid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_locuid', array( - 'map_id' => $mapID, - 'map_guid' => $guid - ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) - { - Horde::logMessage('SyncML: found LocID: '.$locuid, __FILE__, __LINE__, PEAR_LOG_DEBUG); - } - return $locuid; - } + if (($locuid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_locuid', array( + 'map_id' => $mapID, + 'map_guid' => $guid + ), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) { + Horde::logMessage('SyncML: found LocID: '.$locuid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + return $locuid; + } - /** - * Retrieves information about the previous sync if any. Returns - * false if no info found or a DateTreeObject with at least the - * following attributes: - * - * ClientAnchor: the clients Next Anchor of the previous sync. - * ServerAnchor: the Server Next Anchor of the previous sync. - */ - function getSyncSummary($type) - { + /** + * Retrieves information about the previous sync if any. Returns + * false if no info found or a DateTreeObject with at least the + * following attributes: + * + * ClientAnchor: the clients Next Anchor of the previous sync. + * ServerAnchor: the Server Next Anchor of the previous sync. + */ + function getSyncSummary($type) { $deviceID = $this->_locName . $this->_sourceURI; - #Horde::logMessage("SyncML: get SYNCSummary for $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage("SyncML: getSyncSummary for $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG); - if (($row = $GLOBALS['egw']->db->select('egw_syncmlsummary', array('sync_serverts','sync_clientts'), array( - 'dev_id' => $deviceID, - 'sync_path' => $type - ), __LINE__, __FILE__, false, '', 'syncml')->fetch())) - { - #Horde::logMessage("SyncML: get SYNCSummary for $deviceID serverts: ".$row['sync_serverts']." clients: ".$row['sync_clientts'], __FILE__, __LINE__, PEAR_LOG_DEBUG); - return array( - 'ClientAnchor' => $row['sync_clientts'], - 'ServerAnchor' => $row['sync_serverts'], - ); - } - return false; - } + if (($row = $GLOBALS['egw']->db->select('egw_syncmlsummary', array('sync_serverts','sync_clientts'), array( + 'dev_id' => $deviceID, + 'sync_path' => $type + ), __LINE__, __FILE__, false, '', 'syncml')->fetch())) { + Horde::logMessage("SyncML: getSyncSummary for $deviceID serverts: ".$row['sync_serverts']." clients: ".$row['sync_clientts'], __FILE__, __LINE__, PEAR_LOG_DEBUG); + return array( + 'ClientAnchor' => $row['sync_clientts'], + 'ServerAnchor' => $row['sync_serverts'], + ); + } + return false; + } - function isAuthorized() - { - if (!$this->_isAuthorized) - { - if(!isset($this->_locName) && !isset($this->_password)) - { + function isAuthorized() { + if (!$this->_isAuthorized) { + if(!isset($this->_locName) && !isset($this->_password)) { Horde::logMessage('SyncML: Authentication not yet possible currently. Username and password not available' , __FILE__, __LINE__, PEAR_LOG_DEBUG); return FALSE; } - if(!isset($this->_password)) - { + if(!isset($this->_password)) { Horde::logMessage('SyncML: Authentication not yet possible currently. Password not available' , __FILE__, __LINE__, PEAR_LOG_DEBUG); return FALSE; } - if(strpos($this->_locName,'@') === False) - { + if(strpos($this->_locName,'@') === False) { $this->_locName .= '@'.$GLOBALS['egw_info']['server']['default_domain']; } #Horde::logMessage('SyncML: authenticate with username: ' . $this->_locName . ' and password: ' . $this->_password, __FILE__, __LINE__, PEAR_LOG_DEBUG); - if($GLOBALS['sessionid'] = $GLOBALS['egw']->session->create($this->_locName,$this->_password,'text')) - { - $this->_isAuthorized = true; - Horde::logMessage('SyncML_EGW: Authentication of ' . $this->_locName . '/' . $GLOBALS['sessionid'] . ' succeded' , __FILE__, __LINE__, PEAR_LOG_DEBUG); - } - else - { + if($GLOBALS['sessionid'] = $GLOBALS['egw']->session->create($this->_locName,$this->_password,'text')) { + + if ($GLOBALS['egw_info']['user']['apps']['syncml']) { + $this->_isAuthorized = true; + Horde::logMessage('SyncML_EGW: Authentication of ' . $this->_locName . '/' . $GLOBALS['sessionid'] . ' succeded' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } else { + $this->_isAuthorized = false; + Horde::logMessage('SyncML is not enabled for this user', __FILE__, __LINE__, PEAR_LOG_ERROR); + } + } else { $this->_isAuthorized = false; Horde::logMessage('SyncML: Authentication of ' . $this->_locName . ' failed' , __FILE__, __LINE__, PEAR_LOG_INFO); } - } - else - { + } else { // store sessionID in a variable, because ->verify maybe resets that value $sessionID = session_id(); if(!$GLOBALS['egw']->session->verify($sessionID, 'staticsyncmlkp3')) { @@ -290,11 +328,10 @@ class EGW_SyncML_State extends Horde_SyncML_State } /** - * Removes all locid<->guid mappings for the given type. - * Returns always true. - */ - function removeAllUID($type) - { + * Removes all locid<->guid mappings for the given type. + * Returns always true. + */ + function removeAllUID($type) { $mapID = $this->_locName . $this->_sourceURI . $type; Horde::logMessage("SyncML: state->removeAllUID(type=$type)", __FILE__, __LINE__, PEAR_LOG_DEBUG); @@ -302,17 +339,16 @@ class EGW_SyncML_State extends Horde_SyncML_State $GLOBALS['egw']->db->delete('egw_contentmap', array('map_id' => $mapID), __LINE__, __FILE__, 'syncml'); return true; - } - + } + /** - * Used in SlowSync - * Removes all locid<->guid mappings for the given type, - * that are older than $ts. - * - * Returns always true. - */ - function removeOldUID($type, $ts) - { + * Used in SlowSync + * Removes all locid<->guid mappings for the given type, + * that are older than $ts. + * + * Returns always true. + */ + function removeOldUID($type, $ts) { $mapID = $this->_locName . $this->_sourceURI . $type; $where[] = "map_id = '".$mapID."' AND map_timestamp < '".$GLOBALS['egw']->db->to_timestamp($ts)."'"; @@ -324,102 +360,151 @@ class EGW_SyncML_State extends Horde_SyncML_State } /** - * Removes the locid<->guid mapping for the given locid. Returns - * the guid that was removed or false if no mapping entry was - * found. - */ - function removeUID($type, $locid) - { + * Used at session end to cleanup expired entries + * Removes all locid<->guid mappings for the given type, + * that are marked as expired and older than $ts. + * + * Returns always true. + */ + function removeExpiredUID($type, $ts) { $mapID = $this->_locName . $this->_sourceURI . $type; - + $where['map_id'] = $mapID; + $where['map_expired'] = true; + $where[] = "map_timestamp <= '".$GLOBALS['egw']->db->to_timestamp($ts)."'"; + + Horde::logMessage("SyncML: state->removeExpiredUID(type=$type)", + __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $GLOBALS['egw']->db->delete('egw_contentmap', $where, + __LINE__, __FILE__, 'syncml'); + + return true; + } + + /** + * Check if an entry is already expired + * + * Returns true for expired mappings. + */ + function isExpiredUID($type, $locid) { + $mapID = $this->_locName . $this->_sourceURI . $type; + $expired = false; + $where = array( + 'map_id' => $mapID, + 'map_locuid' => $locid, + ); + if (($expired = $GLOBALS['egw']->db->select('egw_contentmap', 'map_expired', + $where, __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) { + Horde::logMessage('SyncML: found LocID: '. $locid, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + return $expired; + } + + /** + * Removes the locid<->guid mapping for the given locid. Returns + * the guid that was removed or false if no mapping entry was + * found. + */ + function removeUID($type, $locid) { + $mapID = $this->_locName . $this->_sourceURI . $type; + $where = array ( 'map_id' => $mapID, 'map_locuid' => $locid ); - if (!($guid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', $where, __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) - { - Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : nothing to remove", __FILE__, __LINE__, PEAR_LOG_INFO); + if (!($guid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', $where, + __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) { + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid)" + . " nothing to remove", __FILE__, __LINE__, PEAR_LOG_INFO); return false; } - Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : removing guid:$guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); - + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid): " + . "removing guid $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml'); return $guid; } - /** - * Puts a given client $locid and Horde server $guid pair into the - * map table to allow mapping between the client's and server's - * IDs. Actually there are two maps: from the localid to the guid - * and vice versa. The localid is converted to a key as follows: - * this->_locName . $this->_sourceURI . $type . $locid so you can - * have different syncs with different devices. If an entry - * already exists, it is overwritten. - */ - function setUID($type, $locid, $_guid, $ts=0) - { - #Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG); - #Horde::logMessage("SyncML: setUID ". $this->getUIDMapping($guid), __FILE__, __LINE__, PEAR_LOG_DEBUG); - - // problem: entries created from client, come here with the (long) server guid, - // but getUIDMapping does not know them and can not map server-guid <--> client guid - $guid = $this->getUIDMapping($_guid); - if($guid === false) - { - // this message is not really usefull here because setUIDMapping is only called when adding content to the client, - // however setUID is called also when adding content from the client. So in all other conditions this - // message will be logged. - Horde::logMessage("SyncML: setUID $type, $locid, $guid something went wrong!!! Mapping not found.", __FILE__, __LINE__, PEAR_LOG_INFO); - $guid = $_guid; - //return false; - } - Horde::logMessage("SyncML: setUID $_guid => $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + /** + * Puts a given client $locid and Horde server $guid pair into the + * map table to allow mapping between the client's and server's + * IDs. Actually there are two maps: from the localid to the guid + * and vice versa. The localid is converted to a key as follows: + * this->_locName . $this->_sourceURI . $type . $locid so you can + * have different syncs with different devices. If an entry + * already exists, it is overwritten. + * Expired entries can be deleted at the next session start. + */ + function setUID($type, $locid, $_guid, $ts=0, $expired=false) { + #Horde::logMessage("SyncML: setUID $type, $locid, $_guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG); - if(!$ts) $ts = time(); + if (!strlen("$_guid")) { + // We can't handle this case otherwise + return; + } - Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG); + // problem: entries created from client, come here with the (long) server guid, + // but getUIDMapping does not know them and can not map server-guid <--> client guid + $guid = $this->getUIDMapping($_guid); + if($guid === false) { + // this message is not really usefull here because setUIDMapping is only called when adding content to the client, + // however setUID is called also when adding content from the client. So in all other conditions this + // message will be logged. + //Horde::logMessage("SyncML: setUID $type, $locid, $guid something went wrong!!! Mapping not found.", __FILE__, __LINE__, PEAR_LOG_INFO); + $guid = $_guid; + //return false; + } + #Horde::logMessage("SyncML: setUID $_guid => $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $mapID = $this->_locName . $this->_sourceURI . $type; + if(!$ts) $ts = time(); - // delete all client id's - $where = array( - 'map_id' => $mapID, - 'map_locuid' => $locid, - ); - $GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml'); + Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ", + __FILE__, __LINE__, PEAR_LOG_DEBUG); - // delete all egw id's - $where = array( - 'map_id' => $mapID, - 'map_guid' => $guid, - ); - $GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml'); + $mapID = $this->_locName . $this->_sourceURI . $type; - $data = $where + array( - 'map_locuid' => $locid, - 'map_timestamp' => $ts, - 'map_expired' => 0, - ); - $GLOBALS['egw']->db->insert('egw_contentmap', $data, $where, __LINE__, __FILE__, 'syncml'); + // expire all client id's + $where = array( + 'map_id' => $mapID, + 'map_locuid' => $locid, + ); + $data = array ( + 'map_expired' => true, + ); + $GLOBALS['egw']->db->delete('egw_contentmap', $where, + __LINE__, __FILE__, 'syncml'); - #Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts $mapID", __FILE__, __LINE__, PEAR_LOG_DEBUG); - } + // delete all egw id's + $where = array( + 'map_id' => $mapID, + 'map_guid' => $guid, + ); + $GLOBALS['egw']->db->delete('egw_contentmap', $where, + __LINE__, __FILE__, 'syncml'); + + $data = $where + array( + 'map_locuid' => $locid, + 'map_timestamp' => $ts, + 'map_expired' => ($expired ? true : false), + ); + $GLOBALS['egw']->db->insert('egw_contentmap', $data, $where, + __LINE__, __FILE__, 'syncml'); + } /** - * writes clients deviceinfo into database - */ - function writeClientDeviceInfo() - { - if (!isset($this->_clientDeviceInfo) || !is_array($this->_clientDeviceInfo)) - { + * writes clients deviceinfo into database + */ + function writeClientDeviceInfo() { + if (!isset($this->_clientDeviceInfo) + || !is_array($this->_clientDeviceInfo)) { return false; } - if(!isset($this->size_dev_hwversion)) - { + if(!isset($this->size_dev_hwversion)) { $tableDefDevInfo = $GLOBALS['egw']->db->get_table_definitions('syncml',$this->table_devinfo); $this->size_dev_hwversion = $tableDefDevInfo['fd']['dev_hwversion']['precision']; unset($tableDefDevInfo); @@ -437,77 +522,89 @@ class EGW_SyncML_State extends Horde_SyncML_State 'dev_fwversion' => $firmwareVersion, ); - if (($deviceID = $GLOBALS['egw']->db->select('egw_syncmldevinfo', 'dev_id', $where, __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) - { + if (($deviceID = $GLOBALS['egw']->db->select('egw_syncmldevinfo', 'dev_id', $where, + __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) { $data = array ( 'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']), ); - $GLOBALS['egw']->db->update('egw_syncmldevinfo', $data, $where, __LINE__, __FILE__, 'syncml'); - } - else - { + $GLOBALS['egw']->db->update('egw_syncmldevinfo', $data, $where, + __LINE__, __FILE__, 'syncml'); + } else { $data = array ( - 'dev_dtdversion' => $this->_clientDeviceInfo['DTDVersion'], - 'dev_numberofchanges' => $this->_clientDeviceInfo['supportNumberOfChanges'] ? true : false, - 'dev_largeobjs' => $this->_clientDeviceInfo['supportLargeObjs'] ? true : false, - 'dev_utc' => $this->_clientDeviceInfo['UTC'] ? true : false, - 'dev_swversion' => $softwareVersion, - 'dev_hwversion' => $hardwareVersion, - 'dev_fwversion' => $firmwareVersion, - 'dev_oem' => $this->_clientDeviceInfo['oem'], - 'dev_model' => $this->_clientDeviceInfo['model'], - 'dev_manufacturer' => $this->_clientDeviceInfo['manufacturer'], - 'dev_devicetype' => $this->_clientDeviceInfo['deviceType'], - 'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']), + 'dev_dtdversion' => $this->_clientDeviceInfo['DTDVersion'], + 'dev_numberofchanges' => ($this->_clientDeviceInfo['supportNumberOfChanges'] ? true : false), + 'dev_largeobjs' => ($this->_clientDeviceInfo['supportLargeObjs'] ? true : false), + 'dev_utc' => ($this->_clientDeviceInfo['UTC'] ? true : false), + 'dev_swversion' => $softwareVersion, + 'dev_hwversion' => $hardwareVersion, + 'dev_fwversion' => $firmwareVersion, + 'dev_oem' => $this->_clientDeviceInfo['oem'], + 'dev_model' => $this->_clientDeviceInfo['model'], + 'dev_manufacturer' => $this->_clientDeviceInfo['manufacturer'], + 'dev_devicetype' => $this->_clientDeviceInfo['deviceType'], + 'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']), ); $GLOBALS['egw']->db->insert('egw_syncmldevinfo', $data, $where, __LINE__, __FILE__, 'syncml'); $deviceID = $GLOBALS['egw']->db->get_last_insert_id('egw_syncmldevinfo', 'dev_id'); } - $data = array ( - 'owner_locname' => $this->_locName, - 'owner_deviceid' => $this->_sourceURI, - 'owner_devid' => $deviceID, - ); $where = array ( 'owner_locname' => $this->_locName, 'owner_deviceid' => $this->_sourceURI, ); - $GLOBALS['egw']->db->insert('egw_syncmldeviceowner', $data, $where, __LINE__, __FILE__, 'syncml'); + + if ($GLOBALS['egw']->db->select('egw_syncmldeviceowner', 'owner_devid', $where, + __LINE__, __FILE__, false, '', 'syncml')->fetchColumn()) { + $data = array ( + 'owner_devid' => $deviceID, + ); + $GLOBALS['egw']->db->update('egw_syncmldeviceowner', $data, $where, + __LINE__, __FILE__, 'syncml'); + } else { + $data = array ( + 'owner_locname' => $this->_locName, + 'owner_deviceid' => $this->_sourceURI, + 'owner_devid' => $deviceID, + ); + $GLOBALS['egw']->db->insert('egw_syncmldeviceowner', $data, $where, + __LINE__, __FILE__, 'syncml'); + } } - /** - * After a successful sync, the client and server's Next Anchors - * are written to the database so they can be used to negotiate - * upcoming syncs. - */ - function writeSyncSummary() - { - #parent::writeSyncSummary(); + /** + * After a successful sync, the client and server's Next Anchors + * are written to the database so they can be used to negotiate + * upcoming syncs. + */ + function writeSyncSummary() { + #parent::writeSyncSummary(); - if (!isset($this->_serverAnchorNext) || !is_array($this->_serverAnchorNext)) - { - return; - } + if (!isset($this->_serverAnchorNext) + || !is_array($this->_serverAnchorNext)) { + return; + } - $deviceID = $this->_locName . $this->_sourceURI; + $deviceID = $this->_locName . $this->_sourceURI; - foreach((array)$this->_serverAnchorNext as $type => $a) - { - Horde::logMessage("SyncML: write SYNCSummary for $deviceID $type serverts: $a clients: ".$this->_clientAnchorNext[$type], __FILE__, __LINE__, PEAR_LOG_DEBUG); + foreach((array)$this->_serverAnchorNext as $type => $a) { + Horde::logMessage("SyncML: write SYNCSummary for $deviceID " + . "$type serverts: $a clients: " + . $this->_clientAnchorNext[$type], + __FILE__, __LINE__, PEAR_LOG_DEBUG); - $where = array( - 'dev_id' => $deviceID, - 'sync_path' => $type, - ); + $where = array( + 'dev_id' => $deviceID, + 'sync_path' => $type, + ); - $data = array( - 'sync_serverts' => $a, - 'sync_clientts' => $this->_clientAnchorNext[$type] - ); + $data = array( + 'sync_serverts' => $a, + 'sync_clientts' => $this->_clientAnchorNext[$type] + ); - $GLOBALS['egw']->db->insert('egw_syncmlsummary', $data, $where, __LINE__, __FILE__, 'syncml'); - } - } + $GLOBALS['egw']->db->insert('egw_syncmlsummary', $data, $where, + __LINE__, __FILE__, 'syncml'); + } + } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync.php index 184cb2761d..51609c6864 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync.php @@ -1,230 +1,466 @@ * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * Using the PEAR Log class (which need to be installed!) * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ + class Horde_SyncML_Sync { - /** - * Target, either contacts, notes, events, - */ - var $_targetLocURI; + /** + * Target, either contacts, notes, events, + */ + var $_targetLocURI; - var $_sourceLocURI; + var $_sourceLocURI; - /** - * Return if all commands success. - */ - var $globalSuccess; - - /** - * This is the content type to use to export data. - */ - var $preferedContentType; - - /** - * Do have the sync data loaded from the database already? - */ - var $syncDataLoaded; - - function &factory($alert) - { - Horde::logMessage('SyncML: new sync for alerttype ' . $alert, __FILE__, __LINE__, PEAR_LOG_DEBUG); - switch ($alert) { - case ALERT_TWO_WAY: - include_once 'Horde/SyncML/Sync/TwoWaySync.php'; - return $sync = new Horde_SyncML_Sync_TwoWaySync(); - - case ALERT_SLOW_SYNC: - include_once 'Horde/SyncML/Sync/SlowSync.php'; - return $sync = new Horde_SyncML_Sync_SlowSync(); - - case ALERT_ONE_WAY_FROM_CLIENT: - include_once 'Horde/SyncML/Sync/OneWayFromClientSync.php'; - return $sync = new Horde_SyncML_Sync_OneWayFromClientSync(); - - case ALERT_REFRESH_FROM_CLIENT: - include_once 'Horde/SyncML/Sync/RefreshFromClientSync.php'; - return $sync = new Horde_SyncML_Sync_RefreshFromClientSync(); - - case ALERT_ONE_WAY_FROM_SERVER: - include_once 'Horde/SyncML/Sync/OneWayFromServerSync.php'; - return $sync = new Horde_SyncML_Sync_OneWayFromServerSync(); - - case ALERT_REFRESH_FROM_SERVER: - include_once 'Horde/SyncML/Sync/RefreshFromServerSync.php'; - return $sync = new Horde_SyncML_Sync_RefreshFromServerSync(); - } - - require_once 'PEAR.php'; - return PEAR::raiseError('Alert ' . $alert . ' not found.'); - } - - function nextSyncCommand($currentCmdID, &$syncCommand, &$output) - { - $result = $this->runSyncCommand($syncCommand); - return $syncCommand->output($currentCmdID, $output); - } - - function startSync($currentCmdID, &$output) - { - return $currentCmdID; - } - - function endSync($currentCmdID, &$output) - { - return $currentCmdID; - } + var $_locName; /** - * Here's where the actual processing of a client-sent Sync - * Command takes place. Entries are added, deleted or replaced - * from the server database by using Horde API (Registry) calls. - */ + * The synchronization method, one of the ALERT_* constants. + * + * @var integer + */ + var $_syncType; + + /** + * Return if all commands success. + */ + var $globalSuccess; + + /** + * This is the content type to use to export data. + */ + var $preferedContentType; + + /** + * Optional filter expression for this content. + * + * @var string + */ + var $_filterExpression = ''; + + + + /** + * Do have the sync data loaded from the database already? + */ + var $syncDataLoaded; + + function &factory($alert) { + Horde::logMessage('SyncML: new sync for alerttype ' . $alert, __FILE__, __LINE__, PEAR_LOG_DEBUG); + switch ($alert) { + case ALERT_TWO_WAY: + include_once 'Horde/SyncML/Sync/TwoWaySync.php'; + return $sync = new Horde_SyncML_Sync_TwoWaySync(); + + case ALERT_SLOW_SYNC: + include_once 'Horde/SyncML/Sync/SlowSync.php'; + return $sync = new Horde_SyncML_Sync_SlowSync(); + + case ALERT_ONE_WAY_FROM_CLIENT: + include_once 'Horde/SyncML/Sync/OneWayFromClientSync.php'; + return $sync = new Horde_SyncML_Sync_OneWayFromClientSync(); + + case ALERT_REFRESH_FROM_CLIENT: + include_once 'Horde/SyncML/Sync/RefreshFromClientSync.php'; + return $sync = new Horde_SyncML_Sync_RefreshFromClientSync(); + + case ALERT_ONE_WAY_FROM_SERVER: + include_once 'Horde/SyncML/Sync/OneWayFromServerSync.php'; + return $sync = new Horde_SyncML_Sync_OneWayFromServerSync(); + + case ALERT_REFRESH_FROM_SERVER: + include_once 'Horde/SyncML/Sync/RefreshFromServerSync.php'; + return $sync = new Horde_SyncML_Sync_RefreshFromServerSync(); + } + + require_once 'PEAR.php'; + return PEAR::raiseError('Alert ' . $alert . ' not found.'); + } + + function nextSyncCommand($currentCmdID, &$syncCommand, &$output) { + $result = $this->runSyncCommand($syncCommand); + return $syncCommand->output($currentCmdID, $output); + } + + function startSync($currentCmdID, &$output) { + return $currentCmdID; + } + + function endSync($currentCmdID, &$output) { + return $currentCmdID; + } + + /** + * Setter for property sourceURI. + * + * @param string $sourceURI New value of property sourceLocURI. + */ + function setSourceLocURI($sourceURI) { + $this->_sourceLocURI = $sourceURI; + } + + /** + * Setter for property targetURI. + * + * @param string $targetURI New value of property targetLocURI. + */ + function setTargetLocURI($targetURI) { + $this->_targetLocURI = $targetURI; + } + + /** + * Setter for property syncType. + * + * @param integer $syncType New value of property syncType. + */ + function setSyncType($syncType) { + $this->_syncType = $syncType; + } + + /** + * Setter for property locName. + * + * @param string $locName New value of property locName. + */ + function setLocName($locName) { + $this->_locName = $locName; + } + + /** + * Setter for property filterExpression. + * + * @param string $expression New value of property filterExpression. + */ + function setFilterExpression($expression) { + $this->_filterExpression = $expression; + } + + /** + * Here's where the actual processing of a client-sent Sync + * Command takes place. Entries are added, deleted or replaced + * from the server database by using Horde API (Registry) calls. + */ function runSyncCommand(&$command) { - #Horde::logMessage('SyncML: content type is ' . $command->getContentType() .' moreData '. $command->_moreData, __FILE__, __LINE__, PEAR_LOG_DEBUG); global $registry; - $history = $GLOBALS['egw']->contenthistory; - $state = &$_SESSION['SyncML.state']; - - if(isset($state->_moreData['luid'])) { - if(($command->_luid == $state->_moreData['luid'])) { - Horde::logMessage('SyncML: got next moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - $lastChunks = implode('',$state->_moreData['chunks']); - $command->_content = $lastChunks.$command->_content; - $stringlen1 = strlen($lastChunks); - $stringlen2 = strlen($command->_content); - - if(!$command->_moreData && strlen($command->_content) != $state->_moreData['contentSize']) { - $command->_status = RESPONSE_SIZE_MISMATCH; - $state->_moreData = array(); - - return; - } elseif(!$command->_moreData && strlen($command->_content) == $state->_moreData['contentSize']) { - $state->_moreData = array(); - Horde::logMessage('SyncML: chunk ended successful type is ' . $command->getContentType() .' content is '. $command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - } - } else { - // alert 223 needed too - #$command->_status = ALERT_NO_END_OF_DATA; - - $state->_moreData = array(); - - return; + + if ($command->hasMoreData()) { + Horde::logMessage('SyncML: moreData: TRUE', __FILE__, __LINE__, PEAR_LOG_DEBUG); + $command->setStatus(RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED); + return true; + } + + $type = $this->_targetLocURI; + + $syncml_prefs = $GLOBALS['egw_info']['user']['preferences']['syncml']; + if (isset($syncml_prefs[$type])) { + $sync_conflicts = $syncml_prefs[$type]; + } else { + $sync_conflicts = CONFLICT_SERVER_WINNING; + } + + $state->setLocName($this->_locName); + $locName = $state->getLocName(); + $sourceURI = $state->getSourceURI(); + $hordeType = $state->getHordeType($type); + $refts = $state->getServerAnchorLast($type); + $state->setTargetURI($type); + $changes = array(); + // First we get all changes done after the previous sync start + foreach ($registry->call($hordeType. '/listBy', + array('action' => 'modify', + 'timestamp' => $refts, + 'type' => $type, + 'filter' => $this->_filterExpression)) as $change) { + // now we have to remove the ones + // that came from the last sync with this client + $guid_ts = $state->getSyncTSforAction($change, 'modify'); + $sync_ts = $state->getChangeTS($type, $change); + if ($sync_ts && $sync_ts == $guid_ts) { + // Change was done by us upon request of client. + Horde::logMessage("SyncML: change: $change ignored, " . + "came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; } + $changes[] = $change; } - - // don't add/replace the data currently, they are not yet complete - if($command->_moreData == TRUE) { - $state->_moreData['chunks'][] = $command->_content; - $state->_moreData['luid'] = $command->_luid; - - // gets only set with the first chunk of data - if(isset($command->_contentSize)) - $state->_moreData['contentSize'] = $command->_contentSize; - - $command->_status = RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED; - Horde::logMessage('SyncML: added moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - - return; - } - - $hordeType = $type = $this->_targetLocURI; - $hordeType = $state->getHordeType($hordeType); + + Horde::logMessage('SyncML: runSyncCommand found ' . count($changes) . + " possible conflicts for $type", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if(!$contentType = $command->getContentType()) { $contentType = $state->getPreferedContentType($type); } - + if (($contentType == 'text/x-vcalendar' || $contentType == 'text/calendar') - && strpos($command->getContent(), 'BEGIN:VTODO') !== false) - { + && strpos($command->getContent(), 'BEGIN:VTODO') !== false) { $hordeType = 'tasks'; } $syncElementItems = $command->getSyncElementItems(); - + foreach($syncElementItems as $syncItem) { - + $guid = false; - + + $contentSize = strlen($syncItem->_content); + if ((($size = $syncItem->getContentSize()) !== false) && + ($contentSize != $size) && + ($contentSize + 1 != $size)) { + Horde::logMessage('SyncML: content size missmatch for LocURI ' + . $syncItem->getLocURI() . ": $contentSize ($size)", + __FILE__, __LINE__, PEAR_LOG_ERROR); + $command->setStatus(RESPONSE_SIZE_MISMATCH); + return false; + } + if (is_a($command, 'Horde_SyncML_Command_Sync_Add')) { + if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) { + // We enforce the client not to change anything + if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) { + // delete this item from client + Horde::logMessage('SyncML: Server RO! REMOVE ' + . $syncItem->getLocURI() . ' from client', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->addConflictItem($type, '!D' . $syncItem->getLocURI()); + } else { + Horde::logMessage('SyncML: Server RO! ' + . 'REJECT all client changes', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->log('Client-AddReplaceIgnored'); + } + continue; + } + $guid = $registry->call($hordeType . '/import', array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + if (!is_a($guid, 'PEAR_Error') && $guid != false) { $ts = $state->getSyncTSforAction($guid, 'add'); $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); - $state->log("Client-Add"); - Horde::logMessage('SyncML: added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->log('Client-Add'); + Horde::logMessage('SyncML: added client entry as ' + . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); } else { - $state->log("Client-AddFailure"); - Horde::logMessage('SyncML: Error in adding client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); + $state->log('Client-AddFailure'); + Horde::logMessage('SyncML: Error in adding client entry: ' + . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); } } elseif (is_a($command, 'Horde_SyncML_Command_Sync_Delete')) { - // We can't remove the mapping entry as we need to keep - // the timestamp information. $guid = $state->removeUID($type, $syncItem->getLocURI()); - #$guid = $state->getGlobalUID($type, $syncItem->getLocURI()); - Horde::logMessage('SyncML: about to delete entry ' . $type .' / '. $guid . ' due to client request '.$syncItem->getLocURI(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - + if (!$guid) { + // the entry is no longer on the server + $state->log('Client-DeleteFailure'); + Horde::logMessage('SyncML: Failure deleting client entry, ' + . 'gone already on server!', + __FILE__, __LINE__, PEAR_LOG_ERR); + continue; + } + if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) { + // We enforce the client not to change anything + if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) { + Horde::logMessage('SyncML: Server RO! ADD ' + . $guid . ' to client again', + __FILE__, __LINE__, PEAR_LOG_WARNING); + // $state->addConflictItem($type, $guid); + } else { + Horde::logMessage('SyncML: '. + 'Server RO! REJECT all client changes', + __FILE__, __LINE__, PEAR_LOG_WARNING); + } + $state->log('Client-DeleteIgnored'); + continue; + } + elseif ($sync_conflicts == CONFLICT_RESOLVED_WITH_DUPLICATE && + in_array($guid, $changes)) + { + Horde::logMessage('SyncML: '. + 'Server has updated version to keep', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->log('Client-DeleteIgnored'); + continue; + } + elseif ($sync_conflicts == CONFLICT_MERGE_DATA) + { + Horde::logMessage('SyncML: Server Merge Only: ADD ' + . $guid . ' to client again', + __FILE__, __LINE__, PEAR_LOG_WARNING); + // $state->addConflictItem($type, $guid); + $state->log('Client-DeleteIgnored'); + continue; + } + + Horde::logMessage('SyncML: about to delete entry ' + . $type .' / '. $guid . ' due to client request ' + . $syncItem->getLocURI(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (!is_a($guid, 'PEAR_Error') && $guid != false) { $registry->call($hordeType . '/delete', array($guid)); - #$ts = $state->getSyncTSforAction($guid, 'delete'); - #$state->setUID($type, $syncItem->getLocURI(), $guid, $ts); - $state->log("Client-Delete"); - Horde::logMessage('SyncML: deleted entry ' . $guid . ' due to client request', __FILE__, __LINE__, PEAR_LOG_DEBUG); + $ts = $state->getSyncTSforAction($guid, 'delete'); + $state->setUID($type, $syncItem->getLocURI(), $guid, $ts, 1); + $state->log('Client-Delete'); + Horde::logMessage('SyncML: deleted entry ' + . $guid . ' due to client request', + __FILE__, __LINE__, PEAR_LOG_DEBUG); } else { - $state->log("Client-DeleteFailure"); - Horde::logMessage('SyncML: Failure deleting client entry, maybe gone already on server. msg:'. $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); + $state->log('Client-DeleteFailure'); + Horde::logMessage('SyncML: Failure deleting client entry, ' + . 'maybe gone already on server. msg: ' + . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); } } elseif (is_a($command, 'Horde_SyncML_Command_Sync_Replace')) { $guid = $state->getGlobalUID($type, $syncItem->getLocURI()); + $replace = true; $ok = false; - if ($guid) { - Horde::logMessage('SyncML: locuri'. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_ERR); - // Entry exists: replace current one. - $ok = $registry->call($hordeType . '/replace', - array($guid, $state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); - if (!is_a($ok, 'PEAR_Error')) { - $ts = $state->getSyncTSforAction($guid, 'modify'); - $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); - Horde::logMessage('SyncML: replaced entry due to client request guid: ' .$guid. ' ts: ' .$ts, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->log("Client-Replace"); - $ok = true; - } else { - // Entry may have been deleted; try adding it. - $ok = false; + $merge = false; + if ($guid) + { + Horde::logMessage('SyncML: locuri'. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) || in_array($guid, $changes)) + { + Horde::logMessage('SyncML: CONFLICT for locuri'. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_WARNING); + switch ($sync_conflicts) + { + case CONFLICT_CLIENT_WINNING: + $command->setStatus(RESPONSE_CONFLICT_RESOLVED_WITH_CLIENT_WINNING); + break; + case CONFLICT_SERVER_WINNING: + Horde::logMessage('SyncML: REJECT client change for locuri ' . + $syncItem->getLocURI() . ' guid ' . $guid , + __FILE__, __LINE__, PEAR_LOG_WARNING); + $ok = true; + $replace = false; + $state->log('Client-AddReplaceIgnored'); + break; + case CONFLICT_MERGE_DATA: + Horde::logMessage('SyncML: Merge server and client data for locuri ' . + $syncItem->getLocURI() . ' guid ' . $guid , + __FILE__, __LINE__, PEAR_LOG_WARNING); + $merge = true; + break; + case CONFLICT_RESOLVED_WITH_DUPLICATE: + $replace = false; + break; + case CONFLICT_CLIENT_CHANGES_IGNORED: + Horde::logMessage('SyncML: Server RO! REJECT client change for locuri ' . + $syncItem->getLocURI() . ' guid ' . $guid , + __FILE__, __LINE__, PEAR_LOG_WARNING); + $ok = true; + $replace = false; + $ts = $state->getSyncTSforAction($guid, 'modify'); + $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); + $state->log('Client-AddReplaceIgnored'); + break; + default: // We enforce our data on client + Horde::logMessage('SyncML: Server RO! UNDO client change for locuri ' . + $syncItem->getLocURI() . ' guid ' . $guid , + __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->addConflictItem($type, $guid); + $ok = true; + $replace = false; + } + } + elseif ($sync_conflicts == CONFLICT_MERGE_DATA) + { + Horde::logMessage('SyncML: Merge server and client data for locuri ' . + $syncItem->getLocURI() . ' guid ' . $guid , + __FILE__, __LINE__, PEAR_LOG_WARNING); + $merge = true; + } + + if ($replace) + { + // Entry exists: replace/merge with current one. + $ok = $registry->call($hordeType . '/replace', + array($guid, $state->convertClient2Server($syncItem->getContent(), + $contentType), $contentType, $merge)); + if (!is_a($ok, 'PEAR_Error') && $ok != false) + { + $ts = $state->getSyncTSforAction($guid, 'modify'); + $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); + if ($merge) + { + Horde::logMessage('SyncML: Merged entry due to client request guid: ' . + $guid . ' ts: ' . $ts, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + else + { + Horde::logMessage('SyncML: replaced entry due to client request guid: ' . + $guid . ' ts: ' . $ts, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + $state->log('Client-Replace'); + $ok = true; + } + else + { + // Entry may have been deleted; try adding it. + $ok = false; + } } } - + if (!$ok) { - // Entry does not exist in map or database: add a new one. - Horde::logMessage('SyncML: try to add contentype ' . $contentType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + // Entry does either not exist in map or database, or should be added due to a conflict + + if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) { + // We enforce the client not to change anything + if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) { + // delete this item from client + Horde::logMessage('SyncML: Server RO! REMOVE ' . $syncItem->getLocURI() . ' from client', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->addConflictItem($type, '!D' . $syncItem->getLocURI()); + } else { + Horde::logMessage('SyncML: Server RO! REJECT all client changes', + __FILE__, __LINE__, PEAR_LOG_WARNING); + } + continue; + } + Horde::logMessage('SyncML: try to add contentype ' + . $contentType . ' for locuri ' . $syncItem->getLocURI(), + __FILE__, __LINE__, PEAR_LOG_DEBUG); + //continue; + $oguid = $guid; $guid = $registry->call($hordeType . '/import', - array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType, $guid)); if (!is_a($guid, 'PEAR_Error')) { - $ts = $state->getSyncTSforAction($guid, 'add'); + if ($oguid != $guid) { + // We add a new entry + $ts = $state->getSyncTSforAction($guid, 'add'); + Horde::logMessage('SyncML: added entry ' + . $guid . ' from client', + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->log('Client-Add'); + } else { + // We replaced an entry + $ts = $state->getSyncTSforAction($guid, 'modify'); + Horde::logMessage('SyncML: replaced entry ' + . $guid . ' from client', + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->log('Client-Replace'); + } $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); - $state->log("Client-AddReplace"); - Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); } else { - Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); - $state->log("Client-AddFailure"); + Horde::logMessage('SyncML: Error in replacing/' + . 'add client entry:' . $guid->message, + __FILE__, __LINE__, PEAR_LOG_ERR); + $state->log('Client-AddFailure'); } } } } - return $guid; } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php index 087105940d..e9ef2c0c23 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php @@ -1,6 +1,6 @@ call($this->targetLocURI, '/list', array()); - foreach ($delete as $deletes) { - $registry->call($this->targetLocURI . '/delete', array($delete)); - } - - return $currentCmdID; - } - } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php index 4a5c957f9b..3e37a43345 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php @@ -1,87 +1,167 @@ + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Sync.php'; -/** - * $Horde: framework/SyncML/SyncML/Sync/RefreshFromServerSync.php,v 1.9 2004/07/03 15:21:15 chuck Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Sync_RefreshFromServerSync extends Horde_SyncML_Sync_TwoWaySync { function handleSync($currentCmdID, $hordeType, $syncType, &$output, $refts) { global $registry; - + $state = &$_SESSION['SyncML.state']; - - $adds = &$state->getAddedItems($hordeType); - - Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); + $maxMsgSize = $state->getMaxMsgSizeClient(); + $deviceInfo = $state->getClientDeviceInfo(); + + if (isset($deviceInfo['maxEntries'])) { + $maxEntries = $deviceInfo['maxEntries']; + if (!$maxMsgSize && !$maxEntries) { + // fallback to default + $maxEntries = MAX_ENTRIES; + } + } else { + $maxEntries = MAX_ENTRIES; + } $serverAnchorNext = $state->getServerAnchorNext($syncType); - $counter = 0; - + + if (isset($state->curSyncItem)) { + // Finish the pending sync item + $cmd = &$state->curSyncItem; + unset($state->curSyncItem); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Sync'); + + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = &$cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); + } + + $adds = &$state->getAddedItems($syncType); + Horde::logMessage("SyncML: ".count($adds). + ' added items found for '.$syncType , + __FILE__, __LINE__, PEAR_LOG_DEBUG); + if(is_array($adds)) { while($guid = array_shift($adds)) { + $currentSize = $output->getOutputSize(); + // return if we have to much data + if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries) + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) + || ($maxMsgSize + && (($currentSize + MIN_MSG_LEFT * 2) > $maxMsgSize))) { + // put the item back in the queue + $adds[] = $guid; + $state->maxNumberOfElements(); + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + if ($locID = $state->getLocID($syncType, $guid)) { Horde::logMessage("SyncML: RefreshFromServerSync add to client: $guid ignored, already at client($locID)", __FILE__, __LINE__, PEAR_LOG_DEBUG); continue; } - + + $guid_ts = $state->getSyncTSforAction($guid, 'add'); + if ($guid_ts > $serverAnchorNext) { + // Change was made after we started this sync. + // Don't sent this now to the client. + Horde::logMessage("SyncML: RefreshFromServerSync add $guid is in our future", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); - $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); $c = $registry->call($hordeType . '/export', array('guid' => $guid, 'contentType' => $contentType)); - Horde::logMessage("SyncML: slowsync add $guid to client ". print_r($c, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); - if (!is_a($c, 'PEAR_Error')) { - $cmd->setContent($c); - $cmd->setContentType($contentType['ContentType']); - if (isset($contentType['ContentFormat'])) - { - $cmd->setContentFormat($contentType['ContentFormat']); - } - - $cmd->setSourceURI($guid); - $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); - $state->log('Server-Add'); - - // return if we have to much data - if(++$counter >= MAX_ENTRIES - && isset($contentType['mayFragment']) - && $contentType['mayFragment']) - { - $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); - return $currentCmdID; - } + if (is_a($c, 'PEAR_Error')) { + Horde::logMessage("SyncML: refresh failed to export guid $guid:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->log("Server-ExportFailed"); + continue; } + + $size = strlen($c); + // return if we have to much data + if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) { + if (($size + MIN_MSG_LEFT * 2) > $maxMsgSize) { + Horde::logMessage("SyncML: refresh failed to export guid $guid due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR); + $state->log("Server-ExportFailed"); + continue; + } + if (($currentSize + $size + MIN_MSG_LEFT * 2) > $maxMsgSize) { + // put the item back in the queue + $adds[] = $guid; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + + Horde::logMessage("SyncML: refresh add $guid to client\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); + + $cmd->setContent($c); + + $cmd->setContentType($contentType['ContentType']); + if (isset($contentType['ContentFormat'])) { + $cmd->setContentFormat($contentType['ContentFormat']); + } + + $cmd->setGUID($guid); + + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // moreData split; put the guid back in the list and return + if ($cmd->hasMoreData()) { + $state->curSyncItem = &$cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); } } - - #Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); - + + Horde::logMessage("SyncML: All items handled for sync $syncType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $state->removeExpiredUID($syncType, $serverAnchorNext); $state->clearSync($syncType); - + return $currentCmdID; } - + function loadData() { global $registry; - + $state = &$_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; $hordeType = $state->getHordeType($syncType); - + $state->setTargetURI($syncType); + $future = $state->getServerAnchorNext($syncType); + $delta_add = 0; + Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); - $adds = &$state->getAddedItems($hordeType); + $state->setAddedItems($syncType, $registry->call($hordeType. '/listBy', + array('action' => 'add', + 'timestamp' => $future, + 'type' => $syncType, + 'filter' => $this->_filterExpression))); + $delta_add = count($state->getAddedItems($hordeType)); + $state->setAddedItems($syncType, $registry->call($hordeType. '/list', array('filter' => $this->_filterExpression))); $this->_syncDataLoaded = TRUE; - - return count($state->getAddedItems($hordeType)); + + return count($state->getAddedItems($syncType)) - $delta_add; } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php index de538829c7..51903e2185 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php @@ -1,194 +1,282 @@ + * Using the PEAR Log class (which need to be installed!) * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Anthony Mills + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ */ +include_once 'Horde/SyncML/Sync/TwoWaySync.php'; + class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { + function handleSync($currentCmdID, $hordeType, $syncType, &$output, $refts) { global $registry; - + $history = $GLOBALS['egw']->contenthistory; $state = &$_SESSION['SyncML.state']; - + $maxMsgSize = $state->getMaxMsgSizeClient(); + $deviceInfo = $state->getClientDeviceInfo(); + + if (isset($deviceInfo['maxEntries'])) { + $maxEntries = $deviceInfo['maxEntries']; + if (!$maxMsgSize && !$maxEntries) { + // fallback to default + $maxEntries = MAX_ENTRIES; + } + } else { + $maxEntries = MAX_ENTRIES; + } + $serverAnchorNext = $state->getServerAnchorNext($syncType); - + // now we remove all UID from contentmap that have not been verified in this slowsync $state->removeOldUID($syncType, $serverAnchorNext); - $adds = &$state->getAddedItems($hordeType); - Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); - $counter = 0; - + + if (isset($state->curSyncItem)) { + // Finish the pending sync item + $cmd = &$state->curSyncItem; + unset($state->curSyncItem); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Sync'); + + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = &$cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); + } + + $adds = &$state->getAddedItems($syncType); + Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + if(is_array($adds)) { while($guid = array_shift($adds)) { + + $currentSize = $output->getOutputSize(); + + // return if we have to much data + if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries) + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) || + ($maxMsgSize && (($currentSize + MIN_MSG_LEFT * 2) > $maxMsgSize))) { + // put the item back in the queue + $adds[] = $guid; + $state->maxNumberOfElements(); + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + if ($locID = $state->getLocID($syncType, $guid)) { Horde::logMessage("SyncML: slowsync add to client: $guid ignored, already at client($locID)", __FILE__, __LINE__, PEAR_LOG_DEBUG); continue; } - + + $guid_ts = $state->getSyncTSforAction($guid, 'add'); + if ($guid_ts > $serverAnchorNext) { + // Change was made after we started this sync. + // Don't sent this now to the client. + Horde::logMessage("SyncML: slowsync add $guid is in our future", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); - $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); $c = $registry->call($hordeType . '/export', array('guid' => $guid, 'contentType' => $contentType)); - #Horde::logMessage("SyncML: slowsync add guid $guid to client ". print_r($c, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); - if (!is_a($c, 'PEAR_Error')) { - $cmd->setContent($c); - $cmd->setContentType($contentType['ContentType']); - if (isset($contentType['ContentFormat'])) - { - $cmd->setContentFormat($contentType['ContentFormat']); + if (is_a($c, 'PEAR_Error')) { + Horde::logMessage("SyncML: slowsync failed to export guid $guid:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING); + continue; + } + + $size = strlen($c); + // return if we have to much data + if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) { + if (($size + MIN_MSG_LEFT * 2) > $maxMsgSize) { + Horde::logMessage("SyncML: slowsync failed to export guid $guid due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR); + continue; } - - $cmd->setSourceURI($guid); - $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); - $state->log('Server-Add'); - - // return if we have to much data - if(++$counter >= MAX_ENTRIES - && isset($contentType['mayFragment']) - && $contentType['mayFragment']) - { + if (($currentSize + $size + MIN_MSG_LEFT * 2) > $maxMsgSize) { + // put the item back in the queue + $adds[] = $guid; $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } } + + Horde::logMessage("SyncML: slowsync add guid $guid to client\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); + $cmd->setContent($c); + $cmd->setContentType($contentType['ContentType']); + if (isset($contentType['ContentFormat'])) { + $cmd->setContentFormat($contentType['ContentFormat']); + } + $cmd->setGUID($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = &$cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); } } - - #Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); - + + Horde::logMessage("SyncML: All items handled for sync $syncType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $state->removeExpiredUID($syncType, $serverAnchorNext); $state->clearSync($syncType); - + return $currentCmdID; } - + /** * Here's where the actual processing of a client-sent Sync * Command takes place. Entries are added or replaced * from the server database by using Horde API (Registry) calls. */ function runSyncCommand(&$command) { - #Horde::logMessage('SyncML: content type is ' . $command->getContentType() .' moreData '. $command->_moreData, __FILE__, __LINE__, PEAR_LOG_DEBUG); global $registry; - $history = $GLOBALS['egw']->contenthistory; - $state = &$_SESSION['SyncML.state']; - - if(isset($state->_moreData['luid'])) { - if(($command->_luid == $state->_moreData['luid'])) { - Horde::logMessage('SyncML: got next moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - $lastChunks = implode('',$state->_moreData['chunks']); - $command->_content = $lastChunks.$command->_content; - $stringlen1 = strlen($lastChunks); - $stringlen2 = strlen($command->_content); - - if(!$command->_moreData && strlen($command->_content) != $state->_moreData['contentSize']) { - $command->_status = RESPONSE_SIZE_MISMATCH; - $state->_moreData = array(); - - return; - } elseif(!$command->_moreData && strlen($command->_content) == $state->_moreData['contentSize']) { - $state->_moreData = array(); - Horde::logMessage('SyncML: chunk ended successful type is ' . $command->getContentType() .' content is '. $command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - } - } else { - // alert 223 needed too - #$command->_status = ALERT_NO_END_OF_DATA; - - $state->_moreData = array(); - + + if ($command->hasMoreData()) { + Horde::logMessage('SyncML: moreData: TRUE', __FILE__, __LINE__, PEAR_LOG_DEBUG); + $command->setStatus(RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED); + return true; + } + + $type = $this->_targetLocURI; + + $syncml_prefs = $GLOBALS['egw_info']['user']['preferences']['syncml']; + if (isset($syncml_prefs[$type])) { + $sync_conflicts = $syncml_prefs[$type]; + } else { + $sync_conflicts = CONFLICT_SERVER_WINNING; + } + + $hordeType = $state->getHordeType($type); + + $syncElementItems = $command->getSyncElementItems(); + + foreach($syncElementItems as $syncItem) { + + $contentSize = strlen($syncItem->_content); + if ((($size = $syncItem->getContentSize()) !== false) && + ($contentSize != $size) && + ($contentSize + 1 != $size)) { + Horde::logMessage('SyncML: content size missmatch for LocURI ' . $syncItem->_luid . + ": $contentSize ($size)", __FILE__, __LINE__, PEAR_LOG_ERROR); + $command->setStatus(RESPONSE_SIZE_MISMATCH); return; } - } - - // don't add/replace the data currently, they are not yet complete - if($command->_moreData == TRUE) { - $state->_moreData['chunks'][] = $command->_content; - $state->_moreData['luid'] = $command->_luid; - - // gets only set with the first chunk of data - if(isset($command->_contentSize)) - $state->_moreData['contentSize'] = $command->_contentSize; - - $command->_status = RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED; - Horde::logMessage('SyncML: added moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); - - return; - } - - $type = $this->_targetLocURI; - $hordeType = $state->getHordeType($type); - - $syncElementItems = $command->getSyncElementItems(); - - foreach($syncElementItems as $syncItem) { + if(!$contentType = $syncItem->getContentType()) { $contentType = $state->getPreferedContentType($type); } - + if (($contentType == 'text/x-vcalendar' || $contentType == 'text/calendar') - && strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false) - { + && strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false) { $hordeType = 'tasks'; } - + $guid = false; - + $guid = $registry->call($hordeType . '/search', array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType, $state->getGlobalUID($type, $syncItem->getLocURI()) )); - + if ($guid) { - # entry exists in database already. Just update the mapping - Horde::logMessage('SyncML: adding mapping for locuri:'. $syncItem->getLocURI() . ' and guid:' . $guid , __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setUID($type, $syncItem->getLocURI(), $guid, mktime()); - $state->log("Client-Replace"); - } else { - # Entry does not exist in database: add a new one. - $state->removeUID($type, $syncItem->getLocURI()); - Horde::logMessage('SyncML: try to add contentype ' . $contentType .' to '. $hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $guid = $registry->call($hordeType . '/import', - array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); - if (!is_a($guid, 'PEAR_Error') && $guid != false) { - $ts = $state->getSyncTSforAction($guid, 'add'); - $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); - $state->log("Client-AddReplace"); - Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + // Check if the found entry came from the client + $guid_ts = $state->getSyncTSforAction($guid, 'add'); + $sync_ts = $state->getChangeTS($type, $guid); + if ($sync_ts && $sync_ts == $guid_ts) { + // Entry came from the client, so we get a duplicate here + Horde::logMessage('SyncML: CONFLICT for locuri ' . $syncItem->getLocURI() + . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_WARNING); + if ($sync_conflicts != CONFLICT_RESOLVED_WITH_DUPLICATE) { + $state->log("Client-AddReplaceIgnored"); + continue; + } } else { - Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); - $state->log("Client-AddFailure"); + # Entry exists in database already. Just update the mapping + Horde::logMessage('SyncML: adding mapping for locuri:' + . $syncItem->getLocURI() . ' and guid:' . $guid, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setUID($type, $syncItem->getLocURI(), $guid, mktime()); + $state->log("Client-Map"); + continue; } } + if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) { + // We enforce the client not to change anything + if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) { + // delete this item from client + Horde::logMessage('SyncML: Server RO! REMOVE ' . $syncItem->getLocURI() + . ' from client', __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->addConflictItem($type, '!D' . $syncItem->getLocURI()); + } else { + Horde::logMessage('SyncML: Server RO! REJECT all client changes', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $state->log("Client-AddReplaceIgnored"); + } + $command->setStatus(RESPONSE_NO_EXECUTED); + continue; + } + + // Add entry to the database. + $state->removeUID($type, $syncItem->getLocURI()); + Horde::logMessage('SyncML: try to add contentype ' . $contentType .' to '. $hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $guid = $registry->call($hordeType . '/import', + array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + if (!is_a($guid, 'PEAR_Error') && $guid != false) { + $ts = $state->getSyncTSforAction($guid, 'modify'); + if (!$ts) { + $ts = $state->getSyncTSforAction($guid, 'add'); + } + $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); + $state->log("Client-AddReplace"); + Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } else { + Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); + $state->log("Client-AddFailure"); + } } return true; } - + function loadData() { global $registry; - + $state = &$_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; $hordeType = $state->getHordeType($syncType); - + $state->setTargetURI($syncType); + $future = $state->getServerAnchorNext($syncType); + $delta_add = 0; + Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); - $adds = &$state->getAddedItems($hordeType); + $delta_add = count($registry->call($hordeType. '/listBy', + array('action' => 'add', + 'timestamp' => $future, + 'type' => $syncType, + 'filter' => $this->_filterExpression))); + $state->setAddedItems($syncType, $registry->call($hordeType. '/list', array('filter' => $this->_filterExpression))); $this->_syncDataLoaded = TRUE; - - return count($state->getAddedItems($hordeType)); + + return count($state->getAddedItems($syncType)) - $delta_add; } } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php index 2ff089d12f..69ccdf9be0 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php @@ -1,175 +1,329 @@ + * @author Karsten Fourmont + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ include_once 'Horde/SyncML/Sync.php'; include_once 'Horde/SyncML/Command/Sync/ContentSyncElement.php'; -/** - * $Horde: framework/SyncML/SyncML/Sync/TwoWaySync.php,v 1.12 2004/07/26 09:24:38 jan Exp $ - * - * Copyright 2003-2004 Anthony Mills - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Anthony Mills - * @author Karsten Fourmont - * - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_SyncML - */ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { - function endSync($currentCmdID, &$output) { + function endSync($currentCmdID, & $output) { global $registry; - $state = &$_SESSION['SyncML.state']; + $state = & $_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; $hordeType = $state->getHordeType($syncType); $refts = $state->getServerAnchorLast($syncType); - $currentCmdID = $this->handleSync($currentCmdID, - $hordeType, - $syncType, - $output, - $refts); + $currentCmdID = $this->handleSync($currentCmdID, $hordeType, $syncType, $output, $refts); return $currentCmdID; } - function handleSync($currentCmdID, $hordeType, $syncType,&$output, $refts) { + function handleSync($currentCmdID, $hordeType, $syncType, & $output, $refts) { global $registry; // array of Items which got modified, but got never send to the client before - $missedAdds = array(); + $missedAdds = array (); + // array of Items which the client wanted to add, but must be deleted due to + // user's sync policy + $remoteDeletes = array (); $history = $GLOBALS['egw']->contenthistory; - $state = &$_SESSION['SyncML.state']; - $counter = 0; + $state = & $_SESSION['SyncML.state']; + $maxMsgSize = $state->getMaxMsgSizeClient(); + $deviceInfo = $state->getClientDeviceInfo(); - $changes = &$state->getChangedItems($hordeType); - $deletes = &$state->getDeletedItems($hordeType); - $adds = &$state->getAddedItems($hordeType); + if (isset($deviceInfo['maxEntries'])) { + $maxEntries = $deviceInfo['maxEntries']; + if (!$maxMsgSize && !$maxEntries) { + // fallback to default + $maxEntries = MAX_ENTRIES; + } + } else { + $maxEntries = MAX_ENTRIES; + } - Horde::logMessage("SyncML: ".count($changes).' changed items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); - Horde::logMessage("SyncML: ".count($deletes).' deleted items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); - Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); + $serverAnchorNext = $state->getServerAnchorNext($syncType); + + + if (isset ($state->curSyncItem)) { + // Finish the pending sync item + $cmd = & $state->curSyncItem; + unset ($state->curSyncItem); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Sync'); + + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = & $cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); + } + + $changes = & $state->getChangedItems($syncType); + $deletes = & $state->getDeletedItems($syncType); + $adds = & $state->getAddedItems($syncType); + $conflicts = & $state->getConflictItems($syncType); + + // manage the conflict items + foreach ( $conflicts as $refresh) { + if (preg_match('/^!D(.*)/', $refresh, $matches)) { + // delete locuri + $remoteDeletes[] = $matches[1]; + } else { + $adds[] = $refresh; + } + } + + Horde :: logMessage('SyncML: ' . count($changes) . ' changed items found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage('SyncML: ' . count($deletes) . ' deleted items found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage('SyncML: ' . count($remoteDeletes) . ' items to delete on client found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage('SyncML: ' . count($adds) . ' added items (' . count($refresh) . ' refreshs) found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG); // handle changes - if(is_array($changes)) { - while($guid = array_shift($changes)) { + if (is_array($changes)) { + while ($guid = array_shift($changes)) { + $currentSize = $output->getOutputSize(); + // return if we have to much data + if (($maxEntries + && ($state->getNumberOfElements() >= $maxEntries) + && isset ($contentType['mayFragment']) + && $contentType['mayFragment']) + || ($maxMsgSize + && (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) { + // put the item back in the queue + $changes[] = $guid; + $state->maxNumberOfElements(); + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $guid_ts = $state->getSyncTSforAction($guid, 'modify'); $sync_ts = $state->getChangeTS($syncType, $guid); - Horde::logMessage("SyncML: timestamp modify guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: timestamp modify $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); if ($sync_ts && $sync_ts == $guid_ts) { // Change was done by us upon request of client. // Don't mirror that back to the client. - Horde::logMessage("SyncML: change: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: change: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + if ($guid_ts > $serverAnchorNext) { + // Change was made after we started this sync. + // Don't sent this now to the client. + Horde :: logMessage("SyncML: change $guid is in our future: $serverAnchorNext", __FILE__, __LINE__, PEAR_LOG_DEBUG); continue; } - Horde::logMessage("SyncML: change $guid hs_ts:$guid_ts dt_ts:" . $state->getChangeTS($syncType, $guid), __FILE__, __LINE__, PEAR_LOG_DEBUG); $locid = $state->getLocID($syncType, $guid); if (!$locid) { // somehow we missed to add, lets store the uid, so we add this entry later $missedAdds[] = $guid; - Horde::logMessage("SyncML: unable to create change for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING); + Horde :: logMessage("SyncML: unable to create change for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING); continue; } // Create a replace request for client. $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); - $c = $registry->call($hordeType. '/export', - array('guid' => $guid, 'contentType' => $contentType)); - if (!is_a($c, 'PEAR_Error')) { + $c = $registry->call($hordeType . '/export', array ( + 'guid' => $guid, + 'contentType' => $contentType + )); + if (is_a($c, 'PEAR_Error')) { // Item in history but not in database. Strange, but can happen. - Horde::logMessage("SyncML: change: $guid export content: $c", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); -# LK $cmd->setContent($state->convertServer2Client($c, $contentType)); - $cmd->setContent($c); - $cmd->setSourceURI($guid); - $cmd->setTargetURI($locid); - $cmd->setContentType($contentType['ContentType']); - if (isset($contentType['ContentFormat'])) - { - $cmd->setContentFormat($contentType['ContentFormat']); + Horde :: logMessage("SyncML: change: export of guid $guid failed:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING); + continue; + } + + $size = strlen($c); + // return if we have to much data + if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) { + if (($size +MIN_MSG_LEFT * 2) > $maxMsgSize) { + Horde :: logMessage("SyncML: change: export of guid $guid failed due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR); + $state->log('Server-ExportFailed'); + continue; } - - $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace'); - $state->log('Server-Replace'); - - // return if we have to much data - if (++$counter >= MAX_ENTRIES - && isset($contentType['mayFragment']) - && $contentType['mayFragment']) - { + if (($currentSize + $size +MIN_MSG_LEFT * 2) > $maxMsgSize) { + // put the item back in the queue + $changes[] = $guid; $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } } + + Horde :: logMessage("SyncML: change: export guid $guid, content:\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); + # LK $cmd->setContent($state->convertServer2Client($c, $contentType)); + $cmd->setContent($c); + $cmd->setLocURI($locid); + $cmd->setContentType($contentType['ContentType']); + if (isset ($contentType['ContentFormat'])) { + $cmd->setContentFormat($contentType['ContentFormat']); + } + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace'); + $state->log('Server-Replace'); + + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = & $cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); } } - Horde::logMessage("SyncML: handling sync (changes done) ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: handling sync (changes done) " . $currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); // handle deletes - if(is_array($deletes)) { - while($guid = array_shift($deletes)) { + if (is_array($deletes)) { + while ($guid = array_shift($deletes)) { + $currentSize = $output->getOutputSize(); + // return if we have to much data + if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries) + && isset ($contentType['mayFragment']) + && $contentType['mayFragment']) + || ($maxMsgSize + && (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) { + // put the item back in the queue + $deletes[] = $guid; + $state->maxNumberOfElements(); + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $guid_ts = $state->getSyncTSforAction($guid, 'delete'); $sync_ts = $state->getChangeTS($syncType, $guid); - Horde::logMessage("SyncML: timestamp delete guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: timestamp delete guid_ts: $guid_ts sync_ts: $sync_ts", + __FILE__, __LINE__, PEAR_LOG_DEBUG); if ($sync_ts && $sync_ts == $guid_ts) { // Change was done by us upon request of client. // Don't mirror that back to the client. - Horde::logMessage("SyncML: delete $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: delete $guid ignored, came from client", + __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($sync_ts < $serverAnchorNext + && ($locid = $state->getLocID($syncType, $guid))) { + // Now we can remove the past + $state->removeUID($syncType, $locid); + } + continue; + } + if ($guid_ts > $serverAnchorNext) { + // Change was made after we started this sync. + // Don't sent this now to the client. + Horde :: logMessage("SyncML: delete $guid is in our future: $serverAnchorNext", __FILE__, __LINE__, PEAR_LOG_DEBUG); continue; } - $locid = $state->getLocID($syncType, $guid); if (!$locid) { - Horde::logMessage("SyncML: unable to create delete for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING); + Horde :: logMessage("SyncML: unable to delete $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_INFO); + $state->log("Server-DeleteFailure"); continue; } - Horde::logMessage("SyncML: delete: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: delete: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); // Create a Delete request for client. $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); - $cmd->setTargetURI($locid); - $cmd->setSourceURI($guid); + $cmd->setLocURI($locid); $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete'); $state->log('Server-Delete'); $state->removeUID($syncType, $locid); - $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); - // return if we have to much data - if(++$counter >= MAX_ENTRIES - && isset($contentType['mayFragment']) - && $contentType['mayFragment']) - { + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = & $cmd; $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } + $state->incNumberOfElements(); + } + } + + // handle remote deletes due to conflicts + if (count($remoteDeletes) > 0) { + while ($locid = array_shift($remoteDeletes)) { + $currentSize = $output->getOutputSize(); + // return if we have to much data + if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries) + && isset ($contentType['mayFragment']) + && $contentType['mayFragment']) + || ($maxMsgSize + && (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) { + // put the item back in the queue + $remoteDeletes[] = $locid; + $state->maxNumberOfElements(); + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + Horde :: logMessage("SyncML: delete client locid: $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + // Create a Delete request for client. + $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); + $cmd->setLocURI($locid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete'); + $state->log('Server-DeletedConflicts'); + $state->removeUID($syncType, $locid); + + // moreData split; save in session state and end current message + if ($cmd->hasMoreData()) { + $state->curSyncItem = & $cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); } } #Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); // handle missing adds. - if(count($missedAdds) > 0) { - Horde::logMessage("SyncML: add missed changes as adds ".count($adds).' / '.$missedAdds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setAddedItems($hordeType, array_merge($adds, $missedAdds)); - $adds = &$state->getAddedItems($hordeType); - Horde::logMessage("SyncML: merged adds counter ".count($adds).' / '.$adds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (count($missedAdds) > 0) { + Horde :: logMessage("SyncML: add missed changes as adds " . count($adds) . ' / ' . $missedAdds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); + $adds = array_merge($adds, $missedAdds); + Horde :: logMessage("SyncML: merged adds counter " . count($adds) . ' / ' . $adds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); } - if(is_array($adds)) { - while($guid = array_shift($adds)) { + if (is_array($adds)) { + while ($guid = array_shift($adds)) { + $currentSize = $output->getOutputSize(); + // return if we have to much data + if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries) + && isset ($contentType['mayFragment']) + && $contentType['mayFragment']) + || ($maxMsgSize + && (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) { + // put the item back in the queue + $adds[] = $guid; + $state->maxNumberOfElements(); + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $guid_ts = $state->getSyncTSforAction($guid, 'add'); $sync_ts = $state->getChangeTS($syncType, $guid); - Horde::logMessage("SyncML: timestamp add $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: timestamp add $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); if ($sync_ts && $sync_ts == $guid_ts) { // Change was done by us upon request of client. // Don't mirror that back to the client. - Horde::logMessage("SyncML: add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + if ($guid_ts > $serverAnchorNext && !in_array($guid, $conflicts)) { + // Change was made after we started this sync. + // Don't sent this now to the client. + Horde :: logMessage("SyncML: add $guid is in our future: $serverAnchorNext", __FILE__, __LINE__, PEAR_LOG_DEBUG); continue; } @@ -179,47 +333,65 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { // For slow sync (ts=0): do not add data for which we // have a locid again. This is a heuristic to avoid // duplication of entries. - Horde::logMessage("SyncML: skipping add of guid $guid as there already is a locid $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: skipping add of guid $guid as there already is a locid $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG); continue; } - Horde::logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde :: logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); // Create an Add request for client. $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); - $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); - $c = $registry->call($hordeType . '/export', - array( - 'guid' => $guid , - 'contentType' => $contentType , - ) - ); + $c = $registry->call($hordeType . '/export', array ( + 'guid' => $guid, + 'contentType' => $contentType, - if (!is_a($c, 'PEAR_Error')) { + )); + + if (is_a($c, 'PEAR_Error')) { // Item in history but not in database. Strange, but can happen. - $cmd->setContent($c); - $cmd->setContentType($contentType['ContentType']); - if (isset($contentType['ContentFormat'])) - { - $cmd->setContentFormat($contentType['ContentFormat']); - } - $cmd->setSourceURI($guid); - $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); - $state->log('Server-Add'); + Horde :: logMessage("SyncML: add: export of guid $guid failed:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING); + continue; + } - // return if we have to much data - if(++$counter >= MAX_ENTRIES - && isset($contentType['mayFragment']) - && $contentType['mayFragment']) - { + $size = strlen($c); + // return if we have to much data + if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) { + if (($size +MIN_MSG_LEFT * 2) > $maxMsgSize) { + Horde :: logMessage("SyncML: add: export of guid $guid failed due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR); + $state->log("Server-ExportFailed"); + continue; + } + if (($currentSize + $size +MIN_MSG_LEFT * 2) > $maxMsgSize) { + // put the item back in the queue + $adds[] = $guid; $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } } + + Horde :: logMessage("SyncML: add guid $guid to client\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $cmd = new Horde_SyncML_Command_Sync_ContentSyncElement(); + $cmd->setContent($c); + $cmd->setContentType($contentType['ContentType']); + if (isset ($contentType['ContentFormat'])) { + $cmd->setContentFormat($contentType['ContentFormat']); + } + $cmd->setGUID($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // moreData split; put the guid back in the list and return + if ($cmd->hasMoreData()) { + $state->curSyncItem = & $cmd; + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + $state->incNumberOfElements(); } } - #Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage("SyncML: All items handled for sync $syncType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->removeExpiredUID($syncType, time()); $state->clearSync($syncType); return $currentCmdID; @@ -228,24 +400,53 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { function loadData() { global $registry; - $state = &$_SESSION['SyncML.state']; + $state = & $_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; $hordeType = $state->getHordeType($syncType); + $state->setTargetURI($syncType); $refts = $state->getServerAnchorLast($syncType); + $future = $state->getServerAnchorNext($syncType); + $delta_mod = 0; + $delta_add = 0; - Horde::logMessage("SyncML: reading changed items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setChangedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts))); + Horde :: logMessage("SyncML: reading changed items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $delta_mod = count($registry->call($hordeType . '/listBy', array ( + 'action' => 'modify', + 'timestamp' => $future, + 'type' => $syncType, + 'filter' => $this->_filterExpression + ))); + $state->setChangedItems($syncType, $registry->call($hordeType . '/listBy', array ( + 'action' => 'modify', + 'timestamp' => $refts, + 'type' => $syncType, + 'filter' => $this->_filterExpression + ))); - Horde::logMessage("SyncML: reading deleted items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setDeletedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts))); + Horde :: logMessage("SyncML: reading deleted items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setDeletedItems($syncType, $registry->call($hordeType . '/listBy', array ( + 'action' => 'delete', + 'timestamp' => $refts, + 'type' => $syncType, + 'filter' => $this->_filterExpression + ))); - Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); - $state->setAddedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts))); + Horde :: logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $delta_add = count($registry->call($hordeType . '/listBy', array ( + 'action' => 'add', + 'timestamp' => $future, + 'type' => $syncType, + 'filter' => $this->_filterExpression + ))); + $state->setAddedItems($syncType, $registry->call($hordeType . '/listBy', array ( + 'action' => 'add', + 'timestamp' => $refts, + 'type' => $syncType, + 'filter' => $this->_filterExpression + ))); $this->_syncDataLoaded = TRUE; - return count($state->getChangedItems($hordeType)) + - count($state->getDeletedItems($hordeType)) + - count($state->getAddedItems($hordeType)); + return count($state->getChangedItems($syncType)) - $delta_mod +count($state->getDeletedItems($syncType)) + count($state->getAddedItems($syncType)) - $delta_add +count($state->getConflictItems($syncType)); } -} +} \ No newline at end of file diff --git a/phpgwapi/inc/horde/Horde/iCalendar.php b/phpgwapi/inc/horde/Horde/iCalendar.php index d11227aaed..d0aecd085e 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar.php +++ b/phpgwapi/inc/horde/Horde/iCalendar.php @@ -1,104 +1,149 @@ + * Using the PEAR Log class (which need to be installed!) * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @author Mike Cochrane - * @version $Revision$ - * @since Horde 3.0 - * @package Horde_iCalendar + * @link http://www.egroupware.org + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage horde + * @author Mike Cochrane + * @author Joerg Lehrke + * @copyright (c) The Horde Project (http://www.horde.org/) + * @version $Id$ + */ + +/** + * Class representing iCalendar files. */ class Horde_iCalendar { /** * The parent (containing) iCalendar object. * - * @var object Horde_iCalendar $_container + * @var Horde_iCalendar */ var $_container = false; + /** + * The name/value pairs of attributes for this object (UID, + * DTSTART, etc.). Which are present depends on the object and on + * what kind of component it is. + * + * @var array + */ var $_attributes = array(); + /** + * Any children (contained) iCalendar components of this object. + * + * @var array + */ var $_components = array(); /** - * According to RFC 2425, we should always use CRLF-terminated - * lines. + * According to RFC 2425, we should always use CRLF-terminated lines. * - * @var string $_newline + * @var string */ var $_newline = "\r\n"; /** - * Return a reference to a new component. + * iCalendar format version (different behavior for 1.0 and 2.0 + * especially with recurring events). * - * @param string $type The type of component to return - * @param object $container A container that this component - * will be associated with. - * - * @return object Reference to a Horde_iCalendar_* object as specified. + * @var string */ - function &newComponent($type, &$container) + var $_version; + + function Horde_iCalendar($version = '2.0') { -# require_once 'Horde/String.php'; - $type = strtolower($type); - #error_log("called horde ical new comp". print_r($type,true) ); - $class = 'Horde_iCalendar_' . strtolower($type); - if (!class_exists($class,false)) { - include_once dirname(__FILE__) . '/iCalendar/' . $type . '.php'; - } - if (class_exists($class)) { - #include_once dirname(__FILE__) . '/iCalendar/' . $type . '.php'; - $component = new $class(); - if ($container !== false) { - $component->_container = &$container; - } - return $component; - } else { - // Should return an dummy x-unknown type class here. - return false; - } + $this->_version = $version; + $this->setAttribute('VERSION', $version); } /** - * Set the value of an attribute. + * Return a reference to a new component. * - * @param string $name The name of the attribute. - * @param string $value The value of the attribute. - * @param array $params (optional) Array containing any addition - * parameters for this attribute. - * @param boolean $append (optional) True to append the attribute, False - * to replace the first matching attribute found. - * @param array $values (optional) array representation of $value. - * For comma/semicolon seperated lists of values. - * If not set use $value as single array element. + * @param string $type The type of component to return + * @param Horde_iCalendar $container A container that this component + * will be associated with. + * + * @return object Reference to a Horde_iCalendar_* object as specified. + * + * @static */ - function setAttribute($name, $value, $params = array(), $append = true, $values = false) + function &newComponent($type, &$container) { - $found = $append; + $type = String::lower($type); + $class = 'Horde_iCalendar_' . $type; + if (!class_exists($class)) { + include 'Horde/iCalendar/' . $type . '.php'; + } + if (class_exists($class)) { + $component = new $class(); + if ($container !== false) { + $component->_container = &$container; + // Use version of container, not default set by component + // constructor. + $component->_version = $container->_version; + } + } else { + // Should return an dummy x-unknown type class here. + $component = false; + } + + return $component; + } + + /** + * Sets the value of an attribute. + * + * @param string $name The name of the attribute. + * @param string $value The value of the attribute. + * @param array $params Array containing any addition parameters for + * this attribute. + * @param boolean $append True to append the attribute, False to replace + * the first matching attribute found. + * @param array $values Array representation of $value. For + * comma/semicolon seperated lists of values. If + * not set use $value as single array element. + */ + function setAttribute($name, $value, $params = array(), $append = true, + $values = false) + { + // Make sure we update the internal format version if + // setAttribute('VERSION', ...) is called. + if ($name == 'VERSION') { + $this->_version = $value; + if ($this->_container !== false) { + $this->_container->_version = $value; + } + } + if (!$values) { $values = array($value); } - $keys = array_keys($this->_attributes); - foreach ($keys as $key) { - if ($found) break; - if ($this->_attributes[$key]['name'] == $name) { - $this->_attributes[$key]['params'] = $params; - $this->_attributes[$key]['value'] = $value; - $this->_attributes[$key]['values'] = $values; - $found = true; + $found = false; + if (!$append) { + foreach (array_keys($this->_attributes) as $key) { + if ($this->_attributes[$key]['name'] == String::upper($name)) { + $this->_attributes[$key]['params'] = $params; + $this->_attributes[$key]['value'] = $value; + $this->_attributes[$key]['values'] = $values; + $found = true; + break; + } } } if ($append || !$found) { $this->_attributes[] = array( - 'name' => $name, + 'name' => String::upper($name), 'params' => $params, 'value' => $value, 'values' => $values @@ -110,18 +155,18 @@ class Horde_iCalendar { * Sets parameter(s) for an (already existing) attribute. The * parameter set is merged into the existing set. * - * @param string $name The name of the attribute. - * @param array $params Array containing any additional - * parameters for this attribute. + * @param string $name The name of the attribute. + * @param array $params Array containing any additional parameters for + * this attribute. * @return boolean True on success, false if no attribute $name exists. */ - function setParameter($name, $params) + function setParameter($name, $params = array()) { $keys = array_keys($this->_attributes); foreach ($keys as $key) { if ($this->_attributes[$key]['name'] == $name) { $this->_attributes[$key]['params'] = - array_merge((array)$this->_attributes[$key]['params'] , $params); + array_merge($this->_attributes[$key]['params'], $params); return true; } } @@ -132,9 +177,9 @@ class Horde_iCalendar { /** * Get the value of an attribute. * - * @param string $name The name of the attribute. - * @param boolean $params Return the parameters for this attribute - * instead of its value. + * @param string $name The name of the attribute. + * @param boolean $params Return the parameters for this attribute instead + * of its value. * * @return mixed (object) PEAR_Error if the attribute does not exist. * (string) The value of the attribute. @@ -153,7 +198,7 @@ class Horde_iCalendar { } } } - if (count($result) == 0) { + if (!count($result)) { require_once 'PEAR.php'; return PEAR::raiseError('Attribute "' . $name . '" Not Found'); } if (count($result) == 1 && !$params) { @@ -171,7 +216,7 @@ class Horde_iCalendar { * b) (unsecapd) comma seperated lists. * * So for a vcard like "KEY:a,b\nKEY:c" getAttributesValues('KEY') - * will return array('a','b','c'). + * will return array('a', 'b', 'c'). * * @param string $name The name of the attribute. * @return mixed (object) PEAR_Error if the attribute does not exist. @@ -195,9 +240,9 @@ class Horde_iCalendar { * Returns the value of an attribute, or a specified default value * if the attribute does not exist. * - * @param string $name The name of the attribute. - * @param mixed $default (optional) What to return if the attribute - * specified by $name does not exist. + * @param string $name The name of the attribute. + * @param mixed $default What to return if the attribute specified by + * $name does not exist. * * @return mixed (string) The value of $name. * (mixed) $default if $name does not exist. @@ -211,7 +256,7 @@ class Horde_iCalendar { /** * Remove all occurences of an attribute. * - * @param string $name The name of the attribute. + * @param string $name The name of the attribute. */ function removeAttribute($name) { @@ -226,9 +271,10 @@ class Horde_iCalendar { /** * Get attributes for all tags or for a given tag. * - * @param string $tag (optional) return attributes for this tag. - * or all attributes if not given - * @return array Array containing all the attributes and their types. + * @param string $tag Return attributes for this tag, or all attributes if + * not given. + * + * @return array An array containing all the attributes and their types. */ function getAllAttributes($tag = false) { @@ -247,7 +293,7 @@ class Horde_iCalendar { /** * Add a vCalendar component (eg vEvent, vTimezone, etc.). * - * @param object Horde_iCalendar $component Component (subclass) to add. + * @param Horde_iCalendar $component Component (subclass) to add. */ function addComponent($component) { @@ -267,6 +313,11 @@ class Horde_iCalendar { return $this->_components; } + function getType() + { + return 'vcalendar'; + } + /** * Return the classes (entry types) we have. * @@ -316,20 +367,18 @@ class Horde_iCalendar { } /** - * Locates the first child component of the specified class, and - * returns a reference to this component. + * Locates the first child component of the specified class, and returns a + * reference to it. * * @param string $type The type of component to find. * - * @return mixed (boolean) False if no subcomponent of the specified - * class exists. - * (Horde_iCalendar_*) A reference to the requested component. + * @return boolean|Horde_iCalendar_* False if no subcomponent of the + * specified class exists or a reference + * to the requested component. */ function &findComponent($childclass) { -# require_once 'Horde/String.php'; -# $childclass = 'Horde_iCalendar_' . String::lower($childclass); - $childclass = 'Horde_iCalendar_' . strtolower($childclass); + $childclass = 'Horde_iCalendar_' . String::lower($childclass); $keys = array_keys($this->_components); foreach ($keys as $key) { if (is_a($this->_components[$key], $childclass)) { @@ -337,12 +386,47 @@ class Horde_iCalendar { } } - return false; + $component = false; + return $component; } /** - * Clears the iCalendar object (resets the components and - * attributes arrays). + * Locates the first matching child component of the specified class, and + * returns a reference to it. + * + * @param string $childclass The type of component to find. + * @param string $attribute This attribute must be set in the component + * for it to match. + * @param string $value Optional value that $attribute must match. + * + * @return boolean|Horde_iCalendar_* False if no matching subcomponent of + * the specified class exists, or a + * reference to the requested component. + */ + function &findComponentByAttribute($childclass, $attribute, $value = null) + { + $childclass = 'Horde_iCalendar_' . String::lower($childclass); + $keys = array_keys($this->_components); + foreach ($keys as $key) { + if (is_a($this->_components[$key], $childclass)) { + $attr = $this->_components[$key]->getAttribute($attribute); + if (is_a($attr, 'PEAR_Error')) { + continue; + } + if ($value !== null && $value != $attr) { + continue; + } + return $this->_components[$key]; + } + } + + $component = false; + return $component; + } + + /** + * Clears the iCalendar object (resets the components and attributes + * arrays). */ function clear() { @@ -350,15 +434,39 @@ class Horde_iCalendar { $this->_attributes = array(); } + /** + * Checks if entry is vcalendar 1.0, vcard 2.1 or vnote 1.1. + * + * These 'old' formats are defined by www.imc.org. The 'new' (non-old) + * formats icalendar 2.0 and vcard 3.0 are defined in rfc2426 and rfc2445 + * respectively. + * + * @since Horde 3.1.2 + */ + function isOldFormat() + { + $retval = true; + switch ($this->getType()) { + case 'vcard': + $retval = ($this->_version < 3); + break; + case 'vNote': + $retval = ($this->_version < 2); + break; + default: + $retval = ($this->_version < 2); + break; + } + return $retval; + } + /** * Export as vCalendar format. */ function exportvCalendar() { - #error_log(__METHOD__.": called"); // Default values. - $requiredAttributes['VERSION'] = '2.0'; - $requiredAttributes['PRODID'] = '-//The Horde Project//Horde_iCalendar Library, Horde 3.0-cvs //EN'; + $requiredAttributes['PRODID'] = '-//The Horde Project//Horde_iCalendar Library' . (defined('HORDE_VERSION') ? ', Horde ' . constant('HORDE_VERSION') : '') . '//EN'; $requiredAttributes['METHOD'] = 'PUBLISH'; foreach ($requiredAttributes as $name => $default_value) { @@ -366,17 +474,14 @@ class Horde_iCalendar { $this->setAttribute($name, $default_value); } } - //error_log(__METHOD__.":requiredAttributes->".print_r($requiredAttributes,true)); - //njv:$buffcontent = ob_get_clean(); - #error_log(__METHOD__.":".print_r($buffcontent,true)); - #ob_end_clean(); - return $this->_exportvData('VCALENDAR') . $this->_newline; + + return $this->_exportvData('VCALENDAR'); } /** * Export this entry as a hash array with tag names as keys. * - * @param boolean (optional) $paramsInKeys + * @param boolean $paramsInKeys * If false, the operation can be quite lossy as the * parameters are ignored when building the array keys. * So if you export a vcard with @@ -403,85 +508,112 @@ class Horde_iCalendar { } /** - * Parse a string containing vCalendar data. + * Parses a string containing vCalendar data. * - * @param string $text The data to parse. - * @param string $base The type of the base object. - * @param string $charset (optional) The encoding charset for $text. Defaults to utf-8 - * @param boolean $clear (optional) True to clear() the iCal object before parsing. + * @todo This method doesn't work well at all, if $base is VCARD. + * + * @param string $text The data to parse. + * @param string $base The type of the base object. + * @param string $charset The encoding charset for $text. Defaults to + * utf-8 for new format, iso-8859-1 for old format. + * @param boolean $clear If true clears the iCal object before parsing. * * @return boolean True on successful import, false otherwise. */ - function parsevCalendar($text, $base = 'VCALENDAR', $charset = 'utf-8', $clear = true) + function parsevCalendar($text, $base = 'VCALENDAR', $charset = null, + $clear = true) { if ($clear) { $this->clear(); } - error_log(__FILE__ . __METHOD__ . ":\n".$text."\n xxxxxxxxx"); - if (preg_match('/(BEGIN:' . $base . '\r?\n)([\W\w]*)(END:' . $base . '\r?\n?)/i', $text, $matches)) { - $vCal = $matches[2]; + + if (preg_match('/^BEGIN:' . $base . '(.*)^END:' . $base . '/ism', $text, $matches)) { + $container = true; + $vCal = $matches[1]; } else { // Text isn't enclosed in BEGIN:VCALENDAR // .. END:VCALENDAR. We'll try to parse it anyway. + $container = false; $vCal = $text; } + if (preg_match('/^VERSION:(\d\.\d)\s*$/ism', $vCal, $matches)) { + // define the version asap + #Horde::logMessage("iCalendar VERSION:" . $matches[1], __FILE__, __LINE__, PEAR_LOG_DEBUG); + $this->setAttribute('VERSION', $matches[1]); + } + + // Preserve a trailing CR + $vCal = trim($vCal) . "\n"; + // All subcomponents. $matches = null; - if (preg_match_all('/BEGIN:([\S]*)(\r\n|\r|\n)([\W\w]*)END:\1(\r\n|\r|\n)/U', $vCal, $matches)) { + if (preg_match_all('/^BEGIN:(.*)(\r\n|\r|\n)(.*)^END:\1/Uims', $vCal, $matches)) { + // vTimezone components are processed first. They are + // needed to process vEvents that may use a TZID. foreach ($matches[0] as $key => $data) { - $type = $matches[1][$key]; - $component = &Horde_iCalendar::newComponent(trim($type), $this); + $type = trim($matches[1][$key]); + if ($type != 'VTIMEZONE') { + continue; + } + $component = &Horde_iCalendar::newComponent($type, $this); if ($component === false) { return PEAR::raiseError("Unable to create object for type $type"); } - $component->parsevCalendar($data); + $component->parsevCalendar($data, $type, $charset); $this->addComponent($component); // Remove from the vCalendar data. $vCal = str_replace($data, '', $vCal); } + + // Now process the non-vTimezone components. + foreach ($matches[0] as $key => $data) { + $type = trim($matches[1][$key]); + if ($type == 'VTIMEZONE') { + continue; + } + $component = &Horde_iCalendar::newComponent($type, $this); + if ($component === false) { + return PEAR::raiseError("Unable to create object for type $type"); + } + $component->parsevCalendar($data, $type, $charset); + + $this->addComponent($component); + + // Remove from the vCalendar data. + $vCal = str_replace($data, '', $vCal); + } + } elseif (!$container) { + return false; + } + + // Unfold "quoted printable" folded lines like: + // BODY;ENCODING=QUOTED-PRINTABLE:= + // another=20line= + // last=20line + while (preg_match_all('/^([^:]+;\s*((ENCODING=)?QUOTED-PRINTABLE|ENCODING=[Q|q])(.*=\r?\n)+(.*[^=])?\r?\n)/mU', $vCal, $matches)) { + foreach ($matches[1] as $s) { + $r = preg_replace('/=\r?\n[ \t]*/', '', $s); + $vCal = str_replace($s, $r, $vCal); + } } // Unfold any folded lines. - #$vCal = preg_replace ('/(\r|\n)+ /', ' ', $vCal); - - // Unfold "quoted printable" folded lines like: - // BODY;ENCODING=QUOTED-PRINTABLE:= - // another=20line= - // last=20line -# Horde::logMessage("SymcML: match 1", __FILE__, __LINE__, PEAR_LOG_DEBUG); -# if (preg_match_all('/^([^:]+;\s*ENCODING=QUOTED-PRINTABLE(.*=[\r\n|\r|\n])+(.*[^=])[\r\n|\r|\n])/mU', $vCal, $matches)) { -## if (preg_match_all('/^(BODY;ENCODING=QUOTED-PRINTABLE(.*=\r\n)+(.*)?\r?\n)/mU', $vCal, $matches)) { -# Horde::logMessage("SymcML: match 2", __FILE__, __LINE__, PEAR_LOG_DEBUG); -# foreach ($matches[1] as $s) { -# Horde::logMessage("SymcML: match 3 $s", __FILE__, __LINE__, PEAR_LOG_DEBUG); -# $r = preg_replace('/=[\r\n|\r|\n]/', '', $s); -# Horde::logMessage("SymcML: match 4 $r", __FILE__, __LINE__, PEAR_LOG_DEBUG); -# $vCal = str_replace($s, $r, $vCal); -# } -# } - - // Unfold "quoted printable" folded lines like: - // BODY;ENCODING=QUOTED-PRINTABLE:= - // another=20line= - // last=20line - #if (preg_match_all('/^([^:]+;\s*ENCODING=QUOTED-PRINTABLE(.*=*\s))/mU', $vCal, $matches)) { - # $matches = preg_split('/=+\s/',$vCal); - # $vCal = implode('',$matches); - #} - if (preg_match_all('/^([^:]+;\s*ENCODING=QUOTED-PRINTABLE(.*=*\s))/mU', $vCal, $matches)) { - $matches = preg_split('/=(\r\n|\r|\n)/',$vCal); - $vCal = implode('',$matches); + if ($this->isOldFormat()) { + $vCal = preg_replace('/[\r\n]+([ \t])/', '\1', $vCal); + } else { + $vCal = preg_replace('/[\r\n]+[ \t]/', '', $vCal); } - // Parse the remaining attributes. + $isDate = false; - if (preg_match_all('/(.*):([^\r\n]*)[\r\n]+/', $vCal, $matches)) { + // Parse the remaining attributes. + if (preg_match_all('/^((?:[^":]+|(?:"[^"]*")+)*):([^\r\n]*)\r?$/m', $vCal, $matches)) { foreach ($matches[0] as $attribute) { - preg_match('/([^;^:]*)((;[^:]*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts); - $tag = $parts[1]; + preg_match('/([^;^:]*)((;(?:[^":]+|(?:"[^"]*")+)*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts); + $tag = trim(String::upper($parts[1])); $value = $parts[4]; $params = array(); @@ -489,64 +621,91 @@ class Horde_iCalendar { if (!empty($parts[2])) { preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts); foreach ($param_parts[2] as $key => $paramName) { + $paramName = String::upper($paramName); $paramValue = $param_parts[4][$key]; + if ($paramName == 'TYPE') { + $paramValue = preg_split('/(?translation->convert($value, $params['CHARSET']); + if (isset($params['QUOTED-PRINTABLE'])) { + $params['ENCODING'] = 'QUOTED-PRINTABLE'; + } + if (isset($params['BASE64'])) { + $params['ENCODING'] = 'BASE64'; + } + if (isset($params['ENCODING'])) { + switch (String::upper($params['ENCODING'])) { + case 'Q': + case 'QUOTED-PRINTABLE': + $value = quoted_printable_decode($value); + if (isset($params['CHARSET'])) { + $value = $GLOBALS['egw']->translation->convert($value, $params['CHARSET']); + } else { + $value = $GLOBALS['egw']->translation->convert($value, + empty($charset) ? ($this->isOldFormat() ? 'iso-8859-1' : 'utf-8') : $charset); + } + break; + case 'B': + case 'BASE64': + $value = base64_decode($value); + break; + } + } elseif (isset($params['CHARSET'])) { + $value = $GLOBALS['egw']->translation->convert($value, $params['CHARSET']); } else { - // As per RFC 2279, assume UTF8 if we don't have - // an explicit charset parameter. - $value = $GLOBALS['egw']->translation->convert($value, 'utf-8'); + // As per RFC 2279, assume UTF8 if we don't have an + // explicit charset parameter. + $value = $GLOBALS['egw']->translation->convert($value, + empty($charset) ? ($this->isOldFormat() ? 'iso-8859-1' : 'utf-8') : $charset); } + // Get timezone info for date fields from $params. + $tzid = isset($params['TZID']) ? trim($params['TZID'], '\"') : false; + switch ($tag) { + case 'VERSION': // already processed + break; // Date fields. - case 'DTSTAMP': case 'COMPLETED': case 'CREATED': case 'LAST-MODIFIED': - case 'BDAY': - case 'DTEND': - case 'DTSTART': - case 'DUE': - case 'RECURRENCE-ID': - $this->setAttribute($tag, $this->_parseDateTime($value), $params); + $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params); break; - case 'RDATE': - if (isset($params['VALUE'])) { - if ($params['VALUE'] == 'DATE') { - $this->setAttribute($tag, $this->_parseDate($value), $params); - } elseif ($params['VALUE'] == 'PERIOD') { - $this->setAttribute($tag, $this->_parsePeriod($value), $params); - } else { - $this->setAttribute($tag, $this->_parseDateTime($value), $params); - } + case 'BDAY': + case 'X-SYNCJE-ANNIVERSARY': + $this->setAttribute($tag, $value, $params, true, $this->_parseDate($value)); + break; + + case 'DTEND': + case 'DTSTART': + case 'DTSTAMP': + case 'DUE': + case 'AALARM': + case 'DALARM': + case 'RECURRENCE-ID': + // types like AALARM may contain additional data after a ; + // ignore these. + $ts = explode(';', $value); + if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') { + $isDate = true; + $this->setAttribute($tag, $this->_parseDateTime($ts[0], $tzid), $params, true, $this->_parseDate($ts[0])); } else { - $this->setAttribute($tag, $this->_parseDateTime($value), $params); + $this->setAttribute($tag, $this->_parseDateTime($ts[0], $tzid), $params); } break; case 'TRIGGER': if (isset($params['VALUE'])) { if ($params['VALUE'] == 'DATE-TIME') { - $this->setAttribute($tag, $this->_parseDateTime($value), $params); + $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params); } else { $this->setAttribute($tag, $this->_parseDuration($value), $params); } @@ -555,24 +714,21 @@ class Horde_iCalendar { } break; - // Comma and semicolon seperated dates. + // Comma or semicolon seperated dates. case 'EXDATE': - $values = array(); - $dates = array(); - preg_match_all('/[,;]([^,;]*)/', ';' . $value, $values); + case 'RDATE': + $dates = array(); + preg_match_all('/[;,]([^;,]*)/', ';' . $value, $values); foreach ($values[1] as $value) { - if (isset($params['VALUE'])) { - if ($params['VALUE'] == 'DATE-TIME') { - $dates[] = $this->_parseDateTime($value); - } elseif ($params['VALUE'] == 'DATE') { - $dates[] = $this->_parseDate($value); - } - } else { - $dates[] = $this->_parseDateTime($value); - } + if ((isset($params['VALUE']) + && $params['VALUE'] == 'DATE') || (!isset($params['VALUE']) && $isDate)) { + $dates[] = $this->_parseDate($value); + } else { + $dates[] = $this->_parseDateTime($value, $tzid); + } } - $this->setAttribute($tag, $dates, $params); + $this->setAttribute($tag, isset($dates[0]) ? $dates[0] : null, $params, true, $dates); break; // Duration fields. @@ -582,14 +738,13 @@ class Horde_iCalendar { // Period of time fields. case 'FREEBUSY': - $values = array(); $periods = array(); - preg_match_all('/[,;]([^,;]*)/', ';' . $value, $values); + preg_match_all('/,([^,]*)/', ',' . $value, $values); foreach ($values[1] as $value) { $periods[] = $this->_parsePeriod($value); } - $this->setAttribute($tag, $periods, $params); + $this->setAttribute($tag, isset($periods[0]) ? $periods[0] : null, $params, true, $periods); break; // UTC offset fields. @@ -608,61 +763,96 @@ class Horde_iCalendar { // Geo fields. case 'GEO': - $floats = explode(';', $value); - $value['latitude'] = floatval($floats[0]); - $value['longitude'] = floatval($floats[1]); + if ($this->isOldFormat()) { + $floats = explode(',', $value); + $value = array('latitude' => floatval($floats[1]), + 'longitude' => floatval($floats[0])); + } else { + $floats = explode(';', $value); + $value = array('latitude' => floatval($floats[0]), + 'longitude' => floatval($floats[1])); + } $this->setAttribute($tag, $value, $params); break; - // Recursion fields. - case 'EXRULE': - case 'RRULE': - $this->setAttribute($tag, trim($value), $params); + // Recursion fields. # add more flexibility + #case 'EXRULE': + #case 'RRULE': + # $this->setAttribute($tag, trim($value), $params); + # break; + + // Binary fields. + case 'PHOTO': + $this->setAttribute($tag, $value, $params); break; - // ADR an N are lists seperated by unescaped semi-colons. + // ADR, ORG and N are lists seperated by unescaped semicolons + // with a specific number of slots. case 'ADR': case 'N': case 'ORG': - $value = trim($value); - // As of rfc 2426 2.4.2 semi-colon, comma, and - // colon must be escaped. - // njv an "urban myth" a colon is tsafe and should not be escaped - $value = str_replace('\\n', $this->_newline, $value); - $value = str_replace('\\,', ',', $value); - //njv:$value = str_replace('\\:', ':', $value); + // As of rfc 2426 2.4.2 semicolon, comma, and colon must + // be escaped (comma is unescaped after splitting below). + $value = str_replace(array('\\n', '\\N', '\\;', '\\:'), + array("\n", "\n", ';', ':'), + $value); - // Split by unescaped semi-colons: - $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + break; + // CATEGORIES is a lists seperated by unescaped commas + // with a unspecific number of slots. + case 'CATEGORIES': + $value = trim($value); + // As of rfc 2426 2.4.2 semicolon, comma, and colon must + // be escaped (semicolon is unescaped after splitting below). + $value = str_replace(array('\\n', '\\N', '\\,', '\\:'), + array("\n", "\n", ',', ':'), + $value); + + // Split by unescaped commas: + $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); break; // String fields. default: - - $value = trim($value); - - //sanity $value should not contain qp - if(preg_match('/^=[24]/',$value)){ - error_log(__FILE__ .__METHOD__ ."?qp decoded : ". print_r($value , true)); - quoted_printable_decode($value); - } - // As of rfc 2426 2.4.2 semi-colon, comma, and - // colon must be escaped. - $value = str_replace('\\n', $this->_newline, $value); - $value = str_replace('\\;', ';', $value); - //njv:$value = str_replace('\\:', ':', $value); + if ($this->isOldFormat()) { + // vCalendar 1.0 and vCard 2.1 only escape semicolons + // and use unescaped semicolons to create lists. + $value = trim($value); + // Split by unescaped semicolons: + $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + } else { + $value = trim($value); + // As of rfc 2426 2.4.2 semicolon, comma, and colon + // must be escaped (comma is unescaped after splitting + // below). + $value = str_replace(array('\\n', '\\N', '\\;', '\\:', '\\\\'), + array("\n", "\n", ';', ':', '\\'), + $value); - // Split by unescaped commas: - $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + $this->setAttribute($tag, trim($value), $params, true, $values); + } break; } } @@ -674,20 +864,21 @@ class Horde_iCalendar { /** * Export this component in vCal format. * - * @param string $base (optional) The type of the base object. + * @param string $base The type of the base object. * * @return string vCal format data. */ function _exportvData($base = 'VCALENDAR') { - $result = 'BEGIN:' . strtoupper($base) . $this->_newline; + $result = 'BEGIN:' . String::upper($base) . $this->_newline; - // Ensure that version is the first attribute. - $v = $this->getAttributeDefault('VERSION', false); - if ($v) { - $result .= 'VERSION:' . $v. $this->_newline; + // VERSION is not allowed for entries enclosed in VCALENDAR/ICALENDAR, + // as it is part of the enclosing VCALENDAR/ICALENDAR. See rfc2445 + if ($base !== 'VEVENT' && $base !== 'VTODO' && $base !== 'VALARM' && + $base !== 'VJOURNAL' && $base !== 'VFREEBUSY') { + // Ensure that version is the first attribute. + $result .= 'VERSION:' . $this->_version . $this->_newline; } - foreach ($this->_attributes as $attribute) { $name = $attribute['name']; if ($name == 'VERSION') { @@ -695,12 +886,25 @@ class Horde_iCalendar { continue; } - $params = $attribute['params']; $params_str = ''; - - if (count($params)) { + $params = $attribute['params']; + if ($params) { foreach ($params as $param_name => $param_value) { - $params_str .= ";$param_name=$param_value"; + /* Skip CHARSET for iCalendar 2.0 data, not allowed. */ + if ($param_name == 'CHARSET' && !$this->isOldFormat()) { + continue; + } + /* Skip VALUE=DATE for vCalendar 1.0 data, not allowed. */ + if ($this->isOldFormat() && + $param_name == 'VALUE' && $param_value == 'DATE') { + continue; + } + + if ($param_value === null) { + $params_str .= ";$param_name"; + } else { + $params_str .= ";$param_name=$param_value"; + } } } @@ -708,7 +912,6 @@ class Horde_iCalendar { switch ($name) { // Date fields. - case 'DTSTAMP': case 'COMPLETED': case 'CREATED': case 'DCREATED': @@ -718,11 +921,25 @@ class Horde_iCalendar { case 'DTEND': case 'DTSTART': + case 'DTSTAMP': case 'DUE': + case 'AALARM': + case 'DALARM': case 'RECURRENCE-ID': if (isset($params['VALUE'])) { if ($params['VALUE'] == 'DATE') { - $value = $this->_exportDate($value); + // VCALENDAR 1.0 uses T000000 - T235959 for all day events: + if ($this->isOldFormat() && $name == 'DTEND') { + $d = new Horde_Date($value); + $value = new Horde_Date(array( + 'year' => $d->year, + 'month' => $d->month, + 'mday' => $d->mday - 1)); + $value->correct(); + $value = $this->_exportDate($value, '235959'); + } else { + $value = $this->_exportDate($value, '000000'); + } } else { $value = $this->_exportDateTime($value); } @@ -731,17 +948,38 @@ class Horde_iCalendar { } break; + // Comma or semicolon seperated dates. + case 'EXDATE': case 'RDATE': - if (isset($params['VALUE'])) { - if ($params['VALUE'] == 'DATE') { - $value = $this->_exportDate($value); - } elseif ($params['VALUE'] == 'PERIOD') { - $value = $this->_exportPeriod($value); + if (is_array($attribute['values'])) { + $values = $attribute['values']; + } elseif (!empty($value)) { + if ($this->isOldFormat()) { + $values = explode(';', $value); + } else { + $values = explode(',', $value); + } + } else { + break; + } + $dates = array(); + foreach ($values as $date) { + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE') { + $dates[] = $this->_exportDate($date, '000000'); + } elseif ($params['VALUE'] == 'PERIOD') { + $dates[] = $this->_exportPeriod($date); + } else { + $dates[] = $this->_exportDateTime($date); + } } else { - $value = $this->_exportDateTime($value); + $dates[] = $this->_exportDateTime($date); } + } + if ($this->isOldFormat()) { + $value = implode(';', $dates); } else { - $value = $this->_exportDateTime($value); + $value = implode(',', $dates); } break; @@ -788,58 +1026,119 @@ class Horde_iCalendar { // Geo fields. case 'GEO': - $value = $value['latitude'] . ',' . $value['longitude']; + if ($this->isOldFormat()) { + $value = $value['longitude'] . ',' . $value['latitude']; + } else { + $value = $value['latitude'] . ';' . $value['longitude']; + } break; // Recurrence fields. + case 'EXRULE': case 'RRULE': break; - case 'EXRULE': - //Text Fields - case 'SUMMARY': - case 'DESCRIPTION': - case 'COMMENT': - $value = str_replace('\\', '\\\\', $value); - #$value = str_replace($this->_newline, '\n', $value); - $value = str_replace(',', '\,', $value); - $value = str_replace(';', '\;', $value); - //njv:RFC 2445 says very definately NO! - //$value = str_replace(':', '\:', $value); + case 'PHOTO': break; default: + if ($this->isOldFormat()) { + if (is_array($attribute['values']) && + count($attribute['values']) > 1) { + $values = $attribute['values']; + if ($name == 'N' || $name == 'ADR' || $name == 'ORG') { + $glue = ';'; + } else { + $glue = ','; + } + $values = str_replace(';', '\\;', $values); + $value = implode($glue, $values); + } else { + /* vcard 2.1 and vcalendar 1.0 escape only + * semicolons */ + $value = str_replace(';', '\\;', $value); + } + // Text containing newlines or ASCII >= 127 must be BASE64 + // or QUOTED-PRINTABLE encoded. Currently we use + // QUOTED-PRINTABLE as default. + if (preg_match("/[^\x20-\x7F]/", $value) && + empty($params['ENCODING'])) { + $params['ENCODING'] = 'QUOTED-PRINTABLE'; + $params_str .= ';ENCODING=QUOTED-PRINTABLE'; + // Add CHARSET as well. At least the synthesis client + // gets confused otherwise + if (empty($params['CHARSET'])) { + $params['CHARSET'] = NLS::getCharset(); + $params_str .= ';CHARSET=' . $params['CHARSET']; + } + } + } else { + if (is_array($attribute['values']) && + count($attribute['values'])) { + $values = $attribute['values']; + if ($name == 'N' || $name == 'ADR' || $name == 'ORG') { + $glue = ';'; + } else { + $glue = ','; + } + // As of rfc 2426 2.5 semicolon and comma must be + // escaped. + $values = str_replace(array('\\', ';', ','), + array('\\\\', '\\;', '\\,'), + $values); + $value = implode($glue, $values); + } else { + // As of rfc 2426 2.5 semicolon and comma must be + // escaped. + $value = str_replace(array('\\', ';', ','), + array('\\\\', '\\;', '\\,'), + $value); + } + $value = preg_replace('/\r?\n/', "\n", $value); + } break; } - if (!empty($params['ENCODING']) && - $params['ENCODING'] == 'QUOTED-PRINTABLE' && strlen(trim($value)) > 0) { - $value = str_replace("\r", '', $value); -# $result .= "$name$params_str:=" . $this->_newline -# . $this->_quotedPrintableEncode($value) -# . $this->_newline; - $result .= "$name$params_str:" - . $this->_quotedPrintableEncode($value) - . $this->_newline; + if (!empty($params['ENCODING']) && strlen(trim($value))) { + switch($params['ENCODING']) { + case 'Q': + case 'QUOTED-PRINTABLE': + $value = str_replace("\r", '', $value); + $result .= $name . $params_str . ':' + . str_replace('=0A', '=0D=0A', + $this->_quotedPrintableEncode($value)) + . $this->_newline; + break; + case 'B': + case 'BASE64': + $attr_string = $name . $params_str . ":" . $this->_newline . ' ' . $this->_base64Encode($value); + $attr_string = String::wordwrap($attr_string, 75, $this->_newline . ' ', + true, 'utf-8', true); + $result .= $attr_string . $this->_newline; + if ($this->isOldFormat()) { + $result .= $this->_newline; // Append an empty line + } + break; + } } else { - $attr_string = "$name$params_str:$value"; - - $result .= $this->_foldLine($attr_string) . $this->_newline; - if (!empty($params['ENCODING']) && $params['ENCODING'] == 'BASE64' && - strlen(trim($value)) > 0) - { - $result .= $this->_newline; - } + $value = str_replace(array("\r", "\n"), array('', '\\n'), $value); + $attr_string = $name . $params_str; + if (!empty($value)) { + $attr_string .= ':' . $value; + } + if (!$this->isOldFormat()) { + $attr_string = String::wordwrap($attr_string, 75, $this->_newline . ' ', + true, 'utf-8', true); + } + $result .= $attr_string . $this->_newline; } } - foreach ($this->getComponents() as $component) { - $result .= $component->exportvCalendar() . $this->_newline; + foreach ($this->_components as $component) { + $result .= $component->exportvCalendar(); } - $result .= 'END:' . $base; - - return $result; + return $result . 'END:' . $base . $this->_newline; } /** @@ -849,7 +1148,7 @@ class Horde_iCalendar { { $offset = array(); if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) { - $offset['ahead'] = (boolean)($timeParts[1] == '+'); + $offset['ahead'] = (bool)($timeParts[1] == '+'); $offset['hour'] = intval($timeParts[2]); $offset['minute'] = intval($timeParts[3]); if (isset($timeParts[4])) { @@ -908,35 +1207,97 @@ class Horde_iCalendar { } /** - * Parse a DateTime field into a unix timestamp. + * Grok the TZID and return an offset in seconds from UTC for this + * date and time. */ - function _parseDateTime($text) + function _parseTZID($date, $time, $tzid) + { + $vtimezone = $this->_container->findComponentByAttribute('vtimezone', 'TZID', $tzid); + if (!$vtimezone) { + return false; + } + + $change_times = array(); + foreach ($vtimezone->getComponents() as $o) { + $t = $vtimezone->parseChild($o, $date['year']); + if ($t !== false) { + $change_times[] = $t; + } + } + + if (!$change_times) { + return false; + } + + sort($change_times); + + // Time is arbitrarily based on UTC for comparison. + $t = @gmmktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); + + if ($t < $change_times[0]['time']) { + return $change_times[0]['from']; + } + + for ($i = 0, $n = count($change_times); $i < $n - 1; $i++) { + if (($t >= $change_times[$i]['time']) && + ($t < $change_times[$i + 1]['time'])) { + return $change_times[$i]['to']; + } + } + + if ($t >= $change_times[$n - 1]['time']) { + return $change_times[$n - 1]['to']; + } + + return false; + } + + /** + * Parses a DateTime field and returns a unix timestamp. If the + * field cannot be parsed then the original text is returned + * unmodified. + * + * @todo This function should be moved to Horde_Date and made public. + */ + function _parseDateTime($text, $tzid = false) { $dateParts = explode('T', $text); if (count($dateParts) != 2 && !empty($text)) { // Not a datetime field but may be just a date field. - if (!$date = $this->_parseDate($text)) { - return $date; + if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) { + // Or not + return $text; } - return @mktime(0, 0, 0, $date['month'], $date['mday'], $date['year']); + $newtext = $text.'T000000'; + $dateParts = explode('T', $newtext); } - if (!$date = $this->_parseDate($dateParts[0])) { - return $date; + if (!$date = Horde_iCalendar::_parseDate($dateParts[0])) { + return $text; } - if (!$time = $this->_parseTime($dateParts[1])) { - return $time; + if (!$time = Horde_iCalendar::_parseTime($dateParts[1])) { + return $text; } - //error_log("parseDateTime: ".$text." => ".print_r($time, true)); - - if ($time['zone'] == 'UTC') { - return @gmmktime($time['hour'], $time['minute'], $time['second'], - $date['month'], $date['mday'], $date['year']); + // Get timezone info for date fields from $tzid and container. + $tzoffset = ($time['zone'] == 'Local' && $tzid && is_a($this->_container, 'Horde_iCalendar')) + ? $this->_parseTZID($date, $time, $tzid) : false; + if ($time['zone'] == 'UTC' || $tzoffset !== false) { + $result = @gmmktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); + if ($tzoffset) { + $result -= $tzoffset; + } } else { - return @mktime($time['hour'], $time['minute'], $time['second'], - $date['month'], $date['mday'], $date['year']); + // We don't know the timezone so assume local timezone. + // FIXME: shouldn't this be based on the user's timezone + // preference rather than the server's timezone? + $result = @mktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); } + + return ($result !== false) ? $result : $text; } /** @@ -944,10 +1305,10 @@ class Horde_iCalendar { */ function _exportDateTime($value) { - $temp = array(); - if (!is_object($value) || is_array($value)) { - $TZOffset = 3600 * substr(date('O',$value), 0, 3); - $TZOffset += 60 * substr(date('O',$value), 3, 2); + if (is_numeric($value)) { + $temp = array(); + $tz = date('O', $value); + $TZOffset = (3600 * substr($tz, 0, 3)) + (60 * substr(date('O', $value), 3, 2)); $value -= $TZOffset; $temp['zone'] = 'UTC'; @@ -957,47 +1318,18 @@ class Horde_iCalendar { $temp['hour'] = date('G', $value); $temp['minute'] = date('i', $value); $temp['second'] = date('s', $value); - } else { - $dateOb = (object)$value; - - $TZOffset = date('O',mktime($dateOb->hour,$dateOb->min,$dateOb->sec,$dateOb->month,$dateOb->mday,$dateOb->year)); - - // Minutes. - $TZOffsetMin = substr($TZOffset, 0, 1) . substr($TZOffset, 3, 2); - $thisMin = $dateOb->min - $TZOffsetMin; - - // Hours. - $TZOffsetHour = substr($TZOffset, 0, 3); - $thisHour = $dateOb->hour - $TZOffsetHour; - - if ($thisMin < 0) { - $thisHour -= 1; - $thisMin += 60; - } - - if ($thisHour < 0) { - require_once 'Date/Calc.php'; - $prevday = Date_Calc::prevDay($dateOb->mday, $dateOb->month, $dateOb->year); - $dateOb->mday = substr($prevday, 6, 2); - $dateOb->month = substr($prevday, 4, 2); - $dateOb->year = substr($prevday, 0, 4); - $thisHour += 24; - } - - $temp['zone'] = 'UTC'; - $temp['year'] = $dateOb->year; - $temp['month'] = $dateOb->month; - $temp['mday'] = $dateOb->mday; - $temp['hour'] = $thisHour; - $temp['minute'] = $dateOb->min; - $temp['second'] = $dateOb->sec; + return Horde_iCalendar::_exportDate($temp) . 'T' . Horde_iCalendar::_exportTime($temp); + } else if (is_object($value) || is_array($value)) { + $dateOb = new Horde_Date($value); + return Horde_iCalendar::_exportDateTime($dateOb->timestamp()); } - - return Horde_iCalendar::_exportDate($temp) . 'T' . Horde_iCalendar::_exportTime($temp); + return $value; // nothing to do with us, let's not touch it } /** - * Parse a Time field. + * Parses a Time field. + * + * @static */ function _parseTime($text) { @@ -1017,7 +1349,7 @@ class Horde_iCalendar { } /** - * Export a Time field. + * Exports a Time field. */ function _exportTime($value) { @@ -1030,33 +1362,44 @@ class Horde_iCalendar { } /** - * Parse a Date field. + * Parses a Date field. + * + * @static */ function _parseDate($text) { - if (strlen($text) == 10) - { - $text = str_replace('-','',$text); - } - if (strlen($text) != 8) - { + $parts = explode('T', $text); + if (count($parts) == 2) { + $text = $parts[0]; + } + + if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) { return false; } - $date['year'] = intval(substr($text, 0, 4)); - $date['month'] = intval(substr($text, 4, 2)); - $date['mday'] = intval(substr($text, 6, 2)); - - return $date; + return array('year' => $match[1], + 'month' => $match[2], + 'mday' => $match[3]); } /** - * Export a Date field. + * Exports a date field. + * + * @param object|array $value Date object or hash. + * @param string $autoconvert If set, use this as time part to export the + * date as datetime when exporting to Vcalendar + * 1.0. Examples: '000000' or '235959' */ - function _exportDate($value) + function _exportDate($value, $autoconvert = false) { - return sprintf('%04d%02d%02d', - $value['year'], $value['month'], $value['mday']); + if (is_object($value)) { + $value = array('year' => $value->year, 'month' => $value->month, 'mday' => $value->mday); + } + if ($autoconvert !== false && $this->isOldFormat()) { + return sprintf('%04d%02d%02dT%s', $value['year'], $value['month'], $value['mday'], $autoconvert); + } else { + return sprintf('%04d%02d%02d', $value['year'], $value['month'], $value['mday']); + } } /** @@ -1145,310 +1488,77 @@ class Horde_iCalendar { return $duration; } - /** - * Return the folded version of a line. - * JVL rewritten to fold on any ; or: or = if present before column 75 - * this is still rfc2445 section 4.1 compliant - */ - function _foldLine($line) - { - $line = preg_replace("/\r\n|\n|\r/", '\n', $line); - if (strlen($line) > 75) { - $foldedline = ''; - while (!empty($line)) { - $maxLine = substr($line, 0, 75); - $cutPoint = 1+max(is_numeric($p1 = strrpos($maxLine,';')) ? $p1 : -1, - is_numeric($p1 = strrpos($maxLine,':')) ? $p1 : -1, - is_numeric($p1 = strrpos($maxLine,'=')) ? $p1 : -1); - if ($cutPoint < 1) // nothing found, then fold complete maxLine - $cutPoint = 75; - // now fold [0..(cutPoint-1)] - $foldedline .= (empty($foldedline)) - ? substr($line, 0, $cutPoint) - : $this->_newline . ' ' . substr($line, 0, $cutPoint); - $line = (strlen($line) <= $cutPoint) - ? '' - : substr($line, $cutPoint); - - if (strlen($line) < 75) { - $foldedline .= $this->_newline . ' ' . $line; - $line = ''; - } - - } - return $foldedline; - } - return $line; - } - - /** - * Convert an 8bit string to a quoted-printable string according + /** + * Convert an 8bit string to a base64 string * to RFC2045, section 6.7. * - * Uses imap_8bit if available. - * * @param string $input The string to be encoded. * - * @return string The quoted-printable encoded string. + * @return string The base64 encoded string. + */ + function _base64Encode($input = '') + { + return base64_encode($input); + } + + + /** + * Converts an 8bit string to a quoted-printable string according to RFC + * 2045, section 6.7. + * + * imap_8bit() does not apply all necessary rules. + * + * @param string $input The string to be encoded. + * + * @return string The quoted-printable encoded string. */ function _quotedPrintableEncode($input = '') { - return $this->EncodeQP($input); - - #$input = preg_replace('!(\r\n|\r|\n)!',"\n",$input); - - // If imap_8bit() is available, use it. - if (function_exists('imap_8bit')) { - $retValue = imap_8bit($input); - #$retValue = preg_replace('/=0A/',"=0D=0A=\r\n",$retValue); - return $retValue; - } - - // Rather dumb replacment: just encode everything. - $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F'); - - $output = ''; + $output = $line = ''; $len = strlen($input); + for ($i = 0; $i < $len; ++$i) { - $c = substr($input, $i, 1); - $dec = ord($c); - $output .= '=' . $hex[floor($dec / 16)] . $hex[floor($dec % 16)]; - if (($i + 1) % 25 == 0) { - $output .= "=\r\n"; + $ord = ord($input[$i]); + // Encode non-printable characters (rule 2). + if ($ord == 9 || + ($ord >= 32 && $ord <= 60) || + ($ord >= 62 && $ord <= 126)) { + $chunk = $input[$i]; + } else { + // Quoted printable encoding (rule 1). + $chunk = '=' . String::upper(sprintf('%02X', $ord)); + } + $line .= $chunk; + // Wrap long lines (rule 5) + if (strlen($line) + 1 > 76) { + $line = String::wordwrap($line, 75, "=\r\n", true, 'us-ascii', true); + $newline = strrchr($line, "\r\n"); + if ($newline !== false) { + $output .= substr($line, 0, -strlen($newline) + 2); + $line = substr($newline, 2); + } else { + $output .= $line; + } + continue; + } + // Wrap at line breaks for better readability (rule 4). + if (substr($line, -3) == '=0A') { + $output .= $line . "=\r\n "; + $line = ''; } } + $output .= $line; + + // Trailing whitespace must be encoded (rule 3). + $lastpos = strlen($output) - 1; + if ($output[$lastpos] == chr(9) || + $output[$lastpos] == chr(32)) { + $output[$lastpos] = '='; + $output .= String::upper(sprintf('%02X', ord($output[$lastpos]))); + } + return $output; } - var $LE = "\r\n"; - - /** - * Encode string to quoted-printable. - * @access private - * @return string - */ - function EncodeQP_old ($str) { - $encoded = $this->FixEOL($str); - #$encoded = $str; - #if (substr($encoded, -(strlen($this->LE))) != $this->LE) - # $encoded .= $this->LE; - - // Replace every high ascii, control and = characters - #$encoded = preg_replace('/([\000-\010\013\014\016-\037\075\177-\377])/e', - # "'='.sprintf('%02X', ord('\\1'))", $encoded); - $encoded = preg_replace('/([\000-\012\015\016\020-\037\075\177-\377])/e', - "'='.sprintf('%02X', ord('\\1'))", $encoded); - // Replace every spaces and tabs when it's the last character on a line - #$encoded = preg_replace("/([\011\040])".$this->LE."/e", - # "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); - $encoded = preg_replace("/([\011\040])".$this->LE."/e", - "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); - - // Maximum line length of 76 characters before CRLF (74 + space + '=') - $encoded = $this->WrapText($encoded, 74, true); - - return $encoded; - } - - /** - * Wraps message for use with mailers that do not - * automatically perform wrapping and for quoted-printable. - * Original written by philippe. - * @access private - * @return string - */ - function WrapText_old($message, $length, $qp_mode = false) { - $soft_break = ($qp_mode) ? "=\r\n" : $this->LE; - - #$message = $this->FixEOL($message); - if (substr($message, -1) == $this->LE) - $message = substr($message, 0, -1); - - $line = explode("=0D=0A", $message); - $message = ""; - for ($i=0 ;$i < count($line); $i++) - { - $line_part = explode(" ", $line[$i]); - $buf = ""; - for ($e = 0; $e $length)) - { - $space_left = $length - strlen($buf) - 1; - if ($e != 0) - { - if ($space_left > 20) - { - $len = $space_left; - if (substr($word, $len - 1, 1) == "=") - $len--; - elseif (substr($word, $len - 2, 1) == "=") - $len -= 2; - $part = substr($word, 0, $len); - $word = substr($word, $len); - $buf .= " " . $part; - $message .= $buf . sprintf("=%s", $this->LE); - } - else - { - $message .= $buf . $soft_break; - } - $buf = ""; - } - while (strlen($word) > 0) - { - $len = $length; - if (substr($word, $len - 1, 1) == "=") - $len--; - elseif (substr($word, $len - 2, 1) == "=") - $len -= 2; - $part = substr($word, 0, $len); - $word = substr($word, $len); - - if (strlen($word) > 0) - $message .= $part . sprintf("=%s", $this->LE); - else - $buf = $part; - } - } - else - { - $buf_o = $buf; - $buf .= ($e == 0) ? $word : (" " . $word); - - if (strlen($buf) > $length and $buf_o != "") - { - $message .= $buf_o . $soft_break; - $buf = $word; - } - } - } - $message .= $buf; - if((count($line)-1) > $i) - $message .= "=0D=0A=\r\n"; - } - - return $message; - } - /** - * Changes every end of line from CR or LF to CRLF. - * @access private - * @return string - */ - function FixEOL($str) { - $str = str_replace("\r\n", "\n", $str); - $str = str_replace("\r", "\n", $str); - $str = str_replace("\n", $this->LE, $str); - return $str; - } - - /** - * Encode string to quoted-printable. - * @access private - * @return string - */ - function EncodeQP ($str) { - $encoded = $this->FixEOL($str); - # see bug report http://sourceforge.net/tracker/index.php?func=detail&aid=1536674&group_id=78745&atid=554338 - #if (substr($encoded, -(strlen($this->LE))) != $this->LE) - # $encoded .= $this->LE; - - // Replace every high ascii, control and = characters - #$encoded = preg_replace('/([\000-\010\013\014\016-\037\075\177-\377])/e', - # "'='.sprintf('%02X', ord('\\1'))", $encoded); - $encoded = preg_replace('/([\000-\012\015\016\020-\037\075\177-\377])/e', - "'='.sprintf('%02X', ord('\\1'))", $encoded); - // Replace every spaces and tabs when it's the last character on a line - $encoded = preg_replace("/([\011\040])".$this->LE."/e", - "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); - - // Maximum line length of 76 characters before CRLF (74 + space + '=') - #$encoded = $this->WrapText($encoded, 74, true); - - return $encoded; - } - - /** - * Wraps message for use with mailers that do not - * automatically perform wrapping and for quoted-printable. - * Original written by philippe. - * @access private - * @return string - */ - function WrapText($message, $length, $qp_mode = false) { - $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; - $soft_break = "..="; - - $message = $this->FixEOL($message); - if (substr($message, -1) == $this->LE) - $message = substr($message, 0, -1); - - $line = explode($this->LE, $message); - $message = ""; - for ($i=0 ;$i < count($line); $i++) - { - $line_part = explode(" ", $line[$i]); - $buf = ""; - for ($e = 0; $e $length)) - { - $space_left = $length - strlen($buf) - 1; - if ($e != 0) - { - if ($space_left > 20) - { - $len = $space_left; - if (substr($word, $len - 1, 1) == "=") - $len--; - elseif (substr($word, $len - 2, 1) == "=") - $len -= 2; - $part = substr($word, 0, $len); - $word = substr($word, $len); - $buf .= " " . $part; - $message .= $buf . sprintf("=%s", $this->LE); - } - else - { - $message .= $buf . $soft_break; - } - $buf = ""; - } - while (strlen($word) > 0) - { - $len = $length; - if (substr($word, $len - 1, 1) == "=") - $len--; - elseif (substr($word, $len - 2, 1) == "=") - $len -= 2; - $part = substr($word, 0, $len); - $word = substr($word, $len); - - if (strlen($word) > 0) - $message .= $part . sprintf("=%s", $this->LE); - else - $buf = $part; - } - } - else - { - $buf_o = $buf; - $buf .= ($e == 0) ? $word : (" " . $word); - - if (strlen($buf) > $length and $buf_o != "") - { - $message .= $buf_o . $soft_break; - $buf = $word; - } - } - } - $message .= $buf . $this->LE; - } - - return $message; - } } diff --git a/phpgwapi/inc/horde/Horde/iCalendar/valarm.php b/phpgwapi/inc/horde/Horde/iCalendar/valarm.php index dee9f2471c..224c44bc5e 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/valarm.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/valarm.php @@ -2,15 +2,14 @@ /** * Class representing vAlarms. * - * $Horde: framework/iCalendar/iCalendar/valarm.php,v 1.8 2004/08/13 19:11:35 karsten Exp $ + * $Horde: framework/iCalendar/iCalendar/valarm.php,v 1.8.10.8 2008/07/03 08:42:58 jan Exp $ * - * Copyright 2003-2004 Mike Cochrane + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Mike Cochrane - * @version $Revision$ * @since Horde 3.0 * @package Horde_iCalendar */ @@ -21,11 +20,6 @@ class Horde_iCalendar_valarm extends Horde_iCalendar { return 'vAlarm'; } - function parsevCalendar($data) - { - parent::parsevCalendar($data, 'VALARM'); - } - function exportvCalendar() { return parent::_exportvData('VALARM'); diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vcard.php b/phpgwapi/inc/horde/Horde/iCalendar/vcard.php index 465e044169..a814246758 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vcard.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vcard.php @@ -2,7 +2,6 @@ require_once EGW_API_INC.'/horde/Horde/iCalendar.php'; - // The following were shamelessly yoinked from Contact_Vcard_Build // Part numbers for N components. define('VCARD_N_FAMILY', 0); @@ -27,49 +26,47 @@ define('VCARD_GEO_LON', 1); /** * Class representing vCard entries. * - * $Horde: framework/iCalendar/iCalendar/vcard.php,v 1.2 2004/08/18 03:16:24 chuck Exp $ + * $Horde: framework/iCalendar/iCalendar/vcard.php,v 1.3.10.16 2008/09/22 04:16:30 chuck Exp $ * - * Copyright 2003-2004 Karsten Fourmont (karsten@horde.org) + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Karsten Fourmont - * @version $Revision$ * @package Horde_iCalendar */ class Horde_iCalendar_vcard extends Horde_iCalendar { + function Horde_iCalendar_vcard($version = '2.1') + { + return parent::Horde_iCalendar($version); + } + function getType() { return 'vcard'; } - function parsevCalendar($data) - { - return parent::parsevCalendar($data, 'vcard'); - } - /** * Unlike vevent and vtodo, a vcard is normally not enclosed in an * iCalendar container. (BEGIN..END) */ function exportvCalendar() { - #$requiredAttributes['BODY'] = ''; - $requiredAttributes['VERSION'] = '2.1'; + $requiredAttributes['VERSION'] = $this->_version; + $requiredAttributes['N'] = ';;;;;;'; + if ($this->_version == '3.0') { + $requiredAttributes['FN'] = ''; + } foreach ($requiredAttributes as $name => $default_value) { - if (is_a($this->getattribute($name), 'PEAR_Error')) { + if (is_a($this->getAttribute($name), 'PEAR_Error')) { $this->setAttribute($name, $default_value); } } - //error_log(__METHOD__.":requiredAttributes->".print_r($requiredAttributes,true)); - //njv:$buffcontent = ob_get_clean(); - #error_log(__METHOD__.":".print_r($buffcontent,true)); - #ob_end_clean(); - return $this->_exportvData('VCARD') . $this->_newline; + return $this->_exportvData('VCARD'); } /** @@ -80,8 +77,7 @@ class Horde_iCalendar_vcard extends Horde_iCalendar { * to * "Professor Dagobert T Duck Sen" * - * @return string Full name of vcard "N" tag - * or null if no N tag. + * @return string Full name of vcard "N" tag or null if no N tag. */ function printableName() { @@ -90,6 +86,8 @@ class Horde_iCalendar_vcard extends Horde_iCalendar { return null; } + $name_arr = array(); + if (!empty($name_parts[VCARD_N_PREFIX])) { $name_arr[] = $name_parts[VCARD_N_PREFIX]; } @@ -118,6 +116,11 @@ class Horde_iCalendar_vcard extends Horde_iCalendar { */ function getBareEmail($address) { + // Empty values are still empty. + if (!$address) { + return $address; + } + require_once 'Mail/RFC822.php'; require_once 'Horde/MIME.php'; @@ -126,8 +129,9 @@ class Horde_iCalendar_vcard extends Horde_iCalendar { $rfc822 = new Mail_RFC822(); } - $rfc822->validateMailbox($address); - + if (!$rfc822->validateMailbox($address)) { + return $address; + } return MIME::rfc822WriteAddress($address->mailbox, $address->host); } diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vevent.php b/phpgwapi/inc/horde/Horde/iCalendar/vevent.php index bf0bee75ec..3dd520ca62 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vevent.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vevent.php @@ -2,15 +2,14 @@ /** * Class representing vEvents. * - * $Horde: framework/iCalendar/iCalendar/vevent.php,v 1.31 2004/08/18 03:16:24 chuck Exp $ + * $Horde: framework/iCalendar/iCalendar/vevent.php,v 1.31.10.15 2008/07/03 08:42:58 jan Exp $ * - * Copyright 2003-2004 Mike Cochrane + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Mike Cochrane - * @version $Revision$ * @since Horde 3.0 * @package Horde_iCalendar */ @@ -21,32 +20,28 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { return 'vEvent'; } - function parsevCalendar($data) - { - parent::parsevCalendar($data, 'VEVENT'); - } - function exportvCalendar() { // Default values. $requiredAttributes = array(); $requiredAttributes['DTSTAMP'] = time(); - #$requiredAttributes['ORGANIZER'] = 'Unknown Organizer'; - $requiredAttributes['UID'] = $this->_exportDateTime(time()) . '@' . $_SERVER['SERVER_NAME']; + $requiredAttributes['UID'] = $this->_exportDateTime(time()) + . substr(str_pad(base_convert(microtime(), 10, 36), 16, uniqid(mt_rand()), STR_PAD_LEFT), -16) + . '@' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost'); $method = !empty($this->_container) ? $this->_container->getAttribute('METHOD') : 'PUBLISH'; switch ($method) { case 'PUBLISH': - $requiredAttributes['DTSTART'] = time(); - $requiredAttributes['SUMMARY'] = ''; + $requiredAttributes['DTSTART'] = time(); + $requiredAttributes['SUMMARY'] = ''; break; case 'REQUEST': $requiredAttributes['ATTENDEE'] = ''; - $requiredAttributes['DTSTART'] = time(); - $requiredAttributes['SUMMARY'] = ''; + $requiredAttributes['DTSTART'] = time(); + $requiredAttributes['SUMMARY'] = ''; break; case 'REPLY': @@ -54,9 +49,9 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { break; case 'ADD': - $requiredAttributes['DTSTART'] = time(); + $requiredAttributes['DTSTART'] = time(); $requiredAttributes['SEQUENCE'] = 1; - $requiredAttributes['SUMMARY'] = ''; + $requiredAttributes['SUMMARY'] = ''; break; case 'CANCEL': @@ -88,7 +83,8 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { function updateAttendee($email, $status, $fullname = '') { foreach ($this->_attributes as $key => $attribute) { - if ($attribute['name'] == 'ATTENDEE' && $attribute['value'] == 'MAILTO:' . $email) { + if ($attribute['name'] == 'ATTENDEE' && + $attribute['value'] == 'mailto:' . $email) { $this->_attributes[$key]['params']['PARTSTAT'] = $status; if (!empty($fullname)) { $this->_attributes[$key]['params']['CN'] = $fullname; @@ -101,7 +97,7 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { if (!empty($fullname)) { $params['CN'] = $fullname; } - $this->setAttribute('ATTENDEE', 'MAILTO:' . $email, $params); + $this->setAttribute('ATTENDEE', 'mailto:' . $email, $params); } /** @@ -113,7 +109,7 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { { $organizer = $this->getAttribute('ORGANIZER', true); if (is_a($organizer, 'PEAR_Error')) { - return null; + return _("An unknown person"); } if (isset($organizer[0]['CN'])) { @@ -128,7 +124,7 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { /** * Update this event with details from another event. * - * @param object Horde_iCalendar_vEvent $vevent The vEvent with latest details. + * @param Horde_iCalendar_vEvent $vevent The vEvent with latest details. */ function updateFromvEvent($vevent) { @@ -137,7 +133,9 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { $currentValue = $this->getAttribute($newAttribute['name']); if (is_a($currentValue, 'PEAR_error')) { // Already exists so just add it. - $this->setAttribute($newAttribute['name'], $newAttribute['value'], $newAttribute['params']); + $this->setAttribute($newAttribute['name'], + $newAttribute['value'], + $newAttribute['params']); } else { // Already exists so locate and modify. $found = false; @@ -178,19 +176,21 @@ class Horde_iCalendar_vevent extends Horde_iCalendar { * Update just the attendess of event with details from another * event. * - * @param object Horde_iCalendar_vEvent $vevent The vEvent with latest details + * @param Horde_iCalendar_vEvent $vevent The vEvent with latest details */ function updateAttendeesFromvEvent($vevent) { $newAttributes = $vevent->getAllAttributes(); foreach ($newAttributes as $newAttribute) { - if (!$newAttribute['name'] == 'ATTENDEE') { + if ($newAttribute['name'] != 'ATTENDEE') { continue; } $currentValue = $this->getAttribute($newAttribute['name']); if (is_a($currentValue, 'PEAR_error')) { // Already exists so just add it. - $this->setAttribute($newAttribute['name'], $newAttribute['value'], $newAttribute['params']); + $this->setAttribute($newAttribute['name'], + $newAttribute['value'], + $newAttribute['params']); } else { // Already exists so locate and modify. $found = false; diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php b/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php index 8eed1a07fb..8811cfef0a 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php @@ -1,52 +1,78 @@ + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * + * @todo Don't use timestamps + * * @author Mike Cochrane - * @version $Revision$ * @since Horde 3.0 * @package Horde_iCalendar */ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { var $_busyPeriods = array(); + var $_extraParams = array(); + /** + * Returns the type of this calendar component. + * + * @return string The type of this component. + */ function getType() { return 'vFreebusy'; } - function parsevCalendar($data) + /** + * Parses a string containing vFreebusy data. + * + * @param string $data The data to parse. + */ + function parsevCalendar($data, $type = null, $charset = null) { - parent::parsevCalendar($data, 'VFREEBUSY'); + parent::parsevCalendar($data, 'VFREEBUSY', $charset); // Do something with all the busy periods. foreach ($this->_attributes as $key => $attribute) { - if ($attribute['name'] == 'FREEBUSY') { - foreach ($attribute['value'] as $value) { - if (array_key_exists('duration', $attribute['value'])) { - $this->addBusyPeriod('BUSY', $value['start'], null, $value['duration']); - } else { - $this->addBusyPeriod('BUSY', $value['start'], $value['end']); - } - } - unset($this->_attributes[$key]); + if ($attribute['name'] != 'FREEBUSY') { + continue; } + foreach ($attribute['values'] as $value) { + $params = isset($attribute['params']) + ? $attribute['params'] + : array(); + if (isset($value['duration'])) { + $this->addBusyPeriod('BUSY', $value['start'], null, + $value['duration'], $params); + } else { + $this->addBusyPeriod('BUSY', $value['start'], + $value['end'], null, $params); + } + } + unset($this->_attributes[$key]); } } + /** + * Returns the component exported as string. + * + * @return string The exported vFreeBusy information according to the + * iCalender format specification. + */ function exportvCalendar() { foreach ($this->_busyPeriods as $start => $end) { $periods = array(array('start' => $start, 'end' => $end)); - $this->setAttribute('FREEBUSY', $periods); + $this->setAttribute('FREEBUSY', $periods, + isset($this->_extraParams[$start]) + ? $this->_extraParams[$start] : array()); } $res = parent::_exportvData('VFREEBUSY'); @@ -61,7 +87,9 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { } /** - * Get a display name for this object. + * Returns a display name for this object. + * + * @return string A clear text name for displaying this object. */ function getName() { @@ -71,12 +99,12 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') { $attr = 'ORGANIZER'; - } else if ($method == 'REPLY') { + } elseif ($method == 'REPLY') { $attr = 'ATTENDEE'; } $name = $this->getAttribute($attr, true); - if (array_key_exists('CN', $name[0])) { + if (!is_a($name, 'PEAR_Error') && isset($name[0]['CN'])) { return $name[0]['CN']; } @@ -90,7 +118,9 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { } /** - * Get the email address for this object. + * Returns the email address for this object. + * + * @return string The email address of this object's owner. */ function getEmail() { @@ -100,7 +130,7 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') { $attr = 'ORGANIZER'; - } else if ($method == 'REPLY') { + } elseif ($method == 'REPLY') { $attr = 'ATTENDEE'; } @@ -113,13 +143,34 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { } } + /** + * Returns the busy periods. + * + * @return array All busy periods. + */ function getBusyPeriods() { return $this->_busyPeriods; } /** - * Return all the free periods of time in a given period. + * Returns any additional freebusy parameters. + * + * @return array Additional parameters of the freebusy periods. + */ + function getExtraParams() + { + return $this->_extraParams; + } + + /** + * Returns all the free periods of time in a given period. + * + * @param integer $startStamp The start timestamp. + * @param integer $endStamp The end timestamp. + * + * @return array A hash with free time periods, the start times as the + * keys and the end times as the values. */ function getFreePeriods($startStamp, $endStamp) { @@ -131,21 +182,21 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { return $periods; } - // Locate the first time in the requested period we have data - // for. + // Locate the first time in the requested period we have data for. $nextstart = max($startStamp, $this->getStart()); // Check each busy period and add free periods in between. foreach ($this->_busyPeriods as $start => $end) { if ($start <= $endStamp && $end >= $nextstart) { - $periods[$nextstart] = min($start, $endStamp); + if ($nextstart <= $start) { + $periods[$nextstart] = min($start, $endStamp); + } $nextstart = min($end, $endStamp); } } - // If we didn't read the end of the requested period but still - // have data then mark as free to the end of the period or - // available data. + // If we didn't read the end of the requested period but still have + // data then mark as free to the end of the period or available data. if ($nextstart < $endStamp && $nextstart < $this->getEnd()) { $periods[$nextstart] = min($this->getEnd(), $endStamp); } @@ -154,16 +205,29 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { } /** - * Add a busy period to the info. + * Adds a busy period to the info. + * + * This function may throw away data in case you add a period with a start + * date that already exists. The longer of the two periods will be chosen + * (and all information associated with the shorter one will be removed). + * + * @param string $type The type of the period. Either 'FREE' or + * 'BUSY'; only 'BUSY' supported at the moment. + * @param integer $start The start timestamp of the period. + * @param integer $end The end timestamp of the period. + * @param integer $duration The duration of the period. If specified, the + * $end parameter will be ignored. + * @param array $extra Additional parameters for this busy period. */ - function addBusyPeriod($type, $start, $end = null, $duration = null) + function addBusyPeriod($type, $start, $end = null, $duration = null, + $extra = array()) { - if ($type == "FREE") { + if ($type == 'FREE') { // Make sure this period is not marked as busy. return false; } - // Calculate the end time is duration was specified. + // Calculate the end time if duration was specified. $tempEnd = is_null($duration) ? $end : $start + $duration; // Make sure the period length is always positive. @@ -171,26 +235,35 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { $start = min($start, $tempEnd); if (isset($this->_busyPeriods[$start])) { - // Already a period starting at this time. Extend to the - // length of the longest of the two. - $this->_busyPeriods[$start] = max($end, $this->_busyPeriods[$start]); + // Already a period starting at this time. Change the current + // period only if the new one is longer. This might be a problem + // if the callee assumes that there is no simplification going + // on. But since the periods are stored using the start time of + // the busy periods we have to throw away data here. + if ($end > $this->_busyPeriods[$start]) { + $this->_busyPeriods[$start] = $end; + $this->_extraParams[$start] = $extra; + } } else { // Add a new busy period. $this->_busyPeriods[$start] = $end; + $this->_extraParams[$start] = $extra; } return true; } /** - * Get the timestamp of the start of the time period this free - * busy information covers. + * Returns the timestamp of the start of the time period this free busy + * information covers. + * + * @return integer A timestamp. */ function getStart() { if (!is_a($this->getAttribute('DTSTART'), 'PEAR_Error')) { return $this->getAttribute('DTSTART'); - } else if (count($this->_busyPeriods)) { + } elseif (count($this->_busyPeriods)) { return min(array_keys($this->_busyPeriods)); } else { return false; @@ -198,14 +271,16 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { } /** - * Get the timestamp of the end of the time period this free busy + * Returns the timestamp of the end of the time period this free busy * information covers. + * + * @return integer A timestamp. */ function getEnd() { if (!is_a($this->getAttribute('DTEND'), 'PEAR_Error')) { return $this->getAttribute('DTEND'); - } else if (count($this->_busyPeriods)) { + } elseif (count($this->_busyPeriods)) { return max(array_values($this->_busyPeriods)); } else { return false; @@ -213,7 +288,16 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { } /** - * Merge the busy periods of another VFreebusy into this one. + * Merges the busy periods of another Horde_iCalendar_vfreebusy object + * into this one. + * + * This might lead to simplification no matter what you specify for the + * "simplify" flag since periods with the same start date will lead to the + * shorter period being removed (see addBusyPeriod). + * + * @param Horde_iCalendar_vfreebusy $freebusy A freebusy object. + * @param boolean $simplify If true, simplify() will + * called after the merge. */ function merge($freebusy, $simplify = true) { @@ -221,70 +305,155 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar { return false; } + $extra = $freebusy->getExtraParams(); foreach ($freebusy->getBusyPeriods() as $start => $end) { - $this->addBusyPeriod('BUSY', $start, $end); + // This might simplify the busy periods without taking the + // "simplify" flag into account. + $this->addBusyPeriod('BUSY', $start, $end, null, + isset($extra[$start]) + ? $extra[$start] : array()); } $thisattr = $this->getAttribute('DTSTART'); $thatattr = $freebusy->getAttribute('DTSTART'); if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) { - $this->setAttribute('DTSTART', $thatattr); + $this->setAttribute('DTSTART', $thatattr, array(), false); } elseif (!is_a($thatattr, 'PEAR_Error')) { - if ($thatattr > $thisattr) { - $this->setAttribute('DTSTART', $thatattr); + if ($thatattr < $thisattr) { + $this->setAttribute('DTSTART', $thatattr, array(), false); } } $thisattr = $this->getAttribute('DTEND'); $thatattr = $freebusy->getAttribute('DTEND'); if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) { - $this->setAttribute('DTEND', $thatattr); + $this->setAttribute('DTEND', $thatattr, array(), false); } elseif (!is_a($thatattr, 'PEAR_Error')) { - if ($thatattr < $thisattr) { - $this->setAttribute('DTEND', $thatattr); + if ($thatattr > $thisattr) { + $this->setAttribute('DTEND', $thatattr, array(), false); } } if ($simplify) { $this->simplify(); } + return true; } /** - * Remove all overlaps and simplify the busy periods array as much - * as possible. + * Removes all overlaps and simplifies the busy periods array as much as + * possible. */ function simplify() + { + $clean = false; + $busy = array($this->_busyPeriods, $this->_extraParams); + while (!$clean) { + $result = $this->_simplify($busy[0], $busy[1]); + $clean = $result === $busy; + $busy = $result; + } + + ksort($result[1], SORT_NUMERIC); + $this->_extraParams = $result[1]; + + ksort($result[0], SORT_NUMERIC); + $this->_busyPeriods = $result[0]; + } + + function _simplify($busyPeriods, $extraParams = array()) { $checked = array(); + $checkedExtra = array(); $checkedEmpty = true; - foreach ($this->_busyPeriods as $start => $end) { + + foreach ($busyPeriods as $start => $end) { if ($checkedEmpty) { $checked[$start] = $end; + $checkedExtra[$start] = isset($extraParams[$start]) + ? $extraParams[$start] : array(); $checkedEmpty = false; } else { $added = false; foreach ($checked as $testStart => $testEnd) { - if ($start == $testStart) { - $checked[$testStart] = max($testEnd, $end); - $added = true; - } else if ($end <= $testEnd && $end >= $testStart) { + // Replace old period if the new period lies around the + // old period. + if ($start <= $testStart && $end >= $testEnd) { + // Remove old period entry. unset($checked[$testStart]); - $checked[min($testStart, $start)] = max($testEnd, $end); + unset($checkedExtra[$testStart]); + // Add replacing entry. + $checked[$start] = $end; + $checkedExtra[$start] = isset($extraParams[$start]) + ? $extraParams[$start] : array(); + $added = true; + } elseif ($start >= $testStart && $end <= $testEnd) { + // The new period lies fully within the old + // period. Just forget about it. + $added = true; + } elseif (($end <= $testEnd && $end >= $testStart) || + ($start >= $testStart && $start <= $testEnd)) { + // Now we are in trouble: Overlapping time periods. If + // we allow for additional parameters we cannot simply + // choose one of the two parameter sets. It's better + // to leave two separated time periods. + $extra = isset($extraParams[$start]) + ? $extraParams[$start] : array(); + $testExtra = isset($checkedExtra[$testStart]) + ? $checkedExtra[$testStart] : array(); + // Remove old period entry. + unset($checked[$testStart]); + unset($checkedExtra[$testStart]); + // We have two periods overlapping. Are their + // additional parameters the same or different? + $newStart = min($start, $testStart); + $newEnd = max($end, $testEnd); + if ($extra === $testExtra) { + // Both periods have the same information. So we + // can just merge. + $checked[$newStart] = $newEnd; + $checkedExtra[$newStart] = $extra; + } else { + // Extra parameters are different. Create one + // period at the beginning with the params of the + // first period and create a trailing period with + // the params of the second period. The break + // point will be the end of the first period. + $break = min($end, $testEnd); + $checked[$newStart] = $break; + $checkedExtra[$newStart] = + isset($extraParams[$newStart]) + ? $extraParams[$newStart] : array(); + $checked[$break] = $newEnd; + $highStart = max($start, $testStart); + $checkedExtra[$break] = + isset($extraParams[$highStart]) + ? $extraParams[$highStart] : array(); + + // Ensure we also have the extra data in the + // extraParams. + $extraParams[$break] = + isset($extraParams[$highStart]) + ? $extraParams[$highStart] : array(); + } $added = true; } + if ($added) { break; } } + if (!$added) { $checked[$start] = $end; + $checkedExtra[$start] = isset($extraParams[$start]) + ? $extraParams[$start] : array(); } } } - ksort($checked, SORT_NUMERIC); - $this->_busyPeriods = $checked; + + return array($checked, $checkedExtra); } } diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php b/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php index 632bc1b7ba..2ad56e5a5f 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php @@ -2,15 +2,14 @@ /** * Class representing vJournals. * - * $Horde: framework/iCalendar/iCalendar/vjournal.php,v 1.8 2004/08/13 19:11:35 karsten Exp $ + * $Horde: framework/iCalendar/iCalendar/vjournal.php,v 1.8.10.8 2008/07/03 08:42:58 jan Exp $ * - * Copyright 2003-2004 Mike Cochrane + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Mike Cochrane - * @version $Revision$ * @since Horde 3.0 * @package Horde_iCalendar */ @@ -21,11 +20,6 @@ class Horde_iCalendar_vjournal extends Horde_iCalendar { return 'vJournal'; } - function parsevCalendar($data) - { - parent::parsevCalendar($data, 'VJOURNAL'); - } - function exportvCalendar() { return parent::_exportvData('VJOURNAL'); diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vnote.php b/phpgwapi/inc/horde/Horde/iCalendar/vnote.php index 4b76007b42..eb625dbaf4 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vnote.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vnote.php @@ -5,29 +5,29 @@ require_once EGW_API_INC.'/horde/Horde/iCalendar.php'; /** * Class representing vNotes. * - * $Horde: framework/iCalendar/iCalendar/vnote.php,v 1.2 2004/08/13 19:11:35 karsten Exp $ + * $Horde: framework/iCalendar/iCalendar/vnote.php,v 1.3.10.9 2008/07/03 08:42:58 jan Exp $ * - * Copyright 2003-2004 Mike Cochrane + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * + * @author Mike Cochrane * @author Karsten Fourmont - * @version $Revision$ * @package Horde_iCalendar */ class Horde_iCalendar_vnote extends Horde_iCalendar { + function Horde_iCalendar_vnote($version = '1.1') + { + return parent::Horde_iCalendar($version); + } + function getType() { return 'vNote'; } - function parsevCalendar($data) - { - return parent::parsevCalendar($data, 'VNOTE'); - } - /** * Unlike vevent and vtodo, a vnote is normally not enclosed in an * iCalendar container. (BEGIN..END) @@ -43,7 +43,7 @@ class Horde_iCalendar_vnote extends Horde_iCalendar { } } - return $this->_exportvData('VNOTE') . $this->_newline; + return $this->_exportvData('VNOTE'); } } diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php b/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php index 922b21e85a..332c14f5c3 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php @@ -2,15 +2,14 @@ /** * Class representing vTimezones. * - * $Horde: framework/iCalendar/iCalendar/vtimezone.php,v 1.8 2004/08/13 19:11:35 karsten Exp $ + * $Horde: framework/iCalendar/iCalendar/vtimezone.php,v 1.8.10.9 2008/07/03 08:42:58 jan Exp $ * - * Copyright 2003-2004 Mike Cochrane + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Mike Cochrane - * @version $Revision$ * @since Horde 3.0 * @package Horde_iCalendar */ @@ -21,16 +20,145 @@ class Horde_iCalendar_vtimezone extends Horde_iCalendar { return 'vTimeZone'; } - function parsevCalendar($data) - { - parent::parsevCalendar($data, 'VTIMEZONE'); - } - function exportvCalendar() { return parent::_exportvData('VTIMEZONE'); } + /** + * Parse child components of the vTimezone component. Returns an + * array with the exact time of the time change as well as the + * 'from' and 'to' offsets around the change. Time is arbitrarily + * based on UTC for comparison. + */ + function parseChild(&$child, $year) + { + // Make sure 'time' key is first for sort(). + $result['time'] = 0; + + $t = $child->getAttribute('TZOFFSETFROM'); + if (is_a($t, 'PEAR_Error')) { + return false; + } + $result['from'] = ($t['hour'] * 60 * 60 + $t['minute'] * 60) * ($t['ahead'] ? 1 : -1); + + $t = $child->getAttribute('TZOFFSETTO'); + if (is_a($t, 'PEAR_Error')) { + return false; + } + $result['to'] = ($t['hour'] * 60 * 60 + $t['minute'] * 60) * ($t['ahead'] ? 1 : -1); + + $switch_time = $child->getAttribute('DTSTART'); + if (is_a($switch_time, 'PEAR_Error')) { + return false; + } + + $rrules = $child->getAttribute('RRULE'); + if (is_a($rrules, 'PEAR_Error')) { + if (!is_int($switch_time)) { + return false; + } + // Convert this timestamp from local time to UTC for + // comparison (All dates are compared as if they are UTC). + $t = getdate($switch_time); + $result['time'] = @gmmktime($t['hours'], $t['minutes'], $t['seconds'], + $t['mon'], $t['mday'], $t['year']); + return $result; + } + + $rrules = explode(';', $rrules); + foreach ($rrules as $rrule) { + $t = explode('=', $rrule); + switch ($t[0]) { + case 'FREQ': + if ($t[1] != 'YEARLY') { + return false; + } + break; + + case 'INTERVAL': + if ($t[1] != '1') { + return false; + } + break; + + case 'BYMONTH': + $month = intval($t[1]); + break; + + case 'BYDAY': + $len = strspn($t[1], '1234567890-+'); + if ($len == 0) { + return false; + } + $weekday = substr($t[1], $len); + $weekdays = array( + 'SU' => 0, + 'MO' => 1, + 'TU' => 2, + 'WE' => 3, + 'TH' => 4, + 'FR' => 5, + 'SA' => 6 + ); + $weekday = $weekdays[$weekday]; + $which = intval(substr($t[1], 0, $len)); + break; + + case 'UNTIL': + if (intval($year) > intval(substr($t[1], 0, 4))) { + return false; + } + break; + } + } + + if (empty($month) || !isset($weekday)) { + return false; + } + + if (is_int($switch_time)) { + // Was stored as localtime. + $switch_time = strftime('%H:%M:%S', $switch_time); + $switch_time = explode(':', $switch_time); + } else { + $switch_time = explode('T', $switch_time); + if (count($switch_time) != 2) { + return false; + } + $switch_time[0] = substr($switch_time[1], 0, 2); + $switch_time[2] = substr($switch_time[1], 4, 2); + $switch_time[1] = substr($switch_time[1], 2, 2); + } + + // Get the timestamp for the first day of $month. + $when = gmmktime($switch_time[0], $switch_time[1], $switch_time[2], + $month, 1, $year); + // Get the day of the week for the first day of $month. + $first_of_month_weekday = intval(gmstrftime('%w', $when)); + + // Go to the first $weekday before first day of $month. + if ($weekday >= $first_of_month_weekday) { + $weekday -= 7; + } + $when -= ($first_of_month_weekday - $weekday) * 60 * 60 * 24; + + // If going backwards go to the first $weekday after last day + // of $month. + if ($which < 0) { + do { + $when += 60*60*24*7; + } while (intval(gmstrftime('%m', $when)) == $month); + } + + // Calculate $weekday number $which. + $when += $which * 60 * 60 * 24 * 7; + + $result['time'] = $when; + + return $result; + } + } /** diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php b/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php index 5dfff21d7e..a7cce95dd0 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php +++ b/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php @@ -2,15 +2,14 @@ /** * Class representing vTodos. * - * $Horde: framework/iCalendar/iCalendar/vtodo.php,v 1.13 2004/08/13 19:11:35 karsten Exp $ + * $Horde: framework/iCalendar/iCalendar/vtodo.php,v 1.13.10.8 2008/07/03 08:42:58 jan Exp $ * - * Copyright 2003-2004 Mike Cochrane + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Mike Cochrane - * @version $Revision$ * @since Horde 3.0 * @package Horde_iCalendar */ @@ -21,11 +20,6 @@ class Horde_iCalendar_vtodo extends Horde_iCalendar { return 'vTodo'; } - function parsevCalendar($data) - { - parent::parsevCalendar($data, 'VTODO'); - } - function exportvCalendar() { return parent::_exportvData('VTODO'); diff --git a/phpgwapi/inc/horde/XML/WBXML.php b/phpgwapi/inc/horde/XML/WBXML.php index 382d0bb18b..bbf8ac2bf0 100644 --- a/phpgwapi/inc/horde/XML/WBXML.php +++ b/phpgwapi/inc/horde/XML/WBXML.php @@ -1,8 +1,8 @@ + * $Horde: framework/XML_WBXML/WBXML.php,v 1.13.12.11 2008/01/02 11:31:02 jan Exp $ * - * See the enclosed file COPYING for license information (LGPL). If you + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * - * $Horde: framework/XML_WBXML/WBXML.php,v 1.18 2006/01/01 21:10:25 jan Exp $ - * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML { @@ -173,8 +182,17 @@ class XML_WBXML { // Not all SyncML clients know this, so we // should use the string table. // 0xFD1 => DPI_DTD_SYNCML_1_1, - 4051 => DPI_DTD_SYNCML_1_1, - 4052 => DPI_DTD_DEVINF_1_1, + // These codes are taken from libwbxml wbxml_tables.h: + 4049 => DPI_DTD_SYNCML_1_0, // 0x0fd1 + 4050 => DPI_DTD_DEVINF_1_0, // 0x0fd2 + 4051 => DPI_DTD_SYNCML_1_1, // 0x0fd3 + 4052 => DPI_DTD_DEVINF_1_1, // 0x0fd4 + 4609 => DPI_DTD_SYNCML_1_2, // 0x1201 + //@todo: verify this: + 4611 => DPI_DTD_DEVINF_1_2 // 0x1203 +// taken from libxml but might be wrong: +// 4610 => DPI_DTD_DEVINF_1_2, // 0x1202 +// 4611 => DPI_DTD_METINF_1_2 // 0x1203 ); return isset($DPIString[$i]) ? $DPIString[$i] : null; } @@ -197,8 +215,18 @@ class XML_WBXML { DPI_DTD_WTA_WML_1_2 => 12, DPI_DTD_CHANNEL_1_2 => 13, - // Not all SyncML clients know this, so we + // Not all SyncML clients know this, so maybe we // should use the string table. + // These codes are taken from libwbxml wbxml_tables.h: + DPI_DTD_SYNCML_1_0 => 4049, + DPI_DTD_DEVINF_1_0 => 4050, + DPI_DTD_SYNCML_1_1 => 4051, + DPI_DTD_DEVINF_1_1 => 4052, + DPI_DTD_SYNCML_1_2 => 4609, // 0x1201 +// DPI_DTD_DEVINF_1_2 => 4610, // 0x1202 +// DPI_DTD_METINF_1_2 => 4611 // 0x1203 + //@todo: verify this + DPI_DTD_DEVINF_1_2 => 4611 // 0x1203 // DPI_DTD_SYNCML_1_1 => 0xFD1, // DPI_DTD_DEVINF_1_1 => 0xFD2, ); diff --git a/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php b/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php index 98cdcba8c7..7306b351cd 100644 --- a/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php +++ b/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php @@ -1,15 +1,16 @@ - * - * See the enclosed file COPYING for license information (LGPL). If you did - * not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * * From Binary XML Content Format Specification Version 1.3, 25 July 2001 * found at http://www.wapforum.org * + * $Horde: framework/XML_WBXML/WBXML/ContentHandler.php,v 1.9.10.11 2008/08/26 15:41:13 jan Exp $ + * + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_ContentHandler { @@ -35,9 +36,13 @@ class XML_WBXML_ContentHandler { $this->_currentUri = new XML_WBXML_LifoQueue(); } + /** + */ function raiseError($error) { - include_once 'PEAR.php'; + if (!class_exists('PEAR')) { + require 'PEAR.php'; + } return PEAR::raiseError($error); } @@ -71,7 +76,7 @@ class XML_WBXML_ContentHandler { return strlen($this->_output); } - function startElement($uri, $element, $attrs) + function startElement($uri, $element, $attrs = array()) { $this->_output .= '<' . $element; @@ -104,12 +109,7 @@ class XML_WBXML_ContentHandler { function opaque($o) { - // I can check the first chanracter and see if it is WBXML. - if (ord($o[0]) < 10) { - // Should decode this, I really need a call back function. - } else { - $this->_output .= $o; - } + $this->_output .= $o; } function setOpaqueHandler($opaqueHandler) @@ -122,6 +122,15 @@ class XML_WBXML_ContentHandler { unset($this->_opaqueHandler); } + function createSubHandler() + { + $name = get_class($this); // clone current class + $sh = new $name(); + $sh->setCharset($this->getCharsetStr()); + $sh->setVersion($this->getVersion()); + return $sh; + } + } class XML_WBXML_LifoQueue { diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD.php b/phpgwapi/inc/horde/XML/WBXML/DTD.php index 880c3dec00..39c336ea50 100644 --- a/phpgwapi/inc/horde/XML/WBXML/DTD.php +++ b/phpgwapi/inc/horde/XML/WBXML/DTD.php @@ -1,15 +1,16 @@ + * $Horde: framework/XML_WBXML/WBXML/DTD.php,v 1.6.12.8 2008/01/02 11:31:02 jan Exp $ * - * See the enclosed file COPYING for license information (LGPL). If you + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_DTD { @@ -88,6 +89,11 @@ class XML_WBXML_DTD { function toCodePageURI($uri) { $uri = strtolower($uri); + if (!isset($this->strCodePagesURI[$uri])) { + //Horde::logMessage("WBXML unable to find codepage for $uri!", __FILE__, __LINE__, PEAR_LOG_DEBUG); + //die("unable to find codepage for $uri!\n"); + } + $ret = isset($this->strCodePagesURI[$uri]) ? $this->strCodePagesURI[$uri] : false; return $ret; diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php index 6f2edfa703..bbee83f931 100644 --- a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php +++ b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php @@ -3,16 +3,17 @@ include_once 'XML/WBXML/DTD.php'; /** - * $Horde: framework/XML_WBXML/WBXML/DTD/SyncML.php,v 1.11 2006/01/01 21:10:26 jan Exp $ - * * From Binary XML Content Format Specification Version 1.3, 25 July 2001 * found at http://www.wapforum.org * - * Copyright 2003-2006 Anthony Mills + * $Horde: framework/XML_WBXML/WBXML/DTD/SyncML.php,v 1.6.12.8 2008/01/02 11:31:03 jan Exp $ * - * See the enclosed file COPYING for license information (LGPL). If you + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_DTD_SyncML extends XML_WBXML_DTD { @@ -72,17 +73,29 @@ class XML_WBXML_DTD_SyncML extends XML_WBXML_DTD { $this->setTag(0x30, "Reserved for future use"); // 0x00 $this->setTag(0x31, "VerDTD"); // 0x00 $this->setTag(0x32, "VerProto"); // 0x00 - $this->setTag(0x33, "NumberOfChanged"); // 0x00 + $this->setTag(0x33, "NumberOfChanges"); // 0x00 $this->setTag(0x34, "MoreData"); // 0x00 + $this->setTag(0x35, "Field"); // 0x00 + $this->setTag(0x36, "Filter"); // 0x00 + $this->setTag(0x37, "Record"); // 0x00 + $this->setTag(0x38, "FilterType"); // 0x00 + $this->setTag(0x39, "SourceParent"); // 0x00 + $this->setTag(0x3a, "TargetParent"); // 0x00 + $this->setTag(0x3b, "Move"); // 0x00 + $this->setTag(0x3c, "Correlator"); // 0x00 - if ($this->version == 0) { - $this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml1.0'); - $this->setCodePage(1, '-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf'); - $this->setURI('syncml:syncml1.0'); - } else { - $this->setCodePage(0, '-//SYNCML//DTD SyncML 1.1//EN', 'syncml:syncml1.1'); - $this->setCodePage(1, '-//SYNCML//DTD MetInf 1.1//EN', 'syncml:metinf1.1'); + if ($this->version == 1) { + $this->setCodePage(0, DPI_DTD_SYNCML_1_1, 'syncml:syncml1.1'); + $this->setCodePage(1, DPI_DTD_METINF_1_1, 'syncml:metinf1.1'); $this->setURI('syncml:syncml1.1'); + } elseif ($this->version == 2) { + $this->setCodePage(0, DPI_DTD_SYNCML_1_2, 'syncml:syncml1.2'); + $this->setCodePage(1, DPI_DTD_METINF_1_2, 'syncml:metinf1.2'); + $this->setURI('syncml:syncml1.2'); + } else { + $this->setCodePage(0, DPI_DTD_SYNCML_1_0, 'syncml:syncml1.0'); + $this->setCodePage(1, DPI_DTD_METINF_1_0, 'syncml:metinf1.0'); + $this->setURI('syncml:syncml1.0'); } } diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php index 076236b4ce..6cb83c88b7 100644 --- a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php +++ b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php @@ -3,16 +3,17 @@ include_once 'XML/WBXML/DTD.php'; /** - * $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLDevInf.php,v 1.11 2006/01/01 21:10:26 jan Exp $ - * - * Copyright 2003-2006 Anthony Mills - * * From Binary XML Content Format Specification Version 1.3, 25 July 2001 * found at http://www.wapforum.org * - * See the enclosed file COPYING for license information (LGPL). If you + * $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLDevInf.php,v 1.4.12.8 2008/01/02 11:31:03 jan Exp $ + * + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD { @@ -26,6 +27,8 @@ class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD { * | sed -e 's#^.*\"\([^\"]*\)\", *\(0x..\), \(0x..\) },.*$# \$this->setTag\(\3, \"\1\"\); // \2#g' */ + #Horde::logMessage("XML_WBXML_DTD_SyncMLDevInf version=" . $this->version, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $this->setTag(0x05, "CTCap"); // 0x00 $this->setTag(0x06, "CTType"); // 0x00 $this->setTag(0x07, "DataStore"); // 0x00 @@ -64,13 +67,25 @@ class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD { $this->setTag(0x28, "UTC"); // 0x00 $this->setTag(0x29, "SupportNumberOfChanges"); // 0x00 $this->setTag(0x2a, "SupportLargeObjs"); // 0x00 + $this->setTag(0x2b, "Property"); // 0x00 + $this->setTag(0x2c, "PropParam"); // 0x00 + $this->setTag(0x2d, "MaxOccur"); // 0x00 + $this->setTag(0x2e, "NoTruncate"); // 0x00 + $this->setTag(0x30, "Filter-Rx"); // 0x00 + $this->setTag(0x31, "FilterCap"); // 0x00 + $this->setTag(0x32, "FilterKeyword"); // 0x00 + $this->setTag(0x33, "FieldLevel"); // 0x00 + $this->setTag(0x34, "SupportHierarchicalSync"); // 0x00 - if ($this->version == 0) { - $this->setCodePage(0, '-//SYNCML//DTD DevInf 1.0//EN', 'syncml:devinf'); - $this->setURI('syncml:devinf'); - } else { - $this->setCodePage(0, '-//SYNCML//DTD DevInf 1.1//EN', 'syncml:devinf1.1'); + if ($this->version == 1) { + $this->setCodePage(0, DPI_DTD_DEVINF_1_1, 'syncml:devinf1.1'); $this->setURI('syncml:devinf1.1'); + } elseif ($this->version == 2) { + $this->setCodePage(0, DPI_DTD_DEVINF_1_2, 'syncml:devinf1.2'); + $this->setURI('syncml:devinf1.2'); + } else { + $this->setCodePage(0, DPI_DTD_DEVINF_1_0, 'syncml:devinf1.0'); + $this->setURI('syncml:devinf1.0'); } } diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php index 43a5737c2a..650ba74189 100644 --- a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php +++ b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php @@ -3,16 +3,17 @@ include_once 'XML/WBXML/DTD.php'; /** - * $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLMetInf.php,v 1.9 2006/01/01 21:10:26 jan Exp $ - * - * Copyright 2003-2006 Anthony Mills - * * From Binary XML Content Format Specification Version 1.3, 25 July 2001 * found at http://www.wapforum.org * - * See the enclosed file COPYING for license information (LGPL). If you + * $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLMetInf.php,v 1.4.12.8 2008/01/02 11:31:03 jan Exp $ + * + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_DTD_SyncMLMetInf extends XML_WBXML_DTD { @@ -43,17 +44,22 @@ class XML_WBXML_DTD_SyncMLMetInf extends XML_WBXML_DTD { $this->setTag(0x12, "Size"); // 0x01 $this->setTag(0x13, "Type"); // 0x01 $this->setTag(0x14, "Version"); // 0x01 + $this->setTag(0x15, "MaxObjSize"); // 0x01 + $this->setTag(0x16, "FieldLevel"); // 0x01 - if ($this->version == 0) { - #$this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:SYNCML1.0'); - $this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml1.0'); - $this->setCodePage(1, '-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf'); - $this->setURI('syncml:metinf'); - } else { - $this->setCodePage(0, '-//SYNCML//DTD SyncML 1.1//EN', 'syncml:syncml1.1'); - $this->setCodePage(1, '-//SYNCML//DTD MetInf 1.1//EN', 'syncml:metinf1.1'); + if ($this->version == 1) { + $this->setCodePage(0, DPI_DTD_SYNCML_1_1, 'syncml:syncml1.1'); + $this->setCodePage(1, DPI_DTD_METINF_1_1, 'syncml:metinf1.1'); $this->setURI('syncml:metinf1.1'); //$this->setURI('syncml:metinf'); // for some funny reason, libwbxml produces no :metinf1.1 here + } elseif ($this->version == 2) { + $this->setCodePage(0, DPI_DTD_SYNCML_1_2, 'syncml:syncml1.2'); + $this->setCodePage(1, DPI_DTD_METINF_1_2, 'syncml:metinf1.2'); + $this->setURI('syncml:metinf1.2'); + } else { + $this->setCodePage(0, DPI_DTD_SYNCML_1_0, 'syncml:syncml1.0'); + $this->setCodePage(1, DPI_DTD_METINF_1_0, 'syncml:metinf1.0'); + $this->setURI('syncml:metinf1.0'); } } diff --git a/phpgwapi/inc/horde/XML/WBXML/DTDManager.php b/phpgwapi/inc/horde/XML/WBXML/DTDManager.php index 1f57e28195..d31cb33fdd 100644 --- a/phpgwapi/inc/horde/XML/WBXML/DTDManager.php +++ b/phpgwapi/inc/horde/XML/WBXML/DTDManager.php @@ -5,50 +5,94 @@ include_once 'XML/WBXML/DTD/SyncMLMetInf.php'; include_once 'XML/WBXML/DTD/SyncMLDevInf.php'; /** - * $Horde: framework/XML_WBXML/WBXML/DTDManager.php,v 1.7 2006/01/01 21:10:25 jan Exp $ + * From Binary XML Content Format Specification Version 1.3, 25 July 2001 + * found at http://www.wapforum.org * - * Copyright 2003-2006 Anthony Mills + * $Horde: framework/XML_WBXML/WBXML/DTDManager.php,v 1.3.12.14 2008/01/02 11:31:02 jan Exp $ * - * See the enclosed file COPYING for license information (LGPL). If you + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * - * From Binary XML Content Format Specification Version 1.3, 25 July - * 2001 found at http://www.wapforum.org - * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_DTDManager { + /** + * @var array + */ var $_strDTD = array(); + + /** + * @var array + */ var $_strDTDURI = array(); + /** + */ function XML_WBXML_DTDManager() { - $this->registerDTD('-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml1.0', new XML_WBXML_DTD_SyncML(0)); - $this->registerDTD('-//SYNCML//DTD SyncML 1.1//EN', 'syncml:syncml1.1', new XML_WBXML_DTD_SyncML(1)); + $this->registerDTD(DPI_DTD_SYNCML_1_0, 'syncml:syncml1.0', new XML_WBXML_DTD_SyncML(0)); + $this->registerDTD(DPI_DTD_SYNCML_1_1, 'syncml:syncml1.1', new XML_WBXML_DTD_SyncML(1)); + $this->registerDTD(DPI_DTD_SYNCML_1_2, 'syncml:syncml1.2', new XML_WBXML_DTD_SyncML(2)); - $this->registerDTD('-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf', new XML_WBXML_DTD_SyncMLMetInf(0)); - $this->registerDTD('-//SYNCML//DTD MetInf 1.1//EN', 'syncml:metinf1.1', new XML_WBXML_DTD_SyncMLMetInf(1)); + $this->registerDTD(DPI_DTD_METINF_1_0, 'syncml:metinf1.0', new XML_WBXML_DTD_SyncMLMetInf(0)); + $this->registerDTD(DPI_DTD_METINF_1_1, 'syncml:metinf1.1', new XML_WBXML_DTD_SyncMLMetInf(1)); + $this->registerDTD(DPI_DTD_METINF_1_2, 'syncml:metinf1.2', new XML_WBXML_DTD_SyncMLMetInf(2)); - $this->registerDTD('-//SYNCML//DTD DevInf 1.0//EN', 'syncml:devinf', new XML_WBXML_DTD_SyncMLDevInf(0)); - $this->registerDTD('-//SYNCML//DTD DevInf 1.1//EN', 'syncml:devinf1.1', new XML_WBXML_DTD_SyncMLDevInf(1)); + $this->registerDTD(DPI_DTD_DEVINF_1_0, 'syncml:devinf1.0', new XML_WBXML_DTD_SyncMLDevInf(0)); + $this->registerDTD(DPI_DTD_DEVINF_1_1, 'syncml:devinf1.1', new XML_WBXML_DTD_SyncMLDevInf(1)); + $this->registerDTD(DPI_DTD_DEVINF_1_2, 'syncml:devinf1.2', new XML_WBXML_DTD_SyncMLDevInf(2)); } - function getInstance($publicIdentifier) + /** + */ + function &getInstance($publicIdentifier) { - return isset($this->_strDTD[$publicIdentifier]) ? $this->_strDTD[$publicIdentifier] : null; + $publicIdentifier = strtolower($publicIdentifier); + if (isset($this->_strDTD[$publicIdentifier])) { + $dtd = &$this->_strDTD[$publicIdentifier]; + } else { + $dtd = null; + } + return $dtd; } - function getInstanceURI($uri) + /** + */ + function &getInstanceURI($uri) { $uri = strtolower($uri); - return isset($this->_strDTDURI[$uri]) ? $this->_strDTDURI[$uri] : null; + + // some manual hacks: + if ($uri == 'syncml:syncml') { + $uri = 'syncml:syncml1.0'; + } + if ($uri == 'syncml:metinf') { + $uri = 'syncml:metinf1.0'; + } + if ($uri == 'syncml:devinf') { + $uri = 'syncml:devinf1.0'; + } + + if (isset($this->_strDTDURI[$uri])) { + $dtd = &$this->_strDTDURI[$uri]; + } else { + $dtd = null; + } + return $dtd; } + /** + */ function registerDTD($publicIdentifier, $uri, &$dtd) { $dtd->setDPI($publicIdentifier); + $publicIdentifier = strtolower($publicIdentifier); + $this->_strDTD[$publicIdentifier] = $dtd; $this->_strDTDURI[strtolower($uri)] = $dtd; } diff --git a/phpgwapi/inc/horde/XML/WBXML/Decoder.php b/phpgwapi/inc/horde/XML/WBXML/Decoder.php index 9ef6695659..c2591619f6 100644 --- a/phpgwapi/inc/horde/XML/WBXML/Decoder.php +++ b/phpgwapi/inc/horde/XML/WBXML/Decoder.php @@ -5,16 +5,17 @@ include_once 'XML/WBXML/DTDManager.php'; include_once 'XML/WBXML/ContentHandler.php'; /** - * $Horde: framework/XML_WBXML/WBXML/Decoder.php,v 1.36 2006/01/01 21:10:25 jan Exp $ + * From Binary XML Content Format Specification Version 1.3, 25 July 2001 + * found at http://www.wapforum.org * - * Copyright 2003-2006 Anthony Mills + * $Horde: framework/XML_WBXML/WBXML/Decoder.php,v 1.22.10.11 2008/01/02 11:31:02 jan Exp $ * - * See the enclosed file COPYING for license information (LGPL). If you + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * - * From Binary XML Content Format Specification Version 1.3, 25 July - * 2001 found at http://www.wapforum.org - * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { @@ -84,7 +85,8 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { * * @param XML_WBXML_ContentHandler $ch The contentHandler */ - function setContentHandler(&$ch) { + function setContentHandler(&$ch) + { $this->_ch = &$ch; } /** @@ -96,7 +98,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { { $value = $input{$this->_strpos++}; $value = ord($value); - + return $value; } @@ -133,12 +135,8 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { */ function decode($wbxml) { - // fix for Nokia Series 60 which seem to send empty data block sometimes - if(strlen($wbxml) == 0) { - return true; - } - $this->_error = false; // reset state + $this->_strpos = 0; if (empty($this->_ch)) { @@ -149,6 +147,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { // version = u_int8 // currently 1, 2 or 3 $this->_wbxmlVersion = $this->getVersionNumber($wbxml); + #Horde::logMessage("WBXML[" . $this->_strpos . "] version " . $this->_wbxmlVersion, __FILE__, __LINE__, PEAR_LOG_DEBUG); // Get Document Public Idetifier from Section 5.5 // publicid = mb_u_int32 | (zero index) @@ -156,9 +155,11 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { // Containing the value zero (0) // The actual DPI is determined after the String Table is read. $dpiStruct = $this->getDocumentPublicIdentifier($wbxml); + // Get Charset from 5.6 // charset = mb_u_int32 $this->_charset = $this->getCharset($wbxml); + #Horde::logMessage("WBXML[" . $this->_strpos . "] charset " . $this->_charset, __FILE__, __LINE__, PEAR_LOG_DEBUG); // Get String Table from 5.7 // strb1 = length *byte @@ -166,8 +167,8 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { // Get Document Public Idetifier from Section 5.5. $this->_dpi = $this->getDocumentPublicIdentifierImpl($dpiStruct['dpiType'], - $dpiStruct['dpiNumber'], - $this->_stringTable); + $dpiStruct['dpiNumber']); + #$this->_stringTable); // Now the real fun begins. // From Sections 5.2 and 5.8 @@ -180,8 +181,8 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { $this->_tagDTD = $this->_dtdManager->getInstance($this->_dpi); if (!$this->_tagDTD) { - return $this->raiseError('No DTD found for ' - . $this->_dpi . '/' + return $this->raiseError('No DTD found for ' + . $this->_dpi . '/' . $dpiStruct['dpiNumber']); } @@ -204,6 +205,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { function getDocumentPublicIdentifier($input) { $i = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + if ($i == 0) { return array('dpiType' => 2, 'dpiNumber' => $this->getByte($input)); @@ -218,6 +220,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { if ($dpiType == 1) { return XML_WBXML::getDPIString($dpiNumber); } else { + #Horde::logMessage("WBXML string table $dpiNumber:\n" . print_r($this->_stringTable, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); return $this->getStringTableEntry($dpiNumber); } } @@ -235,7 +238,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { } /** - * Retrieves the string table. + * Retrieves the string table. * The string table consists of an mb_u_int32 length * and then length bytes forming the table. * References to the string table refer to the @@ -254,10 +257,10 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { { if ($index >= strlen($this->_stringTable)) { $this->_error = - $this->_ch->raiseError('Invalid offset ' . $index - . ' value encountered around position ' - . $this->_strpos - . '. Broken wbxml?'); + $this->raiseError('Invalid offset ' . $index + . ' value encountered around position ' + . $this->_strpos + . '. Broken wbxml?'); return ''; } @@ -270,17 +273,17 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { if (ord($ch) == 0) { return ''; // don't return '#' } - + while (ord($ch) != 0) { $str[$i++] = $ch; if ($index >= strlen($this->_stringTable)) { - break; + break; } $ch = $this->_stringTable[$index++]; } // print "string table entry: $str\n"; return $str; - + } function _decode($input) @@ -288,7 +291,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { $token = $this->getByte($input); $str = ''; - #print "position: " . $this->_strpos . " token: " . $token . " str10: " . substr($input, $this->_strpos, 10) . "\n"; // @todo: remove debug output + // print "position: " . $this->_strpos . " token: " . $token . " str10: " . substr($input, $this->_strpos, 10) . "\n"; // @todo: remove debug output switch ($token) { case XML_WBXML_GLOBAL_TOKEN_STR_I: @@ -370,34 +373,36 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { case XML_WBXML_GLOBAL_TOKEN_OPAQUE: // Section 5.8.4.6 $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); - // print "opaque of size $size\n"; // @todo remove debug - $b = $this->_substr($input, $this->_strpos, $size); - #$b = mb_substr($input, $this->_strpos, $size, 'ISO-8859-1'); - $this->_strpos += $size; + if ($size > 0) { + #Horde::logMessage("WBXML opaque document size=$size, next=" . ord($input{$this->_strpos}), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $b = $this->_substr($input, $this->_strpos, $size); + // print "opaque of size $size: ($b)\n"; // @todo remove debug + $this->_strpos += $size; + // opaque data inside a element may or may not be + // a nested wbxml document (for example devinf data). + // We find out by checking the first byte of the data: if it's + // 1, 2 or 3 we expect it to be the version number of a wbxml + // document and thus start a new wbxml decoder instance on it. - // opaque data inside a element may or may not be - // a nested wbxml document (for example devinf data). - // We find out by checking the first byte of the data: if it's - // 1, 2 or 3 we expect it to be the version number of a wbxml - // document and thus start a new wbxml decoder instance on it. - - if ($this->_isData && ord($b) <= 10) { - $decoder = new XML_WBXML_Decoder(true); - $decoder->setContentHandler($this->_ch); - $s = $decoder->decode($b); - // /* // @todo: FIXME currently we can't decode Nokia - // DevInf data. So ignore error for the time beeing. - if (is_a($s, 'PEAR_Error')) { - $this->_error = $s; - return; + if ($this->_isData && ord($b) < 10) { + #Horde::logMessage("WBXML opaque document size=$size, \$b[0]=" . ord($b), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $decoder = new XML_WBXML_Decoder(true); + $decoder->setContentHandler($this->_ch); + $s = $decoder->decode($b); + // /* // @todo: FIXME currently we can't decode Nokia + // DevInf data. So ignore error for the time beeing. + if (is_a($s, 'PEAR_Error')) { + $this->_error = $s; + return; + } + // */ + // $this->_ch->characters($s); + } else { + /* normal opaque behaviour: just copy the raw data: */ + // print "opaque handled as string=$b\n"; // @todo remove debug + $this->_ch->characters($b); } - // */ - // $this->_ch->characters($s); - } else { - /* normal opaque behaviour: just copy the raw data: */ - $this->_ch->characters( $b); } - // old approach to deal with opaque data inside ContentHandler: // FIXME Opaque is used by SYNCML. Opaque data that depends on the context // if (contentHandler instanceof OpaqueContentHandler) { @@ -650,7 +655,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { */ function termstr($input) { - $str = '#'; // must start with nonempty string to allow array access + $str = '#'; // must start with nonempty string to allow array access $i = 0; $ch = $input[$this->_strpos++]; if (ord($ch) == 0) { @@ -679,6 +684,5 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { } return $ret; } - } diff --git a/phpgwapi/inc/horde/XML/WBXML/Encoder.php b/phpgwapi/inc/horde/XML/WBXML/Encoder.php index 2633302443..39860df82f 100644 --- a/phpgwapi/inc/horde/XML/WBXML/Encoder.php +++ b/phpgwapi/inc/horde/XML/WBXML/Encoder.php @@ -6,16 +6,17 @@ include_once 'XML/WBXML/DTDManager.php'; include_once 'Horde/String.php'; /** - * $Horde: framework/XML_WBXML/WBXML/Encoder.php,v 1.39 2006/01/01 21:10:25 jan Exp $ + * From Binary XML Content Format Specification Version 1.3, 25 July 2001 + * found at http://www.wapforum.org * - * Copyright 2003-2006 Anthony Mills + * $Horde: framework/XML_WBXML/WBXML/Encoder.php,v 1.25.10.17 2008/08/26 15:41:21 jan Exp $ * - * See the enclosed file COPYING for license information (LGPL). If you + * Copyright 2003-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * - * From Binary XML Content Format Specification Version 1.3, 25 July - * 2001 found at http://www.wapforum.org - * + * @author Anthony Mills * @package XML_WBXML */ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { @@ -38,7 +39,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { var $_subParser = null; var $_subParserStack = 0; - + /** * The XML parser. * @@ -97,7 +98,10 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { function writeHeader($uri) { $this->_dtd = &$this->_dtdManager->getInstanceURI($uri); - + if (!$this->_dtd) { + // TODO: proper error handling + die('Unable to find dtd for ' . $uri); + } $dpiString = $this->_dtd->getDPI(); // Set Version Number from Section 5.4 @@ -132,7 +136,11 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { function writeDocumentPublicIdentifier($dpiString, &$strings) { - $i = XML_WBXML::getDPIInt($dpiString); + $i = 0; + + // The OMA test suite doesn't like DPI as integer code. + // So don't try lookup and always send full DPI string. + // $i = XML_WBXML::getDPIInt($dpiString); if ($i == 0) { $strings[0] = $dpiString; @@ -191,7 +199,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { function _getBytes($string, $cs) { - $string = String::convertCharset($string, $cs, 'utf-8'); + $string = String::convertCharset($string, $cs, 'utf-8'); $nbytes = strlen($string); $bytes = array(); @@ -210,8 +218,10 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { return array($uri, $name); } - function startElement($uri, $name, $attributes) + function startElement($uri, $name, $attributes = array()) { + #Horde::logMessage("WBXML Encoder $uri, " . ($this->_hasWrittenHeader ? 'true' : 'false'), __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($this->_subParser == null) { if (!$this->_hasWrittenHeader) { $this->writeHeader($uri); @@ -222,11 +232,11 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { if ($this->_subParser == null) { $this->writeTag($name, $attributes, true, $this->_charset); } else { - $this->_subParser->startElement($uri,$name, $attributes); + $this->_subParser->startElement($uri, $name, $attributes); } } else { $this->_subParserStack++; - $this->_subParser->startElement($uri,$name,$attributes); + $this->_subParser->startElement($uri, $name, $attributes); } } @@ -237,12 +247,12 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { $this->startElement($uri, $name, $attributes); } - function opaque($bytes) + function opaque($o) { if ($this->_subParser == null) { $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_OPAQUE); - XML_WBXML::intToMBUInt32($this->_output, count($bytes)); - $this->_output .= $bytes; + XML_WBXML::intToMBUInt32($this->_output, strlen($o)); + $this->_output .= $o; } } @@ -274,7 +284,6 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { function writeTag($name, $attrs, $hasContent, $cs) { - if ($attrs != null && !count($attrs)) { $attrs = null; } @@ -309,7 +318,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { } } - if ($attrs != null && is_array($attrs) && count($attrs) > 0 ) { + if ($attrs != null && is_array($attrs) && count($attrs) > 0) { $this->writeAttributes($attrs, $cs); } } @@ -376,11 +385,16 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { function changecodepage($uri) { - // @todo: this is a hack! - // what's the reason for this hack???? Lars - if ($uri != 'syncml:devinf' && $uri != 'syncml:metinf' && $uri != 'syncml:syncml1.0' && !preg_match('/1\.1$/', $uri)) { + if ($this->_dtd->getVersion() == 2 && !preg_match('/1\.2$/', $uri)) { + $uri .= '1.2'; + } + if ($this->_dtd->getVersion() == 1 && !preg_match('/1\.1$/', $uri)) { $uri .= '1.1'; } + if ($this->_dtd->getVersion() == 0 && !preg_match('/1\.0$/', $uri)) { + $uri .= '1.0'; + } + $cp = $this->_dtd->toCodePageURI($uri); if (strlen($cp)) { $this->_dtd = &$this->_dtdManager->getInstanceURI($uri); diff --git a/phpgwapi/inc/horde/config/conf.php b/phpgwapi/inc/horde/config/conf.php index 1e981e0ee9..27ac1dc063 100644 --- a/phpgwapi/inc/horde/config/conf.php +++ b/phpgwapi/inc/horde/config/conf.php @@ -21,7 +21,7 @@ $conf['auth']['driver'] = 'auto'; $conf['log']['priority'] = PEAR_LOG_DEBUG; $conf['log']['ident'] = 'EGWSYNC'; $conf['log']['params'] = array(); -$conf['log']['name'] = '/tmp/egroupware_syncml.log'; +$conf['log']['name'] = '/tmp/egroupware_syncml-1.6.log'; $conf['log']['params']['append'] = true; $conf['log']['type'] = 'error_log'; $conf['log']['enabled'] = true; diff --git a/phpgwapi/inc/horde/lib/core.php b/phpgwapi/inc/horde/lib/core.php index c3fe658592..1eac9a0eb7 100644 --- a/phpgwapi/inc/horde/lib/core.php +++ b/phpgwapi/inc/horde/lib/core.php @@ -21,8 +21,16 @@ ini_set('magic_quotes_runtime', 0); * include_path, you must add an ini_set() call here to add their location to * the include_path. */ // ini_set('include_path', dirname(__FILE__) . PATH_SEPARATOR . ini_get('include_path')); -set_include_path(dirname(__FILE__). '/../../horde/' . PATH_SEPARATOR . dirname(__FILE__). '/../../../../egw-pear/' . PATH_SEPARATOR . get_include_path()); +//set_include_path(dirname(__FILE__). '/../../horde/' . PATH_SEPARATOR . dirname(__FILE__). '/../../../../egw-pear/' . PATH_SEPARATOR . get_include_path()); +@define('EGW_BASE', dirname(dirname(__FILE__) . '/../../../../rpc.php')); +// Check for a prior definition of HORDE_BASE (perhaps by an +// auto_prepend_file definition for site customization). +if (!defined('HORDE_BASE')) { + @define('HORDE_BASE', EGW_BASE . '/phpgwapi/inc/horde/'); +} + +set_include_path(HORDE_BASE . PATH_SEPARATOR . EGW_BASE . '/egw-pear/' . PATH_SEPARATOR . get_include_path()); /* PEAR base class. */ include_once 'PEAR.php'; @@ -30,14 +38,17 @@ include_once 'PEAR.php'; include_once 'Horde.php'; include_once 'Horde/Registry.php'; #include_once 'Horde/DataTree.php'; -#include_once 'Horde/String.php'; +include_once 'Horde/String.php'; +include_once 'Horde/Date.php'; include_once 'Horde/NLS.php'; -#include_once 'Horde/Notification.php'; -#include_once 'Horde/Auth.php'; -#include_once 'Horde/Browser.php'; -#include_once 'Horde/Perms.php'; +include_once 'Horde/iCalendar.php'; +//include_once 'Horde/Notification.php'; +//include_once 'Horde/Auth.php'; +//include_once 'Horde/Browser.php'; +//include_once 'Horde/Perms.php'; -#/* Browser detection object. */ -#if (class_exists('Browser')) { -# $browser = &Browser::singleton(); -#} +/* Browser detection object. * +if (class_exists('Browser')) { + $browser = &Browser::singleton(); +} +*/ \ No newline at end of file