From d43bf7d44fc14c9a3e208d6a5f4f5edbd738ef2e Mon Sep 17 00:00:00 2001 From: Lars Kneschke Date: Sun, 19 Jun 2005 19:00:58 +0000 Subject: [PATCH] first commit of syncml code --- phpgwapi/inc/horde/Horde.php | 1474 +++++++++++++++++ phpgwapi/inc/horde/Horde/Browser.php | 1082 ++++++++++++ phpgwapi/inc/horde/Horde/Browser/imode.php | 412 +++++ phpgwapi/inc/horde/Horde/NLS.php | 523 ++++++ phpgwapi/inc/horde/Horde/RPC.php | 261 +++ phpgwapi/inc/horde/Horde/RPC/syncml.php | 280 ++++ phpgwapi/inc/horde/Horde/RPC/syncml_wbxml.php | 110 ++ phpgwapi/inc/horde/Horde/Registry.php | 1028 ++++++++++++ phpgwapi/inc/horde/Horde/String.php | 555 +++++++ phpgwapi/inc/horde/Horde/SyncML.php | 611 +++++++ phpgwapi/inc/horde/Horde/SyncML/Command.php | 76 + .../inc/horde/Horde/SyncML/Command/Alert.php | 364 ++++ .../inc/horde/Horde/SyncML/Command/Final.php | 34 + .../inc/horde/Horde/SyncML/Command/Get.php | 101 ++ .../inc/horde/Horde/SyncML/Command/Map.php | 180 ++ .../inc/horde/Horde/SyncML/Command/Put.php | 157 ++ .../horde/Horde/SyncML/Command/Replace.php | 25 + .../horde/Horde/SyncML/Command/Results.php | 229 +++ .../inc/horde/Horde/SyncML/Command/Status.php | 251 +++ .../inc/horde/Horde/SyncML/Command/Sync.php | 222 +++ .../horde/Horde/SyncML/Command/Sync/Add.php | 31 + .../Command/Sync/ContentSyncElement.php | 144 ++ .../Horde/SyncML/Command/Sync/Delete.php | 32 + .../Horde/SyncML/Command/Sync/Replace.php | 32 + .../Horde/SyncML/Command/Sync/SyncElement.php | 123 ++ phpgwapi/inc/horde/Horde/SyncML/State.php | 865 ++++++++++ phpgwapi/inc/horde/Horde/SyncML/State_egw.php | 393 +++++ phpgwapi/inc/horde/Horde/SyncML/Sync.php | 184 ++ .../SyncML/Sync/OneWayFromClientSync.php | 23 + .../SyncML/Sync/OneWayFromServerSync.php | 79 + .../SyncML/Sync/RefreshFromClientSync.php | 34 + .../SyncML/Sync/RefreshFromServerSync.php | 64 + .../inc/horde/Horde/SyncML/Sync/SlowSync.php | 109 ++ .../horde/Horde/SyncML/Sync/TwoWaySync.php | 242 +++ phpgwapi/inc/horde/Horde/Util.php | 827 +++++++++ phpgwapi/inc/horde/Horde/iCalendar.php | 1164 +++++++++++++ phpgwapi/inc/horde/Horde/iCalendar/valarm.php | 34 + phpgwapi/inc/horde/Horde/iCalendar/vcard.php | 130 ++ phpgwapi/inc/horde/Horde/iCalendar/vevent.php | 230 +++ .../inc/horde/Horde/iCalendar/vfreebusy.php | 290 ++++ .../inc/horde/Horde/iCalendar/vjournal.php | 34 + phpgwapi/inc/horde/Horde/iCalendar/vnote.php | 49 + .../inc/horde/Horde/iCalendar/vtimezone.php | 78 + phpgwapi/inc/horde/Horde/iCalendar/vtodo.php | 90 + phpgwapi/inc/horde/XML/WBXML.php | 286 ++++ .../inc/horde/XML/WBXML/ContentHandler.php | 153 ++ phpgwapi/inc/horde/XML/WBXML/DTD.php | 150 ++ phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php | 85 + .../inc/horde/XML/WBXML/DTD/SyncMLDevInf.php | 70 + .../inc/horde/XML/WBXML/DTD/SyncMLMetInf.php | 51 + phpgwapi/inc/horde/XML/WBXML/DTDManager.php | 56 + phpgwapi/inc/horde/XML/WBXML/Decoder.php | 618 +++++++ phpgwapi/inc/horde/XML/WBXML/Encoder.php | 486 ++++++ phpgwapi/inc/horde/config/conf.php | 44 + phpgwapi/inc/horde/config/nls.php | 594 +++++++ phpgwapi/inc/horde/config/registry.php | 102 ++ phpgwapi/inc/horde/lib/base.php | 54 + phpgwapi/inc/horde/lib/core.php | 43 + 58 files changed, 16048 insertions(+) create mode 100644 phpgwapi/inc/horde/Horde.php create mode 100644 phpgwapi/inc/horde/Horde/Browser.php create mode 100644 phpgwapi/inc/horde/Horde/Browser/imode.php create mode 100644 phpgwapi/inc/horde/Horde/NLS.php create mode 100644 phpgwapi/inc/horde/Horde/RPC.php create mode 100644 phpgwapi/inc/horde/Horde/RPC/syncml.php create mode 100644 phpgwapi/inc/horde/Horde/RPC/syncml_wbxml.php create mode 100644 phpgwapi/inc/horde/Horde/Registry.php create mode 100644 phpgwapi/inc/horde/Horde/String.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Final.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Get.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Map.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Put.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Replace.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Results.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Status.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Add.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Replace.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElement.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/State.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/State_egw.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromClientSync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromServerSync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php create mode 100644 phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php create mode 100644 phpgwapi/inc/horde/Horde/Util.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/valarm.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vcard.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vevent.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vjournal.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vnote.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php create mode 100644 phpgwapi/inc/horde/Horde/iCalendar/vtodo.php create mode 100644 phpgwapi/inc/horde/XML/WBXML.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/ContentHandler.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/DTD.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/DTDManager.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/Decoder.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/Encoder.php create mode 100644 phpgwapi/inc/horde/config/conf.php create mode 100644 phpgwapi/inc/horde/config/nls.php create mode 100644 phpgwapi/inc/horde/config/registry.php create mode 100644 phpgwapi/inc/horde/lib/base.php create mode 100644 phpgwapi/inc/horde/lib/core.php diff --git a/phpgwapi/inc/horde/Horde.php b/phpgwapi/inc/horde/Horde.php new file mode 100644 index 0000000000..eebf5afb17 --- /dev/null +++ b/phpgwapi/inc/horde/Horde.php @@ -0,0 +1,1474 @@ + + * Copyright 1999-2005 Jon Parise + * + * 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 Chuck Hagenbuch + * @author Jon Parise + * @since Horde 1.3 + * @package Horde_Framework + */ +class Horde { + + /** + * Logs a message to the global Horde log backend. + * + * @access public + * + * @param mixed $message Either a string or a PEAR_Error object. + * @param string $file What file was the log function called from + * (e.g. __FILE__)? + * @param integer $line What line was the log function called from + * (e.g. __LINE__)? + * @param integer $priority The priority of the message. One of: + *
+     * PEAR_LOG_EMERG
+     * PEAR_LOG_ALERT
+     * PEAR_LOG_CRIT
+     * PEAR_LOG_ERR
+     * PEAR_LOG_WARNING
+     * PEAR_LOG_NOTICE
+     * PEAR_LOG_INFO
+     * PEAR_LOG_DEBUG
+     * 
+ */ + function logMessage($message, $file, $line, $priority = PEAR_LOG_INFO) + { + global $conf; + + if (!$conf['log']['enabled']) { + return; + } + + if ($priority > $conf['log']['priority']) { + return; + } + + $logger = &Horde::getLogger(); + if (!is_a($logger, 'Log')) { + Horde::fatal(PEAR::raiseError('An error has occurred. Furthermore, Horde encountered an error attempting to log this error. Please check your Horde logging configuration in horde/config/conf.php.'), __FILE__, __LINE__, false); + } + + if (is_a($message, 'PEAR_Error')) { + $userinfo = $message->getUserInfo(); + $message = $message->getMessage(); + if (!empty($userinfo)) { + if (is_array($userinfo)) { + $userinfo = implode(', ', $userinfo); + } + $message .= ': ' . $userinfo; + } + } elseif (is_callable(array($message, 'getMessage'))) { + $message = $message->getMessage(); + } + + $app = isset($GLOBALS['registry']) ? $GLOBALS['registry']->getApp() : 'horde'; + $message = '[' . $app . '] ' . $message . ' [on line ' . $line . ' of "' . $file . '"]'; + + /* Make sure to log in the system's locale. */ + $locale = setlocale(LC_TIME, 0); + setlocale(LC_TIME, 'C'); + + $logger->log($message, $priority); + + /* Restore original locale. */ + setlocale(LC_TIME, $locale); + + return true; + } + + function &getLogger() + { + global $conf; + + if (empty($conf['log']['enabled'])) { + return false; + } + + static $logcheck; + if (!isset($logcheck)) { + // Try to make sure that we can log messages somehow. + if (empty($conf['log']) || + empty($conf['log']['type']) || + empty($conf['log']['name']) || + empty($conf['log']['ident']) || + !isset($conf['log']['params'])) { + Horde::fatal(PEAR::raiseError('Horde is not correctly configured to log error messages. You must configure at least a text file log in horde/config/conf.php.'), __FILE__, __LINE__, false); + } + $logcheck = true; + } + + return $logger = &Log::singleton($conf['log']['type'], $conf['log']['name'], + $conf['log']['ident'], $conf['log']['params']); + } + + /** + * Destroys any existing session on login and make sure to use a new + * session ID, to avoid session fixation issues. Should be called before + * checking a login. + * + * @access public + */ + function getCleanSession() + { + // Make sure to force a completely new session ID and clear + // all session data. + if (version_compare(phpversion(), '4.3.3') !== -1) { + session_regenerate_id(); + session_unset(); + } else { + @session_destroy(); + if (Util::extensionExists('posix')) { + $new_session_id = md5(microtime() . posix_getpid()); + } else { + $new_session_id = md5(uniqid(mt_rand(), true)); + } + session_id($new_session_id); + + // Restart the session, including setting up the session + // handler. + Horde::setupSessionHandler(); + @session_start(); + } + } + + /** + * Aborts with a fatal error, displaying debug information to the user. + * + * @access public + * + * @param mixed $error A PEAR_Error object with debug information or an + * error message. + * @param integer $file The file in which the error occured. + * @param integer $line The line on which the error occured. + * @param boolean $log Log this message via Horde::logMesage()? + */ + function fatal($error, $file, $line, $log = true) + { + @include_once 'Horde/Auth.php'; + @include_once 'Horde/CLI.php'; + + $admin = class_exists('Auth') && Auth::isAdmin(); + $cli = class_exists('Horde_CLI') && Horde_CLI::runningFromCLI(); + + $errortext = '

' . _("A fatal error has occurred") . '

'; + if (is_a($error, 'PEAR_Error')) { + $info = array_merge(array('file' => 'conf.php', 'variable' => '$conf'), + array($error->getUserInfo())); + + switch ($error->getCode()) { + case HORDE_ERROR_DRIVER_CONFIG_MISSING: + $message = sprintf(_("No configuration information specified for %s."), $info['name']) . '
' . + sprintf(_("The file %s should contain some %s settings."), + $GLOBALS['registry']->get('fileroot') . '/config/' . $info['file'], + sprintf("%s['%s']['params']", $info['variable'], $info['driver'])); + break; + + case HORDE_ERROR_DRIVER_CONFIG: + $message = sprintf(_("Required '%s' not specified in %s configuration."), $info['field'], $info['name']) . '
' . + sprintf(_("The file %s should contain a %s setting."), + $GLOBALS['registry']->get('fileroot') . '/config/' . $info['file'], + sprintf("%s['%s']['params']['%s']", $info['variable'], $info['driver'], $info['field'])); + break; + + default: + $message = $error->getMessage(); + break; + } + + $errortext .= '

' . htmlspecialchars($message) . '

'; + } elseif (is_object($error) && method_exists($error, 'getMessage')) { + $errortext .= '

' . htmlspecialchars($error->getMessage()) . '

'; + } elseif (is_string($error)) { + $errortext .= '

' . $error . '

'; + } + + if ($admin) { + $errortext .= '

' . sprintf(_("[line %s of %s]"), $line, $file) . '

'; + if (is_object($error)) { + $errortext .= '

' . _("Details (also in Horde's logfile):") . '

'; + $errortext .= '

' . htmlspecialchars(Util::bufferOutput('var_dump', $error)) . '

'; + } + } elseif ($log) { + $errortext .= '

' . _("Details have been logged for the administrator.") . '

'; + } + + // Log the error via Horde::logMessage() if requested. + if ($log) { + Horde::logMessage($error, $file, $line, PEAR_LOG_EMERG); + } + + if ($cli) { + echo strip_tags(str_replace(array('
', '

', '

', '

', '

', '

', '

'), "\n", $errortext)); + } else { + echo <<< HTML + +Horde :: Fatal Error +$errortext + +HTML; + } + exit; + } + + /** + * Adds the javascript code to the output (if output has already started) + * or to the list of script files to include via includeScriptFiles(). + * + * @access public + * + * @param string $file The full javascript file name. + * @param string $app The application name. Defaults to the current + * application. + * @param boolean $direct Include the file directly without passing it + * through javascript.php? + */ + function addScriptFile($file, $app = null, $direct = false) + { + global $registry; + static $included = array(); + + if (empty($app)) { + $app = $registry->getApp(); + } + + // Don't include scripts multiple times. + if (!empty($included[$app][$file])) { + return; + } + $included[$app][$file] = true; + + if (ob_get_length() || headers_sent()) { + if ($direct) { + $url = Horde::url($file{0} == '/' ? $registry->get('webroot', $app) . $file : $registry->get('jsuri', $app) . '/' . $file); + } else { + $url = Horde::url($registry->get('webroot', 'horde') . '/services/javascript.php'); + $url = Util::addParameter($url, array('file' => $file, + 'app' => $app)); + } + echo ''; + } else { + global $_horde_script_files; + $_horde_script_files[$app][] = array($file, $direct); + } + } + + /** + * Includes javascript files that were needed before any headers were sent. + * + * @access public + */ + function includeScriptFiles() + { + global $_horde_script_files, $registry; + + if (!empty($_horde_script_files)) { + $base_url = Horde::url($registry->get('webroot', 'horde') . '/services/javascript.php'); + foreach ($_horde_script_files as $app => $files) { + foreach ($files as $file) { + if (!empty($file[1])) { + $url = $file[0]{0} == '/' ? $registry->get('webroot', $app) . $file[0] : $registry->get('jsuri', $app) . '/' . $file[0]; + echo '\n"; + } else { + $url = Util::addParameter($base_url, array('file' => $file[0], + 'app' => $app)); + echo '\n"; + } + } + } + } + } + + /** + * Includes javascript files that were needed before any headers were sent. + * + * @access public + */ + function inlineScriptFiles() + { + global $_horde_script_files, $registry; + + if (!empty($_horde_script_files)) { + $jsWrapper = $registry->get('fileroot', 'horde') . '/services/javascript.php'; + foreach ($_horde_script_files as $app => $files) { + foreach ($files as $file) { + if (!empty($file[1])) { + @readfile($file[0]{0} == '/' ? $registry->get('fileroot', $app) . $file[0] : $registry->get('jsfs', $app) . '/' . $file[0]); + } else { + $file = $file[0]; + require $jsWrapper; + } + } + } + } + } + + /** + * Checks if link should be shown and return the nescessary code. + * + * @access public + * + * @param string $type Type of link to display + * @param string $app The name of the current Horde application. + * @param boolean $override Override Horde settings? + * @param boolean $referrer Include the current page as the referrer (url=)? + * + * @return string The HTML to create the link. + */ + function getServiceLink($type, $app, $override = false, $referrer = true) + { + if (!Horde::showService($type) && !$override) { + return false; + } + + switch ($type) { + case 'help': + if ($GLOBALS['browser']->hasFeature('javascript')) { + Horde::addScriptFile('open_help_win.js', 'horde'); + return "javascript:open_help_win('$app');"; + } else { + $url = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/help/', true); + return Util::addParameter($url, array('module' => $app, + 'show' => 'topics')); + } + break; + + case 'problem': + return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/problem.php?return_url=' . urlencode(Horde::selfUrl(true))); + + case 'logout': + return Auth::addLogoutParameters(Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/login.php'), AUTH_REASON_LOGOUT); + + case 'login': + return Auth::getLoginScreen('', $referrer ? Horde::selfUrl(true) : null); + + case 'options': + global $conf; + if (($conf['prefs']['driver'] != '') && ($conf['prefs']['driver'] != 'none')) { + return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/prefs.php?app=' . $app); + } + break; + } + + return false; + } + + /** + * @access public + * + * @param string $type The type of link. + * + * @return boolean True if the link is to be shown. + */ + function showService($type) + { + global $conf; + + if (empty($conf['menu']['links'][$type])) { + return false; + } + + switch ($conf['menu']['links'][$type]) { + case 'all': + return true; + + case 'never': + return false; + + case 'authenticated': + return (bool)Auth::getAuth(); + + default: + return false; + } + } + + /** + * Returns the driver parameters for the specified backend. + * + * @param mixed $backend The backend system (e.g. 'prefs', 'categories', + * 'contacts') being used. + * The used configuration array will be + * $conf[$backend]. If an array gets passed, it will + * be $conf[$key1][$key2]. + * @param string $type The type of driver. + * + * @return array The connection parameters. + */ + function getDriverConfig($backend, $type = 'sql') + { + global $conf; + + $c = null; + if (is_array($backend)) { + require_once 'Horde/Array.php'; + $c = Horde_Array::getElement($conf, $backend); + } elseif (isset($conf[$backend])) { + $c = $conf[$backend]; + } + if (!is_null($c) && isset($c['params'])) { + if (isset($conf[$type])) { + return array_merge($conf[$type], $c['params']); + } else { + return $c['params']; + } + } + + return isset($conf[$type]) ? $conf[$type] : array(); + } + + + /** + * Returns the VFS driver parameters for the specified backend. + * + * @param string $name The VFS system name (e.g. 'images', 'documents') + * being used. + * + * @return array A hash with the VFS parameters; the VFS driver in 'type' + * and the connection parameters in 'params'. + */ + function getVFSConfig($name) + { + global $conf; + + if (!isset($conf[$name]['type'])) { + return PEAR::raiseError(_("You must configure a VFS backend.")); + } + + if ($conf[$name]['type'] == 'horde') { + $vfs = $conf['vfs']; + } else { + $vfs = $conf[$name]; + } + + if ($vfs['type'] == 'sql') { + $vfs['params'] = Horde::getDriverConfig($name, 'sql'); + } + + return $vfs; + } + + /** + * Checks if all necessary parameters for a driver configuration + * are set and throws a fatal error with a detailed explaination + * how to fix this, if something is missing. + * + * @param array $params The configuration array with all parameters. + * @param string $driver The key name (in the configuration array) of + * the driver. + * @param array $fields An array with mandatory parameter names for + * this driver. + * @param string $name The clear text name of the driver. If not + * specified, the application name will be used. + * @param string $file The configuration file that should contain + * these settings. + * @param string $variable The name of the configuration variable. + */ + function assertDriverConfig($params, $driver, $fields, $name = null, + $file = 'conf.php', $variable = '$conf') + { + global $registry; + + // Don't generate a fatal error if we fail during or before + // Registry instantiation. + if (is_null($name)) { + $name = isset($registry) ? $registry->getApp() : '[unknown]'; + } + $fileroot = isset($registry) ? $registry->get('fileroot') : ''; + + if (!is_array($params) || !count($params)) { + Horde::fatal(PEAR::raiseError( + sprintf(_("No configuration information specified for %s."), $name) . "\n\n" . + sprintf(_("The file %s should contain some %s settings."), + $fileroot . '/config/' . $file, + sprintf("%s['%s']['params']", $variable, $driver))), + __FILE__, __LINE__); + } + + foreach ($fields as $field) { + if (!isset($params[$field])) { + Horde::fatal(PEAR::raiseError( + sprintf(_("Required '%s' not specified in %s configuration."), $field, $name) . "\n\n" . + sprintf(_("The file %s should contain a %s setting."), + $fileroot . '/config/' . $file, + sprintf("%s['%s']['params']['%s']", $variable, $driver, $field))), + __FILE__, __LINE__); + } + } + } + + /** + * Returns a session-id-ified version of $uri. + * If a full URL is requested, all parameter separators get converted to + * "&", otherwise to "&". + * + * @access public + * + * @param string $uri The URI to be modified. + * @param bool $full Generate a full (http://server/path/) URL. + * @param int $append_session 0 = only if needed, 1 = always, -1 = never. + * + * @return string The URL with the session id appended (if needed). + */ + function url($uri, $full = false, $append_session = 0, $force_ssl = false) + { + if ($force_ssl) { + $full = true; + } + + if ($full) { + global $conf, $registry, $browser; + + /* Store connection parameters in local variables. */ + $server_name = $conf['server']['name']; + $server_port = $conf['server']['port']; + + $protocol = 'http'; + if ($conf['use_ssl'] == 1) { + $protocol = 'https'; + } elseif ($conf['use_ssl'] == 2 && + $browser->usingSSLConnection()) { + $protocol = 'https'; + } elseif ($conf['use_ssl'] == 3) { + $server_port = ''; + if ($force_ssl) { + $protocol = 'https'; + } + } + + /* If using non-standard ports, add the port to the URL. */ + if (!empty($server_port) && + ((($protocol == 'http') && ($server_port != 80)) || + (($protocol == 'https') && ($server_port != 443)))) { + $server_name .= ':' . $server_port; + } + + /* Store the webroot in a local variable. */ + $webroot = $registry->get('webroot'); + + $url = $protocol . '://' . $server_name; + if (substr($uri, 0, 1) != '/') { + if (substr($webroot, -1) == '/') { + $url .= $webroot . $uri; + } else { + $url .= $webroot . '/' . $uri; + } + } else { + $url .= $uri; + } + } else { + $url = $uri; + } + + if (($append_session == 1) || + (($append_session == 0) && + !isset($_COOKIE[session_name()]))) { + $url = Util::addParameter($url, session_name(), session_id()); + } + + if ($full) { + /* We need to run the replace twice, because we only catch every + * second match. */ + return preg_replace(array('/(=?.*?)&(.*?=)/', + '/(=?.*?)&(.*?=)/'), + '$1&$2', $url); + } elseif (preg_match('/=.*&.*=/', $url)) { + return $url; + } else { + return htmlentities($url); + } + } + + /** + * Returns a session-id-ified version of $uri, using the current + * application's webroot setting. + * + * @access public + * + * @param string $uri The URI to be modified. + * @param bool $full Generate a full (http://server/path/) URL. + * @param int $append_session 0 = only if needed, 1 = always, -1 = never. + * + * @return string The url with the session id appended. + */ + function applicationUrl($uri, $full = false, $append_session = 0) + { + global $registry; + + /* Store the webroot in a local variable. */ + $webroot = $registry->get('webroot'); + + if ($full) { + return Horde::url($uri, $full, $append_session); + } elseif (substr($webroot, -1) == '/') { + return Horde::url($webroot . $uri, $full, $append_session); + } else { + return Horde::url($webroot . '/' . $uri, $full, $append_session); + } + } + + /** + * Returns an external link passed through the dereferer to strip session + * IDs from the referer. + * + * @param string $url The external URL to link to. + * @param boolean $tag If true, a complete tag is returned, only the + * url otherwise. + * + * @return string The correct link to the dereferer script. + */ + function externalUrl($url, $tag = false) + { + $ext = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . + '/services/go.php', true, -1); + + /* We must make sure there are no &'s in the URL. */ + $url = preg_replace(array('/(=?.*?)&(.*?=)/', '/(=?.*?)&(.*?=)/'), '$1&$2', $url); + $ext = Util::addParameter($ext, 'url', $url); + if ($tag) { + $ext = Horde::link($ext, $url, '', '_blank'); + } + return $ext; + } + + /** + * Returns a URL to be used for downloading, that takes into account any + * special browser quirks (i.e. IE's broken filename handling). + * + * @access public + * + * @param string $filename The filename of the download data. + * @param array $params Any additional parameters needed. + * @param string $url The URL to alter. If none passed in, will use + * the file 'view.php' located in the current + * module's base directory. + * + * @return string The download URL. + */ + function downloadUrl($filename, $params = array(), $url = null) + { + global $browser; + + $horde_url = false; + + if (is_null($url)) { + global $registry; + $url = Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/download/'), 'module', $registry->getApp()); + $horde_url = true; + } + + /* Add parameters. */ + if (!is_null($params)) { + foreach ($params as $key => $val) { + $url = Util::addParameter($url, $key, $val); + } + } + + /* If we are using the default Horde download link, add the + * filename to the end of the URL. Although not necessary for + * many browsers, this should allow every browser to download + * correctly. */ + if ($horde_url) { + $url = Util::addParameter($url, 'fn=/' . rawurlencode($filename)); + } elseif ($browser->hasQuirk('break_disposition_filename')) { + /* Some browsers will only obtain the filename correctly + * if the extension is the last argument in the query + * string and rest of the filename appears in the + * PATH_INFO element. */ + $filename = rawurlencode($filename); + + /* Get the webserver ID. */ + $server = Horde::webServerID(); + + /* Get the name and extension of the file. Apache 2 does + * NOT support PATH_INFO information being passed to the + * PHP module by default, so disable that + * functionality. */ + if (($server != 'apache2')) { + if (($pos = strrpos($filename, '.'))) { + $name = '/' . preg_replace('/\./', '%2E', substr($filename, 0, $pos)); + $ext = substr($filename, $pos); + } else { + $name = '/' . $filename; + $ext = ''; + } + + /* Enter the PATH_INFO information. */ + if (($pos = strpos($url, '?'))) { + $url = substr($url, 0, $pos) . $name . substr($url, $pos); + } else { + $url .= $name; + } + } + + /* Append the extension, if it exists. */ + if (($server == 'apache2') || !empty($ext)) { + $url = Util::addParameter($url, 'fn_ext=/' . $filename); + } + } + + return $url; + } + + /** + * Returns an anchor tag with the relevant parameters + * + * @access public + * + * @param string $url The full URL to be linked to + * @param string $status The JavaScript mouse-over string + * @param string $class The CSS class of the link + * @param string $target The window target to point to. + * @param string $onclick JavaScript action for the 'onclick' event. + * @param string $title The link title (tooltip). + * @param string $accesskey The access key to use. + * @param array $attributes Any other name/value pairs to add to the + * tag. + * + * @return string The full tag. + */ + function link($url, $status = '', $class = '', $target = '', $onclick = '', + $title = '', $accesskey = '', $attributes = array()) + { + $ret = " '', "\n" => '')), ENT_QUOTES, NLS::getCharset()) . '\'; return true;"'; + } + if (!empty($class)) { + $ret .= " class=\"$class\""; + } + if (!empty($target)) { + $ret .= " target=\"$target\""; + } + if (!empty($title)) { + $ret .= ' title="' . @htmlspecialchars($title, ENT_QUOTES, NLS::getCharset()) . '"'; + } + if (!empty($accesskey)) { + $ret .= ' accesskey="' . htmlspecialchars($accesskey) . '"'; + } + + foreach ($attributes as $name => $value) { + $ret .= ' ' . htmlspecialchars($name) . '="' . htmlspecialchars($value) . '"'; + } + + return "$ret>"; + } + + /** + * Returns an anchor sequence with the relevant parameters for a widget + * with accesskey and text. + * + * @access public + * + * @param string $url The full URL to be linked to + * @param string $status The JavaScript mouse-over string + * @param string $class The CSS class of the link + * @param string $target The window target to point to. + * @param string $onclick JavaScript action for the 'onclick' event. + * @param string $title The link title (tooltip). + * @param boolean $nocheck Don't check if the access key already has been + * used? + * + * @return string The full Title sequence. + */ + function widget($url, $status = '', $class = 'widget', $target = '', + $onclick = '', $title = '', $nocheck = false) + { + $ak = Horde::getAccessKey($title, $nocheck); + $plaintitle = Horde::stripAccessKey($title); + + return Horde::link($url, $status, $class, $target, $onclick, $plaintitle, $ak) . Horde::highlightAccessKey($title, $ak) . ''; + } + + /** + * Returns a session-id-ified version of $PHP_SELF. + * + * @access public + * + * @param boolean $query_string Include any QUERY_STRING? + * @param boolean $nocache Include a nocache parameter in the URL? + * @param boolean $full Return a full URL? + * + * @return string The requested URI. + */ + function selfUrl($query_string = false, $nocache = true, $full = false, + $force_ssl = false) + { + $url = $_SERVER['PHP_SELF']; + + if ($query_string && !empty($_SERVER['QUERY_STRING'])) { + $url .= '?' . $_SERVER['QUERY_STRING']; + } + + $url = Horde::url($url, $full, 0, $force_ssl); + + if ($nocache) { + return Util::nocacheUrl($url); + } else { + return $url; + } + } + + /** + * Constructs a correctly-pathed link to an image. + * + * @access public + * + * @param string $src The image file. + * @param optional string $alt Text describing the image. + * @param optional mixed $attr Any additional attributes for the image tag. + * Can be a pre-built string or an array of key/value + * pairs that will be assembled and html-encoded. + * @param optional string $dir The root graphics directory. + * + * @return string The full image tag. + */ + function img($src, $alt = '', $attr = '', $dir = null) + { + /* If browser does not support images, simply return the ALT text. */ + if (!$GLOBALS['browser']->hasFeature('images')) { + return @htmlspecialchars($alt, ENT_COMPAT, NLS::getCharset()); + } + + /* If no directory has been specified, get it from the registry. */ + if ($dir === null) { + global $registry; + $dir = $registry->getImageDir(); + } + + /* If a directory has been provided, prepend it to the image source. */ + if (!empty($dir)) { + $src = $dir . '/' . $src; + } + + /* Build all of the tag attributes. */ + $attributes = array('src' => $src, + 'alt' => $alt, + 'title' => $alt); + if (is_array($attr)) { + $attributes = array_merge($attributes, $attr); + } + + $img = ' $value) { + $img .= ' ' . $attribute . '="' . ($attribute == 'src' ? $value : @htmlspecialchars($value, ENT_COMPAT, NLS::getCharset())) . '"'; + } + + /* If the user supplied a pre-built string of attributes, add that. */ + if (is_string($attr) && !empty($attr)) { + $img .= ' ' . $attr; + } + + /* Return the closed image tag. */ + return $img . ' />'; + } + + /** + * Determines the location of the system temporary directory. If a specific + * setting cannot be found, it defaults to /tmp. + * + * @access public + * + * @return string A directory name which can be used for temp files. + * Returns false if one could not be found. + */ + function getTempDir() + { + global $conf; + + /* If one has been specifically set, then use that */ + if (!empty($conf['tmpdir'])) { + $tmp = $conf['tmpdir']; + } + + /* Next, try Util::getTempDir(). */ + if (empty($tmp)) { + $tmp = Util::getTempDir(); + } + + /* If it is still empty, we have failed, so return false; + * otherwise return the directory determined. */ + return empty($tmp) ? false : $tmp; + } + + /** + * Creates a temporary filename for the lifetime of the script, and + * (optionally) registers it to be deleted at request shutdown. + * + * @access public + * + * @param string $prefix Prefix to make the temporary name more + * recognizable. + * @param boolean $delete Delete the file at the end of the request? + * @param string $dir Directory to create the temporary file in. + * @param boolean $secure If deleting file, should we securely delete the + * file? + * + * @return string Returns the full path-name to the temporary file or + * false if a temporary file could not be created. + */ + function getTempFile($prefix = 'Horde', $delete = true, $dir = '', + $secure = false) + { + if (empty($dir) || !is_dir($dir)) { + $dir = Horde::getTempDir(); + } + + return Util::getTempFile($prefix, $delete, $dir, $secure); + } + + /** + * Starts output compression, if requested. + * + * @access public + * + * @since Horde 2.2 + */ + function compressOutput() + { + static $started; + + if (isset($started)) { + return; + } + + /* Compress output if requested and possible. */ + if ($GLOBALS['conf']['compress_pages'] && + !$GLOBALS['browser']->hasQuirk('buggy_compression') && + ini_get('zlib.output_compression') == '' && + ini_get('output_handler') != 'ob_gzhandler') { + if (ob_get_level()) { + ob_end_clean(); + } + ob_start('ob_gzhandler'); + } + + $started = true; + } + + /** + * Determines if output compression can be used. + * + * @access public + * + * @return boolean True if output compression can be used, false if not. + */ + function allowOutputCompression() + { + require_once 'Horde/Browser.php'; + $browser = &Browser::singleton(); + + /* Turn off compression for buggy browsers. */ + if ($browser->hasQuirk('buggy_compression')) { + return false; + } + + return (ini_get('zlib.output_compression') == '' && + ini_get('output_handler') != 'ob_gzhandler'); + } + + /** + * Returns the Web server being used. + * PHP string list built from the PHP 'configure' script. + * + * @access public + * + * @return string A web server identification string. + *
+     * 'aolserver' = AOL Server
+     * 'apache1'   = Apache 1.x
+     * 'apache2'   = Apache 2.x
+     * 'caudium'   = Caudium
+     * 'cgi'       = Unknown server - PHP built as CGI program
+     * 'cli'       = Command Line Interface build
+     * 'embed'     = Embedded PHP
+     * 'isapi'     = Zeus ISAPI
+     * 'milter'    = Milter
+     * 'nsapi'     = NSAPI
+     * 'phttpd'    = PHTTPD
+     * 'pi3web'    = Pi3Web
+     * 'roxen'     = Roxen/Pike
+     * 'servlet'   = Servlet
+     * 'thttpd'    = thttpd
+     * 'tux'       = Tux
+     * 'webjames'  = Webjames
+     * 
+ */ + function webServerID() + { + $server = php_sapi_name(); + + if ($server == 'apache') { + return 'apache1'; + } elseif (($server == 'apache2filter') || + ($server == 'apache2handler')) { + return 'apache2'; + } else { + return $server; + } + } + + /** + * Returns the tags for the CSS stylesheets. + * + * @access public + * + * @param string|array $app The Horde application(s). + * @param mixed $theme The theme to use; specify an empty value to + * retrieve the theme from user preferences, and + * false for no theme. + * @param boolean $inherit Inherit Horde-wide CSS? + * + * @return string tags for CSS stylesheets. + */ + function stylesheetLink($apps = null, $theme = '', $inherit = true) + { + if ($theme !== false && empty($theme)) { + $theme = $GLOBALS['prefs']->getValue('theme'); + } + + $css = array(); + $themes_fs = $GLOBALS['registry']->get('themesfs', 'horde'); + if ($inherit) { + $themes_uri = Horde::url($GLOBALS['registry']->get('themesuri', 'horde'), false, -1); + $css[] = $themes_uri . '/screen.css'; + if (!empty($theme) && + file_exists($themes_fs . '/' . $theme . '/screen.css')) { + $css[] = $themes_uri . '/' . $theme . '/screen.css'; + } + } + + if (!empty($apps)) { + if (!is_array($apps)) { + $apps = array($apps); + } + foreach ($apps as $app) { + if ($inherit && $app == 'horde') { + continue; + } + + $themes_fs = $GLOBALS['registry']->get('themesfs', $app); + if (file_exists($themes_fs . '/screen.css')) { + $themes_uri = Horde::url($GLOBALS['registry']->get('themesuri', $app), false, -1); + $css[] = $themes_uri . '/screen.css'; + } + if (!empty($theme) && + file_exists($themes_fs . '/' . $theme . '/screen.css')) { + $css[] = $themes_uri . '/' . $theme . '/screen.css'; + } + } + } + + $html = ''; + foreach ($css as $css_link) { + $html .= '' . "\n"; + } + + /* Load IE PNG transparency code if needed. */ + if ($GLOBALS['browser']->hasQuirk('png_transparency') && + $GLOBALS['prefs']->getValue('alpha_filter')) { + $url = Horde::url($GLOBALS['registry']->get('jsuri', 'horde') . '/alphaImageLoader.php', true, -1); + $html .= ''; + } + + /* Load browser specific stylesheets if needed. */ + if ($GLOBALS['browser']->isBrowser('msie')) { + $html .= '' . "\n"; + if ($GLOBALS['browser']->getPlatform() == 'mac') { + $html .= '' . "\n"; + } + } + if ($GLOBALS['browser']->isBrowser('opera')) { + $html .= '' . "\n"; + } + if (strpos(strtolower($GLOBALS['browser']->getAgentString()), 'safari') !== false) { + $html .= '' . "\n"; + } + + return $html; + } + + /** + * Sets a custom session handler up, if there is one. + * + * @access public + */ + function setupSessionHandler() + { + global $conf; + + ini_set('url_rewriter.tags', 0); + session_set_cookie_params($conf['session']['timeout'], + $conf['cookie']['path'], $conf['cookie']['domain'], $conf['use_ssl'] == 1 ? 1 : 0); + session_cache_limiter($conf['session']['cache_limiter']); + session_name(urlencode($conf['session']['name'])); + + $type = !empty($conf['sessionhandler']['type']) ? $conf['sessionhandler']['type'] : 'none'; + + if ($type == 'external') { + $calls = $conf['sessionhandler']['params']; + session_set_save_handler($calls['open'], + $calls['close'], + $calls['read'], + $calls['write'], + $calls['destroy'], + $calls['gc']); + } elseif ($type != 'none') { + global $_session_handler; + require_once 'Horde/SessionHandler.php'; + $_session_handler = &SessionHandler::singleton($conf['sessionhandler']['type']); + if (!empty($_session_handler) && + !is_a($_session_handler, 'PEAR_Error')) { + ini_set('session.save_handler', 'user'); + session_set_save_handler(array(&$_session_handler, 'open'), + array(&$_session_handler, 'close'), + array(&$_session_handler, 'read'), + array(&$_session_handler, 'write'), + array(&$_session_handler, 'destroy'), + array(&$_session_handler, 'gc')); + } else { + Horde::fatal(PEAR::raiseError('Horde is unable to correctly start the custom session handler.'), __FILE__, __LINE__, false); + } + } + } + + /** + * Returns an un-used access key from the label given. + * + * @access public + * + * @param string $label The label to choose an access key from. + * @param boolean $nocheck Don't check if the access key already has been + * used? + * + * @return string A single lower case character access key or empty + * string if none can be found + */ + function getAccessKey($label, $nocheck = false, $shutdown = false) + { + /* The access keys already used in this page */ + static $_used = array(); + + /* The labels already used in this page */ + static $_labels = array(); + + /* Shutdown call for translators? */ + if ($shutdown) { + if (!count($_labels)) { + return; + } + $script = basename($_SERVER['PHP_SELF']); + $labels = array_keys($_labels); + sort($labels); + $used = array_keys($_used); + sort($used); + $remaining = str_replace($used, array(), 'abcdefghijklmnopqrstuvwxyz'); + Horde::logMessage('Access key information for ' . $script, __FILE__, __LINE__); + Horde::logMessage('Used labels: ' . implode(',', $labels), __FILE__, __LINE__); + Horde::logMessage('Used keys: ' . implode('', $used), __FILE__, __LINE__); + Horde::logMessage('Free keys: ' . $remaining, __FILE__, __LINE__); + return; + } + + /* Use access keys at all? */ + static $notsupported; + if (!isset($notsupported)) { + $notsupported = !$GLOBALS['browser']->hasFeature('accesskey') || + !$GLOBALS['prefs']->getValue('widget_accesskey'); + } + + if ($notsupported || !preg_match('/_([A-Za-z])/', $label, $match)) { + return ''; + } + $key = $match[1]; + + /* Has this key already been used? */ + if (isset($_used[strtolower($key)]) && + !($nocheck && isset($_labels[$label]))) { + return ''; + } + + /* Save key and label. */ + $_used[strtolower($key)] = true; + $_labels[$label] = true; + + return $key; + } + + /** + * Strips an access key from a label. + * For multibyte charset strings the access key gets removed completely, + * otherwise only the underscore gets removed. + * + * @access public + * + * @param string $label The label containing an access key. + * + * @return string The label with the access key being stripped. + */ + function stripAccessKey($label) + { + include_once HORDE_BASE . '/config/nls.php'; + $multibyte = isset($GLOBALS['nls']['multibyte'][NLS::getCharset(true)]); + + return preg_replace('/_([A-Za-z])/', + $multibyte && preg_match('/[\x80-\xff]/', $label) ? '' : '\1', + $label); + } + + /** + * Highlights an access key in a label. + * + * @access public + * + * @param string $label The label to to highlight the access key in. + * @param string $accessKey The access key to highlight. + * + * @return string The HTML version of the label with the access key + * highlighted. + */ + function highlightAccessKey($label, $accessKey) + { + $stripped_label = Horde::stripAccesskey($label); + + if (empty($accessKey)) { + return $stripped_label; + } + + if (isset($GLOBALS['nls']['multibyte'][NLS::getCharset(true)])) { + return $stripped_label . '(' . '' . + strtoupper($accessKey) . '' . ')'; + } else { + return str_replace('_' . $accessKey, '' . $accessKey . '', $label); + } + } + + /** + * Returns the appropriate "accesskey" and "title" attributes for an HTML + * tag and the given label. + * + * @param string $label The title of an HTML element + * @param boolean $nocheck Don't check if the access key already has been + * used? + * + * @return string The title, and if appropriate, the accesskey attributes + * for the element. + */ + function getAccessKeyAndTitle($label, $nocheck = false) + { + $ak = Horde::getAccessKey($label, $nocheck); + $attributes = 'title="' . Horde::stripAccessKey($label); + if (!empty($ak)) { + $attributes .= sprintf(_(" (Accesskey %s)"), $ak); + $attributes .= '" accesskey="' . $ak; + } + $attributes .= '"'; + return $attributes; + } + + /** + * Returns a label element including an access key for usage in conjuction + * with a form field. User preferences regarding access keys are respected. + * + * @param string $for The form field's id attribute. + * @param string $label The label text. + * @param string $ak The access key to use. If null a new access key + * will be generated. + * + * @return string The html code for the label element. + */ + function label($for, $label, $ak = null) + { + global $prefs; + + if (is_null($ak)) { + $ak = Horde::getAccesskey($label, 1); + } + $label = Horde::highlightAccessKey($label, $ak); + + return sprintf('', + $for, + !empty($ak) ? ' accesskey="' . $ak . '"' : '', + $label); + } + + /** + * Redirects to the main Horde login page on authentication failure. + * + * @access public + */ + function authenticationFailureRedirect() + { + require_once 'Horde/CLI.php'; + if (Horde_CLI::runningFromCLI()) { + $cli = &Horde_CLI::singleton(); + $cli->fatal(_("You are not authenticated.")); + } + + $url = $GLOBALS['registry']->get('webroot', 'horde') . '/login.php'; + $url = Util::addParameter($url, 'url', Horde::selfUrl(true)); + $url = Auth::addLogoutParameters($url); + header('Location: ' . Horde::url($url, true)); + exit; + } + + /** + * Uses DOM Tooltips (via javascript) to display the 'title' + * attribute for Horde::link() calls. + * + * If using this function, the following function must be called: + * Horde::addScriptFile('tooltip.js', 'horde', true); + * + * @access public + * + * @param string $url The full URL to be linked to + * @param string $status The JavaScript mouse-over string + * @param string $class The CSS class of the link + * @param string $target The window target to point to. + * @param string $onclick JavaScript action for the 'onclick' event. + * @param string $title The link title (tooltip). + * @param string $accesskey The access key to use. + * @param array $attributes Any other name/value pairs to add to the + * tag. + * + * @return string The full tag. + */ + function linkTooltip($url, $status = '', $class = '', $target = '', + $onclick = '', $title = '', $accesskey = '', + $attributes = array()) + { + $url = substr(Horde::link($url, null, $class, $target, $onclick, null, $accesskey, $attributes), 0, -1); + + if (strlen($status)) { + $status = @htmlspecialchars(addslashes($status), ENT_QUOTES, NLS::getCharset()); + } + + $title = trim($title); + if (strlen($title)) { + require_once 'Horde/Text/Filter.php'; + $title = Text_Filter::filter($title, 'text2html', array('parselevel' => TEXT_HTML_NOHTML, 'charset' => '', 'class' => '')); + $title = '
' . strtr(addslashes($title), array("\r" => '', "\n" => '')) . '
'; + $url .= ' onmouseover="tooltipLink(\'' . @htmlspecialchars($title, ENT_QUOTES, NLS::getCharset()) . '\', \'' . $status . '\'); return true;" onmouseout="tooltipClose();"'; + } + $url .= '>'; + + return $url; + } + + /** + * Provides a standardised function to call a Horde hook, checking whether + * a hook config file exists and whether the function itself exists. If + * these two conditions are not satisfied it will return the specified + * value (by default a PEAR error). + * + * @access public + * + * @param string $hook The function to call. + * @param array $args An array of any arguments to pass to the hook + * function. + * @param string $app If specified look for hooks in the config directory + * of this app. + * @param mixed $error What to return if $app/config/hooks.php or $hook + * does not exist. If this is the string 'PEAR_Error' + * a PEAR error object is returned instead, detailing + * the failure. + * + * @return mixed Either the results of the hook or PEAR error on failure. + */ + function callHook($hook, $args = array(), $app = 'horde', $error = 'PEAR_Error') + { + global $registry; + static $hooks_loaded; + + if (!isset($hooks_loaded)) { + if (file_exists($registry->get('fileroot', $app) . '/config/hooks.php')) { + require_once $registry->get('fileroot', $app) . '/config/hooks.php'; + $hooks_loaded = true; + } else { + $hooks_loaded = false; + } + } + if ($hooks_loaded && function_exists($hook)) { + return call_user_func_array($hook, $args); + } + + if (is_string($error) && strcmp($error, 'PEAR_Error') == 0) { + $error = PEAR::raiseError(sprintf('Hook %s in application %s not called.', $hook, $app)); + Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + return $error; + } + + /** + * Return the initial page to load when accessing Horde. + * + * @return string The URL of the initial page. + */ + function initialPage() + { + global $prefs, $registry, $perms; + + $initial_app = $prefs->getValue('initial_application'); + if (!empty($initial_app) && $registry->hasPermission($initial_app)) { + return Horde::url($registry->getInitialPage($initial_app), true); + } elseif (isset($registry->applications['horde']['initial_page'])) { + return Horde::applicationUrl($registry->applications['horde']['initial_page'], true); + } elseif (Auth::getAuth()) { + return Horde::applicationUrl('services/portal/', true); + } else { + return Horde::applicationUrl('login.php', true); + } + } + + /** + * Returns an array of available menu items when in a Horde script. + * + * @return array Available menu items. + */ + function getHordeMenu() + { + global $registry, $prefs; + + $menu = array(); + + /* The home page button. If an initial application has been set in the + * prefs this will default to that application. Otherwise it will go + * to the portal screen. */ + $initial_app = $prefs->getValue('initial_application'); + if (!empty($initial_app)) { + $url = Horde::url($registry->getInitialPage($initial_app)); + } else { + $url = Horde::applicationUrl('services/portal/'); + } + $menu[] = array('url' => $url, + 'text' => _("Home"), + 'icon' => 'horde.png', + 'icon_path' => $registry->getImageDir()); + + if (Auth::isAdmin()) { + $menu[] = array('url' => Horde::applicationUrl('admin/'), + 'text' => _("_Administration"), + 'icon' => 'administration.png', + 'icon_path' => $registry->getImageDir()); + } + + return $menu; + } + +} diff --git a/phpgwapi/inc/horde/Horde/Browser.php b/phpgwapi/inc/horde/Horde/Browser.php new file mode 100644 index 0000000000..4f09993479 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/Browser.php @@ -0,0 +1,1082 @@ + + * Copyright 1999-2005 Jon Parise + * + * 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 Chuck Hagenbuch + * @author Jon Parise + * @since Horde 1.3 + * @package Horde_Browser + */ +class Browser { + + /** + * Major version number. + * + * @var integer $_majorVersion + */ + var $_majorVersion = 0; + + /** + * Minor version number. + * + * @var integer $_minorVersion + */ + var $_minorVersion = 0; + + /** + * Browser name. + * + * @var string $_browser + */ + var $_browser = ''; + + /** + * Full user agent string. + * + * @var string $_agent + */ + var $_agent = ''; + + /** + * Lower-case user agent string. + * + * @var string $_agent + */ + var $_lowerAgent = ''; + + /** + * HTTP_ACCEPT string + * + * @var string $_accept + */ + var $_accept = ''; + + /** + * Platform the browser is running on. + * + * @var string $_platform + */ + var $_platform = ''; + + /** + * Known robots. + * + * @var array $_robots + */ + var $_robots = array( + /* The most common ones. */ + 'Googlebot', + 'msnbot', + 'Slurp', + 'Yahoo', + /* The rest alphabetically. */ + 'Arachnoidea', + 'ArchitextSpider', + 'Ask Jeeves', + 'B-l-i-t-z-Bot', + 'ConveraCrawler', + 'ExtractorPro', + 'FAST-WebCrawler', + 'FDSE robot', + 'fido', + 'geckobot', + 'Gigabot', + 'Girafabot', + 'grub-client', + 'Gulliver', + 'ia_archiver', + 'InfoSeek', + 'KIT-Fireball', + 'LEIA', + 'Lycos_Spider', + 'Mediapartners-Google', + 'MuscatFerret', + 'NaverBot', + 'polybot', + 'Pompos', + 'Scooter', + 'Teoma', + 'TurnitinBot', + 'Ultraseek', + 'ViolaBot', + 'webbandit', + 'www.almaden.ibm.com/cs/crawler', + 'ZyBorg', + ); + + /** + * Is this a mobile browser? + * + * @var boolean $_mobile + */ + var $_mobile = false; + + /** + * Features. + * + * @var array $_features + */ + var $_features = array( + 'html' => true, + 'hdml' => false, + 'wml' => false, + 'images' => true, + 'iframes' => false, + 'frames' => true, + 'tables' => true, + 'java' => true, + 'javascript' => true, + 'dom' => false, + 'utf' => false, + 'rte' => false, + 'homepage' => false, + 'accesskey' => false, + 'optgroup' => false, + 'xmlhttpreq' => false, + 'cite' => false, + ); + + /** + * Quirks + * + * @var array $_quirks + */ + var $_quirks = array( + 'avoid_popup_windows' => false, + 'break_disposition_header' => false, + 'break_disposition_filename' => false, + 'broken_multipart_form' => false, + 'buggy_compression' => false, + 'cache_same_url' => false, + 'cache_ssl_downloads' => false, + 'double_linebreak_textarea' => false, + 'empty_file_input_value' => false, + 'must_cache_forms' => false, + 'no_filename_spaces' => false, + 'no_hidden_overflow_tables' => false, + 'ow_gui_1.3' => false, + 'png_transparency' => false, + 'scrollbar_in_way' => false, + 'scroll_tds' => false, + ); + + /** + * List of viewable image MIME subtypes. + * This list of viewable images works for IE and Netscape/Mozilla. + * + * @var array $_images + */ + var $_images = array('jpeg', 'gif', 'png', 'pjpeg', 'x-png', 'bmp'); + + /** + + /** + * Returns a reference to the global Browser object, only creating it + * if it doesn't already exist. + * + * This method must be invoked as: + * $browser = &Browser::singleton([$userAgent[, $accept]]); + * + * @access public + * + * @param optional string $userAgent The browser string to parse. + * @param optional string $accept The HTTP_ACCEPT settings to use. + * + * @return object Browser The Browser object. + */ + function &singleton($userAgent = null, $accept = null) + { + static $instances; + + if (!isset($instances)) { + $instances = array(); + } + + $signature = serialize(array($userAgent, $accept)); + if (empty($instances[$signature])) { + $instances[$signature] = new Browser($userAgent, $accept); + } + + return $instances[$signature]; + } + + /** + * Create a browser instance (Constructor). + * + * @access public + * + * @param optional string $userAgent The browser string to parse. + * @param optional string $accept The HTTP_ACCEPT settings to use. + */ + function Browser($userAgent = null, $accept = null) + { + $this->match($userAgent, $accept); + } + + /** + * Parses the user agent string and inititializes the object with + * all the known features and quirks for the given browser. + * + * @access public + * + * @param optional string $userAgent The browser string to parse. + * @param optional string $accept The HTTP_ACCEPT settings to use. + */ + function match($userAgent = null, $accept = null) + { + // Set our agent string. + if (is_null($userAgent)) { + if (isset($_SERVER['HTTP_USER_AGENT'])) { + $this->_agent = trim($_SERVER['HTTP_USER_AGENT']); + } + } else { + $this->_agent = $userAgent; + } + $this->_lowerAgent = String::lower($this->_agent); + + // Set our accept string. + if (is_null($accept)) { + if (isset($_SERVER['HTTP_ACCEPT'])) { + $this->_accept = String::lower(trim($_SERVER['HTTP_ACCEPT'])); + } + } else { + $this->_accept = String::lower($accept); + } + + // Check for UTF support. + if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) { + $this->setFeature('utf', strpos(String::lower($_SERVER['HTTP_ACCEPT_CHARSET']), 'utf') !== false); + } + + if (!empty($this->_agent)) { + $this->_setPlatform(); + + if (preg_match('|Opera[/ ]([0-9.]+)|', $this->_agent, $version)) { + $this->setBrowser('opera'); + list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); + $this->setFeature('javascript', true); + $this->setQuirk('no_filename_spaces'); + + switch ($this->_majorVersion) { + case 7: + $this->setFeature('dom'); + $this->setFeature('iframes'); + $this->setFeature('accesskey'); + $this->setFeature('optgroup'); + $this->setQuirk('double_linebreak_textarea'); + break; + } + } elseif (strpos($this->_lowerAgent, 'elaine/') !== false || + strpos($this->_lowerAgent, 'palmsource') !== false || + strpos($this->_lowerAgent, 'digital paths') !== false) { + $this->setBrowser('palm'); + $this->setFeature('images', false); + $this->setFeature('frames', false); + $this->setFeature('javascript', false); + $this->setQuirk('avoid_popup_windows'); + $this->_mobile = true; + } elseif ((preg_match('|MSIE ([0-9.]+)|', $this->_agent, $version)) || + (preg_match('|Internet Explorer/([0-9.]+)|', $this->_agent, $version))) { + + $this->setBrowser('msie'); + $this->setQuirk('cache_ssl_downloads'); + $this->setQuirk('cache_same_url'); + $this->setQuirk('break_disposition_filename'); + + if (strpos($version[1], '.') !== false) { + list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); + } else { + $this->_majorVersion = $version[1]; + $this->_minorVersion = 0; + } + + /* IE on Windows does not support alpha transparency in PNG + * images. */ + if (preg_match('/windows/i', $this->_agent)) { + $this->setQuirk('png_transparency'); + } + + /* IE 6 (pre-SP1) and 5.5 (pre-SP1) has buggy compression. + * The versions affected are as follows: + * 6.00.2462.0000 Internet Explorer 6 Public Preview (Beta) + * 6.00.2479.0006 Internet Explorer 6 Public Preview (Beta) + Refresh + * 6.00.2600.0000 Internet Explorer 6 (Windows XP) + * 5.50.3825.1300 Internet Explorer 5.5 Developer Preview (Beta) + * 5.50.4030.2400 Internet Explorer 5.5 & Internet Tools Beta + * 5.50.4134.0100 Internet Explorer 5.5 for Windows Me (4.90.3000) + * 5.50.4134.0600 Internet Explorer 5.5 + * 5.50.4308.2900 Internet Explorer 5.5 Advanced Security Privacy Beta + * + * See: + * ==== + * http://support.microsoft.com/kb/164539; + * http://support.microsoft.com/default.aspx?scid=kb;en-us;Q312496) + * http://support.microsoft.com/default.aspx?scid=kb;en-us;Q313712 + */ + $ie_vers = $this->getIEVersion(); + $buggy_list = array( + '6,00,2462,0000', '6,00,2479,0006', '6,00,2600,0000', + '5,50,3825,1300', '5,50,4030,2400', '5,50,4134,0100', + '5,50,4134,0600', '5,50,4308,2900' + ); + if (!is_null($ie_vers) && in_array($ie_vers, $buggy_list)) { + $this->setQuirk('buggy_compression'); + } + + /* Some Handhelds have their screen resolution in the + * user agent string, which we can use to look for + * mobile agents. */ + if (preg_match('/; (120x160|240x280|240x320)\)/', $this->_agent)) { + $this->_mobile = true; + } + + switch ($this->_majorVersion) { + case 6: + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('iframes'); + $this->setFeature('utf'); + $this->setFeature('rte'); + $this->setFeature('homepage'); + $this->setFeature('accesskey'); + $this->setFeature('optgroup'); + $this->setFeature('xmlhttpreq'); + $this->setQuirk('scrollbar_in_way'); + $this->setQuirk('broken_multipart_form'); + break; + + case 5: + if ($this->getPlatform() == 'mac') { + $this->setFeature('javascript', 1.2); + $this->setFeature('optgroup'); + } else { + // MSIE 5 for Windows. + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('xmlhttpreq'); + if ($this->_minorVersion >= 5) { + $this->setFeature('rte'); + } + } + $this->setFeature('iframes'); + $this->setFeature('utf'); + $this->setFeature('homepage'); + $this->setFeature('accesskey'); + if ($this->_minorVersion == 5) { + $this->setQuirk('break_disposition_header'); + $this->setQuirk('broken_multipart_form'); + } + break; + + case 4: + $this->setFeature('javascript', 1.2); + $this->setFeature('accesskey'); + if ($this->_minorVersion > 0) { + $this->setFeature('utf'); + } + break; + + case 3: + $this->setFeature('javascript', 1.1); + $this->setQuirk('avoid_popup_windows'); + break; + } + } elseif (preg_match('|ANTFresco/([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('fresco'); + $this->setFeature('javascript', 1.1); + $this->setQuirk('avoid_popup_windows'); + } elseif (strpos($this->_lowerAgent, 'avantgo') !== false) { + $this->setBrowser('avantgo'); + $this->_mobile = true; + } elseif (preg_match('|Konqueror/([0-9]+)|', $this->_agent, $version) || + preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $this->_agent, $version)) { + // Konqueror and Apple's Safari both use the KHTML + // rendering engine. + $this->setBrowser('konqueror'); + $this->setQuirk('empty_file_input_value'); + $this->setQuirk('no_hidden_overflow_tables'); + $this->_majorVersion = $version[1]; + if (isset($version[2])) { + $this->_minorVersion = $version[2]; + } + + if (strpos($this->_agent, 'Safari') !== false && + $this->_majorVersion >= 60) { + // Safari. + $this->setFeature('utf'); + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('iframes'); + if ($this->_majorVersion > 125 || + ($this->_majorVersion == 125 && + $this->_minorVersion >= 1)) { + $this->setFeature('utf'); + $this->setFeature('accesskey'); + $this->setFeature('xmlhttpreq'); + } + } else { + // Konqueror. + $this->setFeature('javascript', 1.1); + switch ($this->_majorVersion) { + case 3: + $this->setFeature('dom'); + $this->setFeature('iframes'); + break; + } + } + } elseif (preg_match('|Mozilla/([0-9.]+)|', $this->_agent, $version)) { + $this->setBrowser('mozilla'); + $this->setQuirk('must_cache_forms'); + + list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); + switch ($this->_majorVersion) { + case 5: + if ($this->getPlatform() == 'win') { + $this->setQuirk('break_disposition_filename'); + } + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('accesskey'); + $this->setFeature('optgroup'); + $this->setFeature('xmlhttpreq'); + $this->setFeature('cite'); + if (preg_match('|rv:(.*)\)|', $this->_agent, $revision)) { + if ($revision[1] >= 1) { + $this->setFeature('iframes'); + } + if ($revision[1] >= 1.3) { + $this->setFeature('rte'); + } + } + break; + + case 4: + $this->setFeature('javascript', 1.3); + $this->setQuirk('buggy_compression'); + break; + + case 3: + default: + $this->setFeature('javascript', 1); + $this->setQuirk('buggy_compression'); + break; + } + } elseif (preg_match('|Lynx/([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('lynx'); + $this->setFeature('images', false); + $this->setFeature('frames', false); + $this->setFeature('javascript', false); + $this->setQuirk('avoid_popup_windows'); + } elseif (preg_match('|Links \(([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('links'); + $this->setFeature('images', false); + $this->setFeature('frames', false); + $this->setFeature('javascript', false); + $this->setQuirk('avoid_popup_windows'); + } elseif (preg_match('|HotJava/([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('hotjava'); + $this->setFeature('javascript', false); + } elseif (strpos($this->_agent, 'UP/') !== false || + strpos($this->_agent, 'UP.B') !== false || + strpos($this->_agent, 'UP.L') !== false) { + $this->setBrowser('up'); + $this->setFeature('html', false); + $this->setFeature('javascript', false); + $this->setFeature('hdml'); + $this->setFeature('wml'); + + if (strpos($this->_agent, 'GUI') !== false && + strpos($this->_agent, 'UP.Link') !== false) { + /* The device accepts Openwave GUI extensions for + * WML 1.3. Non-UP.Link gateways sometimes have + * problems, so exclude them. */ + $this->setQuirk('ow_gui_1.3'); + } + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Xiino/') !== false) { + $this->setBrowser('xiino'); + $this->setFeature('hdml'); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Palmscape/') !== false) { + $this->setBrowser('palmscape'); + $this->setFeature('javascript', false); + $this->setFeature('hdml'); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Nokia') !== false) { + $this->setBrowser('nokia'); + $this->setFeature('html', false); + $this->setFeature('wml'); + $this->setFeature('xhtml'); + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Ericsson') !== false) { + $this->setBrowser('ericsson'); + $this->setFeature('html', false); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_lowerAgent, 'wap') !== false) { + $this->setBrowser('wap'); + $this->setFeature('html', false); + $this->setFeature('javascript', false); + $this->setFeature('hdml'); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_lowerAgent, 'docomo') !== false || + strpos($this->_lowerAgent, 'portalmmm') !== false) { + $this->setBrowser('imode'); + $this->setFeature('images', false); + $this->_mobile = true; + } elseif (strpos($this->_lowerAgent, 'j-') !== false) { + $this->setBrowser('mml'); + $this->_mobile = true; + } + } + } + + /** + * Match the platform of the browser. + * + * This is a pretty simplistic implementation, but it's intended + * to let us tell what line breaks to send, so it's good enough + * for its purpose. + * + * @access public + * + * @since Horde 2.2 + */ + function _setPlatform() + { + if (strpos($this->_lowerAgent, 'wind') !== false) { + $this->_platform = 'win'; + } elseif (strpos($this->_lowerAgent, 'mac') !== false) { + $this->_platform = 'mac'; + } else { + $this->_platform = 'unix'; + } + } + + /** + * Return the currently matched platform. + * + * @return string The user's platform. + * + * @since Horde 2.2 + */ + function getPlatform() + { + return $this->_platform; + } + + /** + * Sets the current browser. + * + * @access public + * + * @param string $browser The browser to set as current. + */ + function setBrowser($browser) + { + $this->_browser = $browser; + } + + /** + * Determine if the given browser is the same as the current. + * + * @access public + * + * @param string $browser The browser to check. + * + * @return boolean Is the given browser the same as the current? + */ + function isBrowser($browser) + { + return ($this->_browser === $browser); + } + + /** + * Do we consider the current browser to be a mobile device? + * + * @return boolean True if we do, false if we don't. + */ + function isMobile() + { + return $this->_mobile; + } + + /** + * Determines if the browser is a robot or not. + * + * @access public + * + * @return boolean True if browser is a known robot. + */ + function isRobot() + { + foreach ($this->_robots as $robot) { + if (strpos($this->_agent, $robot) !== false) { + return true; + } + } + return false; + } + + /** + * Retrieve the current browser. + * + * @access public + * + * @return string The current browser. + */ + function getBrowser() + { + return $this->_browser; + } + + /** + * Retrieve the current browser's major version. + * + * @access public + * + * @return integer The current browser's major version. + */ + function getMajor() + { + return $this->_majorVersion; + } + + /** + * Retrieve the current browser's minor version. + * + * @access public + * + * @return integer The current browser's minor version. + */ + function getMinor() + { + return $this->_minorVersion; + } + + /** + * Retrieve the current browser's version. + * + * @access public + * + * @return string The current browser's version. + */ + function getVersion() + { + return $this->_majorVersion . '.' . $this->_minorVersion; + } + + /** + * Return the full browser agent string. + * + * @access public + * + * @return string The browser agent string. + */ + function getAgentString() + { + return $this->_agent; + } + + /** + * Set unique behavior for the current browser. + * + * @access public + * + * @param string $quirk The behavior to set. + * @param optional string $value Special behavior parameter. + */ + function setQuirk($quirk, $value = true) + { + $this->_quirks[$quirk] = $value; + } + + /** + * Check unique behavior for the current browser. + * + * @access public + * + * @param string $quirk The behavior to check. + * + * @return boolean Does the browser have the behavior set? + */ + function hasQuirk($quirk) + { + return !empty($this->_quirks[$quirk]); + } + + /** + * Retreive unique behavior for the current browser. + * + * @access public + * + * @param string $quirk The behavior to retreive. + * + * @return string The value for the requested behavior. + */ + function getQuirk($quirk) + { + return isset($this->_quirks[$quirk]) + ? $this->_quirks[$quirk] + : null; + } + + /** + * Set capabilities for the current browser. + * + * @access public + * + * @param string $feature The capability to set. + * @param optional string $value Special capability parameter. + */ + function setFeature($feature, $value = true) + { + $this->_features[$feature] = $value; + } + + /** + * Check the current browser capabilities. + * + * @access public + * + * @param string $feature The capability to check. + * + * @return boolean Does the browser have the capability set? + */ + function hasFeature($feature) + { + return !empty($this->_features[$feature]); + } + + /** + * Retreive the current browser capability. + * + * @access public + * + * @param string $feature The capability to retreive. + * + * @return string The value of the requested capability. + */ + function getFeature($feature) + { + return isset($this->_features[$feature]) + ? $this->_features[$feature] + : null; + } + + /** + * Determine if we are using a secure (SSL) connection. + * + * @access public + * + * @return boolean True if using SSL, false if not. + */ + function usingSSLConnection() + { + return ((isset($_SERVER['HTTPS']) && + ($_SERVER['HTTPS'] == 'on')) || + getenv('SSL_PROTOCOL_VERSION')); + } + + /** + * Returns the server protocol in use on the current server. + * + * @access public + * + * @return string The HTTP server protocol version. + */ + function getHTTPProtocol() + { + if (isset($_SERVER['SERVER_PROTOCOL'])) { + if (($pos = strrpos($_SERVER['SERVER_PROTOCOL'], '/'))) { + return substr($_SERVER['SERVER_PROTOCOL'], $pos + 1); + } + } + + return null; + } + + /** + * Determine if files can be uploaded to the system. + * + * @access public + * + * @return integer If uploads allowed, returns the maximum size of the + * upload in bytes. Returns 0 if uploads are not + * allowed. + */ + function allowFileUploads() + { + if (ini_get('file_uploads')) { + if (($dir = ini_get('upload_tmp_dir')) && + !is_writable($dir)) { + return 0; + } + $size = ini_get('upload_max_filesize'); + switch (strtolower(substr($size, -1, 1))) { + case 'k': + $size = intval(floatval($size) * 1024); + break; + + case 'm': + $size = intval(floatval($size) * 1024 * 1024); + break; + + default: + $size = intval($size); + break; + } + return $size; + } else { + return 0; + } + } + + /** + * Determines if the file was uploaded or not. If not, will return the + * appropriate error message. + * + * @access public + * + * @param string $field The name of the field containing the + * uploaded file. + * @param optional string $name The file description string to use in the + * error message. Default: 'file'. + * + * @return mixed True on success, PEAR_Error on error. + */ + function wasFileUploaded($field, $name = null) + { + require_once 'PEAR.php'; + + if (is_null($name)) { + $name = _("file"); + } + + if (!($uploadSize = Browser::allowFileUploads())) { + return PEAR::raiseError(_("File uploads not supported.")); + } + + /* Get any index on the field name. */ + require_once 'Horde/Array.php'; + $index = Horde_Array::getArrayParts($field, $base, $keys); + + if ($index) { + /* Index present, fetch the error var to check. */ + $keys_path = array_merge(array($base, 'error'), $keys); + $error = Horde_Array::getElement($_FILES, $keys_path); + + /* Index present, fetch the tmp_name var to check. */ + $keys_path = array_merge(array($base, 'tmp_name'), $keys); + $tmp_name = Horde_Array::getElement($_FILES, $keys_path); + } else { + /* No index, simple set up of vars to check. */ + if (!isset($_FILES[$field])) { + return PEAR::raiseError(_("No file uploaded"), UPLOAD_ERR_NO_FILE); + } + $error = $_FILES[$field]['error']; + $tmp_name = $_FILES[$field]['tmp_name']; + } + + if (!isset($_FILES) || ($error == UPLOAD_ERR_NO_FILE)) { + return PEAR::raiseError(sprintf(_("There was a problem with the file upload: No %s was uploaded."), $name), UPLOAD_ERR_NO_FILE); + } elseif (($error == UPLOAD_ERR_OK) && is_uploaded_file($tmp_name)) { + return true; + } elseif (($error == UPLOAD_ERR_INI_SIZE) || + ($error == UPLOAD_ERR_FORM_SIZE)) { + return PEAR::raiseError(sprintf(_("There was a problem with the file upload: The %s was larger than the maximum allowed size (%d bytes)."), $name, $uploadSize), $error); + } elseif ($error == UPLOAD_ERR_PARTIAL) { + return PEAR::raiseError(sprintf(_("There was a problem with the file upload: The %s was only partially uploaded."), $name), $error); + } + } + + /** + * Returns the headers for a browser download. + * + * @access public + * + * @param optional string $filename The filename of the download. + * @param optional string $cType The content-type description of the + * file. + * @param optional boolean $inline True if inline, false if attachment. + * @param optional string $cLength The content-length of this file. + * + * @since Horde 2.2 + */ + function downloadHeaders($filename = 'unknown', $cType = null, + $inline = false, $cLength = null) + { + /* Some browsers don't like spaces in the filename. */ + if ($this->hasQuirk('no_filename_spaces')) { + $filename = strtr($filename, ' ', '_'); + } + + /* MSIE doesn't like multiple periods in the file name. Convert + all periods (except the last one) to underscores. */ + if ($this->isBrowser('msie')) { + if (($pos = strrpos($filename, '.'))) { + $filename = strtr(substr($filename, 0, $pos), '.', '_') . substr($filename, $pos); + } + } + + /* Content-Type/Content-Disposition Header. */ + if ($inline) { + if (!is_null($cType)) { + header('Content-Type: ' . trim($cType)); + } elseif ($this->isBrowser('msie')) { + header('Content-Type: application/x-msdownload'); + } else { + header('Content-Type: application/octet-stream'); + } + header('Content-Disposition: inline; filename="' . $filename . '"'); + } else { + if ($this->isBrowser('msie')) { + header('Content-Type: application/x-msdownload'); + } elseif (!is_null($cType)) { + header('Content-Type: ' . trim($cType)); + } else { + header('Content-Type: application/octet-stream'); + } + + if ($this->hasQuirk('break_disposition_header')) { + header('Content-Disposition: filename="' . $filename . '"'); + } else { + header('Content-Disposition: attachment; filename="' . $filename . '"'); + } + } + + /* Content-Length Header. Don't send Content-Length for + * HTTP/1.1 servers. */ + if (($this->getHTTPProtocol() != '1.1') && !is_null($cLength)) { + header('Content-Length: ' . $cLength); + } + + /* Overwrite Pragma: and other caching headers for IE. */ + if ($this->hasQuirk('cache_ssl_downloads')) { + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + } + } + + /** + * Determines if a browser can display a given MIME type. + * + * @access public + * + * @param string $mimetype The MIME type to check. + * + * @return boolean True if the browser can display the MIME type. + */ + function isViewable($mimetype) + { + $mimetype = String::lower($mimetype); + list($type, $subtype) = explode('/', $mimetype); + + if (!empty($this->_accept)) { + $wildcard_match = false; + + if (strpos($this->_accept, $mimetype) !== false) { + return true; + } + + if (strpos($this->_accept, '*/*') !== false) { + $wildcard_match = true; + if ($type != 'image') { + return true; + } + } + + /* image/jpeg and image/pjpeg *appear* to be the same + * entity, but Mozilla doesn't seem to want to accept the + * latter. For our purposes, we will treat them the + * same. */ + if ($this->isBrowser('mozilla') && + ($mimetype == 'image/pjpeg') && + (strpos($this->_accept, 'image/jpeg') !== false)) { + return true; + } + + if (!$wildcard_match) { + return false; + } + } + + if (!$this->hasFeature('images') || ($type != 'image')) { + return false; + } + + return (in_array($subtype, $this->_images)); + } + + /** + * Escape characters in javascript code if the browser requires it. + * %23, %26, and %2B (for IE) and %27 need to be escaped or else + * jscript will interpret it as a single quote, pound sign, or + * ampersand and refuse to work. + * + * @access public + * + * @param string $code The JS code to escape. + * + * @return string The escaped code. + */ + function escapeJSCode($code) + { + $from = $to = array(); + + if ($this->isBrowser('msie') || + ($this->isBrowser('mozilla') && ($this->getMajor() >= 5))) { + $from = array('%23', '%26', '%2B'); + $to = array(urlencode('%23'), urlencode('%26'), urlencode('%2B')); + } + $from[] = '%27'; + $to[] = '\%27'; + + return str_replace($from, $to, $code); + } + + /** + * Set the IE version in the session. + * + * @access public + * + * @param string $ver The IE Version string. + */ + function setIEVersion($ver) + { + $_SESSION['__browser'] = array( + 'ie_version' => $ver + ); + } + + /** + * Return the IE version stored in the session, if available. + * + * @access public + * + * @return mixed The IE Version string or null if no string is stored. + */ + function getIEVersion() + { + return isset($_SESSION['__browser']['ie_version']) ? $_SESSION['__browser']['ie_version'] : null; + } + +} diff --git a/phpgwapi/inc/horde/Horde/Browser/imode.php b/phpgwapi/inc/horde/Horde/Browser/imode.php new file mode 100644 index 0000000000..9c244af1a0 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/Browser/imode.php @@ -0,0 +1,412 @@ + + * + * 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 Chuck Hagenbuch + * @since Horde 3.0 + * @package Horde_Browser + */ +class Browser_imode { + + /** + * Device data. From http://www.nttdocomo.co.jp/i/tag/s5.html#5_1 + * + * @var array $_data + */ + var $_data = array( + 'D209i' => array( + 'imagewidth' => 96, 'imageheight' => 90, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'F209i' => array( + 'imagewidth' => 96, 'imageheight' => 91, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'N209i' => array( + 'imagewidth' => 108, 'imageheight' => 82, + 'textwidth' => 9, 'textheight' => 6, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'P209i' => array( + 'imagewidth' => 96, 'imageheight' => 87, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'P209is' => array( + 'imagewidth' => 96, 'imageheight' => 87, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'R209i' => array( + 'imagewidth' => 96, 'imageheight' => 72, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'ER209i' => array( + 'imagewidth' => 120, 'imageheight' => 72, + 'textwidth' => 10, 'textheight' => 6, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'KO209i' => array( + 'imagewidth' => 96, 'imageheight' => 96, + 'textwidth' => 8, 'textheight' => 8, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'D210i' => array( + 'imagewidth' => 96, 'imageheight' => 91, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'F210i' => array( + 'imagewidth' => 96, 'imageheight' => 113, + 'textwidth' => 8, 'textheight' => 8, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'N210i' => array( + 'imagewidth' => 118, 'imageheight' => 113, + 'textwidth' => 10, 'textheight' => 8, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'P210i' => array( + 'imagewidth' => 96, 'imageheight' => 91, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'KO210i' => array( + 'imagewidth' => 96, 'imageheight' => 96, + 'textwidth' => 8, 'textheight' => 8, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'SO210i' => array( + 'imagewidth' => 120, 'imageheight' => 113, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'D501i' => array( + 'imagewidth' => 96, 'imageheight' => 72, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'black', + 'imageformats' => array('gif') + ), + 'F501i' => array( + 'imagewidth' => 112, 'imageheight' => 84, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'black', + 'imageformats' => array('gif') + ), + 'N501i' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' =>10, + 'color' => 'black', + 'imageformats' => array('gif') + ), + 'P501i' => array( + 'imagewidth' => 96, 'imageheight' => 120, + 'textwidth' => 8, 'textheight' => 8, + 'color' => 'black', + 'imageformats' => array('gif') + ), + 'D502i' => array( + 'imagewidth' => 96, 'imageheight' => 90, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'F502i' => array( + 'imagewidth' => 96, 'imageheight' => 91, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'F502it' => array( + 'imagewidth' => 96, 'imageheight' => 91, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'N502i' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'N502it' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'P502i' => array( + 'imagewidth' => 96, 'imageheighth' => 117, + 'textwidth' => 8, 'textheight' => 8, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'NM502i' => array( + 'imagewidth' => 111, 'imageheight' => 77, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'black', + 'imageformats' => array('gif') + ), + 'SO502i' => array( + 'imagewidth' => 120, 'imageheight' => 120, + 'textwidth' => 8, 'textheight' => 8, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'SO502iwm' => array( + 'imagewidth' => 120, 'imageheight' => 113, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'F503i' => array( + 'imagewidth' => 120, 'imageheight' => 130, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'F503iS' => array( + 'imagewidth' => 120, 'imageheight' => 130, + 'textwidth' => 12, 'textheight' => 12, + 'color' => 4096, + 'imageformats' => array('gif') + ), + 'P503i' => array( + 'imagewidth' => 120, 'imageheight' => 130, + 'textwidth' => 12, 'textheight' => 10, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'P503iS' => array( + 'imagewidth' => 120, 'imageheight' => 130, + 'textwidth' => 12, 'textheight' => 10, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'SO503i' => array( + 'imagewidth' => 120, 'imageheight' => 113, + 'textwidth' => 8.5, 'textheight' => 7, + 'color' => 65536, + 'imageformats' => array('gif') + ), + 'D503i' => array( + 'imagewidth' => 132, 'imageheight' => 126, + 'textwidth' => 8, 'textheight' => 7, + 'color' => 4096, + 'imageformats' => array('gif') + ), + 'N503i' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 4096, + 'imageformats' => array('gif', 'jpg') + ), + 'N503iS' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 4096, + 'imageformats' => array('gif', 'jpg') + ), + 'N691i' => array( + 'imagewidth' => 96, 'imageheight' => 72, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'SH821i' => array( + 'imagewidth' => 96, 'imageheight' => 78, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 256, + 'imageformats' => array('gif') + ), + 'N821i' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'P821i' => array( + 'imagewidth' => 118, 'imageheight' => 128, + 'textwidth' => 10, 'textheight' => 10, + 'color' => 'grey', + 'imageformats' => array('gif') + ), + 'safe' => array( + 'imagewidth' => 94, 'imageheight' => 72, + 'textwidth' => 8, 'textheight' => 6, + 'color' => 'black', + 'imageformats' => array('gif') + ) + ); + + var $_manufacturerlist = array( + 'D' => 'Mitsubishi', + 'P' => 'Panasonic (Matsushita)', + 'NM' => 'Nokia', + 'SO' => 'Sony', + 'F' => 'Fujitsu', + 'N' => 'Nec', + 'SH' => 'Sharp', + 'ER' => 'Ericsson', + 'R' => 'Japan Radio', + 'KO' => 'Kokusai (Hitachi)' + ); + + var $_extra = array( + 't' => 'Transport layer', + 'e' => 'English language', + 's' => 'Second version' + ); + + var $_user_agent; + var $_model; + var $_manufacturer; + var $_httpversion; + var $_cache = 5; + var $_extra; + + /** + * Does not handle bogus user_agents or most of the other error + * situation properly yet. + * + * Example usage: + * $ua = &new Browser_imode($_SERVEr['HTTP_USER_AGENT']); + * + * @param string $input The user agent to match. + */ + function Browser_imode($input) + { + $_error = 0; + $temp = explode('/', $input); + + $this->_user_agent = $input; + $this->_httpversion = $temp[1]; + $this->_model = $temp[2]; + if ($temp[3]) { + $this->_cache = substr($temp[3], 1); + } + + preg_match('/(^[a-zA-Z]+)([0-9]+i)(.*)\/?(.*)/', $this->_model, $matches); + + // @TODO: Fix situation of unknown manufacturer. Implement + // extrainfo properly. + $this->_manufacturer = $this->_manufacturerlist[$matches[1]]; + $this->_extra = $matches[3]; + + if (!($this->_data[$this->_model])) { + $_error = PEAR::raiseError('Unknown User Agent'); + } + } + + /** + * Example usage: + * $imagedim = $ua->getImageDimensions(); + * $imagewidth = $imagedim[0]; + * $imageheight = $imagedim[1]; + * + * @return array The maximum imagewidth and imageheight that + * fit on the handset screen without scrolling. + */ + function getImageDimensions() + { + $data = $this->_data[$this->_model]; + return array($data['imagewidth'], $data['imageheight']); + } + + /** + * Example usage: + * $textdim = $ua->getTextDimensions(); + * $textwidth = $textdim[0]; + * $textheight = $textdim[1]; + * + * @return array The Maximum textwidth and textheight that + * fit on the handset screen without scrolling. + */ + function getTextDimensions() + { + return array($this->_data[$this->_model]['textwidth'], + $this->_data[$this->_model]['textheight']); + } + + /** + * @return integer The amount of handset cache in kilobytes. + */ + function getCache() + { + return (int)$this->_cache; + } + + function getManufacturer() + { + return $this->_manufacturer; + } + + function getExtra() + { + return $this->_extra; + } + + function getImageFormats() + { + return $this->_data[$this->_model]['imageformats']; + } + + /** + * @return integer Which color model the handset supports. + * Values have the following meaning: + * 0 -> black and white + * 1 -> 4 tone greyscale + * 2 -> 256 color + */ + function getColor() + { + return $this->_data[$this->_model]['color']; + } + + function getHTTPVersion() + { + return $this->_httpversion; + } + + function isColor() + { + return $this->_data[$this->_model]['color'] == 256; + } + + function isGreyScale() + { + return $this->_data[$this->_model]['color'] == 'grey'; + } + + function isBlackAndWhite() + { + return $this->_data[$this->_model]['color'] == 'black'; + } + +} diff --git a/phpgwapi/inc/horde/Horde/NLS.php b/phpgwapi/inc/horde/Horde/NLS.php new file mode 100644 index 0000000000..c96b801e6b --- /dev/null +++ b/phpgwapi/inc/horde/Horde/NLS.php @@ -0,0 +1,523 @@ +country lookups. + * + * $Horde: framework/NLS/NLS.php,v 1.86 2005/02/28 15:45:56 jan Exp $ + * + * Copyright 1999-2005 Jon Parise + * Copyright 1999-2005 Chuck Hagenbuch + * Copyright 2002-2005 Jan Schneider + * Copyright 2003-2005 Michael Slusarz + * + * 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 Jon Parise + * @author Chuck Hagenbuch + * @author Jan Schneider + * @author Michael Slusarz + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_NLS + */ +class NLS { + + /** + * Selects the most preferred language for the current client session. + * + * @access public + * + * @return string The selected language abbreviation. + */ + function select() + { + global $nls, $prefs; + + $lang = Util::getFormData('new_lang'); + + /* First, check if language pref is locked and, if so, set it to its + value */ + if (isset($prefs) && $prefs->isLocked('language')) { + $language = $prefs->getValue('language'); + /* Check if the user selected a language from the login screen */ + } elseif (!empty($lang)) { + $language = $lang; + /* Check if we have a language set in a cookie */ + } elseif (isset($_SESSION['horde_language'])) { + $language = $_SESSION['horde_language']; + /* Use site-wide default, if one is defined */ + } elseif (!empty($nls['defaults']['language'])) { + $language = $nls['defaults']['language']; + /* Try browser-accepted languages. */ + } elseif (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + /* The browser supplies a list, so return the first valid one. */ + $browser_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); + foreach ($browser_langs as $lang) { + /* Strip quality value for language */ + if (($pos = strpos($lang, ';')) !== false) { + $lang = substr($lang, 0, $pos); + } + $lang = NLS::_map(trim($lang)); + if (NLS::isValid($lang)) { + $language = $lang; + break; + } + /* In case no full match, save best guess based on prefix */ + if (!isset($partial_lang) && + NLS::isValid(NLS::_map(substr($lang, 0, 2)))) { + $partial_lang = NLS::_map(substr($lang, 0, 2)); + } + } + } + + if (!isset($language)) { + if (isset($partial_lang)) { + $language = $partial_lang; + } else { + /* No dice auto-detecting, default to US English. */ + $language = 'en_US'; + } + } + + return basename($language); + } + + /** + * Sets the language. + * + * @access public + * + * @param optional string $lang The language abbriviation. + */ + function setLang($lang = null) + { + include_once HORDE_BASE . '/config/nls.php'; + + if (empty($lang) || !NLS::isValid($lang)) { + $lang = NLS::select(); + } + + if (isset($GLOBALS['language']) && $GLOBALS['language'] == $lang) { + return; + } + + $GLOBALS['language'] = $lang; + + /* First try language with the current charset. */ + $lang_charset = $lang . '.' . NLS::getCharset(); + if ($lang_charset != setlocale(LC_ALL, $lang_charset)) { + /* Next try language with its default charset. */ + global $nls; + $charset = !empty($nls['charsets'][$lang]) ? $nls['charsets'][$lang] : $nls['defaults']['charset']; + $lang_charset = $lang . '.' . $charset; + NLS::_cachedCharset(0, $charset); + if ($lang_charset != setlocale(LC_ALL, $lang_charset)) { + /* At last try language solely. */ + $lang_charset = $lang; + setlocale(LC_ALL, $lang_charset); + } + } + @putenv('LANG=' . $lang_charset); + @putenv('LANGUAGE=' . $lang_charset); + } + + /** + * Sets the gettext domain. + * + * @access public + * + * @param string $app The application name. + * @param string $directory The directory where the application's + * LC_MESSAGES directory resides. + * @param string $charset The charset. + */ + function setTextdomain($app, $directory, $charset) + { + bindtextdomain($app, $directory); + textdomain($app); + + /* The existence of this function depends on the platform. */ + if (function_exists('bind_textdomain_codeset')) { + NLS::_cachedCharset(0, bind_textdomain_codeset($app, $charset)); + } + + if (!headers_sent()) { + header('Content-Type: text/html; charset=' . $charset); + } + } + + /** + * Determines whether the supplied language is valid. + * + * @access public + * + * @param string $language The abbreviated name of the language. + * + * @return boolean True if the language is valid, false if it's not + * valid or unknown. + */ + function isValid($language) + { + return !empty($GLOBALS['nls']['languages'][$language]); + } + + /** + * Maps languages with common two-letter codes (such as nl) to the + * full gettext code (in this case, nl_NL). Returns the language + * unmodified if it isn't an alias. + * + * @access private + * + * @param string $language The language code to map. + * + * @return string The mapped language code. + */ + + function _map($language) + { + require_once 'Horde/String.php'; + + $aliases = &$GLOBALS['nls']['aliases']; + + // Translate the $language to get broader matches. + // (eg. de-DE should match de_DE) + $trans_lang = str_replace('-', '_', $language); + $lang_parts = explode('_', $trans_lang); + $trans_lang = String::lower($lang_parts[0]); + if (isset($lang_parts[1])) { + $trans_lang .= '_' . String::upper($lang_parts[1]); + } + + // See if we get a match for this + if (!empty($aliases[$trans_lang])) { + return $aliases[$trans_lang]; + } + + // If we get that far down, the language cannot be found. + // Return $trans_lang. + return $trans_lang; + } + + /** + * Returns the charset for the current language. + * + * @access public + * + * @param boolean $original If true returns the original charset of the + * translation, the actually used one otherwise. + * + * @return string The character set that should be used with the current + * locale settings. + */ + function getCharset($original = false) + { + global $language, $nls; + + /* Get cached results. */ + $cacheKey = intval($original); + $charset = NLS::_cachedCharset($cacheKey); + if (!is_null($charset)) { + return $charset; + } + + if ($original) { + $charset = empty($nls['charsets'][$language]) ? $nls['defaults']['charset'] : $nls['charsets'][$language]; + } else { + require_once 'Horde/Browser.php'; + $browser = &Browser::singleton(); + + if ($browser->hasFeature('utf') && + (Util::extensionExists('iconv') || + Util::extensionExists('mbstring'))) { + $charset = 'UTF-8'; + } + } + + if (is_null($charset)) { + $charset = NLS::getExternalCharset(); + } + + NLS::_cachedCharset($cacheKey, $charset); + return $charset; + } + + + /** + * Returns the current charset of the environment + * + * @access public + * + * @return string The character set that should be used with the current + * locale settings. + */ + function getExternalCharset() + { + global $language, $nls; + + /* Get cached results. */ + $charset = NLS::_cachedCharset(2); + if (!is_null($charset)) { + return $charset; + } + + $lang_charset = setlocale(LC_ALL, 0); + if (strpos($lang_charset, ';') === false && + strpos($lang_charset, '/') === false) { + $lang_charset = explode('.', $lang_charset); + if ((count($lang_charset) == 2) && !empty($lang_charset[1])) { + NLS::_cachedCharset(2, $lang_charset[1]); + return $lang_charset[1]; + } + } + + return (!empty($nls['charsets'][$language])) ? $nls['charsets'][$language] : $nls['defaults']['charset']; + } + + /** + * Sets or returns the charset used under certain conditions. + * + * @access private + * + * @param integer $index The ID of a cache slot. 0 for the UI charset, 1 + * for the translation charset and 2 for the + * external charset. + * @param string $charset If specified, this charset will be stored in the + * given cache slot. Otherwise the content of the + * specified cache slot will be returned. + */ + function _cachedCharset($index, $charset = null) + { + static $cache; + + if (!isset($cache)) { + $cache = array(); + } + + if ($charset == null) { + return isset($cache[$index]) ? $cache[$index] : null; + } else { + $cache[$index] = $charset; + } + } + + /** + * Returns the charset to use for outgoing emails. + * + * @return string The preferred charset for outgoing mails based on + * the user's preferences and the current language. + */ + function getEmailCharset() + { + global $prefs, $language, $nls; + + $charset = $prefs->getValue('sending_charset'); + if (!empty($charset)) { + return $charset; + } + return isset($nls['emails'][$language]) ? $nls['emails'][$language] : + (isset($nls['charsets'][$language]) ? $nls['charsets'][$language] : $nls['defaults']['charset']); + } + + /** + * Check to see if character set is valid for htmlspecialchars() calls. + * + * @access public + * + * @param string $charset The character set to check. + * + * @return boolean Is charset valid for the current system? + */ + function checkCharset($charset) + { + static $check; + + if (is_null($charset) || empty($charset)) { + return false; + } + + if (isset($check[$charset])) { + return $check[$charset]; + } elseif (!isset($check)) { + $check = array(); + } + + $valid = true; + + ini_set('track_errors', 1); + @htmlspecialchars('', ENT_COMPAT, $charset); + if (isset($php_errormsg)) { + $valid = false; + } + ini_restore('track_errors'); + + $check[$charset] = $valid; + + return $valid; + } + + /** + * Sets the current timezone, if available. + * + * @access public + */ + function setTimeZone() + { + global $prefs; + + $tz = $prefs->getValue('timezone'); + if (!empty($tz)) { + @putenv('TZ=' . $tz); + } + } + + /** + * Get the locale info returned by localeconv(), but cache it, to + * avoid repeated calls. + * + * @access public + * + * @return array The results of localeconv(). + */ + function getLocaleInfo() + { + static $lc_info; + + if (!isset($lc_info)) { + $lc_info = localeconv(); + } + + return $lc_info; + } + + /** + * Get the language info returned by nl_langinfo(), but cache it, to + * avoid repeated calls. + * + * @access public + * @since Horde 3.1 + * + * @param const $item The langinfo item to return. + * + * @return array The results of nl_langinfo(). + */ + function getLangInfo($item) + { + static $nl_info = array(); + + if (!isset($nl_info[$item])) { + $nl_info[$item] = nl_langinfo($item); + } + + return $nl_info[$item]; + } + + /** + * Get country information from a hostname or IP address. + * + * @access public + * + * @param string $host The hostname or IP address. + * + * @return mixed On success, return an array with the following entries: + * 'code' => Country Code + * 'name' => Country Name + * On failure, return false. + */ + function getCountryByHost($host) + { + global $conf; + + /* List of generic domains that we know is not in the country TLD + list. See: http://www.iana.org/gtld/gtld.htm */ + $generic = array( + 'aero', 'biz', 'com', 'coop', 'edu', 'gov', 'info', 'int', 'mil', + 'museum', 'name', 'net', 'org', 'pro' + ); + + $checkHost = $host; + if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $host)) { + $checkHost = @gethostbyaddr($host); + } + + /* Get the TLD of the hostname. */ + $pos = strrpos($checkHost, '.'); + if ($pos === false) { + return false; + } + $domain = String::lower(substr($checkHost, $pos + 1)); + + /* Try lookup via TLD first. */ + if (!in_array($domain, $generic)) { + require 'Horde/NLS/tld.php'; + if (isset($tld[$domain])) { + return array('code' => $domain, 'name' => $tld[$domain]); + } + } + + /* Try GeoIP lookup next. */ + if (!empty($conf['geoip']['datafile'])) { + require_once 'Horde/NLS/GeoIP.php'; + $geoip = &NLS_GeoIP::singleton($conf['geoip']['datafile']); + $id = $geoip->countryIdByName($checkHost); + if (!empty($id)) { + return array('code' => String::lower($GLOBALS['GEOIP_COUNTRY_CODES'][$id]), 'name' => $GLOBALS['GEOIP_COUNTRY_NAMES'][$id]); + } + } + + return false; + } + + /** + * Returns a Horde image link to the country flag. + * + * @access public + * + * @param string $host The hostname or IP address. + * + * @return string The image URL, or the empty string on error. + */ + function generateFlagImageByHost($host) + { + global $registry; + + $data = NLS::getCountryByHost($host); + if ($data !== false) { + $img = $data['code'] . '.png'; + if (file_exists($registry->get('themesfs', 'horde') . '/graphics/flags/' . $img)) { + return Horde::img($img, $data['name'], '', $registry->getImageDir('horde') . '/flags'); + } else { + return '[' . $data['name'] . ']'; + } + } + + return ''; + } + + /** + * Returns either a specific or all ISO-3166 country names. + * + * @access public + * + * @param optional string $code The ISO 3166 country code. + * + * @return mixed If a country code has been requested will return the + * corresponding country name. If empty will return an + * array of all the country codes and their names. + */ + function &getCountryISO($code = '') + { + static $countries = array(); + if (empty($countries)) { + require_once 'Horde/NLS/countries.php'; + } + if (empty($code)) { + return $countries; + } elseif (isset($countries[$code])) { + return $countries[$code]; + } + return false; + } + +} diff --git a/phpgwapi/inc/horde/Horde/RPC.php b/phpgwapi/inc/horde/Horde/RPC.php new file mode 100644 index 0000000000..ed0c56f5b5 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/RPC.php @@ -0,0 +1,261 @@ + + * + * 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 Jan Schneider + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_RPC + */ +class Horde_RPC { + + /** + * Whether we need an authorized user or not. + * + * @access protected + * @var boolean $_authorize. + */ + var $_authorize = true; + + /** + * RPC server constructor + * + * @access private + * @return object An RPC server instance. + */ + function Horde_RPC() + { + register_shutdown_function(array($this, 'shutdown')); + } + + /** + * Cleans up the RPC server. + * + * @abstract + */ + function shutdown() + { + } + + /** + * Check authentication. Different backends may handle + * authentication in different ways. The base class implementation + * checks for HTTP Authentication against the Horde auth setup. + * + * @return boolean Returns true if authentication is successful. + * Should send appropriate "not authorized" headers + * or other response codes/body if auth fails, + * and take care of exiting. + */ + function authorize() + { + if (!$this->_authorize) { + return true; + } + + $auth = &Auth::singleton($GLOBALS['conf']['auth']['driver']); + + if (isset($_SERVER['PHP_AUTH_USER'])) { + $user = $_SERVER['PHP_AUTH_USER']; + $pass = $_SERVER['PHP_AUTH_PW']; + } + + if (!isset($user) + || !$auth->authenticate($user, array('password' => $pass))) { + header('WWW-Authenticate: Basic realm="Horde RPC"'); + header('HTTP/1.0 401 Unauthorized'); + echo '401 Unauthorized'; + exit; + } + + return true; + } + + /** + * Get the request body input. Different RPC backends can override + * this to return an open stream to php://stdin, for instance - + * whatever is easiest to handle in the getResponse() method. + * + * The base class implementation looks for $HTTP_RAW_POST_DATA and + * returns that if it's available; otherwise, it returns the + * contents of php://stdin. + * + * @return mixed The input - a string (default), a filehandle, etc. + */ + function getInput() + { + if (isset($GLOBALS['HTTP_RAW_POST_DATA'])) { + return $GLOBALS['HTTP_RAW_POST_DATA']; + } else { + return implode("\r\n", file('php://input')); + } + } + + /** + * Sends an RPC request to the server and returns the result. + * + * @param string The raw request string. + * + * @return string The XML encoded response from the server. + */ + function getResponse($request) + { + return _("not implemented"); + } + + /** + * Get the Content-Type of the response. + * + * @return string The MIME Content-Type of the RPC response. + */ + function getResponseContentType() + { + return 'text/xml'; + } + + /** + * Builds an RPC request and sends it to the RPC server. + * + * This statically called method is actually the RPC client. + * + * @param string $driver The protocol driver to use. Currently 'soap' and + * 'xmlrpc' are available. + * @param string $url The path to the RPC server on the called host. + * @param string $method The method to call. + * @param array $params (optional) A hash containing any necessary + * parameters for the method call. + * @param $options Optional associative array of parameters depending on + * the selected protocol driver. + * + * @return mixed The returned result from the method or a PEAR + * error object on failure. + */ + function request($driver, $url, $method, $params = null, $options = array()) + { + if (is_array($driver)) { + list($app, $driver) = $driver; + } + + $driver = basename($driver); + + if (!empty($app)) { + require_once $app . '/lib/RPC/' . $driver . '.php'; + } elseif (@file_exists(dirname(__FILE__) . '/RPC/' . $driver . '.php')) { + require_once dirname(__FILE__) . '/RPC/' . $driver . '.php'; + } else { + @include_once 'Horde/RPC/' . $driver . '.php'; + } + $class = 'Horde_RPC_' . $driver; + if (class_exists($class)) { + return call_user_func(array($class, 'request'), $url, $method, $params, $options); + } else { + require_once 'PEAR.php'; + return PEAR::raiseError('Class definition of ' . $class . ' not found.'); + } + } + + /** + * Attempts to return a concrete RPC server instance based on + * $driver. + * + * @access public + * + * @param mixed $driver The type of concrete RPC subclass to + * return. This is based on the protocol + * driver ($driver). The code is dynamically + * included. If $driver is an array, then + * we will look in $driver[0]/lib/RPC/ + * for the subclass implementation named + * $driver[1].php. + * @param optional array $params A hash containing any additional + * configuration or connection parameters + * a subclass might need. + * + * @return object Horde_RPC The newly created concrete Horde_RPC server + * instance, or a PEAR_Error on an error. + */ + function &factory($driver, $params = null) + { + if (is_array($driver)) { + list($app, $driver) = $driver; + } + + $driver = basename($driver); + + if (!empty($app)) { + require_once $app . '/lib/RPC/' . $driver . '.php'; + } elseif (@file_exists(dirname(__FILE__) . '/RPC/' . $driver . '.php')) { + require_once dirname(__FILE__) . '/RPC/' . $driver . '.php'; + } else { + @include_once 'Horde/RPC/' . $driver . '.php'; + } + $class = 'Horde_RPC_' . $driver; + if (class_exists($class)) { + return $ret = &new $class($params); + } else { + require_once 'PEAR.php'; + return PEAR::raiseError('Class definition of ' . $class . ' not found.'); + } + } + + /** + * Attempts to return a reference to a concrete RPC server + * instance based on $driver. It will only create a new instance + * if no RPC server instance with the same parameters currently + * exists. + * + * This should be used if multiple RPC servers (and thus, multiple RPC + * server instances) are required. + * + * This method must be invoked as: $var = &Horde_RPC::singleton() + * + * @access public + * + * @param string $driver The type of concrete RPC subclass to + * return. This is based on the protocol + * driver ($driver). The code is dynamically + * included. + * @param optional array $params A hash containing any additional + * configuration or connection parameters a + * subclass might need. + * + * @return object Horde_RPC The concrete Horde_RPC server reference, or a + * PEAR_Error + * on an error. + */ + function &singleton($driver, $params = null) + { + static $instances; + + if (!isset($instances)) { + $instances = array(); + } + + $signature = serialize(array($driver, $params)); + if (!array_key_exists($signature, $instances)) { + $instances[$signature] = &Horde_RPC::factory($driver, $params); + } + + return $instances[$signature]; + } + +} diff --git a/phpgwapi/inc/horde/Horde/RPC/syncml.php b/phpgwapi/inc/horde/Horde/RPC/syncml.php new file mode 100644 index 0000000000..4658002dac --- /dev/null +++ b/phpgwapi/inc/horde/Horde/RPC/syncml.php @@ -0,0 +1,280 @@ +, 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 Chuck Hagenbuch + * @author Anthony Mills + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_RPC + */ +class Horde_RPC_syncml extends Horde_RPC { + + /** + * Output ContentHandler used to output XML events. + * @var object $_output + */ + var $_output; + + /** + * @var integer $_xmlStack + */ + var $_xmlStack = 0; + + /** + * Debug directory, if set will store copies of all packets. + */ + var $_debugDir = '/var/www/groupware.groupwareappliance.com/htdocs/syncml/'; + + /** + * Default character set. Only supports UTF-8(ASCII?). + */ + var $_charset = 'UTF-8'; + + /** + * SyncML handles authentication internally, so bypass the RPC + * framework auth check by just returning true here. + */ + function authorize() + { + return true; + } + + /** + * Sends an RPC request to the server and returns the result. + * + * @param string $request The raw request string. + * + * @return string The XML encoded response from the server. + */ + function getResponse($request) + { + // Catch any errors/warnings/notices that may get thrown while + // processing. Don't want to let anything go to the client + // that's not part of the valid response. + ob_start(); + + // Very useful for debugging. Logs XML packets to + // $this->_debugDir. + if (!empty($this->_debugDir) && is_dir($this->_debugDir)) { + $today = date('Y-m-d'); + $deviceName = str_replace('/','',$_SERVER["HTTP_USER_AGENT"]); + if(!is_dir($this->_debugDir .'/'. $today)) + { + mkdir($this->_debugDir .'/'. $today); + } + + $debugDir = $this->_debugDir .'/'. $today .'/'. $deviceName; + if(!is_dir($debugDir)) + { + mkdir($debugDir); + } + + $packetNum = @intval(file_get_contents($debugDir . '/syncml.packetnum')); + if (!isset($packetNum)) { + $packetNum = 0; + } + + $f = @fopen($debugDir . '/syncml_client_' . $packetNum . '.xml', 'wb'); + if ($f) { + fwrite($f, $request); + fclose($f); + } + } + + // $this->_output can be already set by a subclass. + // The subclass can use it's own ContentHandler and bypass + // this classes use of the ContentHandler. In this case + // no output is return from this method, instead the output + // comes from the subclasses ContentHandler + // We may need to add this code back when we get to the content + //if (!isset($this->_output)) { + include_once 'XML/WBXML/ContentHandler.php'; + $this->_output = &new XML_WBXML_ContentHandler(); + //} + $this->_parse($request); + $response = $this->_output->getOutput(); + + // Very useful for debugging. + if (!empty($this->_debugDir) && is_dir($this->_debugDir)) { + $f = @fopen($debugDir . '/syncml_server_' . $packetNum . '.xml', 'wb'); + if ($f) { + fwrite($f, $response); + fclose($f); + } + + $fp = @fopen($debugDir . '/syncml.packetnum', 'w'); + if ($fp) { + fwrite($fp, ++$packetNum); + fclose($fp); + } + } + + // Clear the output buffer that we started above, and log + // anything that came up for later debugging. + $errorLogging = ob_get_clean(); + if (!empty($errorLogging)) { + Horde::logMessage($errorLogging, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + return $response; + } + + function _parse($xml) + { + // Create the XML parser and set method references. + $this->_parser = xml_parser_create_ns($this->_charset); + xml_set_object($this->_parser, $this); + xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($this->_parser, '_startElement', '_endElement'); + xml_set_character_data_handler($this->_parser, '_characters'); + xml_set_processing_instruction_handler($this->_parser, ''); + xml_set_external_entity_ref_handler($this->_parser, ''); + + if (!xml_parse($this->_parser, $xml)) { + return $this->raiseError(sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); + } + + xml_parser_free($this->_parser); + } + + function _startElement($parser, $tag, $attributes) + { + list($uri, $name) = $this->_splitURI($tag); + + $this->startElement($uri, $name, $attributes); + } + + function _characters($parser, $chars) + { + $this->characters($chars); + } + + function _endElement($parser, $tag) + { + list($uri, $name) = $this->_splitURI($tag); + + $this->endElement($uri, $name); + } + + function _splitURI($tag) + { + $parts = explode(':', $tag); + $name = array_pop($parts); + $uri = implode(':', $parts); + return array($uri, $name); + } + + /** + * Get the Content-Type of the response. + * + * @return string The MIME Content-Type of the RPC response. + */ + function getResponseContentType() + { + return 'application/vnd.syncml+xml'; + } + + function startElement($uri, $element, $attrs) + { + + $this->_xmlStack++; + + switch ($this->_xmlStack) { + case 1: + // + // Defined in SyncML Representation Protocol, version 1.1 5.2.1 + $this->_output->startElement($uri, $element, $attrs); + break; + + case 2: + // Either or + if (!isset($this->_contentHandler)) { + // If not defined then create SyncHdr. + $this->_contentHandler = &new Horde_SyncML_SyncmlHdr(); + $this->_contentHandler->setOutput($this->_output); + } + + $this->_contentHandler->startElement($uri, $element, $attrs); + break; + + default: + if (isset($this->_contentHandler)) { + $this->_contentHandler->startElement($uri, $element, $attrs); + } + break; + } + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 1: + // + // Defined in SyncML Representation Protocol, version 1.1 5.2.1 + $this->_output->endElement($uri, $element); + break; + + case 2: + // Either or + if ($element == 'SyncHdr') { + // Then we get the state from SyncMLHdr, and create a + // new SyncMLBody. + $this->_contentHandler->endElement($uri, $element); + + unset($this->_contentHandler); + + $this->_contentHandler = &new Horde_SyncML_SyncmlBody(); + $this->_contentHandler->setOutput($this->_output); + } else { + // No longer used. + $this->_contentHandler->endElement($uri, $element); + unset($this->_contentHandler); + } + break; + + default: + // or + if (isset($this->_contentHandler)) { + $this->_contentHandler->endElement($uri, $element); + } + break; + } + + if (isset($this->_chars)) { + unset($this->_chars); + } + + $this->_xmlStack--; + } + + function characters($str) + { + if (isset($this->_contentHandler)) { + $this->_contentHandler->characters($str); + } + } + + function raiseError($str) + { + return Horde::logMessage($str, __FILE__, __LINE__, PEAR_LOG_ERR); + } + +} diff --git a/phpgwapi/inc/horde/Horde/RPC/syncml_wbxml.php b/phpgwapi/inc/horde/Horde/RPC/syncml_wbxml.php new file mode 100644 index 0000000000..ac0736bf41 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/RPC/syncml_wbxml.php @@ -0,0 +1,110 @@ +, 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 Chuck Hagenbuch + * @author Anthony Mills + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_RPC + */ +class Horde_RPC_syncml_wbxml extends Horde_RPC_syncml { + + /** + * Sends an RPC request to the server and returns the result. + * + * @param string $request The raw request string. + * + * @return string The WBXML encoded response from the server (binary). + */ + function getResponse($request) + { + // Catch any errors/warnings/notices that may get thrown while + // processing. Don't want to let anything go to the client + // that's not part of the valid response. + ob_start(); + + // Very useful for debugging. Logs WBXML packets to + // $this->_debugDir. + if (!empty($this->_debugDir) && is_dir($this->_debugDir)) { + $today = date('Y-m-d'); + $deviceName = str_replace('/','',$_SERVER["HTTP_USER_AGENT"]); + if(!is_dir($this->_debugDir .'/'. $today)) + { + mkdir($this->_debugDir .'/'. $today); + } + + $debugDir = $this->_debugDir .'/'. $today .'/'. $deviceName; + if(!is_dir($debugDir)) + { + mkdir($debugDir); + } + + $packetNum = @intval(file_get_contents($debugDir . '/syncml_wbxml.packetnum')); + if (!isset($packetNum)) { + $packetNum = 0; + } + + $fp = fopen($debugDir . '/syncml_client_' . $packetNum . '.wbxml', 'wb'); + fwrite($fp, $request); + fclose($fp); + } + + $decoder = &new XML_WBXML_Decoder(); + $xmlinput = $decoder->decode($request); + if (is_a($xmlinput, 'PEAR_Error')) { + return ''; + } + + + $xmloutput = parent::getResponse($xmlinput); + + $encoder = &new XML_WBXML_Encoder(); + $encoder->setVersion($decoder->getVersion()); + $encoder->setCharset($decoder->getCharsetStr()); + $response = $encoder->encode($xmloutput); + + if (!empty($this->_debugDir) && is_dir($this->_debugDir)) { + $fp = fopen($debugDir . '/syncml_server_' . $packetNum . '.wbxml', 'wb'); + fwrite($fp, $response); + fclose($fp); + + $fp = fopen($debugDir . '/syncml_wbxml.packetnum', 'w'); + fwrite($fp, ++$packetNum); + fclose($fp); + } + + // Clear the output buffer that we started above, and log + // anything that came up for later debugging. + $errorLogging = ob_get_clean(); + if (!empty($errorLogging)) { + Horde::logMessage($errorLogging, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + return $response; + } + + /** + * Get the Content-Type of the response. + * + * @return string The MIME Content-Type of the RPC response. + */ + function getResponseContentType() + { + return 'application/vnd.syncml+wbxml'; + } + +} diff --git a/phpgwapi/inc/horde/Horde/Registry.php b/phpgwapi/inc/horde/Horde/Registry.php new file mode 100644 index 0000000000..639b45a5f1 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/Registry.php @@ -0,0 +1,1028 @@ + + * Copyright 1999-2005 Jon Parise + * Copyright 1999-2005 Anil Madhavapeddy + * + * 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 Chuck Hagenbuch + * @author Jon Parise + * @author Anil Madhavapeddy + * @since Horde 1.3 + * @package Horde_Framework + */ +class Registry { + + /** + * Hash storing all of the known services and callbacks. + * + * @var array $_apiCache + */ + var $_apiCache = array(); + + /** + * Hash storing all known data types. + * + * @var array $_typeCache + */ + var $_typeCache = array(); + + /** + * Hash storing all of the registered interfaces that applications + * provide. + * + * @var array $_interfaces + */ + var $_interfaces = array(); + + /** + * Hash storing information on each registry-aware application. + * + * @var array $applications + */ + var $applications = array(); + + /** + * Stack of in-use applications. + * + * @var array $_appStack + */ + var $_appStack = array(); + + /** + * Quick pointer to the current application. + * + * @var $_currentApp + */ + var $_currentApp = null; + + /** + * Cache of $prefs objects + * + * @var array $_prefsCache + */ + var $_prefsCache = array(); + + /** + * Cache of application configurations. + * + * @var array $_confCache + */ + var $_confCache = array(); + + /** + * Returns a reference to the global Registry object, only + * creating it if it doesn't already exist. + * + * This method must be invoked as: $registry = &Registry::singleton() + * + * @param optional integer $session_flags Any session flags. + * + * @return object Registry The Horde Registry instance. + */ + function &singleton($session_flags = 0) + { + static $registry; + + if (!isset($registry)) { + $registry = new Registry($session_flags); + } + + return $registry; + } + + /** + * Create a new registry instance. Should never be called except + * by &Registry::singleton(). + * + * @param optional integer $session_flags Any session flags. + * + * @access private + */ + function Registry($session_flags = 0) + { + /* Import and global Horde's configuration values. */ + $this->importConfig('horde'); + + /* Start a session. */ + if ($session_flags & HORDE_SESSION_NONE) { + /* Never start a session if the session flags include + HORDE_SESSION_NONE. */ + $_SESSION = array(); + } else { + Horde::setupSessionHandler(); + @session_start(); + if ($session_flags & HORDE_SESSION_READONLY) { + /* Close the session immediately so no changes can be + made but values are still available. */ + @session_write_close(); + } + } + + /* Read the registry configuration file. */ + require_once HORDE_BASE . '/config/registry.php'; + + /* Initialize the localization routines and variables. */ +# NLS::setLang(); +# NLS::setTextdomain('horde', HORDE_BASE . '/locale', NLS::getCharset()); +# String::setDefaultCharset(NLS::getCharset()); + + /* Stop system if Horde is inactive. */ + if ($this->applications['horde']['status'] == 'inactive') { + Horde::fatal(_("This system is currently deactivated."), __FILE__, __LINE__); + } + + /* Scan for all APIs provided by each app, and set other + * common defaults like templates and graphics. */ + $appList = array_keys($this->applications); + foreach ($appList as $appName) { + $app = &$this->applications[$appName]; + if (($app['status'] == 'heading') || + ($app['status'] == 'inactive') || + ($app['status'] == 'admin' && !Auth::isAdmin())) { + continue; + } + if (isset($app['provides'])) { + if (is_array($app['provides'])) { + foreach ($app['provides'] as $interface) { + $this->_interfaces[$interface] = $appName; + } + } else { + $this->_interfaces[$app['provides']] = $appName; + } + } + if (!isset($app['templates']) && isset($app['fileroot'])) { + $app['templates'] = $app['fileroot'] . '/templates'; + } + if (!isset($app['jsuri']) && isset($app['webroot'])) { + $app['jsuri'] = $app['webroot'] . '/js'; + } + if (!isset($app['jsfs']) && isset($app['fileroot'])) { + $app['jsfs'] = $app['fileroot'] . '/js'; + } + if (!isset($app['themesuri']) && isset($app['webroot'])) { + $app['themesuri'] = $app['webroot'] . '/themes'; + } + if (!isset($app['themesfs']) && isset($app['fileroot'])) { + $app['themesfs'] = $app['fileroot'] . '/themes'; + } + } + +# /* Create the global Perms object. */ +# $GLOBALS['perms'] = &Perms::singleton(); + +# /* Attach javascript notification listener. */ +# $notification = &Notification::singleton(); +# $notification->attach('javascript'); + + /* Register access key logger for translators. */ + if (@$GLOBALS['conf']['log_accesskeys']) { + register_shutdown_function(create_function('', 'Horde::getAccessKey(null, null, true);')); + } + } + + /** + * Return a list of the installed and registered applications. + * + * @since Horde 2.2 + * + * @access public + * + * @param array $filter (optional) An array of the statuses that + * should be returned. Defaults to non-hidden. + * @param boolean $assoc (optional) Associative array with app names + * as keys. + * @param integer $permission (optional) The permission level to check + * for in the list. Defaults to PERMS_SHOW. + * + * @return array List of apps registered with Horde. If no + * applications are defined returns an empty array. + */ + function listApps($filter = null, $assoc = false, $permission = PERMS_SHOW) + { + $apps = array(); + if (is_null($filter)) { + $filter = array('notoolbar', 'active'); + } + + foreach ($this->applications as $app => $params) { + if (in_array($params['status'], $filter) && + (defined('AUTH_HANDLER') || $this->hasPermission($app, $permission))) { + $assoc ? $apps[$app] = $app : $apps[] = $app; + } + } + + return $apps; + } + + /** + * Returns all available registry APIs. + * + * @access public + * + * @return array The API list. + */ + function listAPIs() + { + $apis = array(); + + foreach (array_keys($this->_interfaces) as $interface) { + @list($api, ) = explode('/', $interface); + $apis[] = $api; + } + + return array_unique($apis); + } + + /** + * Returns all of the available registry methods, or alternately + * only those for a specified API. + * + * @access public + * + * @param optional string $api Defines the API for which the methods + * shall be returned. + * + * @return array The method list. + */ + function listMethods($api = null) + { + $methods = array(); + + $this->_fillAPICache(); + + foreach (array_keys($this->applications) as $app) { + if (isset($this->applications[$app]['provides'])) { + $provides = $this->applications[$app]['provides']; + if (!is_array($provides)) { + $provides = array($provides); + } + } else { + $provides = array(); + } + + foreach ($provides as $method) { + if (strpos($method, '/') !== false) { + if (is_null($api) || + (substr($method, 0, strlen($api)) == $api)) { + $methods[] = $method; + } + } elseif (is_null($api) || ($method == $api)) { + if (isset($this->_apiCache[$app])) { + foreach (array_keys($this->_apiCache[$app]) as $service) { + $methods[] = $method . '/' . $service; + } + } + } + } + } + + return array_unique($methods); + } + + /** + * Returns all of the available registry data types. + * + * @access public + * + * @return array The data type list. + */ + function listTypes() + { + $this->_fillAPICache(); + return $this->_typeCache; + } + + /** + * Returns a method's signature. + * + * @access public + * + * @param string $method The full name of the method to check for. + * + * @return array A two dimensional array. The first element contains an + * array with the parameter names, the second one the return + * type. + */ + function getSignature($method) + { + if (!($app = $this->hasMethod($method))) { + return; + } + + $this->_fillAPICache(); + + @list(, $function) = explode('/', $method); + if (isset($this->_apiCache[$app][$function]['type']) && + isset($this->_apiCache[$app][$function]['args'])) { + return array($this->_apiCache[$app][$function]['args'], $this->_apiCache[$app][$function]['type']); + } + } + + /** + * Determine if an interface is implemented by an active + * application. + * + * @access public + * + * @param string $interface The interface to check for. + * + * @return mixed The application implementing $interface if we have it, + * false if the interface is not implemented. + */ + function hasInterface($interface) + { + return !empty($this->_interfaces[$interface]) ? + $this->_interfaces[$interface] : + false; + } + + /** + * Determine if a method has been registered with the registry. + * + * @access public + * + * @param string $method The full name of the method to check for. + * @param string $app (optional) Only check this application. + * + * @return mixed The application implementing $method if we have it, + * false if the method doesn't exist. + */ + function hasMethod($method, $app = null) + { + if (is_null($app)) { + @list($interface, $call) = explode('/', $method); + if (!empty($this->_interfaces[$method])) { + $app = $this->_interfaces[$method]; + } elseif (!empty($this->_interfaces[$interface])) { + $app = $this->_interfaces[$interface]; + } else { + return false; + } + } else { + $call = $method; + } + + $this->_fillAPICache(); + + return !empty($this->_apiCache[$app][$call]) ? $app : false; + } + + /** + * Return the hook corresponding to the default package that + * provides the functionality requested by the $method + * parameter. $method is a string consisting of + * "packagetype/methodname". + * + * @access public + * + * @param string $method The method to call. + * @param optional array $args Arguments to the method. + * + * @return TODO + * Returns PEAR_Error on error. + */ + function call($method, $args = array()) + { + @list($interface, $call) = explode('/', $method); + + if (!empty($this->_interfaces[$method])) { + $app = $this->_interfaces[$method]; + } elseif (!empty($this->_interfaces[$interface])) { + $app = $this->_interfaces[$interface]; + } else { + return PEAR::raiseError('The method "' . $method . '" is not defined in the Horde Registry.'); + } + + return $this->callByPackage($app, $call, $args); + } + + /** + * Output the hook corresponding to the specific package named. + * + * @access public + * + * @param string $app The application being called. + * @param string $call The method to call. + * @param optional array $args Arguments to the method. + * + * @return TODO + * Returns PEAR_Error on error. + */ + function callByPackage($app, $call, $args = array()) + { + /* Note: calling hasMethod() makes sure that we've cached + * $app's services and included the API file, so we don't try + * to do it again explicitly in this method. */ + if (!$this->hasMethod($call, $app)) { + return PEAR::raiseError(sprintf('The method "%s" is not defined in the API for %s.', $call, $app)); + } + + /* Make sure that the function actually exists. */ + $function = '_' . $app . '_' . $call; + if (!function_exists($function)) { + return PEAR::raiseError('The function implementing ' . $call . ' (' . $function . ') is not defined in ' . $app . '\'s API.'); + } + + $checkPerms = isset($this->_apiCache[$app][$call]['checkperms']) ? + $this->_apiCache[$app][$call]['checkperms'] : true; + + /* Switch application contexts now, if necessary, before + * including any files which might do it for us. Return an + * error immediately if pushApp() fails. */ + $pushed = $this->pushApp($app, $checkPerms); + if (is_a($pushed, 'PEAR_Error')) { + return $pushed; + } + + $res = call_user_func_array($function, $args); + + /* If we changed application context in the course of this + * call, undo that change now. */ + if ($pushed === true) { + $this->popApp(); + } + + return $res; + } + + /** + * Return the hook corresponding to the default package that + * provides the functionality requested by the $method + * parameter. $method is a string consisting of + * "packagetype/methodname". + * + * @access public + * + * @param string $method The method to link to. + * @param optional array $args Arguments to the method. + * @param optional mixed $extra Extra, non-standard arguments to the + * method. + * + * @return TODO + * Returns PEAR_Error on error. + */ + function link($method, $args = array(), $extra = '') + { + @list($interface, $call) = explode('/', $method); + + if (!empty($this->_interfaces[$method])) { + $app = $this->_interfaces[$method]; + } elseif (!empty($this->_interfaces[$interface])) { + $app = $this->_interfaces[$interface]; + } else { + return PEAR::raiseError('The method "' . $method . '" is not defined in the Horde Registry.'); + } + + return $this->linkByPackage($app, $call, $args, $extra); + } + + /** + * Output the hook corresponding to the specific package named. + * + * @access public + * + * @param string $app The application being called. + * @param string $call The method to link to. + * @param optional array $args Arguments to the method. + * @param optional mixed $extra Extra, non-standard arguments to the + * method. + * + * @return TODO + * Returns PEAR_Error on error. + */ + function linkByPackage($app, $call, $args = array(), $extra = '') + { + /* Note: calling hasMethod makes sure that we've cached $app's + * services and included the API file, so we don't try to do + * it it again explicitly in this method. */ + if (!$this->hasMethod($call, $app)) { + return PEAR::raiseError('The method "' . $call . '" is not defined in ' . $app . '\'s API.'); + } + + /* Make sure the link is defined. */ + if (empty($this->_apiCache[$app][$call]['link'])) { + return PEAR::raiseError('The link ' . $call . ' is not defined in ' . $app . '\'s API.'); + } + + /* Initial link value. */ + $link = $this->_apiCache[$app][$call]['link']; + + /* Fill in html-encoded arguments. */ + foreach ($args as $key => $val) { + $link = str_replace('%' . $key . '%', htmlentities($val), $link); + } + if (isset($this->applications[$app]['webroot'])) { + $link = str_replace('%application%', $this->get('webroot', $app), $link); + } + + /* Replace htmlencoded arguments that haven't been specified with + an empty string (this is where the default would be substituted + in a stricter registry implementation). */ + $link = preg_replace('|%.+%|U', '', $link); + + /* Fill in urlencoded arguments. */ + foreach ($args as $key => $val) { + $link = str_replace('|' . String::lower($key) . '|', urlencode($val), $link); + } + + /* Append any extra, non-standard arguments. */ + if (is_array($extra)) { + $extra_args = ''; + foreach ($extra as $key => $val) { + $extra_args .- '&' . urlencode($key) . '=' . urlencode($val); + } + } else { + $extra_args = $extra; + } + $link = str_replace('|extra|', $extra_args, $link); + + /* Replace html-encoded arguments that haven't been specified with + an empty string (this is where the default would be substituted + in a stricter registry implementation). */ + $link = preg_replace('|\|.+\||U', '', $link); + + return $link; + } + + /** + * Replace any %application% strings with the filesystem path to + * the application. + * + * @access public + * + * @param string $path The application string. + * @param optional string $app The application being called. + * + * @return TODO + * Returns PEAR_Error on error. + */ + function applicationFilePath($path, $app = null) + { + if (is_null($app)) { + $app = $this->_currentApp; + } + + if (!isset($this->applications[$app])) { + return PEAR::raiseError(sprintf(_("'%s' is not configured in the Horde Registry."), $app)); + } + + return str_replace('%application%', $this->applications[$app]['fileroot'], $path); + } + + /** + * Replace any %application% strings with the web path to the + * application. + * + * @access public + * + * @param string $path The application string. + * @param optional string $app The application being called. + * + * @return TODO + * Returns PEAR_Error on error. + */ + function applicationWebPath($path, $app = null) + { + if (!isset($app)) { + $app = $this->_currentApp; + } + + return str_replace('%application%', $this->applications[$app]['webroot'], $path); + } + + /** + * Set the current application, adding it to the top of the Horde + * application stack. If this is the first application to be + * pushed, retrieve session information as well. + * + * pushApp() also reads the application's configuration file and + * sets up its global $conf hash. + * + * @access public + * + * @param string $app The name of the application to push. + * @param boolean $checkPerms (optional) Make sure that the current user + * has permissions to the application being + * loaded. Defaults to true. Should ONLY + * be disabled by system scripts (cron jobs, + * etc.) and scripts that handle login. + * + * @return boolean Whether or not the _appStack was modified. + * Return PEAR_Error on error. + */ + function pushApp($app, $checkPerms = true) + { + if ($app == $this->_currentApp) { + return false; + } + + /* Bail out if application is not present or inactive. */ + if (!isset($this->applications[$app]) || + $this->applications[$app]['status'] == 'inactive' || + ($this->applications[$app]['status'] == 'admin' && !Auth::isAdmin())) { + Horde::fatal($app . ' is not activated', __FILE__, __LINE__); + } + + /* If permissions checking is requested, return an error if + * the current user does not have read perms to the + * application being loaded. We allow access: + * + * - To all admins. + * - To all authenticated users if no permission is set on $app. + * - To anyone who is allowed by an explicit ACL on $app. */ + if ($checkPerms && !$this->hasPermission($app)) { + Horde::logMessage(sprintf('User %s does not have READ permission for %s', Auth::getAuth(), $app), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return PEAR::raiseError(sprintf(_("User %s is not authorised for %s."), Auth::getAuth(), $this->applications[$app]['name']), 'permission_denied'); + } + + /* Import this application's configuration values. */ + $success = $this->importConfig($app); + if (is_a($success, 'PEAR_Error')) { + return $success; + } + + /* Load preferences after the configuration has been loaded to + * make sure the prefs file has all the information it needs. */ + $this->loadPrefs($app); + + /* Reset the language in case there is a different one + * selected in the preferences. */ + $language = ''; + if (isset($this->_prefsCache[$app]) && + isset($this->_prefsCache[$app]->_prefs['language'])) { + $language = $this->_prefsCache[$app]->getValue('language'); + } + NLS::setLang($language); + NLS::setTextdomain($app, $this->applications[$app]['fileroot'] . '/locale', NLS::getCharset()); + String::setDefaultCharset(NLS::getCharset()); + + /* Once we know everything succeeded and is in a consistent + * state again, push the new application onto the stack. */ + array_push($this->_appStack, $app); + $this->_currentApp = $app; + + return true; + } + + /** + * Remove the current app from the application stack, setting the + * current app to whichever app was current before this one took + * over. + * + * @access public + * + * @return string The name of the application that was popped. + */ + function popApp() + { + /* Pop the current application off of the stack. */ + $previous = array_pop($this->_appStack); + + /* Import the new active application's configuration values + * and set the gettext domain and the preferred language. */ + $this->_currentApp = count($this->_appStack) ? end($this->_appStack) : null; + if ($this->_currentApp) { + $this->importConfig($this->_currentApp); + $this->loadPrefs($this->_currentApp); + #$language = $GLOBALS['prefs']->getValue('language'); + #if (isset($language)) { + # NLS::setLang($language); + #} + NLS::setTextdomain($this->_currentApp, $this->applications[$this->_currentApp]['fileroot'] . '/locale', NLS::getCharset()); + String::setDefaultCharset(NLS::getCharset()); + } + + return $previous; + } + + /** + * Return the current application - the app at the top of the + * application stack. + * + * @access public + * + * @return string The current application. + */ + function getApp() + { + return $this->_currentApp; + } + + /** + * Check permissions on an application. + * + * @access public + * + * @return boolean Whether or not access is allowed. + */ + function hasPermission($app, $permission = PERMS_READ) + { + return true; + #return Auth::isAdmin() || ($GLOBALS['perms']->exists($app) ? + # $GLOBALS['perms']->hasPermission($app, Auth::getAuth(), $permission) : + # (bool)Auth::getAuth()); + } + + /** + * Reads the configuration values for the given application and + * imports them into the global $conf variable. + * + * @access public + * + * @param string $app The name of the application. + * + * @return boolean True on success, PEAR_Error on error. + */ + function importConfig($app) + { + /* Don't make config files global $registry themselves. */ + global $registry; + + /* Cache config values so that we don't re-read files on every + * popApp() call. */ + if (!isset($this->_confCache[$app])) { + if (!isset($this->_confCache['horde'])) { + $conf = array(); + ob_start(); + $success = include HORDE_BASE . '/config/conf.php'; + $errors = ob_get_contents(); + ob_end_clean(); + if (!empty($errors)) { + return PEAR::raiseError(sprintf('Failed to import Horde configuration: %s', strip_tags($errors))); + } + if (!$success) { + return PEAR::raiseError('Failed to import Horde configuration.'); + } + + /* Initial Horde-wide settings. */ + + /* Set the error reporting level in accordance with + * the config settings. */ + error_reporting($conf['debug_level']); + + /* Set the maximum execution time in accordance with + * the config settings. */ + @set_time_limit($conf['max_exec_time']); + + /* Set the umask according to config settings. */ + if (isset($conf['umask'])) { + umask($conf['umask']); + } + } else { + $conf = $this->_confCache['horde']; + } + + if ($app !== 'horde') { + $success = @include $this->applications[$app]['fileroot'] . '/config/conf.php'; + if (!$success) { + return PEAR::raiseError('Failed to import application configuration for ' . $app); + } + } + + $this->_confCache[$app] = &$conf; + } + + $GLOBALS['conf'] = &$this->_confCache[$app]; + return true; + } + + /** + * Loads the preferences for the current user for the current + * application and imports them into the global $prefs variable. + * + * @access public + * + * @param string $app The name of the application. + */ + function loadPrefs($app = null) + { + return array(); + + static $prefs_default = false; + + require_once 'Horde/Prefs.php'; + + if ($app === null) { + $app = $this->_currentApp; + } + + /* If there is no logged in user, return an empty Prefs:: + * object with just default preferences. */ +# if (!Auth::getAuth()) { +# $prefs = &Prefs::factory('none', $app, '', '', null, false); +# $prefs->retrieve(); +# $this->_prefsCache[$app] = &$prefs; +# $GLOBALS['prefs'] = &$this->_prefsCache[$app]; +# $prefs_default = true; +# return; +# } + + /* Cache prefs objects so that we don't re-load them on every + * popApp() call. */ +# if (!isset($this->_prefsCache[$app]) || +# !empty($prefs_default)) { +# $prefs = &Prefs::factory($GLOBALS['conf']['prefs']['driver'], $app, +# Auth::getAuth(), Auth::getCredential('password')); +# $prefs->retrieve(); +# $this->_prefsCache[$app] = &$prefs; +# } + + $GLOBALS['prefs'] = &$this->_prefsCache[$app]; + } + + /** + * Unload preferences from an application or (if no application is + * specified) from ALL applications. Useful when a user has logged + * out but you need to continue on the same page, etc. + * + * After unloading, if there is an application on the app stack to + * load preferences from, then we reload a fresh set. + * + * @access public + * + * @param string $app (optional) The application to unload prefrences for. + * If null, ALL preferences are reset. + */ + function unloadPrefs($app = null) + { + if ($app === null) { + $this->_prefsCache = array(); + } elseif (isset($this->_prefsCache[$app])) { + unset($this->_prefsCache[$app]); + } else { + return; + } + + if ($this->_currentApp) { + $this->loadPrefs(); + } + } + + /** + * Return the requested configuration parameter for the specified + * application. If no application is specified, the value of + * $this->_currentApp (the current application) is used. However, + * if the parameter is not present for that application, the + * Horde-wide value is used instead. If that is not present, we + * return null. + * + * @access public + * + * @param string $parameter The configuration value to retrieve. + * @param optional string $app The application to get the value for. + * + * @return string The requested parameter, or null if it is not set. + */ + function get($parameter, $app = null) + { + if (is_null($app)) { + $app = $this->_currentApp; + } + + if (isset($this->applications[$app][$parameter])) { + $pval = $this->applications[$app][$parameter]; + } else { + if ($parameter == 'icon') { + $pval = $this->_getIcon($app); + } else { + $pval = isset($this->applications['horde'][$parameter]) ? $this->applications['horde'][$parameter] : null; + } + } + + if ($parameter == 'name') { + return _($pval); + } else { + return $pval; + } + } + + /** + * Function to work out an application's graphics URI, taking into + * account any themes directories that may be set up. + * + * @access public + * + * @param optional string $app The application for which to get the + * image directory. If blank will default + * to current application. + * + * @return string The image directory uri path. + */ + function getImageDir($app = null) + { + if (empty($app)) { + $app = $this->_currentApp; + } + + static $img_dir = array(); + if (isset($img_dir[$app])) { + return $img_dir[$app]; + } + + /* This is the default location for the graphics. */ + $img_dir[$app] = $this->get('themesuri', $app) . '/graphics'; + + /* Figure out if this is going to be overridden by any theme + * settings. */ + if (isset($GLOBALS['prefs']) && ($theme = $GLOBALS['prefs']->getValue('theme')) && + (@include $this->get('themesfs', 'horde') . '/' . $theme . '/info.php') && + isset($theme_icons) && + in_array($app, $theme_icons)) { + $img_dir[$app] = $this->get('themesuri', $app) . '/' . $theme . '/graphics'; + } + + return $img_dir[$app]; + } + + /** + * Returns the path to an application's icon, respecting whether the + * current theme has its own icons. + * + * @access private + * + * @param string $app The application for which to get the icon. + */ + function _getIcon($app) + { + return $this->getImageDir($app) . '/' . $app . '.png'; + } + + /** + * Query the initial page for an application - the webroot, if + * there is no initial_page set, and the initial_page, if it is + * set. + * + * @access public + * + * @param optional string $app The name of the application. + * + * @return string URL pointing to the inital page of the application. + * Returns PEAR_Error on error. + */ + function getInitialPage($app = null) + { + if (is_null($app)) { + $app = $this->_currentApp; + } + + if (!isset($this->applications[$app])) { + return PEAR::raiseError(sprintf(_("'%s' is not configured in the Horde Registry."), $app)); + } + return $this->applications[$app]['webroot'] . '/' . (isset($this->applications[$app]['initial_page']) ? $this->applications[$app]['initial_page'] : ''); + } + + /** + * Fills the registry's API cache with the available services. + * + * @access private + */ + function _fillAPICache() + { + if (!empty($this->_apiCache)) { + return; + } + + $status = array('active', 'notoolbar', 'hidden'); +# if (Auth::isAdmin()) { +# $status[] = 'admin'; +# } + $apps = $this->listApps($status); + foreach ($apps as $app) { + $_services = $_types = null; + $api = $this->get('fileroot', $app) . '/lib/api.php'; + if (is_readable($api)) { + include_once $api; + } + if (!isset($_services)) { + $this->_apiCache[$app] = array(); + } else { + $this->_apiCache[$app] = $_services; + } + if (isset($_types)) { + foreach ($_types as $type => $params) { + /* Prefix non-Horde types with the application + * name. */ + $prefix = $app == 'horde' ? '' : "${app}_"; + $this->_typeCache[$prefix . $type] = $params; + } + } + } + } + +} diff --git a/phpgwapi/inc/horde/Horde/String.php b/phpgwapi/inc/horde/Horde/String.php new file mode 100644 index 0000000000..7e4ef98878 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/String.php @@ -0,0 +1,555 @@ + + * + * 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 Jan Schneider + * @since Horde 3.0 + * @package Horde_Util + */ +class String { + + /** + * Sets a default charset that the String:: methods will use if none is + * explicitely specified. + * + * @param string $charset The charset to use as the default one. + */ + function setDefaultCharset($charset) + { + $GLOBALS['_HORDE_STRING_CHARSET'] = $charset; + if (Util::extensionExists('mbstring') && + function_exists('mb_regex_encoding')) { + @mb_regex_encoding($charset); + } + } + + /** + * Converts a string from one charset to another. + * + * Works only if either the iconv or the mbstring extension + * are present and best if both are available. + * 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. + * + * @return string The converted string. + */ + function convertCharset($input, $from, $to = null, $recursion = false) + { + /* 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); + } + 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); + } + return $tmp; + } + if (is_object($input)) { + $vars = get_object_vars($input); + foreach ($vars as $key => $val) { + $input->$key = String::convertCharset($val, $from, $to, true); + } + return $input; + } + + if (!is_string($input)) { + return $input; + } + + $output = false; + + /* Use utf8_[en|de]code() if possible. */ + $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); + } + + /* 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. */ + $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'); + } + + /* Next try mbstring. */ + if (!$output && Util::extensionExists('mbstring')) { + $output = @mb_convert_encoding($input, $to, $from); + } + + /* At last try imap_utf7_[en|de]code if appropriate. */ + if (!$output && Util::extensionExists('imap')) { + if ($from_check && ($to == 'utf7-imap')) { + return @imap_utf7_encode($input); + } + if (($from == 'utf7-imap') && $to_check) { + return @imap_utf7_decode($input); + } + } + + return !$output ? $input : $output; + } + + /** + * Makes a string lowercase. + * + * @param string $string The string to be converted. + * @param boolean $locale If true the string will be converted based on a + * given charset, locale independent else. + * @param string $charset If $locale is true, the charset to use when + * converting. If not provided the current charset. + * + * @return string The string with lowercase characters + */ + function lower($string, $locale = false, $charset = null) + { + static $lowers; + + if ($locale) { + /* The existence of mb_strtolower() depends on the platform. */ + if (Util::extensionExists('mbstring') && + function_exists('mb_strtolower')) { + if (is_null($charset)) { + $charset = $GLOBALS['_HORDE_STRING_CHARSET']; + } + $ret = @mb_strtolower($string, $charset); + if (!empty($ret)) { + return $ret; + } + } + return strtolower($string); + } + + if (!isset($lowers)) { + $lowers = array(); + } + if (!isset($lowers[$string])) { + $language = setlocale(LC_CTYPE, 0); + setlocale(LC_CTYPE, 'en_US'); + $lowers[$string] = strtolower($string); + setlocale(LC_CTYPE, $language); + } + + return $lowers[$string]; + } + + /** + * Makes a string uppercase. + * + * @param string $string The string to be converted. + * @param boolean $locale If true the string will be converted based on a + * given charset, locale independent else. + * @param string $charset If $locale is true, the charset to use when + * converting. If not provided the current charset. + * + * @return string The string with uppercase characters + */ + function upper($string, $locale = false, $charset = null) + { + static $uppers; + + if ($locale) { + /* The existence of mb_strtoupper() depends on the + * platform. */ + if (function_exists('mb_strtoupper')) { + if (is_null($charset)) { + $charset = $GLOBALS['_HORDE_STRING_CHARSET']; + } + $ret = @mb_strtoupper($string, $charset); + if (!empty($ret)) { + return $ret; + } + } + return strtoupper($string); + } + + if (!isset($uppers)) { + $uppers = array(); + } + if (!isset($uppers[$string])) { + $language = setlocale(LC_CTYPE, 0); + setlocale(LC_CTYPE, 'en_US'); + $uppers[$string] = strtoupper($string); + setlocale(LC_CTYPE, $language); + } + + return $uppers[$string]; + } + + /** + * Returns a string with the first letter capitalized if it is + * alphabetic. + * + * @param string $string The string to be capitalized. + * @param boolean $locale If true the string will be converted based on a + * given charset, locale independent else. + * @param string $charset The charset to use, defaults to current charset. + * + * @return string The capitalized string. + */ + function ucfirst($string, $locale = false, $charset = null) + { + if ($locale) { + $first = String::substr($string, 0, 1, $charset); + if (String::isAlpha($first, $charset)) { + $string = String::upper($first, true, $charset) . String::substr($string, 1, null, $charset); + } + } else { + $string = String::upper(substr($string, 0, 1), false) . substr($string, 1); + } + return $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. + * + * @return string The string's part. + */ + function substr($string, $start, $length = null, $charset = null) + { + if (Util::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); + if (!empty($ret)) { + return $ret; + } + } + if (is_null($length)) { + $length = String::length($string); + } + return substr($string, $start, $length); + } + + /** + * Returns the character (not byte) length of a string. + * + * @param string $string The string to return the length of. + * @param string $charset The charset to use when calculating the string's + * length. + * + * @return string The string's part. + */ + function length($string, $charset = null) + { + if (Util::extensionExists('mbstring')) { + if (is_null($charset)) { + $charset = $GLOBALS['_HORDE_STRING_CHARSET']; + } + $ret = @mb_strlen($string, $charset); + if (!empty($ret)) { + return $ret; + } + } + return strlen($string); + } + + /** + * Returns the numeric position of the first occurrence of $needle + * in the $haystack 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 + * to start searching. + * @param string $charset The charset to use when searching for the + * $needle string. + * + * @return int The position of first occurrence. + */ + function pos($haystack, $needle, $offset = 0, $charset = null) + { + if (Util::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'); + if (!isset($php_errormsg)) { + return $ret; + } + } + return strpos($haystack, $needle, $offset); + } + + /** + * Returns a string padded to a certain length with another 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 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, + * STR_PAD_RIGHT, or STR_PAD_BOTH. + * @param string $charset The charset of the input and the padding + * strings. + * + * @return string The padded string. + */ + function pad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT, + $charset = null) + { + $mb_length = String::length($input, $charset); + $sb_length = strlen($input); + $pad_length = String::length($pad, $charset); + + /* Return if we already have the length. */ + if ($mb_length >= $length) { + return $input; + } + + /* Shortcut for single byte strings. */ + if ($mb_length == $sb_length && $pad_length == strlen($pad)) { + return str_pad($input, $length, $pad, $type); + } + + switch ($type) { + case STR_PAD_LEFT: + $left = $length - $mb_length; + $output = String::substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $charset) . $input; + break; + case STR_PAD_BOTH: + $left = floor(($length - $mb_length) / 2); + $right = ceil(($length - $mb_length) / 2); + $output = String::substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $charset) . + $input . + String::substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $charset); + break; + case STR_PAD_RIGHT: + $right = $length - $mb_length; + $output = $input . String::substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $charset); + break; + } + + return $output; + } + + /** + * Wraps the text of a message. + * + * @todo Make multibyte-save. + * + * @access public + * + * @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 wrap($text, $length = 80, $break_char = "\n", $charset = null, + $quote = false) + { + $paragraphs = array(); + + foreach (preg_split('/\r?\n/', $text) as $input) { + if ($quote && (strpos($input, '>') === 0)) { + $line = $input; + } else { + /* We need to handle the Usenet-style signature line + * separately; since the space after the two dashes is + * REQUIRED, we don't want to trim the line. */ + if ($input != '-- ') { + $input = rtrim($input); + } + $line = wordwrap($input, $length, $break_char); + } + + $paragraphs[] = $line; + } + + return implode($break_char, $paragraphs); + } + + /** + * 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. + * + * @param $string The string to test. + * @param $charset The charset to use when testing the string. + * + * @return boolean True if the parameter was alphabetic only. + */ + 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; + } + + return ctype_alpha($string); + } + + /** + * Returns true if every character in the parameter is a lowercase + * letter in the current locale. + * + * @access public + * + * @param $string The string to test. + * @param $charset The charset to use when testing the string. + * + * @return boolean True if the parameter was lowercase. + */ + function isLower($string, $charset = null) + { + return ((String::lower($string, true, $charset) === $string) && + String::isAlpha($string, $charset)); + } + + /** + * Returns true if every character in the parameter is an + * uppercase letter in the current locale. + * + * @access public + * + * @param string $string The string to test. + * @param string $charset The charset to use when testing the string. + * + * @return boolean True if the parameter was uppercase. + */ + function isUpper($string, $charset = null) + { + return ((String::upper($string, true, $charset) === $string) && + String::isAlpha($string, $charset)); + } + + /** + * 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. + * + * @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 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; + } + + $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 (isset($old_charset)) { + @mb_regex_encoding($old_charset); + } + + return $matches; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML.php b/phpgwapi/inc/horde/Horde/SyncML.php new file mode 100644 index 0000000000..d454c43d4a --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML.php @@ -0,0 +1,611 @@ + + * + * 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 { + + /** + * Output ContentHandler used to output XML events. + * @var object $_output + */ + var $_output; + + /** + * @var integer $_xmlStack + */ + var $_xmlStack = 1; + + /** + * @var string $_chars + */ + var $_chars; + + function setOutput(&$output) + { + $this->_output = &$output; + } + + function startElement($uri, $element, $attrs) + { + $this->_xmlStack++; + } + + function endElement($uri, $element) + { + if (isset($this->_chars)) { + unset($this->_chars); + } + + $this->_xmlStack--; + } + + function characters($str) + { + if (isset($this->_chars)) { + $this->_chars = $this->_chars . $str; + } else { + $this->_chars = $str; + } + } + +} + +/** + * Defined in SyncML Representation Protocol, version 1.1 5.2.2 + * + * @package Horde_SyncML + */ +class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler { + + /** + * Used to specify if in Source tag. Defined in SyncML + * Representation Protocol, version 1.1 5.1.20. + * + * @var boolean $_isSource + */ + var $_isSource = false; + + /** + * Defined in SyncML Representation Protocol, version 1.1 + * 5.1.9. User name. + * + * @var string $_locName + */ + var $_locName; + + /** + * Defined in SyncML Representation Protocol, version 1.1 5.1.18 + * + * @var string $_sessionID + */ + + var $_sessionID; + + /** + * Defined in SyncML Representation Protocol, version 1.1. Must + * be 1.0 (0) or 1.1 (1). + * + * @var string $_version + */ + var $_version; + + /** + * Defined in SyncML Representation Protocol, version 1.1 5.1.12 + * + * @var string $_msgID + */ + var $_msgID; + + /** + * Defined in SyncML Representation Protocol, version 1.1 5.1.10 + * + * @var string $_targetURI + */ + var $_targetURI; + + /** + * Defined in SyncML Representation Protocol, version 1.1 5.1.10, + * 5.1.20 + * + * @var string $_sourceURI + */ + var $_sourceURI; + + var $_isCred; + + var $_credData; + + var $_credFormat; + + var $_credType; + + function getStateFromSession($sourceURI, $locName, $sessionID) + { + // Remove any existing session since we'll be contructing a + // custom session id. + session_destroy(); + + // 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. + session_id('syncml' . preg_replace('/[^a-zA-Z0-9]/', '', $sourceURI . $sessionID)); + @session_start(); + Horde::logMessage('SyncML: session id = ' . session_id(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + if (!isset($_SESSION['SyncML.state'])) { + // Create a new state if one does not already exist. + Horde::logMessage('SyncML: new session state', __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: is session authorized', __FILE__, __LINE__, PEAR_LOG_DEBUG); + + return $_SESSION['SyncML.state']; + } + + function startElement($uri, $element, $attrs) + { + parent::startElement($uri, $element, $attrs); + + switch ($this->_xmlStack) { + case 3: + if ($element == 'Source') { + // + $this->_isSource = true; + } elseif ($element == 'Cred') { + $this->_isCred = true; + } + break; + } + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 2: + /* + $str = 'localname=' . $this->_locName; + $str .= ' version=' . $this->_version; + $str .= ' msgid=' . $this->_msgID; + $str .= ' source=' . $this->_sourceURI; + $str .= ' target=' . $this->_targetURI; + $str .= ' sessionID=' . $this->_sessionID; + */ + // + // Find the state. + $state = $this->getStateFromSession($this->_sourceURI, $this->_locName, $this->_sessionID); + + $state->setVersion($this->_version); + $state->setMsgID($this->_msgID); + $state->setTargetURI($this->_targetURI); + if(isset($this->_credData) && isset($this->_locName) && !$state->isAuthorized()) + { + $state->setPassword($this->_credData); + $state->setLocName($this->_locName); + } + + #$str = 'authorized=' . $state->isAuthorized(); + #$str .= ' version=' . $state->getVersion(); + #$str .= ' msgid=' . $state->getMsgID(); + #$str .= ' source=' . $state->getSourceURI(); + #$str .= ' target=' . $state->getTargetURI(); + #$str .= ' locName=' . $state->getLocName(); + + $_SESSION['SyncML.state'] = $state; + + #Horde::logMessage('SymcML: session id 2 =' . session_id(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Got the state; now write our SyncHdr header. + $this->outputSyncHdr($this->_output); + break; + + case 3: + if ($element == 'VerProto') { + // + if (trim($this->_chars) == 'SyncML/1.1') { + $this->_version = 1; + } else { + $this->_version = 0; + } + } elseif ($element == 'SessionID') { + // + $this->_sessionID = trim($this->_chars); + } elseif ($element == 'MsgID') { + // + $this->_msgID = intval(trim($this->_chars)); + } elseif ($element == 'Source') { + // + $this->_isSource = false; + } elseif ($element == 'Cred') { + // + $this->_isCred = false; + + //multisync does not specify the cred format + //if ($this->_credFormat == 'b64') { + $this->_credData = base64_decode($this->_credData); + //} + + $tmp = split(':', $this->_credData); + // set only if not set by LocName already + if(!isset($this->_locName)) + { + $this->_locName = $tmp[0]; + } + $this->_credData = $tmp[1]; + + Horde::logMessage('SyncML: $this->_locName: ' . $this->_locName, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + 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; + + case 5: + if ($this->_isCred) { + if ($element == 'Format') { + // + $this->_credFormat = trim($this->_chars); + } elseif ($element == 'Type') { + // + $this->_credType = trim($this->_chars); + } + } + break; + } + + parent::endElement($uri, $element); + } + + function outputSyncHdr(&$output) + { + $attrs = array(); + + $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'; + $output->characters($chars); + $output->endElement($uri, 'VerDTD'); + + $output->startElement($uri, 'VerProto', $attrs); + $chars = ($this->_version == 1) ? 'SyncML/1.1' : 'SyncML/1.0'; + $output->characters($chars); + $output->endElement($uri, 'VerProto'); + + $output->startElement($uri, 'SessionID', $attrs); + $output->characters($this->_sessionID); + $output->endElement($uri, 'SessionID'); + + $output->startElement($uri, 'MsgID', $attrs); + $output->characters($this->_msgID); + $output->endElement($uri, 'MsgID'); + + $output->startElement($uri, 'Target', $attrs); + $output->startElement($uri, 'LocURI', $attrs); + $output->characters($this->_sourceURI); + $output->endElement($uri, 'LocURI'); + $output->endElement($uri, 'Target'); + + $output->startElement($uri, 'Source', $attrs); + $output->startElement($uri, 'LocURI', $attrs); + $output->characters($this->_targetURI); + $output->endElement($uri, 'LocURI'); + $output->endElement($uri, 'Source'); + + #if(!strpos($this->_targetURI,'syncit')) + #{ + # $output->startElement($uri, 'RespURI', $attrs); + # $output->characters($this->_targetURI.'?syncid='.$GLOBALS['sessionid'].'-'.$GLOBALS['phpgw_info']['user']['kp3']); + # $output->characters($this->_targetURI.'?after=20040101T133000Z-syncid=lars'); + # $output->characters($this->_targetURI.'?sincit=10'); + # $output->characters('http://192.168.4.227/horde/rpc.php?after=20040101T133000Z-username=lars'); + # $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. + $output->startElement($uriMeta, 'MaxMsgSize', $attrs); + $chars = '50000'; + $output->characters($chars); + $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'); + + $output->endElement($uri, 'Meta'); + */ + + $output->endElement($uri, 'SyncHdr'); + } + + function getSourceURI() + { + return $this->_sourceURI; + } + + function getLocName() + { + return $this->_locName; + } + + function getSessionID() + { + return $this->_sessionID; + } + + function getVersion() + { + return $this->_version; + } + + function getMsgID() + { + return $this->_msgID; + } + + function getTargetURI() + { + return $this->_targetURI; + } + + function opaque($o) + { + } + +} + +/** + * Defined in SyncML Representation Protocol, version 1.1 5.2.3 + * + * @package Horde_SyncML + */ +class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler { + + var $_currentCmdID = 1; + + var $_currentCommand; + + var $_actionCommands = false; + + function startElement($uri, $element, $attrs) + { + parent::startElement($uri, $element, $attrs); + + 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 + + // + $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 + { + // Request credentials if not sent so far + $status = &new Horde_SyncML_Command_Status(RESPONSE_MISSING_CREDENTIALS, 'SyncHdr'); + } + + $status->setSourceRef($state->getSourceURI()); + $status->setTargetRef($state->getTargetURI()); + $status->setCmdRef(0); + + /*$str = 'authorized=' . $state->isAuthorized(); + $str .= ' version=' . $state->getVersion(); + $str .= ' msgid=' . $state->getMsgID(); + $str .= ' source=' . $state->getSourceURI(); + $str .= ' target=' . $state->getTargetURI(); + */ + $this->_currentCmdID = $status->output($this->_currentCmdID, $this->_output); + break; + + case 3: + $state = & $_SESSION['SyncML.state']; + + // <[Command]> + $this->_currentCommand = Horde_SyncML_Command::factory($element); + $this->_currentCommand->startElement($uri, $element, $attrs); + + if ($element != 'Status' && $element != 'Map' && $element != 'Final') { + // We've got to do something! This can't be the last + // packet. + $this->_actionCommands = true; + } + + switch($element) + { +# case 'Final': +# if($state->getClientSyncStatus() == 1) +# { +# $state->setClientSyncStatus(2); +# } +# break; + case 'Sync': + $state->setSyncStatus(CLIENT_SYNC_STARTED); + break; + } + break; + + default: + // <...> + $this->_currentCommand->startElement($uri, $element, $attrs); + break; + } + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 2: + // + $state = & $_SESSION['SyncML.state']; + + // send the sync reply + // we do still have some data to send OR + // we should reply to the Sync command + $sync = &new Horde_SyncML_Command_Sync(); + $this->_currentCmdID = $sync->syncToClient($this->_currentCmdID, $this->_output); + + // send the Final tag if possible + if($state->getSyncStatus() != SERVER_SYNC_DATA_PENDING && $state->getSyncStatus() != CLIENT_SYNC_STARTED) + { + $final = &new Horde_SyncML_Command_Final(); + $this->_currentCmdID = $final->output($this->_currentCmdID, $this->_output); + } + + $this->_output->endElement($uri, $element); + + Horde::logMessage('SyncML: syncStatus ' . $state->getSyncStatus() .'actionCommands: '.$this->_actionCommands, __FILE__, __LINE__, PEAR_LOG_INFO); + 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: sync' . session_id() . ' completed successfully!', __FILE__, __LINE__, PEAR_LOG_INFO); + $state->writeSyncSummary(); + $log = $state->getLog(); + $s=""; + foreach($log as $k => $v) { + $s .= " $k=$v"; + } + Horde::logMessage('SyncML: summary:' . $s, __FILE__, __LINE__, PEAR_LOG_INFO); + // session can be closed here! + session_unset(); + session_destroy(); + } + 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->getURI(), 'Type', $attrs); + $this->_output->characters('application/vnd.syncml-devinf+xml'); + $this->_output->endElement($state->getURI(), '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: Sync _syncTag = '. $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_INFO); + break; + default: + $this->_currentCmdID = $this->_currentCommand->output($this->_currentCmdID, $this->_output); + break; + } + + unset($this->_currentCommand); + break; + + default: + // + $this->_currentCommand->endElement($uri, $element); + break; + } + + parent::endElement($uri, $element); + } + + function characters($str) + { + if (isset($this->_currentCommand)) { + $this->_currentCommand->characters($str); + } + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command.php b/phpgwapi/inc/horde/Horde/SyncML/Command.php new file mode 100644 index 0000000000..4455eeed5f --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command.php @@ -0,0 +1,76 @@ + + * + * 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 { + + var $_cmdID; + + var $_xmlStack; + + var $_chars; + + function &factory($command, $params = null) + { + 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++; + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 2: + if ($element == 'CmdID') { + $this->_cmdID = intval(trim($this->_chars)); + } + break; + } + + if (isset($this->_chars)) { + unset($this->_chars); + } + + $this->_xmlStack--; + } + + function characters($str) + { + if (isset($this->_chars)) { + $this->_chars = $this->_chars . $str; + } else { + $this->_chars = $str; + } + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php new file mode 100644 index 0000000000..3e3776a427 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php @@ -0,0 +1,364 @@ + + * + * 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_Alert extends Horde_SyncML_Command { + + /** + * @var integer $_alert + */ + var $_alert; + + /** + * @var string $_sourceURI + */ + var $_sourceLocURI; + + /** + * @var string $_targetURI + */ + var $_targetLocURI; + + /** + * @var string $_metaAnchorNext + */ + var $_metaAnchorNext; + + /** + * @var integer $_metaAnchorLast + */ + var $_metaAnchorLast; + + /** + * Use in xml tag. + */ + var $_isInSource; + + /** + * Creates a new instance of Alert. + */ + function Horde_SyncML_Command_Alert($alert = null) + { + if ($alert != null) { + $this->_alert = $alert; + } + } + + function output($currentCmdID, &$output) + { + $attrs = array(); + + $state = &$_SESSION['SyncML.state']; + + // Handle unauthorized first. + if (!$state->isAuthorized()) { + $status = &new Horde_SyncML_Command_Status(RESPONSE_INVALID_CREDENTIALS, 'Alert'); + $status->setCmdRef($this->_cmdID); + $currentCmdID = $status->output($currentCmdID, $output); + return $currentCmdID; + } + + + + if($this->_alert < ALERT_RESULT_ALERT) + { + + $type = $this->_targetLocURI; + + // 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); + + $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 = 0; + $state->setServerAnchorLast($type, 0); + } + + // Set Server Anchor for this sync to current time. + $state->setServerAnchorNext($type,time()); + if ($clientlast && $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. + $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); + } + + $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)); + } + + // Mirror Next Anchor from client back to client. + if (isset($this->_metaAnchorNext)) { + $status->setItemDataAnchorNext($this->_metaAnchorNext); + } + + // Mirror Last Anchor from client back to client. + if (isset($this->_metaAnchorLast)) { + $status->setItemDataAnchorLast($this->_metaAnchorLast); + } + + $currentCmdID = $status->output($currentCmdID, $output); + + if ($state->isAuthorized()) { + $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); + $chars = $this->_alert; + $output->characters($chars); + $output->endElement($state->getURI(), 'Data'); + + $output->startElement($state->getURI(), 'Item', $attrs); + + 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'); + + $currentCmdID++; + } + } + else + { + if ($state->isAuthorized()) { + $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); + $chars = $this->_alert; + $output->characters($chars); + $output->endElement($state->getURI(), 'Data'); + + $output->startElement($state->getURI(), 'Item', $attrs); + + 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->endElement($state->getURI(), 'Item'); + $output->endElement($state->getURI(), 'Alert'); + + $currentCmdID++; + } + } + + + return $currentCmdID; + } + + /** + * Setter for property sourceURI. + * + * @param string $sourceURI New value of property sourceURI. + */ + function setSourceLocURI($sourceURI) + { + $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; + } + break; + } + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 1: + $state = & $_SESSION['SyncML.state']; + Horde::logMessage('SyncML: looking for sync for ' . $this->_targetLocURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $sync = $state->getSync($this->_targetLocURI); + + if (!$sync) { + 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); + } + 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; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php new file mode 100644 index 0000000000..cf62408905 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Final.php @@ -0,0 +1,34 @@ + + * + * 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 { + + function output($currentCmdID, &$output) + { + $state = $_SESSION['SyncML.state']; + + $attrs = array(); + $output->startElement($state->getURI(), 'Final', $attrs); + + $output->endElement($state->getURI(), 'Final'); + + return $currentCmdID; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php new file mode 100644 index 0000000000..7788c5a2e5 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Get.php @@ -0,0 +1,101 @@ +1.0The Horde Framework4711workstation./contactstext/x-vcard2.1text/x-vcard2.11234567./calendartext/x-vcalendar2.0text/x-vcalendar1.0text/x-vcalendar2.0text/x-vcalendar1.01234567text/x-vcalendarBEGINVCALENDARVEVENTVTODODTSTARTDTENDDTSTAMPSEQUENCEENDVCALENDARVEVENTVTODOUIDSUMMARYVERSION1.0AALARMCATEGORIESCLASSDALARMEXDATERESOURCESSTATUSATTACHATTENDEEDCREATEDCOMPLETEDDESCRIPTIONDUELAST-MODIFIEDLOCATIONPRIORITYRELATED-TORRULETRANSPURLtext/calendarBEGINVCALENDARVEVENTVTODOVALARMDTSTARTDTENDDTSTAMPSEQUENCEENDVCALENDARVEVENTVTODOVALARMUIDSUMMARYVERSION2.0CATEGORIESCLASSDALARMEXDATERESOURCESSTATUSATTACHATTENDEEDCREATEDCOMPLETEDDESCRIPTIONDUELAST-MODIFIEDLOCATIONPRIORITYRELATED-TOTRANSPURLRRULECOMMMENTACTIONTRIGGERDURATIONREPEATtext/x-vcardBEGINVCARDENDVCARDVERSION2.1ENCODINGVALUECHARSETFNNNAMENICKNAMEPHOTOBDAYADRLABELTELEMAILMAILERTZGEOTITLEROLELOGOAGENTORGCATEGORIESNOTEPRODIDREVSORT-STRINGSOUNDURLUIDCLASSKEY'); +define('DEFAULT_DEFINF_11', '1.1The Horde Framework4711workstation./contactstext/x-vcard2.1text/x-vcard2.11234567./calendartext/x-vcalendar2.0text/x-vcalendar1.0text/x-vcalendar2.0text/x-vcalendar1.01234567text/x-vcalendarBEGINVCALENDARVEVENTVTODODTSTARTDTENDDTSTAMPSEQUENCEENDVCALENDARVEVENTVTODOUIDSUMMARYVERSION1.0AALARMCATEGORIESCLASSDALARMEXDATERESOURCESSTATUSATTACHATTENDEEDCREATEDCOMPLETEDDESCRIPTIONDUELAST-MODIFIEDLOCATIONPRIORITYRELATED-TORRULETRANSPURLtext/calendarBEGINVCALENDARVEVENTVTODOVALARMDTSTARTDTENDDTSTAMPSEQUENCEENDVCALENDARVEVENTVTODOVALARMUIDSUMMARYVERSION2.0CATEGORIESCLASSDALARMEXDATERESOURCESSTATUSATTACHATTENDEEDCREATEDCOMPLETEDDESCRIPTIONDUELAST-MODIFIEDLOCATIONPRIORITYRELATED-TOTRANSPURLRRULECOMMMENTACTIONTRIGGERDURATIONREPEATtext/x-vcardBEGINVCARDENDVCARDVERSION2.1ENCODINGVALUECHARSETFNNNAMENICKNAMEPHOTOBDAYADRLABELTELEMAILMAILERTZGEOTITLEROLELOGOAGENTORGCATEGORIESNOTEPRODIDREVSORT-STRINGSOUNDURLUIDCLASSKEY'); +#define('DEFAULT_DEFINF', '1.0The Horde Framework4711'. +#'workstationcontactstext/x-vcard2.1'. +#'text/x-vcard2.1127'. +#''. +# +#'calendartext/x-vcalendar2.0'. +#'text/x-vcalendar1.0text/x-vcalendar2.0'. +#'text/x-vcalendar1.017'. +# +#'text/x-vcalendarBEGINVCALENDARVEVENT'. +#'VTODODTSTARTDTENDDTSTAMPSEQUENCE'. +#'ENDVCALENDARVEVENTVTODOUID'. +#'SUMMARYVERSION1.0AALARMCATEGORIES', +#'CLASSDALARMEXDATERESOURCESSTATUS', +#'ATTACHATTENDEEDCREATEDCOMPLETEDDESCRIPTION'. +#'DUELAST-MODIFIEDLOCATIONPRIORITY'. +#'RELATED-TORRULETRANSPURL'. +#'text/calendarBEGINVCALENDARVEVENTVTODO'. +#'VALARMDTSTARTDTENDDTSTAMPSEQUENCE'. +#'ENDVCALENDARVEVENTVTODOVALARM'. +#'UIDSUMMARYVERSION2.0CATEGORIES'. +#'CLASSDALARMEXDATERESOURCESSTATUS'. +#'ATTACHATTENDEEDCREATEDCOMPLETEDDESCRIPTION'. +#'DUELAST-MODIFIEDLOCATIONPRIORITY'. +#'RELATED-TOTRANSPURLRRULECOMMMENT'. +#'ACTIONTRIGGERDURATIONREPEAT'. +# +#'text/x-vcardBEGINVCARDENDVCARD'. +#'VERSION2.1ENCODINGVALUECHARSET'. +#'FNNNAMENICKNAMEPHOTO'. +#'BDAYADRLABELTELEMAIL'. +#'MAILERTZGEOTITLEROLE'. +#'LOGOAGENTORGCATEGORIESNOTE'. +#'PRODIDREVSORT-STRINGSOUNDURL'. +#'UIDCLASSKEY'); + +/** + * 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'; + + $status = &new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Get'); + $status->setCmdRef($this->_cmdID); + $status->setTargetRef($ref); + $currentCmdID = $status->output($currentCmdID, $output); + Horde::logMessage('SyncML: end output ref: '.$ref, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Currently DEVINF seems to be ok only for SyncML 1.0. But + // this is used by P800/P900 and these seem to require it: + if ($state->isAuthorized() && $state->getVersion() == 0) { + $results = &new Horde_SyncML_Command_Results(); + $results->setCmdRef($this->_cmdID); + $results->setType("application/vnd.syncml-devinf+xml"); + $results->setlocSourceURI($ref); + $results->setData(DEFAULT_DEFINF_10); + + $currentCmdID = $results->output($currentCmdID, $output); + } + elseif($state->isAuthorized() && $state->getVersion() == 1) + { + $results = &new Horde_SyncML_Command_Results(); + $results->setCmdRef($this->_cmdID); + $results->setType("application/vnd.syncml-devinf+xml"); + $results->setlocSourceURI($ref); + $results->setData(DEFAULT_DEFINF_11); + + $currentCmdID = $results->output($currentCmdID, $output); + + } + + return $currentCmdID; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php new file mode 100644 index 0000000000..1c2d4c8200 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Map.php @@ -0,0 +1,180 @@ + + * + * 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$ + * @since Horde 3.0 + * @package Horde_SyncML + */ +class Horde_SyncML_Command_Map extends Horde_SyncML_Command { + + /** + * @var string $_sourceURI + */ + var $_sourceLocURI; + + /** + * @var string $_targetURI + */ + var $_targetLocURI; + + /** + * Use in xml tag. + */ + var $_isInSource; + + var $_mapTarget; + var $_mapSource; + + function output($currentCmdID, &$output) + { + $attrs = array(); + + $state = $_SESSION['SyncML.state']; + + $status = &new Horde_SyncML_Command_Status($state->isAuthorized() ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS, 'Map'); + $status->setCmdRef($this->_cmdID); + if ($this->_sourceLocURI != null) { + $status->setSourceRef($this->_sourceLocURI); + } + if ($this->_targetLocURI != null) { + $status->setTargetRef($this->_targetLocURI); + } + + $currentCmdID = $status->output($currentCmdID, $output); + + 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; + } + } + + 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; + + 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); + } else { + Horde::logMessage("SyncML: creating Map for source=" . + $this->_mapSource . " and target=" . $this->_mapTarget, __FILE__, __LINE__, PEAR_LOG_DEBUG); + // Overwrite existing data by removing it first: + $r = $state->setUID($this->_targetLocURI, $this->_mapSource, $this->_mapTarget); + if (is_a($r, 'PEAR_Error')) { + Horde::logMessage('SyncML: PEAR Error: ' . $r->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + } + } + break; + + case 3: + 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 4: + if ($element == 'LocURI') { + if ($this->_isInSource) { + $this->_mapSource = trim($this->_chars); + } else { + $this->_mapTarget = trim($this->_chars); + } + } + break; + } + + parent::endElement($uri, $element); + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php new file mode 100644 index 0000000000..7e03b36687 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php @@ -0,0 +1,157 @@ + + * + * 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 { + + /** + * @var string $_manufacturer + */ + + var $_manufacturer; + + /** + * @var string $_model + */ + + var $_model; + + /** + * @var string $_oem + */ + + var $_oem; + + /** + * @var string $_softwareVersion + */ + + var $_softwareVersion; + + function endElement($uri, $element) + { + #Horde::logMessage('SyncML: put endelement ' . $element . ' stack ' . $this->_xmlStack, __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 '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 'VerDTD': + $this->_deviceInfo['DTDVersion'] = trim($this->_chars); + break; + } + break; + 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); + } + + function output($currentCmdID, &$output ) + { + $state = &$_SESSION['SyncML.state']; + + $status = &new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Put'); + $status->setCmdRef($this->_cmdID); + + $ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11'; + + $status->setSourceRef($ref); + + if($state->isAuthorized()) + { + if(count((array)$this->_deviceInfo) > 0) + { + $state->setClientDeviceInfo($this->_deviceInfo); + $state->writeClientDeviceInfo(); + } + } + + return $status->output($currentCmdID, $output); + } + + function startElement($uri, $element, $attrs) + { + #Horde::logMessage('SyncML: put startelement ' . $element, __FILE__, __LINE__, PEAR_LOG_DEBUG); + 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 new file mode 100644 index 0000000000..73bca5df63 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Replace.php @@ -0,0 +1,25 @@ + + * + * 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 { + + 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 new file mode 100644 index 0000000000..4ad52c8eb2 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php @@ -0,0 +1,229 @@ + + * + * 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_Results extends Horde_SyncML_Command { + + var $_cmdRef; + var $_type; + var $_data; + var $_locSourceURI; + var $_deviceInfo; + + function endElement($uri, $element) + { + #Horde::logMessage('SyncML: put endelement ' . $element . ' stack ' . $this->_xmlStack, __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 '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 'VerDTD': + $this->_deviceInfo['DTDVersion'] = trim($this->_chars); + break; + } + break; + 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); + } + + 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()) + { + 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 new file mode 100644 index 0000000000..41410bedb0 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Status.php @@ -0,0 +1,251 @@ + + * + * 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 { + + var $_response; + + var $_cmdRef; + + /** + * Must be present. + */ + var $_cmd; + + /** + * Must if not null (what does this mean?). + */ + var $_sourceRef; + + var $_targetRef; + + var $_chalMetaFormat; + + var $_chalMetaType; + + var $_chalMetaNextNonce; + + var $_itemDataAnchorNext; + + var $_itemDataAnchorLast; + + function Horde_SyncML_Command_Status($response = null, $cmd = null) + { + if ($response != null) { + $this->_response = $response; + } + + if ($cmd != null) { + $this->_cmd = $cmd; + } + } + + function output($currentCmdID, &$output) + { + $attrs = array(); + + $state = $_SESSION['SyncML.state']; + + if ($this->_cmd != null) { + $attrs = array(); + $output->startElement($state->getURI(), 'Status', $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(), 'Cmd', $attrs); + $chars = $this->_cmd; + $output->characters($chars); + $output->endElement($state->getURI(), 'Cmd'); + + if (isset($this->_sourceRef)) { + $output->startElement($state->getURI(), 'SourceRef', $attrs); + $chars = $this->_sourceRef; + $output->characters($chars); + $output->endElement($state->getURI(), 'SourceRef'); + } + + if (isset($this->_targetRef)) { + $output->startElement($state->getURI(), 'TargetRef', $attrs); + $chars = $this->_targetRef; + $output->characters($chars); + $output->endElement($state->getURI(), 'TargetRef'); + } + + // If we are responding to the SyncHdr and we are not + // authorized then request basic authorization. + // + // FIXME: Right now we always send this, ignoring the + // isAuthorized() test. Is that correct? + if ($this->_cmd == 'SyncHdr' && !$state->isAuthorized()) { + $this->_chalMetaFormat = 'b64'; + $this->_chalMetaType = 'syncml:auth-basic'; + } + + if (isset($this->_chalMetaFormat) && isset($this->_chalMetaType)) { + $output->startElement($state->getURI(), 'Chal', $attrs); + $output->startElement($state->getURI(), 'Meta', $attrs); + + $metainfuri = $state->getURIMeta(); + + $output->startElement($metainfuri, 'Format', $attrs); + $chars = $this->_chalMetaFormat; + $output->characters($chars); + $output->endElement($metainfuri, 'Format'); + + $output->startElement($metainfuri, 'Type', $attrs); + $chars = $this->_chalMetaType; + $output->characters($chars); + $output->endElement($metainfuri, 'Type'); + + // $output->startElement($metainfuri, 'NextNonce', $attrs); + // $chars = $this->_chalMetaNextNonce; + // $output->characters($chars); + // $output->endElement($metainfuri, 'NextNonce'); + + $output->endElement($state->getURI(), 'Meta'); + $output->endElement($state->getURI(), 'Chal'); + } + + $output->startElement($state->getURI(), 'Data', $attrs); + $chars = $this->_response; + $output->characters($chars); + $output->endElement($state->getURI(), 'Data'); + + if (isset($this->_itemDataAnchorNext) || isset($this->_itemDataAnchorLast)) { + $output->startElement($state->getURI(), 'Item', $attrs); + $output->startElement($state->getURI(), 'Data', $attrs); + + // $metainfuri = $state->getURIMeta(); + $metainfuri = $state->getURI(); // debug by FOU + + $output->startElement($metainfuri, 'Anchor', $attrs); + + if (isset($this->_itemDataAnchorNext)) { + + $output->startElement($metainfuri, 'Next', $attrs); + $chars = $this->_itemDataAnchorNext; + $output->characters($chars); + $output->endElement($metainfuri, 'Next'); + } + + if (isset($this->_itemDataAnchorLast)) { + + $output->startElement($metainfuri, 'Last', $attrs); + $chars = $this->_itemDataAnchorLast; + $output->characters($chars); + $output->endElement($metainfuri, 'Last'); + } + + $output->endElement($metainfuri, 'Anchor'); + + $output->endElement($state->getURI(), 'Data'); + $output->endElement($state->getURI(), 'Item'); + } + + $output->endElement($state->getURI(), 'Status'); + + $currentCmdID++; + } + + return $currentCmdID; + } + + /** + * Setter for property response. + * + * @param string $response New value of property response. + */ + function setResponse($response) + { + $this->_response = $response; + } + + /** + * Setter for property cmd. + * + * @param string $cmd New value of property cmd. + */ + function setCmd($cmd) + { + $this->_cmd = $cmd; + } + + /** + * Setter for property cmdRef. + * + * @param string $cmdRef New value of property cmdRef. + */ + function setCmdRef($cmdRef) + { + $this->_cmdRef = $cmdRef; + } + + /** + * Setter for property sourceRef. + * + * @param string $sourceRef New value of property sourceRef. + */ + function setSourceRef($sourceRef) + { + $this->_sourceRef = $sourceRef; + } + + /** + * Setter for property targetRef. + * + * @param string $targetRef New value of property targetRef. + */ + function setTargetRef($targetRef) + { + $this->_targetRef = $targetRef; + } + + /** + * Setter for property itemDataAnchorNext. + * + * @param string $itemDataAnchorNext New value of property itemDataAnchorNext. + */ + function setItemDataAnchorNext($itemDataAnchorNext) + { + $this->_itemDataAnchorNext = $itemDataAnchorNext; + } + + /** + * Setter for property itemDataAnchorLast. + * + * @param string $itemDataAnchorLast New value of property itemDataAnchorLast. + */ + function setItemDataAnchorLast($itemDataAnchorLast) + { + $this->_itemDataAnchorLast = $itemDataAnchorLast; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php new file mode 100644 index 0000000000..8d2cbf6030 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync.php @@ -0,0 +1,222 @@ + + * + * 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 { + + 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); + + $sync = $state->getSync($this->_targetURI); + $currentCmdID = $sync->startSync($currentCmdID, $output); + + foreach ($this->_syncElements as $element ) { + $currentCmdID = $sync->nextSyncCommand($currentCmdID, $element, $output); + } + + return $currentCmdID; + } + + function getTargetURI() + { + return $this->_targetURI; + } + + function startElement($uri, $element, $attrs) + { + parent::startElement($uri, $element, $attrs); + + switch ($this->_xmlStack) { + 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; + } + break; + } + + if (isset($this->_currentSyncElement)) { + $this->_currentSyncElement->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); + + $state = $_SESSION['SyncML.state']; + if($state->getSyncStatus() == CLIENT_SYNC_FINNISHED || $state->getSyncStatus() == SERVER_SYNC_DATA_PENDING) + { +############## + $targets = $state->getTargets(); + Horde::logMessage('SyncML: starting sync to client '.$targets[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); + $attrs = array(); + + foreach($targets as $target) + { + $sync = $state->getSync($target); + + $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(); + $output->startElement($state->getURI(), 'NumberOfChanged', $attrs); + $output->characters($numberOfItems); + $output->endElement($state->getURI(), 'NumberOfChanged'); + } + + $currentCmdID = $sync->endSync($currentCmdID, $output); + + $output->endElement($state->getURI(), 'Sync'); + + if($currentCmdID > MAX_DATA) break; + } + #Horde::logMessage('SyncML: ending sync to client '.$targets[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // no syncs left + if($state->getTargets() === FALSE) + $state->setSyncStatus(SERVER_SYNC_FINNISHED); +############################# + } +# elseif($state->getSyncStatus() == CLIENT_SYNC_STARTED) +# { +# Horde::logMessage('SyncML: client alert '.$state->_currentSourceURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); +# Horde::logMessage('SyncML: client alert '.$state->_currentTargetURI, __FILE__, __LINE__, PEAR_LOG_DEBUG); +# Horde::logMessage('SyncML: client alert '.$state->_currentTargetURIParameters, __FILE__, __LINE__, PEAR_LOG_DEBUG); +# $alert = &new Horde_SyncML_Command_Alert(ALERT_NEXT_MESSAGE); +# $alert->setSourceLocURI($state->_currentSourceURI); +# $alert->setTargetLocURI((isset($state->_currentTargetURIParameters) ? $state->_currentTargetURI.'?/'.$state->_currentTargetURIParameters : $state->_currentTargetURI)); +# $currentCmdID = $alert->output($currentCmdID, $output); +# } + + return $currentCmdID; + } + + function endElement($uri, $element) + { + if (isset($this->_currentSyncElement)) { + $this->_currentSyncElement->endElement($uri, $element); + } + + switch ($this->_xmlStack) { + case 2: + if ($element == 'Replace' || $element == 'Add' || $element == 'Delete') { + $this->_syncElements[] = $this->_currentSyncElement; + unset($this->_currentSyncElement); + } + break; + + case 3: + $state = & $_SESSION['SyncML.state']; + + if ($element == 'LocURI' && !isset($this->_currentSyncElement)) { + if ($this->_isInSource) { + $this->_sourceURI = trim($this->_chars); + $state->_currentSourceURI = $this->_sourceURI; + } else { + $this->_targetURI = trim($this->_chars); + + $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; + } + + } + } + break; + } + + parent::endElement($uri, $element); + + } + + function characters($str) + { + if (isset($this->_currentSyncElement)) { + $this->_currentSyncElement->characters($str); + } else { + if (isset($this->_chars)) { + $this->_chars = $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 new file mode 100644 index 0000000000..c4348f74d5 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Add.php @@ -0,0 +1,31 @@ + + * + * 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 { + + function output($currentCmdID, &$output) + { + $status = &new Horde_SyncML_Command_Status(RESPONSE_ITEM_ADDED, 'Add'); + $status->setCmdRef($this->_cmdID); + + if (isset($this->_luid)) { + $status->setSourceRef($this->_luid); + } + 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 new file mode 100644 index 0000000000..c8ec8bd337 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php @@ -0,0 +1,144 @@ + + * + * 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_ContentSyncElement extends Horde_SyncML_Command_Sync_SyncElement { + + /** + * 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 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); + } + + function outputCommand($currentCmdID, &$output, $command) + { + $state = $_SESSION['SyncML.state']; + + $attrs = array(); + $output->startElement($state->getURI(), $command, $attrs); + + $output->startElement($state->getURI(), 'CmdID', $attrs); + $chars = $currentCmdID; + $output->characters($chars); + $output->endElement($state->getURI(), 'CmdID'); + + if (isset($this->_contentType)) { + $output->startElement($state->getURI(), 'Meta', $attrs); + $output->startElement($state->getURIMeta(), 'Type', $attrs); + $output->characters($this->_contentType); + $output->endElement($state->getURIMeta(), 'Type'); + $output->endElement($state->getURI(), 'Meta'); + } + + if (isset($this->_content) + || isset($this->_locURI) || isset($this->targetURI)) { + $output->startElement($state->getURI(), 'Item', $attrs); + // send only when sending adds + if ($this->_locURI != null && strtolower($command) == 'add') { + $output->startElement($state->getURI(), 'Source', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = substr($this->_locURI,0,39); + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Source'); + } + + if ($this->_targetURI != null) { + $output->startElement($state->getURI(), 'Target', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = $this->_targetURI; + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Target'); + } + if (isset($this->_content)) { + $output->startElement($state->getURI(), 'Data', $attrs); + $chars = $this->_content; + $output->characters($chars); + $output->endElement($state->getURI(), 'Data'); + } + $output->endElement($state->getURI(), 'Item'); + } + + $output->endElement($state->getURI(), $command); + + $currentCmdID++; + + return $currentCmdID; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php new file mode 100644 index 0000000000..d951037a1f --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Delete.php @@ -0,0 +1,32 @@ + + * + * 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) + { + $status = &new Horde_SyncML_Command_Status(RESPONSE_OK, 'Delete'); + $status->setCmdRef($this->_cmdID); + + if (isset($this->_luid)) { + $status->setSourceRef($this->_luid); + } + + 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 new file mode 100644 index 0000000000..e8f9bfe7b9 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/Replace.php @@ -0,0 +1,32 @@ + + * + * 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(RESPONSE_OK, 'Replace'); + $status->setCmdRef($this->_cmdID); + + if (isset($this->_luid)) { + $status->setSourceRef($this->_luid); + } + + 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 new file mode 100644 index 0000000000..01211b236b --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/SyncElement.php @@ -0,0 +1,123 @@ + + * + * 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 $_contentType; + + function &factory($command, $params = null) + { + @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); + } else { + Horde::logMessage('SyncML: Class definition of ' . $class . ' not found in SyncElement::factory.', __FILE__, __LINE__, PEAR_LOG_DEBUG); + require_once 'PEAR.php'; + return PEAR::raiseError('Class definition of ' . $class . ' not found.'); + } + } + + function startElement($uri, $element, $attrs) + { + parent::startElement($uri, $element, $attrs); + + switch ($this->_xmlStack) { + case 3: + if ($element == 'Source') { + $this->_isSource = true; + } + break; + } + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 1: + // Need to add sync elements to the Sync method? + break; + + case 3: + if ($element == 'Source') { + $this->_isSource = false; + } elseif ($element == 'Data') { + $this->_content = trim($this->_chars); + } elseif ($element == 'Type') { + if(!isset($this->_contentType)) + $this->_contentType = trim($this->_chars); + } + break; + + case 4: + if ($element == 'LocURI' && $this->_isSource) { + $this->_luid = trim($this->_chars); + } elseif ($element == 'Type') { + $this->_contentType = trim($this->_chars); + } + break; + } + + parent::endElement($uri, $element); + } + + function getLocURI() + { + return $this->_luid; + } + + function getGUID() + { + return $this->_guid; + } + + function setLocURI($luid) + { + $this->_luid = $luid; + } + + function setGUID($guid) + { + $this->_guid = $guid; + } + + function setContentType($c) + { + $this->_contentType = $c; + } + + function getContentType() + { + return $this->_contentType; + } + + function getContent() + { + return $this->_content; + } + + function setContent($content) + { + $this->_content = $content; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/State.php b/phpgwapi/inc/horde/Horde/SyncML/State.php new file mode 100644 index 0000000000..01d55fa570 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/State.php @@ -0,0 +1,865 @@ + + * + * 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_State { + + var $_sessionID; + + var $_verProto; + + var $_msgID; + + var $_targetURI; + + var $_sourceURI; + + var $_version; + + var $_locName; + + var $_password; + + var $_isAuthorized; + + var $_uri; + + var $_uriMeta; + + var $_syncs = array(); + + var $_clientAnchorNext = array(); // written to db after successful sync + + var $_serverAnchorLast = array(); + + var $_serverAnchorNext = array(); // written to db after successful sync + + var $_clientDeviceInfo = array(); + + // array list of changed items, which need to be synced to the client + var $_changedItems; + + // array list of deleted items, which need to be synced to the client + var $_deletedItems; + + // 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; + + var $_log = array(); + + /** + * Creates a new instance of Horde_SyncML_State. + */ + function Horde_SyncML_State($sourceURI, $locName, $sessionID, $password = false) + { + $this->setSourceURI($sourceURI); + $this->setLocName($locName); + $this->setSessionID($sessionID); + if ($password) { + $this->setPassword($password); + } + + $this->isAuthorized = false; + } + + /** + * Returns the DataTree used as persistence layer for SyncML. The + * datatree var should not be a class member of State as State is + * stored as a session var. Resource handles (=db connections) + * cannot be stored in sessions. + * + * @return object DataTree The DataTree object. + */ + function &getDataTree() + { + $driver = $GLOBALS['conf']['datatree']['driver']; + $params = Horde::getDriverConfig('datatree', $driver); + $params = array_merge($params, array( 'group' => 'syncml' )); + + return DataTree::singleton($driver, $params); + } + + function getLocName() + { + if(isset($this->_locName)) + return $this->_locName; + else + return False; + } + + function getSourceURI() + { + return $this->_sourceURI; + } + + function getTargetURI() + { + return $this->_targetURI; + } + + function getVersion() + { + return $this->_version; + } + + function &getAddedItems($_type) + { + if(isset($this->_addedItems[$_type])) + { + return $this->_addedItems[$_type]; + } + + return false; + } + + function &getChangedItems($_type) + { + if(isset($this->_changedItems[$_type])) + { + return $this->_changedItems[$_type]; + } + + return false; + } + + function &getDeletedItems($_type) + { + if(isset($this->_deletedItems[$_type])) + { + return $this->_deletedItems[$_type]; + } + + return false; + } + + function getMoreDataPending() + { + return $this->_moreDataPending; + } + + function getMsgID() + { + return $this->_msgID; + } + + function &getSyncStatus() + { + return $this->_syncStatus; + } + + function setAddedItems($_type, $_addedItems) + { + $this->_addedItems[$_type] = $_addedItems; + } + + function setChangedItems($_type, $_changedItems) + { + $this->_changedItems[$_type] = $_changedItems; + } + + function setClientDeviceInfo($clientDeviceInfo) + { + $this->_clientDeviceInfo = $clientDeviceInfo; + } + + function setDeletedItems($_type, $_deletedItems) + { + $this->_deletedItems[$_type] = $_deletedItems; + } + + function setMoreDataPending($_state) + { + $this->_moreDataPending = $_state; + } + + /** + * Setter for property msgID. + * @param msgID New value of property msgID. + */ + function setMsgID($msgID) + { + $this->_msgID = $msgID; + } + + /** + * Setter for property locName. + * @param locName New value of property locName. + */ + function setLocName($locName) + { + $this->_locName = $locName; + } + + /** + * Setter for property locName. + * @param locName New value of property locName. + */ + function setPassword($password) + { + $this->_password = $password; + } + + function setSourceURI($sourceURI) + { + $this->_sourceURI = $sourceURI; + } + + function setSyncStatus($_syncStatus) + { + #Horde::logMessage('SyncML: syncState set to ==> ' . $_syncStatus, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $this->_syncStatus = $_syncStatus; + } + + function setTargetURI($targetURI) + { + $this->_targetURI = $targetURI; + } + + function setVersion($version) + { + $this->_version = $version; + + if ($version == 0) { + $this->_uri = NAME_SPACE_URI_SYNCML; + $this->_uriMeta = NAME_SPACE_URI_METINF; + } else { + $this->_uri = NAME_SPACE_URI_SYNCML_1_1; + $this->_uriMeta = NAME_SPACE_URI_METINF_1_1; + } + } + + function setSessionID($sessionID) + { + $this->_sessionID = $sessionID; + } + + function isAuthorized() + { + if (!$this->_isAuthorized) { + + if(strstr($this->_locName,'@') === False) + { + $this->_locName .= '@'.$GLOBALS['phpgw_info']['server']['default_domain']; + } + + #Horde::logMessage('SyncML: Authenticate ' . $this->_locName . ' - ' . $this->_password, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + if($GLOBALS['sessionid'] = $GLOBALS['phpgw']->session->create($this->_locName,$this->_password,'text','u')) + { + $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: Authentication of ' . $this->_locName . ' failed' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + } + else + { + // store sessionID in a variable, because ->verify maybe resets that value + $sessionID = session_id(); + if(!$GLOBALS['phpgw']->session->verify($sessionID, 'staticsyncmlkp3')) + Horde::logMessage('SyncML_EGW: egw session('.$sessionID.') not verified ' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + return $this->_isAuthorized; + } + + function clearSync($target) + { + unset($this->_syncs[$target]); + } + + function setSync($target, $sync) + { + $this->_syncs[$target] = $sync; + } + + function getSync($target) + { + if (isset($this->_syncs[$target])) { + return $this->_syncs[$target]; + } else { + return false; + } + } + + function getTargets() + { + if(count($this->_syncs) < 1) + return FALSE; + + foreach($this->_syncs as $target => $sync) + { + $targets[] = $target; + } + + return $targets; + } + + function getURI() + { + return $this->_uri; + } + + function getURIMeta() + { + return $this->_uriMeta; + } + + /** + * Converts a Horde 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. + * + * Remember that the datatree is really a tree disguised as a + * table. So to look up the guid above, getId first looks for an + * entry 'kronolith' and then for an entry + * 0d1b415fc124d3427722e95f0e926b75 with kronolith as parent. + */ + function getLocID($type, $guid) + { + $dt = &$this->getDataTree(); + $id = $dt->getId($this->_locName . $this->_sourceURI . $type . $guid); + if (is_a($id, 'PEAR_Error')) { + return false; + } + + $gid = $dt->getObjectById($id); + if (is_a($gid, 'PEAR_Error')) { + return false; + } + + return $gid->get('locid'); + } + + /** + * 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) + { + $dt = &$this->getDataTree(); + + // Set $locid. + $gid = &new DataTreeObject($this->_locName . $this->_sourceURI . $type . $guid); + $gid->set('type', $type); + $gid->set('locid', $locid); + $gid->set('ts', $ts); + + $r = $dt->add($gid); + if (is_a($r, 'PEAR_Error')) { + // Object already exists: update instead. + $r = $dt->updateData($gid); + } + $this->dieOnError($r, __FILE__, __LINE__); + + // Set $globaluid + $lid = &new DataTreeObject($this->_locName . $this->_sourceURI . $type . $locid); + $lid->set('globaluid', $guid); + $r = $dt->add($lid); + if (is_a($r, 'PEAR_Error')) { + // object already exists: update instead. + $r = $dt->updateData($lid); + } + $this->dieOnError($r, __FILE__, __LINE__); + } + + /** + * 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) + { + $this->dieOnError($type, __FILE__, __LINE__); + $this->dieOnError($locid, __FILE__, __LINE__); + $this->dieOnError($locid, __FILE__, __LINE__); + $this->dieOnError($this->_locName, __FILE__, __LINE__); + $this->dieOnError($this->_sourceURI, __FILE__, __LINE__); + + $dt = &$this->getDataTree(); + + $id = $dt->getId($this->_locName . $this->_sourceURI . $type . $locid); + if (is_a($id, 'PEAR_Error')) { + return false; + } + $lid = $dt->getObjectById($id); + if (is_a($lid, 'PEAR_Error')) { + return false; + } + + return $lid->get('globaluid'); + } + + /** + * 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) + { + $dt = &$this->getDataTree(); + + $id = $dt->getId($this->_locName . $this->_sourceURI . $type . $guid); + if (is_a($id, 'PEAR_Error')) { + return false; + } + + $gid = $dt->getObjectById($id); + if (is_a($gid, 'PEAR_Error')) { + return false; + } + + return $gid->get('ts'); + } + + /** + * 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) + { + $dt = &$this->getDataTree(); + + $id = $dt->getId($this->_locName . $this->_sourceURI . $type . $locid); + if (is_a($id, 'PEAR_Error')) { + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : nothing to remove", __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + $lid = $dt->getObjectById($id); + $guid = $lid->get('globaluid'); + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : removing guid:$guid and lid:$lid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $dt->remove($guid); + $dt->remove($lid); + + return $guid; + } + + /** + * This function should use DevINF information. + */ + function getPreferedContentType($type) + { + if ($type == 'contacts') { + return 'text/x-vcard'; + } elseif ($type == 'notes') { + return 'text/x-vnote'; + } elseif ($type == 'tasks') { + return 'text/x-vcalendar'; + } elseif ($type == 'calendar') { + return 'text/x-vcalendar'; + } + } + + /** + * Returns the preferred contenttype of the client for the given + * sync data type (database). + * + * This is passed as an option to the Horde API export functions. + */ + function getPreferedContentTypeClient($_sourceLocURI) + { + $deviceInfo = $this->getClientDeviceInfo(); + + if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'])) + { + return array('ContentType' => $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType']); + } + + Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI .' not found', __FILE__, __LINE__, PEAR_LOG_DEBUG); + return PEAR::raiseError(_('sourceLocURI not found')); +# elseif ($type == 'contacts') { +# return 'text/x-vcard'; +# } elseif ($type == 'notes') { +# return array('ContentType' => 'text/x-vnote', +# 'ENCODING' => 'QUOTED-PRINTABLE', +# 'CHARSET' => 'UTF-8'); +# } elseif ($type == 'tasks') { +# return 'text/x-vcalendar'; +# } elseif ($type == 'calendar') { +# return array('ContentType' => 'text/x-vcalendar', +# 'ENCODING' => 'QUOTED-PRINTABLE', +# 'CHARSET' => 'UTF-8'); +# } + } + + function setClientAnchorNext($type, $a) + { + $this->_clientAnchorNext[$type] = $a; + } + + function setServerAnchorLast($type, $a) + { + $this->_serverAnchorLast[$type] = $a; + } + + function setServerAnchorNext($type, $a) + { + $this->_serverAnchorNext[$type] = $a; + } + + function getClientAnchorNext($type) + { + return $this->_clientAnchorNext[$type]; + } + + function getServerAnchorNext($type) + { + return $this->_serverAnchorNext[$type]; + } + + function getServerAnchorLast($type) + { + return $this->_serverAnchorLast[$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) + { + $dt = &$this->getDataTree(); + + $id = $dt->getId($this->_locName . $this->_sourceURI . $type . 'syncSummary'); + if (is_a($id, 'PEAR_Error')) { + return false; + } + + return $dt->getObjectById($id); + } + + /** + * 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() + { + $dt = &$this->getDataTree(); + + $id = $dt->getId($this->_locName . $this->_sourceURI . 'deviceInfo'); + if (is_a($id, 'PEAR_Error')) { + return false; + } + + $info = $dt->getObjectById($id); + + return $info->get('ClientDeviceInfo'); + } + + /** + * write clients device info to database + */ + function writeClientDeviceInfo() + { + if (!isset($this->_clientDeviceInfo) || !is_array($this->_clientDeviceInfo)) { + return; + } + + $dt = &$this->getDataTree(); + + $s = $this->_locName . $this->_sourceURI . 'deviceInfo'; + + // Set $locid. + $info = &new DataTreeObject($s); + $info->set('ClientDeviceInfo', $this->_clientDeviceInfo); + $r = $dt->add($info); + if (is_a($r, 'PEAR_Error')) { + // Object already exists: update instead. + $dt->updateData($info); + } + } + + /** + * 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() + { + if (!isset($this->_serverAnchorNext) || !is_array($this->_serverAnchorNext)) { + return; + } + + $dt = &$this->getDataTree(); + + foreach (array_keys($this->_serverAnchorNext) as $type) { + $s = $this->_locName . $this->_sourceURI . $type . 'syncSummary'; + + // Set $locid. + $info = &new DataTreeObject($s); + $info->set('ClientAnchor', $this->_clientAnchorNext); + $info->set('ServerAnchor', $this->_serverAnchorNext); + $r = $dt->add($info); + if (is_a($r, 'PEAR_Error')) { + // Object already exists: update instead. + $dt->updateData($info); + } + } + } + + /** + * The log simply counts the entries for each topic. + */ + function log($topic) + { + if (isset($this->_log[$topic])) { + $this->_log[$topic] += 1; + } else { + $this->_log[$topic] = 1; + } + } + + /** + * The Log is an array where the key is the event name and the + * value says how often this event occured. + */ + function getLog() + { + return $this->_log; + } + + /** + * Convert the content. + * + * Currently strips uid (primary key) information as client and + * server might use different ones. + * + * Charset conversions might be added here too. + */ + function convertClient2Server($content, $contentType) + { + switch ($contentType) { + case 'text/calendar': + case 'text/x-icalendar': + case 'text/x-vcalendar': + case 'text/x-vevent': + case 'text/x-vtodo': + $content = preg_replace('/^UID:.*\n/m', '', $content, 1); + break; + } + + return $content; + } + + /** + * Convert the content. + * + * Currently strips uid (primary key) information as client and + * server might use different ones. + * + * Charset conversions might be added here too. + */ + function convertServer2Client($content, $contentType) + { + switch ($contentType) { + case 'text/calendar': + case 'text/x-icalendar': + case 'text/x-vcalendar': + case 'text/x-vevent': + case 'text/x-vtodo': + $content = preg_replace('/^UID:.*\n/m', '', $content, 1); + break; + } + + // FIXME: utf8 really should be fine. But the P900 seems to + // expect ISO 8559 even when <?xml version="1.0" + // encoding="utf-8"> is specified. + // + // So at least make this dependant on the device information. + return utf8_decode($content); + } + + /** + * When True, Task Item changes (NAG) are sent to the server + * during "calendar" Syncs. That's the way the P800/900 handles + * things. Should be retrieved from devinf? + */ + function handleTasksInCalendar() + { + return true; + } + + /** + * This is a small helper function that can be included to check + * whether a given $obj is a PEAR_Error or not. If so, it logs + * to debug, var_dumps the $obj and exits. + */ + function dieOnError($obj, $file = __FILE__, $line = __LINE__) + { + if (!is_a($obj, 'PEAR_Error')) { + return; + } + + Horde::logMessage('SyncML: PEAR Error: ' . $obj->getMessage(), $file, $line, PEAR_LOG_ERR); + print "PEAR ERROR\n\n"; + var_dump($obj); + exit; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/State_egw.php b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php new file mode 100644 index 0000000000..a9b5914101 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php @@ -0,0 +1,393 @@ +_locName . $this->_sourceURI . $type; + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array('map_timestamp'); + + $where = array + ( + 'map_id' => $mapID, + 'map_guid' => $guid, + ); + + Horde::logMessage('SyncML: getChangeTS for ' . $mapID .' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + Horde::logMessage('SyncML: getChangeTS changets is ' . $db->from_timestamp($db->f('map_timestamp')), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $db->from_timestamp($db->f('map_timestamp')); + } + + 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() + { + $deviceID = $this->_locName . $this->_sourceURI; + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array + ( + 'dev_dtdversion', + 'dev_numberofchanges', + 'dev_largeobjs', + 'dev_swversion', + 'dev_oem', + 'dev_model', + 'dev_manufacturer', + 'dev_devicetype', + 'dev_deviceid', + 'dev_datastore', + ); + + $where = array + ( + 'dev_id' => $deviceID, + ); + + $db->select('egw_syncmldevinfo', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + $devInfo = array + ( + 'DTDVersion' => $db->f('dev_dtdversion'), + 'supportNumberOfChanges' => $db->f('dev_numberofchanges'), + 'supportLargeObjs' => $db->f('dev_largeobjs'), + 'softwareVersion' => $db->f('dev_swversion'), + 'oem' => $db->f('dev_oem'), + 'model' => $db->f('dev_model'), + 'manufacturer' => $db->f('dev_manufacturer'), + 'deviceType' => $db->f('dev_devicetype'), + 'deviceID' => $db->f('dev_deviceid'), + 'dataStore' => unserialize($db->f('dev_datastore')), + ); + + return $devInfo; + } + + return 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; + + Horde::logMessage('SyncML: search GlobalUID for ' . $mapID .' / '.$locid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array('map_guid'); + + $where = array + ( + 'map_id' => $mapID, + 'map_locuid' => $locid, + 'map_expired' => 0, + ); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + return $db->f('map_guid'); + } + + return false; + } + + /** + * 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; + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array('map_locuid'); + + $where = array + ( + 'map_id' => $mapID, + 'map_guid' => $guid + ); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + return $db->f('map_locuid'); + } + + return false; + } + + /** + * 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; + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array('sync_serverts','sync_clientts'); + + $where = array + ( + 'dev_id' => $deviceID, + 'sync_path' => $type + ); + + $db->select('egw_syncmlsummary', $cols, $where, __LINE__, __FILE__); + + Horde::logMessage("SyncML: get SYNCSummary for $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if($db->next_record()) + { + Horde::logMessage("SyncML: get SYNCSummary for $deviceID serverts: ".$db->f('sync_serverts')." clients: ".$db->f('sync_clientts'), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $retData = array + ( + 'ClientAnchor' => $db->f('sync_clientts'), + 'ServerAnchor' => $db->f('sync_serverts'), + ); + return $retData; + } + + return false; + + } + + function isAuthorized() + { + if (!$this->_isAuthorized) { + + if(!isset($this->_locName) && !isset($this->_password)) + { + Horde::logMessage('SyncML: Authentication not possible currently. No username and password available' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + return FALSE; + } + + if(strstr($this->_locName,'@') === False) + { + $this->_locName .= '@'.$GLOBALS['phpgw_info']['server']['default_domain']; + } + + Horde::logMessage('SyncML: Authenticate ' . $this->_locName . ' - ' . $this->_password, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + if($GLOBALS['sessionid'] = $GLOBALS['phpgw']->session->create($this->_locName,$this->_password,'text','u')) + { + $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: Authentication of ' . $this->_locName . ' failed' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + } + else + { + // store sessionID in a variable, because ->verify maybe resets that value + $sessionID = session_id(); + if(!$GLOBALS['phpgw']->session->verify($sessionID, 'staticsyncmlkp3')) + Horde::logMessage('SyncML_EGW: egw session('.$sessionID.') not verified ' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + return $this->_isAuthorized; + } + + /** + * 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; + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array('map_guid'); + + $where = array + ( + 'map_id' => $mapID, + 'map_locuid' => $locid + ); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if(!$db->next_record()) + { + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : nothing to remove", __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + + $guid = $db->f('map_guid'); + + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : removing guid:$guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db->delete('egw_contentmap', $where, __LINE__, __FILE__); + + 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) + { + // fix $guid, it maybe was to long for some devices + // format is appname-id-systemid + $guidParts = explode('-',$guid); + if(count($guidParts) == 3) + { + $guid = $GLOBALS['phpgw']->common->generate_uid($guidParts[0],$guidParts[1]); + } + + if($ts == 0) + { + $ts = time(); + } + + Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ".count($guidParts), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db = clone($GLOBALS['phpgw']->db); + + $mapID = $this->_locName . $this->_sourceURI . $type; + + $where = array( + 'map_id' => $mapID, + 'map_guid' => $guid, + ); + + $data = $where + array( + 'map_locuid' => $locid, + 'map_timestamp' => $ts, + 'map_expired' => 0, + ); + + $db->insert('egw_contentmap', $data, $where, __LINE__, __FILE__); + + Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts $mapID", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + } + + /** + * write clients device info to database + */ + function writeClientDeviceInfo() + { + if (!isset($this->_clientDeviceInfo) || !is_array($this->_clientDeviceInfo)) { + return false; + } + + $deviceID = $this->_locName . $this->_sourceURI; + + $data = array + ( + 'dev_id' => $deviceID, + 'dev_dtdversion' => $this->_clientDeviceInfo['DTDVersion'], + 'dev_numberofchanges' => $this->_clientDeviceInfo['supportNumberOfChanges'], + 'dev_largeobjs' => $this->_clientDeviceInfo['supportLargeObjs'], + 'dev_swversion' => $this->_clientDeviceInfo['softwareVersion'], + 'dev_oem' => $this->_clientDeviceInfo['oem'], + 'dev_model' => $this->_clientDeviceInfo['model'], + 'dev_manufacturer' => $this->_clientDeviceInfo['manufacturer'], + 'dev_devicetype' => $this->_clientDeviceInfo['deviceType'], + 'dev_deviceid' => $this->_clientDeviceInfo['deviceID'], + 'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']), + ); + + $where = array + ( + 'dev_id' => $deviceID, + ); + + $GLOBALS['phpgw']->db->insert('egw_syncmldevinfo',$data,$where); + } + + /** + * 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; + } + + $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); + + $where = array + ( + 'dev_id' => $deviceID, + 'sync_path' => $type, + ); + + $data = $where + array + ( + 'sync_serverts' => $a, + 'sync_clientts' => $this->_clientAnchorNext[$type] + ); + + $GLOBALS['phpgw']->db->insert('egw_syncmlsummary', $data, $where, __LINE__, __FILE__); + } + } + + +} + +?> \ No newline at end of file diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync.php new file mode 100644 index 0000000000..b583047ec9 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync.php @@ -0,0 +1,184 @@ + + * + * 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 { + + /** + * Target, either contacts, notes, events, + */ + var $_targetLocURI; + + 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) + { + 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; + } + + /** + * 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(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + global $registry; + + #require_once 'Horde/History.php'; + #$history = &Horde_History::singleton(); + $history = $GLOBALS['phpgw']->contenthistory; + + $state = &$_SESSION['SyncML.state']; + $hordeType = $type = $this->_targetLocURI; + // remove the './' from the beginning + $hordeType = str_replace('./','',$hordeType); + if(!$contentType = $command->getContentType()) + { + $contentType = $state->getPreferedContentType($type); + } + if ($this->_targetLocURI == 'calendar' && strpos($command->getContent(), 'BEGIN:VTODO') !== false) { + $hordeType = 'tasks'; + } + + $guid = false; + if (is_a($command, 'Horde_SyncML_Command_Sync_Add')) { + $guid = $registry->call($hordeType . '/import', + array($state->convertClient2Server($command->getContent(), $contentType), $contentType)); + if (!is_a($guid, 'PEAR_Error')) { + $ts = $history->getTSforAction($guid, 'add'); + $state->setUID($type, $command->getLocURI(), $guid, $ts); + $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); + } + } 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, $command->getLocURI()); + #$guid = $state->getGlobalUID($type, $command->getLocURI()); + Horde::logMessage('SyncML: about to delete entry ' . $type .' / '. $guid . ' due to client request '.$command->getLocURI(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + if (!is_a($guid, 'PEAR_Error') && $guid != false) { + $registry->call($hordeType . '/delete', array($guid)); + #$ts = $history->getTSforAction($guid, 'delete'); + #$state->setUID($type, $command->getLocURI(), $guid, $ts); + $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); + } + } elseif (is_a($command, 'Horde_SyncML_Command_Sync_Replace')) { + $guid = $state->getGlobalUID($type, $command->getLocURI()); + $ok = false; + if ($guid) { + Horde::logMessage('SyncML: locuri'. $command->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_ERR); + // Entry exists: replace current one. + $ok = $registry->call($hordeType . '/replace', + array($guid, $state->convertClient2Server($command->getContent(), $contentType), $contentType)); + if (!is_a($ok, 'PEAR_Error')) { + $ts = $history->getTSforAction($guid, 'modify'); + $state->setUID($type, $command->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; + } + } + + 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); + $guid = $registry->call($hordeType . '/import', + array($state->convertClient2Server($command->getContent(), $contentType), $contentType)); + if (!is_a($guid, 'PEAR_Error')) { + $ts = $history->getTSforAction($guid, 'add'); + $state->setUID($type, $command->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 $guid; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromClientSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromClientSync.php new file mode 100644 index 0000000000..6d69bd02b5 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromClientSync.php @@ -0,0 +1,23 @@ + + * + * 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_OneWayFromClientSync extends Horde_SyncML_Sync { + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromServerSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromServerSync.php new file mode 100644 index 0000000000..434f341fc9 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/OneWayFromServerSync.php @@ -0,0 +1,79 @@ + + * + * 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_OneWayFromServerSync extends Horde_SyncML_Sync { + + function endSync($currentCmdID, &$output) + { + global $registry; + $state = &$_SESSION['SyncML.state']; + + // counter for synced items + $syncItems = 0; + + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + + if(!$adds = &$state->getAddedItems($hordeType)) + { + Horde::logMessage("SyncML: reading list of added items", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $adds = $registry->call($hordeType, '/list', array()); + $adds = &$state->getAddedItems($hordeType); + } + Horde::logMessage("SyncML: ....... ".count($adds).' items to send ..............', __FILE__, __LINE__, PEAR_LOG_DEBUG); + + Horde::logMessage("SyncML: starting OneWayFromServerSync ($hordeType)", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + #foreach ($adds as $guid) { + while($guid = array_shift($adds)) + { + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); + + $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); + $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. +#LK $cmd->setContent($state->convertServer2Client($c, $contentType)); + $cmd->setContent($c); + $cmd->setContentType($contentType['ContentType']); + $cmd->setSourceURI($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + $syncItems++; + // return if we have to much data + if($syncItems > MAX_DATA) + { + $state->setMoreDataPending(); + return $currentCmdID; + } + } + } + Horde::logMessage("SyncML: handling OneWayFromServerSync done ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + return $currentCmdID; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php new file mode 100644 index 0000000000..087105940d --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromClientSync.php @@ -0,0 +1,34 @@ + + * + * 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_RefreshFromClientSync extends Horde_SyncML_Sync { + + /** + * We need to erase the current server contents, then we can add + * the client's contents. + */ + function startSync($currentCmdID, &$output) + { + $deletes = $registry->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 new file mode 100644 index 0000000000..06d5dc7194 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php @@ -0,0 +1,64 @@ + + * + * 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 { + + function endSync($currentCmdID, &$output) + { + global $registry; + $state = &$_SESSION['SyncML.state']; + + // counter for synced items + $syncItems = 0; + + if(!$adds = &$state->getAddedItems($hordeType)) + { + Horde::logMessage("SyncML: reading list of added items", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $adds = $registry->call($this->targetLocURI, '/list', array()); + $adds = &$state->getAddedItems($hordeType); + } + Horde::logMessage("SyncML: ....... ".count($adds).' items to send ..............', __FILE__, __LINE__, PEAR_LOG_DEBUG); + + #foreach ($add as $adds) { + while($guid = array_shift($adds)) + { + $locid = $this->_currentState->getLocID($this->targetLocURI, $guid); + // Add a replace. + $add = &new Horde_SyncML_Command_Sync_ContentSyncElement(); + + $add->setContent($registry->call($this->targetLocURI . '/listByAction', + array($this->_currentState->getPreferedContentType($this->targetLocURI)))); + + $currentCmdID = $add->outputCommand($currentCmdID, $output, 'Add'); + + $syncItems++; + // return if we have to much data + if($syncItems > MAX_DATA) + { + $state->setMoreDataPending(); + return $currentCmdID; + } + } + + // TODO deletes + + // TODO modifies + + return $currentCmdID; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php new file mode 100644 index 0000000000..c187e5695d --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php @@ -0,0 +1,109 @@ + + * + * 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_SlowSync extends Horde_SyncML_Sync_TwoWaySync { + + function handleSync($currentCmdID, $hordeType, $syncType,&$output, $refts) + { + global $registry; + + $history = $GLOBALS['phpgw']->contenthistory; + $state = &$_SESSION['SyncML.state']; + + $adds = &$state->getAddedItems($hordeType); + + #if($adds === FALSE) + #{ + # Horde::logMessage("SyncML: reading added items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG); + # $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); + # $adds = &$state->getAddedItems($hordeType); + #} + + Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); + $serverAnchorNext = $state->getServerAnchorNext($syncType); + + while($guid = array_shift($adds)) + { + #$guid_ts = max($history->getTSforAction($guid, 'add'),$history->getTSforAction($guid, 'modify')); + $sync_ts = $state->getChangeTS($syncType, $guid); + #Horde::logMessage("SyncML: timestamp add: $guid sync_ts: $sync_ts anchorNext: ". $serverAnchorNext.' / '.time(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + // $sync_ts it got synced from client to server someone + // $sync_ts >= $serverAnchorNext it got synced from client to server in this sync package already + if ($sync_ts && $sync_ts >= $serverAnchorNext) { + // 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); + continue; + } + +# $locid = $state->getLocID($syncType, $guid); +# + + // Create an Add request for client. +# LK $contentType = $state->getPreferedContentTypeClient($syncType); + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); + + $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); + $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. +#LK $cmd->setContent($state->convertServer2Client($c, $contentType)); + $cmd->setContent($c); + $cmd->setContentType($contentType['ContentType']); + $cmd->setSourceURI($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // return if we have to much data + if($currentCmdID > MAX_DATA) + { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + } + Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $state->clearSync($syncType); + + return $currentCmdID; + } + + function loadData() + { + global $registry; + + $state = &$_SESSION['SyncML.state']; + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + + 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); + + $this->_syncDataLoaded = TRUE; + + return count($state->getAddedItems($hordeType)); + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php new file mode 100644 index 0000000000..3fcdc8a8bb --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php @@ -0,0 +1,242 @@ + + * + * 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) + { + global $registry; + + $state = &$_SESSION['SyncML.state']; + + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + + $refts = $state->getServerAnchorLast($syncType); + $currentCmdID = $this->handleSync($currentCmdID, + $hordeType, + $syncType, + $output, + $refts); + if ($syncType == 'calendar' && $state->handleTasksInCalendar()) { + Horde::logMessage("SyncML: handling tasks in calendar sync", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $currentCmdID = $this->handleSync($currentCmdID, 'tasks', $syncType, + $output, $refts); + } + + return $currentCmdID; + } + + 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(); + + #require_once 'Horde/History.php'; + #$history = &Horde_History::singleton(); + $history = $GLOBALS['phpgw']->contenthistory; + $state = &$_SESSION['SyncML.state']; + + $changes = &$state->getChangedItems($hordeType); + $deletes = &$state->getDeletedItems($hordeType); + $adds = &$state->getAddedItems($hordeType); + + 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); + + while($guid = array_shift($changes)) + { + $guid_ts = $history->getTSforAction($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); + 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); + 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); + continue; + } + + // Create a replace request for client. +# LK $contentType = $state->getPreferedContentTypeClient($syncType); + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); + $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']); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace'); + $state->log('Server-Replace'); + + // return if we have to much data + if($currentCmdID > MAX_DATA) + { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + } + Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // deletes + while($guid = array_shift($deletes)) + { + $guid_ts = $history->getTSforAction($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); + 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); + 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); + continue; + } + + 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); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete'); + $state->log('Server-Delete'); + $state->removeUID($syncType, $locid); + + // return if we have to much data + if($currentCmdID > MAX_DATA) + { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Get 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); + } + while($guid = array_shift($adds)) + { + #if($tempCounter > 10) continue; + $guid_ts = $history->getTSforAction($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); + 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); + continue; + } + + $locid = $state->getLocID($syncType, $guid); + + if ($locid && $refts == 0) { + // 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); + continue; + } + Horde::logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Create an Add request for client. +# LK $contentType = $state->getPreferedContentTypeClient($syncType); + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); + + $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); + $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. +#LK $cmd->setContent($state->convertServer2Client($c, $contentType)); + $cmd->setContent($c); + $cmd->setContentType($contentType['ContentType']); + $cmd->setSourceURI($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // return if we have to much data + if($currentCmdID > MAX_DATA) + { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + } + Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $state->clearSync($syncType); + + return $currentCmdID; + } + + function loadData() + { + global $registry; + + $state = &$_SESSION['SyncML.state']; + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + $refts = $state->getServerAnchorLast($syncType); + + Horde::logMessage("SyncML: reading changed items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setChangedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts))); + + Horde::logMessage("SyncML: reading deleted items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setDeletedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts))); + + Horde::logMessage("SyncML: reading added items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setAddedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts))); + + $this->_syncDataLoaded = TRUE; + + return count($state->getChangedItems($hordeType)) + + count($state->getDeletedItems($hordeType)) + + count($state->getAddedItems($hordeType)); + } + +} diff --git a/phpgwapi/inc/horde/Horde/Util.php b/phpgwapi/inc/horde/Horde/Util.php new file mode 100644 index 0000000000..24253e5240 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/Util.php @@ -0,0 +1,827 @@ + + * Copyright 1999-2005 Jon Parise + * + * 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 Chuck Hagenbuch + * @author Jon Parise + * @since Horde 3.0 + * @package Horde_Util + */ +class Util { + + /** + * Returns an object's clone. + * + * @param object &$obj The object to clone. + * + * @return object The cloned object. + */ + function &cloneObject(&$obj) + { + if (version_compare(zend_version(), '2', '>')) { + return clone($obj); + } else { + $newObj = $obj; + return $newObj; + } + } + + /** + * Buffers the output from a function call, like readfile() or + * highlight_string(), that prints the output directly, so that instead it + * can be returned as a string and used. + * + * @access public + * + * @param string $function The function to run. + * @param mixed $arg1 First argument to $function(). + * @param mixed $arg2 Second argument to $function(). + * @param mixed $arg... ... + * @param mixed $argN Nth argument to $function(). + * + * @return string The output of the function. + */ + function bufferOutput() + { + if (func_num_args() == 0) { + return false; + } + $eval = false; + $args = func_get_args(); + $function = array_shift($args); + if (is_array($function)) { + if (!is_callable($function)) { + return false; + } + } elseif (($function == 'include') || + ($function == 'include_once') || + ($function == 'require') || + ($function == 'require_once')) { + $eval = true; + } elseif (!function_exists($function) && + ($function != 'eval')) { + return false; + } + + ob_start(); + if ($eval) { + eval($function . " '" . implode(',', $args) . "';"); + } elseif ($function == 'eval') { + eval($args[0]); + } else { + call_user_func_array($function, $args); + } + $output = ob_get_contents(); + ob_end_clean(); + + return $output; + } + + /** + * Checks to see if a value has been set by the script and not by GET, + * POST, or cookie input. The value being checked MUST be in the global + * scope. + * + * @access public + * + * @param string $varname The variable name to check. + * + * @return mixed Null if the var is in user input, the variable value + * otherwise. + */ + function nonInputVar($varname) + { + if (isset($_GET[$varname]) || + isset($_POST[$varname]) || + isset($_COOKIE[$varname])) { + return null; + } else { + return isset($GLOBALS[$varname]) ? $GLOBALS[$varname] : null; + } + } + + /** + * Adds a name=value pair to the end of an URL, taking care of whether + * there are existing parameters and whether to use ?, & or & as the + * glue. All data will be urlencoded. + * + * @access public + * + * @param string $url The URL to modify + * @param mixed $parameter Either the name=value pair to add + * (DEPRECATED) -or- + * the name value -or- + * an array of name/value pairs. + * @param string $value If specified, the value part ($parameter is + * then assumed to just be the parameter name). + * @param boolean $encode If true, and we don't have argument separators + * yet, the argument separator gets encoded. + * + * @return string The modified URL. + * + * @since Horde 2.1 + */ + function addParameter($url, $parameter, $value = null, $encode = true) + { + if (empty($parameter)) { + return $url; + } + + if (!is_array($parameter)) { + /* This is deprecated should be removed in the future. */ + if (is_null($value)) { + @list($parameter, $value) = explode('=', $parameter, 2); + } + $add = array($parameter => $value); + } else { + $add = $parameter; + } + + $arg = $encode ? '&' : '&'; + if (($pos = strpos($url, '?')) === false) { + $glue = '?'; + } else { + /* Check if the argument separator has been already + * htmlentities-ized in the URL. */ + $query = substr($url, $pos + 1); + if (preg_match('/=.*?&.*?=/', $query)) { + $arg = '&'; + $query = strtr($query, array_flip(get_html_translation_table(HTML_ENTITIES))); + } elseif (preg_match('/=.*?&.*?=/', $query)) { + $arg = '&'; + } + $pairs = explode($arg, $query); + $params = array(); + foreach ($pairs as $pair) { + $pair = explode('=', $pair, 2); + $params[$pair[0]] = count($pair) == 2 ? $pair[1] : ''; + } + $glue = $arg; + } + + $url_params = array(); + foreach ($add as $parameter => $value) { + if (!isset($params[$parameter])) { + if (is_array($value)) { + foreach ($value as $val) { + $url_params[] = urlencode($parameter) . '[]=' . urlencode($val); + } + } else { + $url_params[] = urlencode($parameter) . '=' . urlencode($value); + } + } + } + + if (count($url_params)) { + return $url . $glue . implode($arg, $url_params); + } else { + return $url; + } + } + + /** + * Removes name=value pairs from a URL. + * + * @access public + * + * @param string $url The URL to modify. + * @param mixed $remove Either a single parameter to remove or an array + * of parameters to remove. + * + * @return string The modified URL. + * + * @since Horde 2.2 + */ + function removeParameter($url, $remove) + { + if (!is_array($remove)) { + $remove = array($remove); + } + + /* Return immediately if there are no parameters to remove. */ + if (($pos = strpos($url, '?')) === false) { + return $url; + } + + $entities = false; + list($url, $query) = explode('?', $url, 2); + + /* Check if the argument separator has been already + * htmlentities-ized in the URL. */ + if (preg_match('/=.*?&.*?=/', $query)) { + $entities = true; + $query = strtr($query, array_flip(get_html_translation_table(HTML_ENTITIES))); + } + + /* Get the list of parameters. */ + $pairs = explode('&', $query); + $params = array(); + foreach ($pairs as $pair) { + $pair = explode('=', $pair, 2); + $params[$pair[0]] = count($pair) == 2 ? $pair[1] : ''; + } + + /* Remove the parameters. */ + foreach ($remove as $param) { + unset($params[$param]); + } + + if (!count($params)) { + return $url; + } + + /* Flatten arrays. + * FIXME: should handle more than one array level somehow. */ + $add = array(); + foreach ($params as $key => $val) { + if (is_array($val)) { + foreach ($val as $v) { + $add[] = $key . '[]=' . $v; + } + } else { + $add[] = $key . '=' . $val; + } + } + + $query = implode('&', $add); + if ($entities) { + $query = htmlentities($query); + } + + return $url . '?' . $query; + } + + /** + * Returns a url with the 'nocache' parameter added, if the browser is + * buggy and caches old URLs. + * + * @access public + * + * @param string $url The URL to modify. + * + * @return string The requested URI. + */ + function nocacheUrl($url) + { + static $rand_num; + + require_once 'Horde/Browser.php'; + $browser = &Browser::singleton(); + + /* We may need to set a dummy parameter 'nocache' since some + * browsers do not always honor the 'no-cache' header. */ + if ($browser->hasQuirk('cache_same_url')) { + if (!isset($rand_num)) { + $rand_num = base_convert(microtime(), 10, 36); + } + return Util::addParameter($url, 'nocache', $rand_num); + } else { + return $url; + } + } + + /** + * Returns a hidden form input containing the session name and id. + * + * @access public + * + * @param boolean $append_session 0 = only if needed, 1 = always. + * + * @return string The hidden form input, if needed/requested. + */ + function formInput($append_session = 0) + { + if ($append_session == 1 || + !isset($_COOKIE[session_name()])) { + return '\n"; + } else { + return ''; + } + } + + /** + * Prints a hidden form input containing the session name and id. + * + * @access public + * + * @param boolean $append_session 0 = only if needed, 1 = always. + */ + function pformInput($append_session = 0) + { + echo Util::formInput($append_session); + } + + /** + * If magic_quotes_gpc is in use, run stripslashes() on $var. + * + * @access public + * + * @param string &$var The string to un-quote, if necessary. + * + * @return string $var, minus any magic quotes. + */ + function dispelMagicQuotes(&$var) + { + static $magic_quotes; + + if (!isset($magic_quotes)) { + $magic_quotes = get_magic_quotes_gpc(); + } + + if ($magic_quotes) { + if (!is_array($var)) { + $var = stripslashes($var); + } else { + array_walk($var, array('Util', 'dispelMagicQuotes')); + } + } + + return $var; + } + + /** + * Gets a form variable from GET or POST data, stripped of magic quotes if + * necessary. If the variable is somehow set in both the GET data and the + * POST data, the value from the POST data will be returned and the GET + * value will be ignored. + * + * @access public + * + * @param string $var The name of the form variable to look for. + * @param string $default The value to return if the variable is not + * there. + * + * @return string The cleaned form variable, or $default. + */ + function getFormData($var, $default = null) + { + return (($val = Util::getPost($var)) !== null) + ? $val : Util::getGet($var, $default); + } + + /** + * Gets a form variable from GET data, stripped of magic quotes if + * necessary. This function will NOT return a POST variable. + * + * @access public + * + * @param string $var The name of the form variable to look for. + * @param string $default The value to return if the variable is not + * there. + * + * @return string The cleaned form variable, or $default. + * + * @since Horde 2.2 + */ + function getGet($var, $default = null) + { + return (isset($_GET[$var])) + ? Util::dispelMagicQuotes($_GET[$var]) + : $default; + } + + /** + * Gets a form variable from POST data, stripped of magic quotes if + * necessary. This function will NOT return a GET variable. + * + * @access public + * + * @param string $var The name of the form variable to look for. + * @param string $default The value to return if the variable is not + * there. + * + * @return string The cleaned form variable, or $default. + * + * @since Horde 2.2 + */ + function getPost($var, $default = null) + { + return (isset($_POST[$var])) + ? Util::dispelMagicQuotes($_POST[$var]) + : $default; + } + + /** + * Determines the location of the system temporary directory. + * + * @access public + * + * @return string A directory name which can be used for temp files. + * Returns false if one could not be found. + */ + function getTempDir() + { + /* First, try PHP's upload_tmp_dir directive. */ + $tmp = ini_get('upload_tmp_dir'); + + /* Otherwise, try to determine the TMPDIR environment + * variable. */ + if (empty($tmp)) { + $tmp = getenv('TMPDIR'); + } + + /* If we still cannot determine a value, then cycle through a + * list of preset possibilities. */ + $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', + 'c:\windows\temp', 'c:\winnt\temp'); + while (empty($tmp) && count($tmp_locations)) { + $tmp_check = array_shift($tmp_locations); + if (@is_dir($tmp_check)) { + $tmp = $tmp_check; + } + } + + /* If it is still empty, we have failed, so return false; + * otherwise return the directory determined. */ + return empty($tmp) ? false : $tmp; + } + + /** + * Creates a temporary filename for the lifetime of the script, and + * (optionally) register it to be deleted at request shutdown. + * + * @param string $prefix Prefix to make the temporary name more + * recognizable. + * @param boolean $delete Delete the file at the end of the request? + * @param string $dir Directory to create the temporary file in. + * @param boolean $secure If deleting file, should we securely delete the + * file? + * + * @return string Returns the full path-name to the temporary file. + * Returns false if a temp file could not be created. + */ + function getTempFile($prefix = '', $delete = true, $dir = '', $secure = false) + { + if (empty($dir) || !is_dir($dir)) { + $tmp_dir = Util::getTempDir(); + } else { + $tmp_dir = $dir; + } + + if (empty($tmp_dir)) { + return false; + } + + $tmp_file = tempnam($tmp_dir, $prefix); + + /* If the file was created, then register it for deletion and return */ + if (empty($tmp_file)) { + return false; + } else { + if ($delete) { + Util::deleteAtShutdown($tmp_file, true, $secure); + } + return $tmp_file; + } + } + + /** + * Creates a temporary directory in the system's temporary directory. + * + * @access public + * + * @param boolean $delete Delete the temporary directory at the end of + * the request? + * @param string $temp_dir Use this temporary directory as the directory + * where the temporary directory will be created. + * + * @return string The pathname to the new temporary directory. + * Returns false if directory not created. + */ + function createTempDir($delete = true, $temp_dir = null) + { + if (is_null($temp_dir)) { + $temp_dir = Util::getTempDir(); + } + + if (empty($temp_dir)) { + return false; + } + + /* Get the first 8 characters of a random string to use as a temporary + directory name. */ + do { + $temp_dir .= '/' . substr(base_convert(mt_rand() . microtime(), 10, 36), 0, 8); + } while (file_exists($temp_dir)); + + $old_umask = umask(0000); + if (!mkdir($temp_dir, 0700)) { + $temp_dir = false; + } elseif ($delete) { + Util::deleteAtShutdown($temp_dir); + } + umask($old_umask); + + return $temp_dir; + } + + /** + * Removes given elements at request shutdown. + * + * If called with a filename will delete that file at request shutdown; if + * called with a directory will remove that directory and all files in that + * directory at request shutdown. + * + * If called with no arguments, return all elements to be deleted (this + * should only be done by Util::_deleteAtShutdown). + * + * The first time it is called, it initializes the array and registers + * Util::_deleteAtShutdown() as a shutdown function - no need to do so + * manually. + * + * The second parameter allows the unregistering of previously registered + * elements. + * + * @access public + * + * @param string $filename The filename to be deleted at the end of the + * request. + * @param boolean $register If true, then register the element for + * deletion, otherwise, unregister it. + * @param boolean $secure If deleting file, should we securely delete + * the file? + */ + function deleteAtShutdown($filename = false, $register = true, + $secure = false) + { + static $dirs, $files, $securedel; + + /* Initialization of variables and shutdown functions. */ + if (is_null($dirs)){ + $dirs = array(); + $files = array(); + $securedel = array(); + register_shutdown_function(array('Util', '_deleteAtShutdown')); + } + + if ($filename) { + if ($register) { + if (@is_dir($filename)) { + $dirs[$filename] = true; + } else { + $files[$filename] = true; + } + if ($secure) { + $securedel[$filename] = true; + } + } else { + unset($dirs[$filename]); + unset($files[$filename]); + unset($securedel[$filename]); + } + } else { + return array($dirs, $files, $securedel); + } + } + + /** + * Deletes registered files at request shutdown. + * + * This function should never be called manually; it is registered as a + * shutdown function by Util::deleteAtShutdown() and called automatically + * at the end of the request. It will retrieve the list of folders and + * files to delete from Util::deleteAtShutdown()'s static array, and then + * iterate through, deleting folders recursively. + * + * Contains code from gpg_functions.php. + * Copyright (c) 2002-2003 Braverock Ventures + * + * @access private + */ + function _deleteAtShutdown() + { + $registered = Util::deleteAtShutdown(); + $dirs = $registered[0]; + $files = $registered[1]; + $secure = $registered[2]; + + foreach ($files as $file => $val) { + /* Delete files */ + if ($val && @file_exists($file)) { + /* Should we securely delete the file by overwriting the + data with a random string? */ + if (isset($secure[$file])) { + $random_str = ''; + for ($i = 0; $i < filesize($file); $i++) { + $random_str .= chr(mt_rand(0, 255)); + } + $fp = fopen($file, 'r+'); + fwrite($fp, $random_str); + fclose($fp); + } + @unlink($file); + } + } + + foreach ($dirs as $dir => $val) { + /* Delete directories */ + if ($val && @file_exists($dir)) { + /* Make sure directory is empty. */ + $dir_class = dir($dir); + while (false !== ($entry = $dir_class->read())) { + if ($entry != '.' && $entry != '..') { + @unlink($dir . '/' . $entry); + } + } + $dir_class->close(); + @rmdir($dir); + } + } + } + + /** + * Outputs javascript code to close the current window. + * + * @access public + * + * @param string $code Any addtional javascript code to run before + * closing the window. + */ + function closeWindowJS($code = '') + { + echo ''; + } + + /** + * Caches the result of extension_loaded() calls. + * + * @access private + * + * @param string $ext The extension name. + * + * @return boolean Is the extension loaded? + */ + function extensionExists($ext) + { + static $cache = array(); + + if (!isset($cache[$ext])) { + $cache[$ext] = extension_loaded($ext); + } + + return $cache[$ext]; + } + + /** + * Tries to load a PHP extension, behaving correctly for all operating + * systems. + * + * @param string $ext The extension to load. + * + * @return boolean True if the extension is now loaded, false if not. + * True can mean that the extension was already loaded, + * OR was loaded dynamically. + */ + function loadExtension($ext) + { + /* If $ext is already loaded, our work is done. */ + if (Util::extensionExists($ext)) { + return true; + } + + /* See if we can call dl() at all, by the current ini settings. */ + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + + if (substr(PHP_OS, 0, 3) == 'WIN') { + $suffix = 'dll'; + } else { + switch (PHP_OS) { + case 'HP-UX': + $suffix = 'sl'; + break; + + case 'AIX': + $suffix = 'a'; + break; + + case 'OSX': + $suffix = 'bundle'; + break; + + default: + $suffix = 'so'; + } + } + + return @dl($ext . '.' . $suffix) || @dl('php_' . $ext . '.' . $suffix); + } + + /** + * Checks if all necessary parameters for a driver's configuration are set + * and returns a PEAR_Error if something is missing. + * + * @param array $params The configuration array with all parameters. + * @param array $fields An array with mandatory parameter names for this + * driver. + * @param string $name The clear text name of the driver. If not + * specified, the application name will be used. + * @param array $info A hash containing detailed information about the + * driver. Will be passed as the userInfo to the + * PEAR_Error. + */ + function assertDriverConfig($params, $fields, $name, $info = array()) + { + $info = array_merge($info, + array('params' => $params, + 'fields' => $fields, + 'name' => $name)); + + if (!is_array($params) || !count($params)) { + require_once 'PEAR.php'; + return PEAR::throwError(sprintf(_("No configuration information specified for %s."), $name), + HORDE_ERROR_DRIVER_CONFIG_MISSING, + $info); + } + + foreach ($fields as $field) { + if (!isset($params[$field])) { + require_once 'PEAR.php'; + return PEAR::throwError(sprintf(_("Required '%s' not specified in configuration."), $field, $name), + HORDE_ERROR_DRIVER_CONFIG, + $info); + } + } + } + + /** + * Returns a format string to be used by strftime(). + * + * @param string $format A format string as used by date(). + * + * @return string A format string as similar as possible to $format. + */ + function date2strftime($format) + { + $dateSymbols = array('a', 'A', 'd', 'D', 'F', 'g', 'G', 'h', 'H', 'i', 'j', 'l', 'm', 'M', 'n', 'r', 's', 'T', 'w', 'W', 'y', 'Y', 'z', 'm/d/Y', 'M', "\n", 'g:i a', 'G:i', "\t", 'H:i:s', '%'); + $strftimeSymbols = array('%p', '%p', '%d', '%a', '%B', '%I', '%H', '%I', '%H', '%M', '%e', '%A', '%m', '%b', '%m', '%a, %e %b %Y %T %Z', '%S', '%Z', '%w', '%V', '%y', '%Y', '%j', '%D', '%h', '%n', '%r', '%R', '%t', '%T', '%%'); + + $result = ''; + for ($pos = 0; $pos < strlen($format);) { + for ($symbol = 0; $symbol < count($dateSymbols); $symbol++) { + if (strpos($format, $dateSymbols[$symbol], $pos) === $pos) { + $result .= $strftimeSymbols[$symbol]; + $pos += strlen($dateSymbols[$symbol]); + continue 2; + } + } + $result .= substr($format, $pos, 1); + $pos++; + } + + return $result; + } + + /** + * Returns a format string to be used by date(). + * + * @param string $format A format string as used by strftime(). + * + * @return string A format string as similar as possible to $format. + */ + function strftime2date($format) + { + $dateSymbols = array('a', 'A', 'd', 'D', 'F', 'g', 'G', 'h', 'H', 'i', 'j', 'l', 'm', 'M', 'n', 'r', 's', 'T', 'w', 'W', 'y', 'Y', 'z', 'm/d/Y', 'M', "\n", 'g:i a', 'G:i', "\t", 'H:i:s', '%'); + $strftimeSymbols = array('%p', '%p', '%d', '%a', '%B', '%I', '%H', '%I', '%H', '%M', '%e', '%A', '%m', '%b', '%m', '%a, %e %b %Y %T %Z', '%S', '%Z', '%w', '%V', '%y', '%Y', '%j', '%D', '%h', '%n', '%r', '%R', '%t', '%T', '%%'); + + return str_replace($strftimeSymbols, $dateSymbols, $format); + } + +} + +if (!function_exists('_')) { + function _($string) + { + return $string; + } + + function bindtextdomain() + { + } + + function textdomain() + { + } + +} diff --git a/phpgwapi/inc/horde/Horde/iCalendar.php b/phpgwapi/inc/horde/Horde/iCalendar.php new file mode 100644 index 0000000000..3717e633e1 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar.php @@ -0,0 +1,1164 @@ + + * + * 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 + */ +class Horde_iCalendar { + + /** + * The parent (containing) iCalendar object. + * + * @var object Horde_iCalendar $_container + */ + var $_container = false; + + var $_attributes = array(); + + var $_components = array(); + + /** + * According to RFC 2425, we should always use CRLF-terminated + * lines. + * + * @var string $_newline + */ + var $_newline = "\r\n"; + + /** + * Return a reference to a new component. + * + * @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. + */ + function &newComponent($type, &$container) + { +# require_once 'Horde/String.php'; + $type = strtolower($type); + $class = 'Horde_iCalendar_' . strtolower($type); + include_once dirname(__FILE__) . '/iCalendar/' . $type . '.php'; + if (class_exists($class)) { + $component = &new $class(); + if ($container !== false) { + $component->_container = &$container; + } + return $component; + } else { + // Should return an dummy x-unknown type class here. + return false; + } + } + + /** + * Set the value of an attribute. + * + * @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. + */ + function setAttribute($name, $value, $params = array(), $append = true, $values = false) + { + $found = $append; + 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; + } + } + + if ($append || !$found) { + $this->_attributes[] = array( + 'name' => $name, + 'params' => $params, + 'value' => $value, + 'values' => $values + ); + } + } + + /** + * 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. + * @return boolean True on success, false if no attribute $name exists. + */ + function setParameter($name, $params) + { + $keys = array_keys($this->_attributes); + foreach ($keys as $key) { + if ($this->_attributes[$key]['name'] == $name) { + $this->_attributes[$key]['params'] = + array_merge($this->_attributes[$key]['params'] , $params); + return true; + } + } + + return false; + } + + /** + * 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. + * + * @return mixed (object) PEAR_Error if the attribute does not exist. + * (string) The value of the attribute. + * (array) The parameters for the attribute or + * multiple values for an attribute. + */ + function getAttribute($name, $params = false) + { + $result = array(); + foreach ($this->_attributes as $attribute) { + if ($attribute['name'] == $name) { + if ($params) { + $result[] = $attribute['params']; + } else { + $result[] = $attribute['value']; + } + } + } + if (count($result) == 0) { + require_once 'PEAR.php'; + return PEAR::raiseError('Attribute "' . $name . '" Not Found'); + } if (count($result) == 1 && !$params) { + return $result[0]; + } else { + return $result; + } + } + + /** + * Gets the values of an attribute as an array. Multiple values + * are possible due to: + * + * a) multiplce occurences of 'name' + * b) (unsecapd) comma seperated lists. + * + * So for a vcard like "KEY:a,b\nKEY:c" getAttributesValues('KEY') + * 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. + * (array) Multiple values for an attribute. + */ + function getAttributeValues($name) + { + $result = array(); + foreach ($this->_attributes as $attribute) { + if ($attribute['name'] == $name) { + $result = array_merge($attribute['values'], $result); + } + } + if (!count($result)) { + return PEAR::raiseError('Attribute "' . $name . '" Not Found'); + } + return $result; + } + + /** + * 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. + * + * @return mixed (string) The value of $name. + * (mixed) $default if $name does not exist. + */ + function getAttributeDefault($name, $default = '') + { + $value = $this->getAttribute($name); + return is_a($value, 'PEAR_Error') ? $default : $value; + } + + /** + * Remove all occurences of an attribute. + * + * @param string $name The name of the attribute. + */ + function removeAttribute($name) + { + $keys = array_keys($this->_attributes); + foreach ($keys as $key) { + if ($this->_attributes[$key]['name'] == $name) { + unset($this->_attributes[$key]); + } + } + } + + /** + * 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. + */ + function getAllAttributes($tag = false) + { + if ($tag === false) { + return $this->_attributes; + } + $result = array(); + foreach ($this->_attributes as $attribute) { + if ($attribute['name'] == $tag) { + $result[] = $attribute; + } + } + return $result; + } + + /** + * Add a vCalendar component (eg vEvent, vTimezone, etc.). + * + * @param object Horde_iCalendar $component Component (subclass) to add. + */ + function addComponent($component) + { + if (is_a($component, 'Horde_iCalendar')) { + $component->_container = &$this; + $this->_components[] = &$component; + } + } + + /** + * Retrieve all the components. + * + * @return array Array of Horde_iCalendar objects. + */ + function getComponents() + { + return $this->_components; + } + + /** + * Return the classes (entry types) we have. + * + * @return array Hash with class names Horde_iCalendar_xxx as keys + * and number of components of this class as value. + */ + function getComponentClasses() + { + $r = array(); + foreach ($this->_components as $c) { + $cn = strtolower(get_class($c)); + if (empty($r[$cn])) { + $r[$cn] = 1; + } else { + $r[$cn]++; + } + } + + return $r; + } + + /** + * Number of components in this container. + * + * @return integer Number of components in this container. + */ + function getComponentCount() + { + return count($this->_components); + } + + /** + * Retrieve a specific component. + * + * @param integer $idx The index of the object to retrieve. + * + * @return mixed (boolean) False if the index does not exist. + * (Horde_iCalendar_*) The requested component. + */ + function getComponent($idx) + { + if (isset($this->_components[$idx])) { + return $this->_components[$idx]; + } else { + return false; + } + } + + /** + * Locates the first child component of the specified class, and + * returns a reference to this component. + * + * @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. + */ + function &findComponent($childclass) + { +# require_once 'Horde/String.php'; +# $childclass = 'Horde_iCalendar_' . String::lower($childclass); + $childclass = 'Horde_iCalendar_' . strtolower($childclass); + $keys = array_keys($this->_components); + foreach ($keys as $key) { + if (is_a($this->_components[$key], $childclass)) { + return $this->_components[$key]; + } + } + + return false; + } + + /** + * Clears the iCalendar object (resets the components and + * attributes arrays). + */ + function clear() + { + $this->_components = array(); + $this->_attributes = array(); + } + + /** + * Export as vCalendar format. + */ + function exportvCalendar() + { + // Default values. + $requiredAttributes['VERSION'] = '2.0'; + $requiredAttributes['PRODID'] = '-//The Horde Project//Horde_iCalendar Library, Horde 3.0-cvs //EN'; + $requiredAttributes['METHOD'] = 'PUBLISH'; + + foreach ($requiredAttributes as $name => $default_value) { + if (is_a($this->getattribute($name), 'PEAR_Error')) { + $this->setAttribute($name, $default_value); + } + } + + return $this->_exportvData('VCALENDAR') . $this->_newline; + } + + /** + * Export this entry as a hash array with tag names as keys. + * + * @param boolean (optional) $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 + * LABEL;TYPE=WORK:foo + * LABEL;TYPE=HOME:bar + * the resulting hash contains only one label field! + * If set to true, array keys look like 'LABEL;TYPE=WORK' + * @return array A hash array with tag names as keys. + */ + function toHash($paramsInKeys = false) + { + $hash = array(); + foreach ($this->_attributes as $a) { + $k = $a['name']; + if ($paramsInKeys && is_array($a['params'])) { + foreach ($a['params'] as $p => $v) { + $k .= ";$p=$v"; + } + } + $hash[$k] = $a['value']; + } + + return $hash; + } + + /** + * Parse 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. + * + * @return boolean True on successful import, false otherwise. + */ + function parsevCalendar($text, $base = 'VCALENDAR', $charset = 'utf-8', $clear = true) + { + $botranslation = CreateObject('phpgwapi.translation'); + if ($clear) { + $this->clear(); + } + if (preg_match('/(BEGIN:' . $base . '\r?\n)([\W\w]*)(END:' . $base . '\r?\n?)/i', $text, $matches)) { + $vCal = $matches[2]; + } else { + // Text isn't enclosed in BEGIN:VCALENDAR + // .. END:VCALENDAR. We'll try to parse it anyway. + $vCal = $text; + } + + // All subcomponents. + $matches = null; + if (preg_match_all('/BEGIN:([\W\w]*)(\r\n|\r|\n)([\W\w]*)END:\1(\r\n|\r|\n)/U', $vCal, $matches)) { + foreach ($matches[0] as $key => $data) { + $type = $matches[1][$key]; + $component = &Horde_iCalendar::newComponent(trim($type), $this); + if ($component === false) { + return PEAR::raiseError("Unable to create object for type $type"); + } + $component->parsevCalendar($data); + + $this->addComponent($component); + + // Remove from the vCalendar data. + $vCal = str_replace($data, '', $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); + } + + // Parse the remaining attributes. + + if (preg_match_all('/(.*):([^\r\n]*)[\r\n]+/', $vCal, $matches)) { + foreach ($matches[0] as $attribute) { + preg_match('/([^;^:]*)((;[^:]*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts); + $tag = $parts[1]; + $value = $parts[4]; + $params = array(); + + // Parse parameters. + if (!empty($parts[2])) { + preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts); + foreach ($param_parts[2] as $key => $paramName) { + $paramValue = $param_parts[4][$key]; + $params[$paramName] = $paramValue; + } + } + + // Charset and encoding handling. + if ((isset($params['ENCODING']) + && $params['ENCODING'] == 'QUOTED-PRINTABLE') + || isset($params['QUOTED-PRINTABLE'])) { + + $value = quoted_printable_decode($value); + // Quoted printable is normally encoded as utf-8. + if (isset($params['CHARSET'])) { + $value = $botranslation->convert($value, $params['CHARSET']); + } else { + $value = $botranslation->convert($value, 'utf-8'); + } + } + + if (isset($params['CHARSET'])) { + $value = $botranslation->convert($value, $params['CHARSET']); + } else { + // As per RFC 2279, assume UTF8 if we don't have + // an explicit charset parameter. + $value = $botranslation->convert($value, $charset); + } + + switch ($tag) { + // Date fields. + case 'DTSTAMP': + case 'COMPLETED': + case 'CREATED': + case 'LAST-MODIFIED': + case 'BDAY': + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + break; + + case 'DTEND': + case 'DTSTART': + case 'DUE': + case 'RECURRENCE-ID': + if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') { + $this->setAttribute($tag, $this->_parseDate($value), $params); + } else { + $this->setAttribute($tag, $this->_parseDateTime($value), $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); + } + } else { + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + } + break; + + case 'TRIGGER': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE-TIME') { + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + } else { + $this->setAttribute($tag, $this->_parseDuration($value), $params); + } + } else { + $this->setAttribute($tag, $this->_parseDuration($value), $params); + } + break; + + // Comma seperated dates. + case 'EXDATE': + $values = array(); + $dates = array(); + preg_match_all('/,([^,]*)/', ',' . $value, $values); + + foreach ($values 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); + } + } + $this->setAttribute($tag, $dates, $params); + break; + + // Duration fields. + case 'DURATION': + $this->setAttribute($tag, $this->_parseDuration($value), $params); + break; + + // Period of time fields. + case 'FREEBUSY': + $values = array(); + $periods = array(); + preg_match_all('/,([^,]*)/', ',' . $value, $values); + foreach ($values[1] as $value) { + $periods[] = $this->_parsePeriod($value); + } + + $this->setAttribute($tag, $periods, $params); + break; + + // UTC offset fields. + case 'TZOFFSETFROM': + case 'TZOFFSETTO': + $this->setAttribute($tag, $this->_parseUtcOffset($value), $params); + break; + + // Integer fields. + case 'PERCENT-COMPLETE': + case 'PRIORITY': + case 'REPEAT': + case 'SEQUENCE': + $this->setAttribute($tag, intval($value), $params); + break; + + // Geo fields. + case 'GEO': + $floats = split(';', $value); + $value['latitude'] = floatval($floats[0]); + $value['longitude'] = floatval($floats[1]); + $this->setAttribute($tag, $value, $params); + break; + + // Recursion fields. + case 'EXRULE': + case 'RRULE': + $this->setAttribute($tag, trim($value), $params); + break; + + // ADR an N are lists seperated by unescaped semi-colons. + case 'ADR': + case 'N': + case 'ORG': + + $value = trim($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); + $value = str_replace('\\:', ':', $value); + + // Split by unescaped semi-colons: + $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + + break; + + // String fields. + default: + $value = trim($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); + $value = str_replace('\\:', ':', $value); + + // Split by unescaped commas: + $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + break; + } + } + } + + return true; + } + + /** + * Export this component in vCal format. + * + * @param string $base (optional) The type of the base object. + * + * @return string vCal format data. + */ + function _exportvData($base = 'VCALENDAR') + { + $result = 'BEGIN:' . strtoupper($base) . $this->_newline; + + // Ensure that version is the first attribute. + $v = $this->getAttributeDefault('VERSION', false); + if ($v) { + $result .= 'VERSION:' . $v. $this->_newline; + } + + foreach ($this->_attributes as $attribute) { + $name = $attribute['name']; + if ($name == 'VERSION') { + // Already done. + continue; + } + + $params = $attribute['params']; + $params_str = ''; + + if (count($params)) { + foreach ($params as $param_name => $param_value) { + $params_str .= ";$param_name=$param_value"; + } + } + + $value = $attribute['value']; + switch ($name) { + // Date fields. + case 'DTSTAMP': + case 'COMPLETED': + case 'CREATED': + case 'DCREATED': + case 'LAST-MODIFIED': + $value = $this->_exportDateTime($value); + break; + + case 'DTEND': + case 'DTSTART': + case 'DUE': + case 'RECURRENCE-ID': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE') { + $value = $this->_exportDate($value); + } else { + $value = $this->_exportDateTime($value); + } + } else { + $value = $this->_exportDateTime($value); + } + break; + + case 'RDATE': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE') { + $value = $this->_exportDate($value); + } elseif ($params['VALUE'] == 'PERIOD') { + $value = $this->_exportPeriod($value); + } else { + $value = $this->_exportDateTime($value); + } + } else { + $value = $this->_exportDateTime($value); + } + break; + + case 'TRIGGER': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE-TIME') { + $value = $this->_exportDateTime($value); + } elseif ($params['VALUE'] == 'DURATION') { + $value = $this->_exportDuration($value); + } + } else { + $value = $this->_exportDuration($value); + } + break; + + // Duration fields. + case 'DURATION': + $value = $this->_exportDuration($value); + break; + + // Period of time fields. + case 'FREEBUSY': + $value_str = ''; + foreach ($value as $period) { + $value_str .= empty($value_str) ? '' : ','; + $value_str .= $this->_exportPeriod($period); + } + $value = $value_str; + break; + + // UTC offset fields. + case 'TZOFFSETFROM': + case 'TZOFFSETTO': + $value = $this->_exportUtcOffset($value); + break; + + // Integer fields. + case 'PERCENT-COMPLETE': + case 'PRIORITY': + case 'REPEAT': + case 'SEQUENCE': + $value = "$value"; + break; + + // Geo fields. + case 'GEO': + $value = $value['latitude'] . ',' . $value['longitude']; + break; + + // Recurrence fields. + case 'EXRULE': + case 'RRULE': + + default: + 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; + } else { + $attr_string = "$name$params_str:$value"; + $result .= $this->_foldLine($attr_string) . $this->_newline; + } + } + + foreach ($this->getComponents() as $component) { + $result .= $component->exportvCalendar() . $this->_newline; + } + + $result .= 'END:' . $base; + + return $result; + } + + /** + * Parse a UTC Offset field. + */ + function _parseUtcOffset($text) + { + $offset = array(); + if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) { + $offset['ahead'] = (boolean)($timeParts[1] == '+'); + $offset['hour'] = intval($timeParts[2]); + $offset['minute'] = intval($timeParts[3]); + if (isset($timeParts[4])) { + $offset['second'] = intval($timeParts[4]); + } + return $offset; + } else { + return false; + } + } + + /** + * Export a UTC Offset field. + */ + function _exportUtcOffset($value) + { + $offset = $value['ahead'] ? '+' : '-'; + $offset .= sprintf('%02d%02d', + $value['hour'], $value['minute']); + if (isset($value['second'])) { + $offset .= sprintf('%02d', $value['second']); + } + + return $offset; + } + + /** + * Parse a Time Period field. + */ + function _parsePeriod($text) + { + $periodParts = split('/', $text); + + $start = $this->_parseDateTime($periodParts[0]); + + if ($duration = $this->_parseDuration($periodParts[1])) { + return array('start' => $start, 'duration' => $duration); + } elseif ($end = $this->_parseDateTime($periodParts[1])) { + return array('start' => $start, 'end' => $end); + } + } + + /** + * Export a Time Period field. + */ + function _exportPeriod($value) + { + $period = $this->_exportDateTime($value['start']); + $period .= '/'; + if (isset($value['duration'])) { + $period .= $this->_exportDuration($value['duration']); + } else { + $period .= $this->_exportDateTime($value['end']); + } + return $period; + } + + /** + * Parse a DateTime field into a unix timestamp. + */ + function _parseDateTime($text) + { + $dateParts = split('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; + } + return @gmmktime(0, 0, 0, $date['month'], $date['mday'], $date['year']); + } + + if (!$date = $this->_parseDate($dateParts[0])) { + return $date; + } + if (!$time = $this->_parseTime($dateParts[1])) { + return $time; + } + + if ($time['zone'] == 'UTC') { + return @gmmktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); + } else { + return @mktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); + } + } + + /** + * Export a DateTime field. + */ + function _exportDateTime($value) + { + $temp = array(); + if (!is_object($value) || is_array($value)) { + $TZOffset = 3600 * substr(date('O'), 0, 3); + $TZOffset += 60 * substr(date('O'), 3, 2); + $value -= $TZOffset; + + $temp['zone'] = 'UTC'; + $temp['year'] = date('Y', $value); + $temp['month'] = date('n', $value); + $temp['mday'] = date('j', $value); + $temp['hour'] = date('G', $value); + $temp['minute'] = date('i', $value); + $temp['second'] = date('s', $value); + } else { + $dateOb = (object)$value; + + // Minutes. + $TZOffset = substr(date('O'), 3, 2); + $thisMin = $dateOb->min - $TZOffset; + + // Hours. + $TZOffset = substr(date('O'), 0, 3); + $thisHour = $dateOb->hour - $TZOffset; + + 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); + } + + /** + * Parse a Time field. + */ + function _parseTime($text) + { + if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) { + $time['hour'] = intval($timeParts[1]); + $time['minute'] = intval($timeParts[2]); + $time['second'] = intval($timeParts[3]); + if (isset($timeParts[4])) { + $time['zone'] = 'UTC'; + } else { + $time['zone'] = 'Local'; + } + return $time; + } else { + return false; + } + } + + /** + * Export a Time field. + */ + function _exportTime($value) + { + $time = sprintf('%02d%02d%02d', + $value['hour'], $value['minute'], $value['second']); + if ($value['zone'] == 'UTC') { + $time .= 'Z'; + } + return $time; + } + + /** + * Parse a Date field. + */ + function _parseDate($text) + { + if (strlen($text) != 8) { + 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; + } + + /** + * Export a Date field. + */ + function _exportDate($value) + { + return sprintf('%04d%02d%02d', + $value['year'], $value['month'], $value['mday']); + } + + /** + * Parse a Duration Value field. + */ + function _parseDuration($text) + { + if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $durvalue)) { + // Weeks. + $duration = 7 * 86400 * intval($durvalue[3]); + + if (count($durvalue) > 4) { + // Days. + $duration += 86400 * intval($durvalue[4]); + } + if (count($durvalue) > 5) { + // Hours. + $duration += 3600 * intval($durvalue[7]); + + // Mins. + if (isset($durvalue[8])) { + $duration += 60 * intval($durvalue[8]); + } + + // Secs. + if (isset($durvalue[9])) { + $duration += intval($durvalue[9]); + } + } + + // Sign. + if ($durvalue[1] == "-") { + $duration *= -1; + } + + return $duration; + } else { + return false; + } + } + + /** + * Export a duration value. + */ + function _exportDuration($value) + { + $duration = ''; + if ($value < 0) { + $value *= -1; + $duration .= '-'; + } + $duration .= 'P'; + + $weeks = floor($value / (7 * 86400)); + $value = $value % (7 * 86400); + if ($weeks) { + $duration .= $weeks . 'W'; + } + + $days = floor($value / (86400)); + $value = $value % (86400); + if ($days) { + $duration .= $days . 'D'; + } + + if ($value) { + $duration .= 'T'; + + $hours = floor($value / 3600); + $value = $value % 3600; + if ($hours) { + $duration .= $hours . 'H'; + } + + $mins = floor($value / 60); + $value = $value % 60; + if ($mins) { + $duration .= $mins . 'M'; + } + + if ($value) { + $duration .= $value . 'S'; + } + } + + return $duration; + } + + /** + * Return the folded version of a line. + */ + 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 = max(60, max(strrpos($maxLine, ';'), strrpos($maxLine, ':')) + 1); + + $foldedline .= (empty($foldedline)) ? + substr($line, 0, $cutPoint) : + $this->_newline . ' ' . substr($line, 0, $cutPoint); + + $line = (strlen($line) <= $cutPoint) ? '' : substr($line, $cutPoint); + } + return $foldedline; + } + return $line; + } + + /** + * Convert an 8bit string to a quoted-printable string according + * 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. + */ + function _quotedPrintableEncode($input = '') + { + // If imap_8bit() is available, use it. + if (function_exists('imap_8bit')) { + return imap_8bit($input); + } + + // 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 = ''; + $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"; + } + } + return $output; + } + +} diff --git a/phpgwapi/inc/horde/Horde/iCalendar/valarm.php b/phpgwapi/inc/horde/Horde/iCalendar/valarm.php new file mode 100644 index 0000000000..dee9f2471c --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/valarm.php @@ -0,0 +1,34 @@ + + * + * 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 + */ +class Horde_iCalendar_valarm extends Horde_iCalendar { + + function getType() + { + 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 new file mode 100644 index 0000000000..a9c29705e3 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vcard.php @@ -0,0 +1,130 @@ + + * @version $Revision$ + * @package Horde_iCalendar + */ +class Horde_iCalendar_vcard extends Horde_iCalendar { + + 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'; + + foreach ($requiredAttributes as $name => $default_value) { + if (is_a($this->getattribute($name), 'PEAR_Error')) { + $this->setAttribute($name, $default_value); + } + } + + return $this->_exportvData('VCARD') . $this->_newline; + } + + /** + * Returns the contents of the "N" tag as a printable Name: + * i.e. converts: + * + * N:Duck;Dagobert;T;Professor;Sen. + * to + * "Professor Dagobert T Duck Sen" + * + * @return string Full name of vcard "N" tag + * or null if no N tag. + */ + function printableName() + { + $name_parts = $this->getAttributeValues('N'); + if (is_a($name_parts, 'PEAR_Error')) { + return null; + } + + if (!empty($name_parts[VCARD_N_PREFIX])) { + $name_arr[] = $name_parts[VCARD_N_PREFIX]; + } + if (!empty($name_parts[VCARD_N_GIVEN])) { + $name_arr[] = $name_parts[VCARD_N_GIVEN]; + } + if (!empty($name_parts[VCARD_N_ADDL])) { + $name_arr[] = $name_parts[VCARD_N_ADDL]; + } + if (!empty($name_parts[VCARD_N_FAMILY])) { + $name_arr[] = $name_parts[VCARD_N_FAMILY]; + } + if (!empty($name_parts[VCARD_N_SUFFIX])) { + $name_arr[] = $name_parts[VCARD_N_SUFFIX]; + } + + return implode(' ', $name_arr); + } + + /** + * Static function to make a given email address rfc822 compliant. + * + * @param string $address An email address. + * + * @return string The RFC822-formatted email address. + */ + function getBareEmail($address) + { + require_once 'Mail/RFC822.php'; + require_once 'Horde/MIME.php'; + + static $rfc822; + if (is_null($rfc822)) { + $rfc822 = &new Mail_RFC822(); + } + + $rfc822->validateMailbox($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 new file mode 100644 index 0000000000..39a5250a83 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vevent.php @@ -0,0 +1,230 @@ + + * + * 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 + */ +class Horde_iCalendar_vevent extends Horde_iCalendar { + + function getType() + { + 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']; + + $method = !empty($this->_container) ? + $this->_container->getAttribute('METHOD') : 'PUBLISH'; + + switch ($method) { + case 'PUBLISH': + $requiredAttributes['DTSTART'] = time(); + $requiredAttributes['SUMMARY'] = ''; + break; + + case 'REQUEST': + $requiredAttributes['ATTENDEE'] = ''; + $requiredAttributes['DTSTART'] = time(); + $requiredAttributes['SUMMARY'] = ''; + break; + + case 'REPLY': + $requiredAttributes['ATTENDEE'] = ''; + break; + + case 'ADD': + $requiredAttributes['DTSTART'] = time(); + $requiredAttributes['SEQUENCE'] = 1; + $requiredAttributes['SUMMARY'] = ''; + break; + + case 'CANCEL': + $requiredAttributes['ATTENDEE'] = ''; + $requiredAttributes['SEQUENCE'] = 1; + break; + + case 'REFRESH': + $requiredAttributes['ATTENDEE'] = ''; + break; + } + + foreach ($requiredAttributes as $name => $default_value) { + if (is_a($this->getAttribute($name), 'PEAR_Error')) { + $this->setAttribute($name, $default_value); + } + } + + return parent::_exportvData('VEVENT'); + } + + /** + * Update the status of an attendee of an event. + * + * @param $email The email address of the attendee. + * @param $status The participant status to set. + * @param $fullname The full name of the participant to set. + */ + function updateAttendee($email, $status, $fullname = '') + { + foreach ($this->_attributes as $key => $attribute) { + if ($attribute['name'] == 'ATTENDEE' && $attribute['value'] == 'MAILTO:' . $email) { + $this->_attributes[$key]['params']['PARTSTAT'] = $status; + if (!empty($fullname)) { + $this->_attributes[$key]['params']['CN'] = $fullname; + } + unset($this->_attributes[$key]['params']['RSVP']); + return; + } + } + $params = array('PARTSTAT' => $status); + if (!empty($fullname)) { + $params['CN'] = $fullname; + } + $this->setAttribute('ATTENDEE', 'MAILTO:' . $email, $params); + } + + /** + * Return the organizer display name or email. + * + * @return string The organizer name to display for this event. + */ + function organizerName() + { + $organizer = $this->getAttribute('ORGANIZER', true); + if (is_a($organizer, 'PEAR_Error')) { + return null; + } + + if (isset($organizer[0]['CN'])) { + return $organizer[0]['CN']; + } + + $organizer = parse_url($this->getAttribute('ORGANIZER')); + + return $organizer['path']; + } + + /** + * Update this event with details from another event. + * + * @param object Horde_iCalendar_vEvent $vevent The vEvent with latest details. + */ + function updateFromvEvent($vevent) + { + $newAttributes = $vevent->getAllAttributes(); + foreach ($newAttributes as $newAttribute) { + $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']); + } else { + // Already exists so locate and modify. + $found = false; + + // Try matching the attribte name and value incase + // only the params changed (eg attendee updating + // status). + foreach ($this->_attributes as $id => $attr) { + if ($attr['name'] == $newAttribute['name'] && + $attr['value'] == $newAttribute['value']) { + // merge the params + foreach ($newAttribute['params'] as $param_id => $param_name) { + $this->_attributes[$id]['params'][$param_id] = $param_name; + } + $found = true; + break; + } + } + if (!$found) { + // Else match the first attribute with the same + // name (eg changing start time). + foreach ($this->_attributes as $id => $attr) { + if ($attr['name'] == $newAttribute['name']) { + $this->_attributes[$id]['value'] = $newAttribute['value']; + // Merge the params. + foreach ($newAttribute['params'] as $param_id => $param_name) { + $this->_attributes[$id]['params'][$param_id] = $param_name; + } + break; + } + } + } + } + } + } + + /** + * Update just the attendess of event with details from another + * event. + * + * @param object Horde_iCalendar_vEvent $vevent The vEvent with latest details + */ + function updateAttendeesFromvEvent($vevent) + { + $newAttributes = $vevent->getAllAttributes(); + foreach ($newAttributes as $newAttribute) { + 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']); + } else { + // Already exists so locate and modify. + $found = false; + // Try matching the attribte name and value incase + // only the params changed (eg attendee updating + // status). + foreach ($this->_attributes as $id => $attr) { + if ($attr['name'] == $newAttribute['name'] && + $attr['value'] == $newAttribute['value']) { + // Merge the params. + foreach ($newAttribute['params'] as $param_id => $param_name) { + $this->_attributes[$id]['params'][$param_id] = $param_name; + } + $found = true; + break; + } + } + + if (!$found) { + // Else match the first attribute with the same + // name (eg changing start time). + foreach ($this->_attributes as $id => $attr) { + if ($attr['name'] == $newAttribute['name']) { + $this->_attributes[$id]['value'] = $newAttribute['value']; + // Merge the params. + foreach ($newAttribute['params'] as $param_id => $param_name) { + $this->_attributes[$id]['params'][$param_id] = $param_name; + } + break; + } + } + } + } + } + } + +} diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php b/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php new file mode 100644 index 0000000000..8eed1a07fb --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php @@ -0,0 +1,290 @@ + + * + * 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 + */ +class Horde_iCalendar_vfreebusy extends Horde_iCalendar { + + var $_busyPeriods = array(); + + function getType() + { + return 'vFreebusy'; + } + + function parsevCalendar($data) + { + parent::parsevCalendar($data, 'VFREEBUSY'); + + // 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]); + } + } + } + + function exportvCalendar() + { + foreach ($this->_busyPeriods as $start => $end) { + $periods = array(array('start' => $start, 'end' => $end)); + $this->setAttribute('FREEBUSY', $periods); + } + + $res = parent::_exportvData('VFREEBUSY'); + + foreach ($this->_attributes as $key => $attribute) { + if ($attribute['name'] == 'FREEBUSY') { + unset($this->_attributes[$key]); + } + } + + return $res; + } + + /** + * Get a display name for this object. + */ + function getName() + { + $name = ''; + $method = !empty($this->_container) ? + $this->_container->getAttribute('METHOD') : 'PUBLISH'; + + if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') { + $attr = 'ORGANIZER'; + } else if ($method == 'REPLY') { + $attr = 'ATTENDEE'; + } + + $name = $this->getAttribute($attr, true); + if (array_key_exists('CN', $name[0])) { + return $name[0]['CN']; + } + + $name = $this->getAttribute($attr); + if (is_a($name, 'PEAR_Error')) { + return ''; + } else { + $name = parse_url($name); + return $name['path']; + } + } + + /** + * Get the email address for this object. + */ + function getEmail() + { + $name = ''; + $method = !empty($this->_container) + ? $this->_container->getAttribute('METHOD') : 'PUBLISH'; + + if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') { + $attr = 'ORGANIZER'; + } else if ($method == 'REPLY') { + $attr = 'ATTENDEE'; + } + + $name = $this->getAttribute($attr); + if (is_a($name, 'PEAR_Error')) { + return ''; + } else { + $name = parse_url($name); + return $name['path']; + } + } + + function getBusyPeriods() + { + return $this->_busyPeriods; + } + + /** + * Return all the free periods of time in a given period. + */ + function getFreePeriods($startStamp, $endStamp) + { + $this->simplify(); + $periods = array(); + + // Check that we have data for some part of this period. + if ($this->getEnd() < $startStamp || $this->getStart() > $endStamp) { + return $periods; + } + + // 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); + $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 ($nextstart < $endStamp && $nextstart < $this->getEnd()) { + $periods[$nextstart] = min($this->getEnd(), $endStamp); + } + + return $periods; + } + + /** + * Add a busy period to the info. + */ + function addBusyPeriod($type, $start, $end = null, $duration = null) + { + if ($type == "FREE") { + // Make sure this period is not marked as busy. + return false; + } + + // Calculate the end time is duration was specified. + $tempEnd = is_null($duration) ? $end : $start + $duration; + + // Make sure the period length is always positive. + $end = max($start, $tempEnd); + $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]); + } else { + // Add a new busy period. + $this->_busyPeriods[$start] = $end; + } + + return true; + } + + /** + * Get the timestamp of the start of the time period this free + * busy information covers. + */ + function getStart() + { + if (!is_a($this->getAttribute('DTSTART'), 'PEAR_Error')) { + return $this->getAttribute('DTSTART'); + } else if (count($this->_busyPeriods)) { + return min(array_keys($this->_busyPeriods)); + } else { + return false; + } + } + + /** + * Get the timestamp of the end of the time period this free busy + * information covers. + */ + function getEnd() + { + if (!is_a($this->getAttribute('DTEND'), 'PEAR_Error')) { + return $this->getAttribute('DTEND'); + } else if (count($this->_busyPeriods)) { + return max(array_values($this->_busyPeriods)); + } else { + return false; + } + } + + /** + * Merge the busy periods of another VFreebusy into this one. + */ + function merge($freebusy, $simplify = true) + { + if (!is_a($freebusy, 'Horde_iCalendar_vfreebusy')) { + return false; + } + + foreach ($freebusy->getBusyPeriods() as $start => $end) { + $this->addBusyPeriod('BUSY', $start, $end); + } + + $thisattr = $this->getAttribute('DTSTART'); + $thatattr = $freebusy->getAttribute('DTSTART'); + if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) { + $this->setAttribute('DTSTART', $thatattr); + } elseif (!is_a($thatattr, 'PEAR_Error')) { + if ($thatattr > $thisattr) { + $this->setAttribute('DTSTART', $thatattr); + } + } + + $thisattr = $this->getAttribute('DTEND'); + $thatattr = $freebusy->getAttribute('DTEND'); + if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) { + $this->setAttribute('DTEND', $thatattr); + } elseif (!is_a($thatattr, 'PEAR_Error')) { + if ($thatattr < $thisattr) { + $this->setAttribute('DTEND', $thatattr); + } + } + + if ($simplify) { + $this->simplify(); + } + return true; + } + + /** + * Remove all overlaps and simplify the busy periods array as much + * as possible. + */ + function simplify() + { + $checked = array(); + $checkedEmpty = true; + foreach ($this->_busyPeriods as $start => $end) { + if ($checkedEmpty) { + $checked[$start] = $end; + $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) { + unset($checked[$testStart]); + $checked[min($testStart, $start)] = max($testEnd, $end); + $added = true; + } + if ($added) { + break; + } + } + if (!$added) { + $checked[$start] = $end; + } + } + } + ksort($checked, SORT_NUMERIC); + $this->_busyPeriods = $checked; + } + +} diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php b/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php new file mode 100644 index 0000000000..632bc1b7ba --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vjournal.php @@ -0,0 +1,34 @@ + + * + * 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 + */ +class Horde_iCalendar_vjournal extends Horde_iCalendar { + + function getType() + { + 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 new file mode 100644 index 0000000000..9a97ef41f1 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vnote.php @@ -0,0 +1,49 @@ + + * + * 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_vnote extends Horde_iCalendar { + + 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) + */ + function exportvCalendar() + { + $requiredAttributes['BODY'] = ''; + $requiredAttributes['VERSION'] = '1.1'; + + foreach ($requiredAttributes as $name => $default_value) { + if (is_a($this->getattribute($name), 'PEAR_Error')) { + $this->setAttribute($name, $default_value); + } + } + + return $this->_exportvData('VNOTE') . $this->_newline; + } + +} diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php b/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php new file mode 100644 index 0000000000..922b21e85a --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vtimezone.php @@ -0,0 +1,78 @@ + + * + * 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 + */ +class Horde_iCalendar_vtimezone extends Horde_iCalendar { + + function getType() + { + return 'vTimeZone'; + } + + function parsevCalendar($data) + { + parent::parsevCalendar($data, 'VTIMEZONE'); + } + + function exportvCalendar() + { + return parent::_exportvData('VTIMEZONE'); + } + +} + +/** + * @package Horde_iCalendar + */ +class Horde_iCalendar_standard extends Horde_iCalendar { + + function getType() + { + return 'standard'; + } + + function parsevCalendar($data) + { + parent::parsevCalendar($data, 'STANDARD'); + } + + function exportvCalendar() + { + return parent::_exportvData('STANDARD'); + } + +} + +/** + * @package Horde_iCalendar + */ +class Horde_iCalendar_daylight extends Horde_iCalendar { + + function getType() + { + return 'daylight'; + } + + function parsevCalendar($data) + { + parent::parsevCalendar($data, 'DAYLIGHT'); + } + + function exportvCalendar() + { + return parent::_exportvData('DAYLIGHT'); + } + +} diff --git a/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php b/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php new file mode 100644 index 0000000000..5dfff21d7e --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar/vtodo.php @@ -0,0 +1,90 @@ + + * + * 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 + */ +class Horde_iCalendar_vtodo extends Horde_iCalendar { + + function getType() + { + return 'vTodo'; + } + + function parsevCalendar($data) + { + parent::parsevCalendar($data, 'VTODO'); + } + + function exportvCalendar() + { + return parent::_exportvData('VTODO'); + } + + /** + * Convert this todo to an array of attributes. + * + * @return array Array containing the details of the todo in a hash + * as used by Horde applications. + */ + function toArray() + { + $todo = array(); + + $name = $this->getAttribute('SUMMARY'); + if (!is_array($name) && !is_a($name, 'PEAR_Error')) { + $todo['name'] = $name; + } + $desc = $this->getAttribute('DESCRIPTION'); + if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) { + $todo['desc'] = $desc; + } + + $priority = $this->getAttribute('PRIORITY'); + if (!is_array($priority) && !is_a($priority, 'PEAR_Error')) { + $todo['priority'] = $priority; + } + + $due = $this->getAttribute('DTSTAMP'); + if (!is_array($due) && !is_a($due, 'PEAR_Error')) { + $todo['due'] = $due; + } + + return $todo; + } + + /** + * Set the attributes for this todo item from an array. + * + * @param array $todo Array containing the details of the todo in + * the same format that toArray() exports. + */ + function fromArray($todo) + { + if (isset($todo['name'])) { + $this->setAttribute('SUMMARY', $todo['name']); + } + if (isset($todo['desc'])) { + $this->setAttribute('DESCRIPTION', $todo['desc']); + } + + if (isset($todo['priority'])) { + $this->setAttribute('PRIORITY', $todo['priority']); + } + + if (isset($todo['due'])) { + $this->setAttribute('DTSTAMP', $todo['due']); + } + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML.php b/phpgwapi/inc/horde/XML/WBXML.php new file mode 100644 index 0000000000..8ead275272 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML.php @@ -0,0 +1,286 @@ + + * + * 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.14 2005/01/03 13:09:24 jan Exp $ + * + * @package XML_WBXML + */ +class XML_WBXML { + + /** + * Decoding Multi-byte Integers from Section 5.1 + * + * Use long because it is unsigned. + */ + function MBUInt32ToInt($in, &$pos) + { + $val = 0; + + do { + $b = ord($in[$pos++]); + $val <<= 7; // Bitshift left 7 bits. + $val += ($b & 127); + } while (($b & 128) != 0); + + return $val; + } + + /** + * Encoding Multi-byte Integers from Section 5.1 + */ + function intToMBUInt32(&$out, $i) + { + if ($i > 268435455) { + $bytes0 = 0 | XML_WBXML::getBits(0, $i); + $bytes1 = 128 | XML_WBXML::getBits(1, $i); + $bytes2 = 128 | XML_WBXML::getBits(2, $i); + $bytes3 = 128 | XML_WBXML::getBits(3, $i); + $bytes4 = 128 | XML_WBXML::getBits(4, $i); + + $out .= chr($bytes4); + $out .= chr($bytes3); + $out .= chr($bytes2); + $out .= chr($bytes1); + $out .= chr($bytes0); + } elseif ($i > 2097151) { + $bytes0 = 0 | XML_WBXML::getBits(0, $i); + $bytes1 = 128 | XML_WBXML::getBits(1, $i); + $bytes2 = 128 | XML_WBXML::getBits(2, $i); + $bytes3 = 128 | XML_WBXML::getBits(3, $i); + + $out .= chr($bytes3); + $out .= chr($bytes2); + $out .= chr($bytes1); + $out .= chr($bytes0); + } elseif ($i > 16383) { + $bytes0 = 0 | XML_WBXML::getBits(0, $i); + $bytes1 = 128 | XML_WBXML::getBits(1, $i); + $bytes2 = 128 | XML_WBXML::getBits(2, $i); + + $out .= chr($bytes2); + $out .= chr($bytes1); + $out .= chr($bytes0); + } elseif ($i > 127) { + $bytes0 = 0 | XML_WBXML::getBits(0, $i); + $bytes1 = 128 | XML_WBXML::getBits(1, $i); + + $out .= chr($bytes1); + $out .= chr($bytes0); + } else { + $bytes0 = 0 | XML_WBXML::getBits(0, $i); + + $out .= chr($bytes0); + } + } + + function getBits($num, $l) + { + switch ($num) { + case 0: + return $l & 127; // 0x7F + + case 1: + return ($l >> 7) & 127; // 0x7F + + case 2: + return ($l >> 14) & 127; // 0x7F + + case 3: + return ($l >> 21) & 127; // 0x7F + + case 4: + return ($l >> 28) & 127; // 0x7F + } + + return 0; + } + + function getDPIString($i) + { + /** + * ADD CHAPTER + * @var array $_DPIString + */ + $DPIString = array(2 => DPI_DTD_WML_1_0, + 3 => DPI_DTD_WTA_1_0, + 4 => DPI_DTD_WML_1_1, + 5 => DPI_DTD_SI_1_1, + 6 => DPI_DTD_SL_1_0, + 7 => DPI_DTD_CO_1_0, + 8 => DPI_DTD_CHANNEL_1_1, + 9 => DPI_DTD_WML_1_2, + 10 => DPI_DTD_WML_1_3, + 11 => DPI_DTD_PROV_1_0, + 12 => DPI_DTD_WTA_WML_1_2, + 13 => DPI_DTD_CHANNEL_1_2, + + // Not all SyncML clients know this, so we + // should use the string table. + // 0xFD1 => DPI_DTD_SYNCML_1_1, + // 0xFD2 => DPI_DTD_DEVINF_1_1, + ); + + return isset($DPIString[$i]) ? $DPIString[$i] : null; + } + + function getDPIInt($dpi) + { + /** + * ADD CHAPTER + * @var array $_DPIInt + */ + $DPIInt = array(DPI_DTD_WML_1_0 => 2, + DPI_DTD_WTA_1_0 => 3, + DPI_DTD_WML_1_1 => 4, + DPI_DTD_SI_1_1 => 5, + DPI_DTD_SL_1_0 => 6, + DPI_DTD_CO_1_0 => 7, + DPI_DTD_CHANNEL_1_1 => 8, + DPI_DTD_WML_1_2 => 9, + DPI_DTD_WML_1_3 => 10, + DPI_DTD_PROV_1_0 => 11, + DPI_DTD_WTA_WML_1_2 => 12, + DPI_DTD_CHANNEL_1_2 => 13, + + // Not all SyncML clients know this, so we + // should use the string table. + // DPI_DTD_SYNCML_1_1 => 0xFD1, + // DPI_DTD_DEVINF_1_1 => 0xFD2, + ); + + return isset($DPIInt[$dpi]) ? $DPIInt[$dpi] : 0; + } + + /** + * Returns the character encoding. + * only default character encodings from J2SE are supported + * from http://www.iana.org/assignments/character-sets + * and http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/Charset.html + */ + function getCharsetString($cs) + { + /** + * From http://www.iana.org/assignments/character-sets + * @var array $_charsetString + */ + $charsetString = array(3 => 'US-ASCII', + 4 => 'ISO-8859-1', + 106 => 'UTF-8', + 1013 => 'UTF-16BE', + 1014 => 'UTF-16LE', + 1015 => 'UTF-16'); + + return isset($charsetString[$cs]) ? $charsetString[$cs] : null; + } + + /** + * Returns the character encoding. + * + * Only default character encodings from J2SE are supported. + * + * From http://www.iana.org/assignments/character-sets and + * http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/Charset.html + */ + function getCharsetInt($cs) + { + /** + * From http://www.iana.org/assignments/character-sets + * @var array $_charsetInt + */ + $charsetInt = array('US-ASCII' => 3, + 'ISO-8859-1' => 4, + 'UTF-8' => 106, + 'UTF-16BE' => 1013, + 'UTF-16LE' => 1014, + 'UTF-16' => 1015); + + return isset($charsetInt[$cs]) ? $charsetInt[$cs] : null; + } + +} + +/** + * @package XML_WBXML + */ +class XML_WBXML_HashTable { + + var $_h; + + function set($k, $v) + { + $this->_h[$k] = $v; + } + + function get($k) + { + return isset($this->_h[$k]) ? $this->_h[$k] : null; + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php b/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php new file mode 100644 index 0000000000..f29a5bf959 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php @@ -0,0 +1,153 @@ + + * + * 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 + * + * @package XML_WBXML + */ +class XML_WBXML_ContentHandler { + + var $_currentUri; + var $_output = ''; + + var $_opaqueHandler; + + /** + * Charset. + */ + var $_charset = 'UTF-8'; + + /** + * WBXML Version. + * 1, 2, or 3 supported + */ + var $_wbxmlVersion = 2; + + function XML_WBXML_ContentHandler() + { + $this->_currentUri = &new XML_WBXML_LifoQueue(); + } + + function raiseError($error) + { + include_once 'PEAR.php'; + return PEAR::raiseError($error); + } + + function getCharsetStr() + { + return $this->_charset; + } + + function setCharset($cs) + { + $this->_charset = $cs; + } + + function getVersion() + { + return $this->_wbxmlVersion; + } + + function setVersion($v) + { + $this->_wbxmlVersion = 2; + } + + function getOutput() + { + return $this->_output; + } + + function startElement($uri, $element, $attrs) + { + $this->_output .= '<' . $element; + + $currentUri = $this->_currentUri->top(); + + if ((!$currentUri) || ($currentUri != $uri)) { + $this->_output .= ' xmlns="' . $uri . '"'; + } + + $this->_currentUri->push($uri); + + foreach ($attrs as $attr) { + $this->_output .= ' ' . $attr['attribute'] . '="' . $attr['value'] . '"'; + } + + $this->_output .= '>'; + } + + function endElement($uri, $element) + { + $this->_output .= ''; + + $this->_currentUri->pop(); + } + + function characters($str) + { + $this->_output .= $str; + } + + 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; + } + } + + function setOpaqueHandler($opaqueHandler) + { + $this->_opaqueHandler = $opaqueHandler; + } + + function removeOpaqueHandler() + { + unset($this->_opaqueHandler); + } + +} + +class XML_WBXML_LifoQueue { + + var $_queue = array(); + + function XML_WBXML_LifoQueue() + { + } + + function push($obj) + { + array_push($this->_queue, $obj); + } + + function pop() + { + if (count($this->_queue)) { + return array_pop($this->_queue); + } else { + return null; + } + } + + function top() + { + if ($count = count($this->_queue)) { + return $this->_queue[$count - 1]; + } else { + return null; + } + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD.php b/phpgwapi/inc/horde/XML/WBXML/DTD.php new file mode 100644 index 0000000000..e9f6bddccf --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/DTD.php @@ -0,0 +1,150 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @package XML_WBXML + */ +class XML_WBXML_DTD { + + var $version; + var $intTags; + var $intAttributes; + var $strTags; + var $strAttributes; + var $intCodePages; + var $strCodePages; + var $strCodePagesURI; + var $URI; + var $XMLNS; + var $DPI; + + function XML_WBXML_DTD($v) + { + $this->version = $v; + $this->init(); + } + + function init() + { + } + + function setAttribute($intAttribute, $strAttribute) + { + $this->strAttributes[$strAttribute] = $intAttribute; + $this->intAttributes[$intAttribute] = $strAttribute; + } + + function setTag($intTag, $strTag) + { + $this->strTags[$strTag] = $intTag; + $this->intTags[$intTag] = $strTag; + } + + function setCodePage($intCodePage, $strCodePage, $strCodePageURI) + { + $this->strCodePagesURI[$strCodePageURI] = $intCodePage; + $this->strCodePages[$strCodePage] = $intCodePage; + $this->intCodePages[$intCodePage] = $strCodePage; + } + + function toTagStr($tag) + { + return isset($this->intTags[$tag]) ? $this->intTags[$tag] : false; + } + + function toAttributeStr($attribute) + { + return isset($this->intTags[$attribute]) ? $this->intTags[$attribute] : false; + } + + function toCodePageStr($codePage) + { + return isset($this->intCodePages[$codePage]) ? $this->intCodePages[$codePage] : false; + } + + function toTagInt($tag) + { + return isset($this->strTags[$tag]) ? $this->strTags[$tag] : false; + } + + function toAttributeInt($attribute) + { + return isset($this->strAttributes[$attribute]) ? $this->strAttributes[$attribute] : false; + } + + function toCodePageInt($codePage) + { + return isset($this->strCodePages[$codePage]) ? $this->strCodePages[$codePage] : false; + } + + function toCodePageURI($uri) + { + $uri = strtolower($uri); + $ret = isset($this->strCodePagesURI[$uri]) ? $this->strCodePagesURI[$uri] : false; + + return $ret; + } + + /** + * Getter for property version. + * @return Value of property version. + */ + function getVersion() + { + return $this->version; + } + + /** + * Setter for property version. + * @param integer $v New value of property version. + */ + function setVersion($v) + { + $this->version = $v; + } + + /** + * Getter for property URI. + * @return Value of property URI. + */ + function getURI() + { + return $this->URI; + } + + /** + * Setter for property URI. + * @param string $u New value of property URI. + */ + function setURI($u) + { + $this->URI = $u; + } + + /** + * Getter for property DPI. + * @return Value of property DPI. + */ + function getDPI() + { + return $this->DPI; + } + + /** + * Setter for property DPI. + * @param DPI New value of property DPI. + */ + function setDPI($d) + { + $this->DPI = $d; + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php new file mode 100644 index 0000000000..1b3c8e22f8 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncML.php @@ -0,0 +1,85 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @package XML_WBXML + */ +class XML_WBXML_DTD_SyncML extends XML_WBXML_DTD { + + function init() + { + $this->setTag(5, 'Add'); // 0x05 + $this->setTag(6, 'Alert'); // 0x06 + $this->setTag(7, 'Archive'); // 0x07 + $this->setTag(8, 'Atomic'); // 0x08 + $this->setTag(9, 'Chal'); // 0x09 + $this->setTag(10, 'Cmd'); // 0x0A + $this->setTag(11, 'CmdID'); // 0x0B + $this->setTag(12, 'CmdRef'); // 0x0C + $this->setTag(13, 'Copy'); // 0x0D + $this->setTag(14, 'Cred'); // 0x0E + $this->setTag(15, 'Data'); // 0x0F + + $this->setTag(16, 'Delete'); // 0x10 + $this->setTag(17, 'Exec'); // 0x11 + $this->setTag(18, 'Final'); // 0x12 + $this->setTag(19, 'Get'); // 0x13 + $this->setTag(20, 'Item'); // 0x14 + $this->setTag(21, 'Lang'); // 0x15 + $this->setTag(22, 'LocName'); // 0x16 + $this->setTag(23, 'LocURI'); // 0x17 + $this->setTag(24, 'Map'); // 0x18 + $this->setTag(25, 'MapItem'); // 0x19 + $this->setTag(26, 'Meta'); // 0x1A + $this->setTag(27, 'MsgID'); // 0x1B + $this->setTag(28, 'MsgRef'); // 0x1C + $this->setTag(29, 'NoRssp'); // 0x1D + $this->setTag(30, 'NoResults'); // 0x1E + $this->setTag(31, 'Put'); // 0x1F + + $this->setTag(32, 'Replace'); // 0x10 + $this->setTag(33, 'RespURI'); // 0x21 + $this->setTag(34, 'Results'); // 0x22 + $this->setTag(35, 'Search'); // 0x23 + $this->setTag(36, 'Sequence'); // 0x24 + $this->setTag(37, 'SessionID'); // 0x25 + $this->setTag(38, 'SftDel'); // 0x26 + $this->setTag(39, 'Source'); // 0x27 + $this->setTag(40, 'SourceRef'); // 0x28 + $this->setTag(41, 'Status'); // 0x29 + $this->setTag(42, 'Sync'); // 0x2A + $this->setTag(43, 'SyncBody'); // 0x2B + $this->setTag(44, 'SyncHdr'); // 0x2C + $this->setTag(45, 'SyncML'); // 0x2D + $this->setTag(46, 'Target'); // 0x2E + $this->setTag(47, 'TargetRef'); // 0x2F + + $this->setTag(48, 'Reserved for future use.'); // 0x30 + $this->setTag(49, 'VerDTD'); // 0x31 + $this->setTag(50, 'VerProto'); // 0x32 + $this->setTag(51, 'NumberOfChanged'); // 0x33 + $this->setTag(52, 'MoreData'); // 0x34 + + if ($this->version == 0) { + $this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml'); + $this->setCodePage(1, '-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf'); + $this->setURI('syncml:syncml'); + } 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'); + $this->setURI('syncml:syncml1.1'); + } + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php new file mode 100644 index 0000000000..fa7f84041e --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLDevInf.php @@ -0,0 +1,70 @@ + + * + * 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 + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @package XML_WBXML + */ +class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD { + + function init() + { + $this->setTag(5, 'CTCap'); // 0x05 + $this->setTag(6, 'CTType'); // 0x06 + $this->setTag(7, 'DataStore'); // 0x07 + $this->setTag(8, 'DataType'); // 0x08 + $this->setTag(9, 'DevID'); // 0x09 + $this->setTag(10, 'DevInf'); // 0x0A + $this->setTag(11, 'DevTyp'); // 0x0B + $this->setTag(12, 'DisplayName'); // 0x0C + $this->setTag(13, 'DSMem'); // 0x0D + $this->setTag(14, 'Ext'); // 0x0E + $this->setTag(15, 'FwV'); // 0x0F + $this->setTag(16, 'HwV'); // 0x10 + $this->setTag(17, 'Man'); // 0x11 + $this->setTag(18, 'MaxGUIDSize'); // 0x12 + $this->setTag(19, 'MaxID'); // 0x13 + $this->setTag(20, 'MaxMem'); // 0x14 + $this->setTag(21, 'Mod'); // 0x15 + $this->setTag(22, 'OEM'); // 0x15 + $this->setTag(23, 'ParamName'); // 0x17 + $this->setTag(24, 'PropName'); // 0x18 + $this->setTag(25, 'Rx'); // 0x19 + $this->setTag(26, 'Rx-Pref'); // 0x1A + $this->setTag(27, 'SharedMem'); // 0x1B + $this->setTag(28, 'Size'); // 0x1C + $this->setTag(29, 'SourceRef'); // 0x1D + $this->setTag(30, 'SwV'); // 0x1E + $this->setTag(31, 'SyncCap'); // 0x1F + $this->setTag(32, 'SyncType'); // 0x20 + $this->setTag(33, 'Tx'); // 0x21 + $this->setTag(34, 'Tx-Pref'); // 0x22 + $this->setTag(35, 'ValEnum'); // 0x23 + $this->setTag(36, 'VerCT'); // 0x24 + $this->setTag(37, 'VerDTD'); // 0x25 + $this->setTag(38, 'Xnam'); // 0x26 + $this->setTag(39, 'Xval'); // 0x27 + $this->setTag(40, 'UTC'); // 0x28 + $this->setTag(41, 'SupportNumberOfChanges'); // 0x29 + $this->setTag(42, 'SupportLargeObjs'); // 0x2A + + if ($this->version == 0) { + $this->setCodePage(0, '-//SYNCML//DTD DevInf 1.0//EN', 'syncml:devinf'); + $this->setURI('sync:devinf'); + } else { + $this->setCodePage(0, '-//SYNCML//DTD DevInf 1.1//EN', 'syncml:devinf1.1'); + $this->setURI('sync:devinf1.1'); + } + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php new file mode 100644 index 0000000000..a93471ce94 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/DTD/SyncMLMetInf.php @@ -0,0 +1,51 @@ + + * + * 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 + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @package XML_WBXML + */ +class XML_WBXML_DTD_SyncMLMetInf extends XML_WBXML_DTD { + + function init() + { + $this->setTag(5, 'Anchor'); // 0x05 + $this->setTag(6, 'EMI'); // 0x06 + $this->setTag(7, 'Format'); // 0x07 + $this->setTag(8, 'FreeID'); // 0x08 + $this->setTag(9, 'FreeMem'); // 0x09 + $this->setTag(10, 'Last'); // 0x0A + $this->setTag(11, 'Mark'); // 0x0B + $this->setTag(12, 'MaxMsgSize'); // 0x0C + $this->setTag(13, 'Mem'); // 0x0D + $this->setTag(14, 'MetInf'); // 0x0E + $this->setTag(15, 'Next'); // 0x0F + $this->setTag(16, 'NextNonce'); // 0x10 + $this->setTag(17, 'SharedMem'); // 0x11 + $this->setTag(18, 'Size'); // 0x12 + $this->setTag(19, 'Type'); // 0x13 + $this->setTag(20, 'Version'); // 0x14 + $this->setTag(21, 'MaxObjSize'); // 0x15 + + if ($this->version == 0) { + $this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml'); + $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'); + $this->setURI('syncml:metinf1.1'); + } + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/DTDManager.php b/phpgwapi/inc/horde/XML/WBXML/DTDManager.php new file mode 100644 index 0000000000..a855034033 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/DTDManager.php @@ -0,0 +1,56 @@ + + * + * 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 + * + * @package XML_WBXML + */ +class XML_WBXML_DTDManager { + + var $_strDTD = array(); + var $_strDTDURI = array(); + + function XML_WBXML_DTDManager() + { + $this->registerDTD('-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml', 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('-//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('-//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)); + } + + function getInstance($publicIdentifier) + { + return isset($this->_strDTD[$publicIdentifier]) ? $this->_strDTD[$publicIdentifier] : null; + } + + function getInstanceURI($uri) + { + $uri = strtolower($uri); + return isset($this->_strDTDURI[$uri]) ? $this->_strDTDURI[$uri] : null; + } + + function registerDTD($publicIdentifier, $uri, &$dtd) + { + $dtd->setDPI($publicIdentifier); + + $this->_strDTD[$publicIdentifier] = $dtd; + $this->_strDTDURI[$uri] = $dtd; + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/Decoder.php b/phpgwapi/inc/horde/XML/WBXML/Decoder.php new file mode 100644 index 0000000000..fd99b0aba1 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/Decoder.php @@ -0,0 +1,618 @@ + + * + * 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 + * + * @package XML_WBXML + */ +class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { + + /** + * Document Public Identifier type + * 1 mb_u_int32 well known type + * 2 string table + * from spec but converted into a string. + * + * Document Public Identifier + * Used with dpiType. + */ + var $_dpi; + + /** + * String table as defined in 5.7 + */ + var $_stringTable = array(); + + /** + * Content handler. + * Currently just outputs raw XML. + */ + var $_ch; + + var $_tagDTD; + + var $_prevAttributeDTD; + + var $_attributeDTD; + + /** + * State variables. + */ + var $_tagStack = array(); + var $_isAttribute; + var $_isData = false; + + /** + * The DTD Manager. + * @var object XML_WBXML_DTDManager $dtdManager + */ + var $_dtdManager; + + /** + * The string position. + * @var integer $_strpos + */ + var $_strpos; + + /** + * Use wbxml2xml from libwbxml. + * @var string $_wbxml2xml + */ + var $_wbxml2xml = '/usr/bin/wbxml2xml'; + + /** + * Arguments to pass to wbxml2xml. + * @var string $_wbxml2xml_args + */ + var $_wbxml2xml_args = '-o - -'; + + /** + * Constructor. + */ + function XML_WBXML_Decoder() + { + if (empty($this->_wbxml2xml) || !is_executable($this->_wbxml2xml)) { + $this->_dtdManager = &new XML_WBXML_DTDManager(); + } + } + + /** + * Return one byte from the input stream. + * + * @param string $input The WBXML input string. + */ + function getByte($input) + { + return ord($input{$this->_strpos++}); + } + + /** + * Takes a WBXML input document and returns decoded XML. + * + * @param string $wbxml The WBXML document to decode. + * + * @return string The decoded XML document. + */ + function decode($wbxml) + { + // Figure out if we're going to use wbxml2xml to do the + // conversion, or do it all in PHP code. + if (!empty($this->_wbxml2xml) && is_executable($this->_wbxml2xml)) { + $descriptorspec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + ); + + $wbxml2xml = proc_open($this->_wbxml2xml . ' ' . $this->_wbxml2xml_args, + $descriptorspec, $pipes); + if (is_resource($wbxml2xml)) { + fwrite($pipes[0], $wbxml); + fclose($pipes[0]); + + // Grab the output of wbxml2xml. + $xml = ''; + while (!feof($pipes[1])) { + $xml .= fread($pipes[1], 8192); + } + fclose($pipes[1]); + + $rv = proc_close($wbxml2xml); + + return $xml; + } else { + return PEAR::raiseError('wbxml2xml failed'); + } + } else { + $this->_strpos = 0; + + // Get Version Number from Section 5.4 + // version = u_int8 + // currently 1, 2 or 3 + $this->_wbxmlVersion = $this->getVersionNumber($wbxml); + + // Get Document Public Idetifier from Section 5.5 + // publicid = mb_u_int32 | (zero index) + // zero = u_int8 + // 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); + + // Get String Table from 5.7 + // strb1 = length *byte + $this->_stringTable = $this->getStringTable($wbxml, $this->_charset); + + // Get Document Public Idetifier from Section 5.5. + $this->_dpi = $this->getDocumentPublicIdentifierImpl($dpiStruct['dpiType'], + $dpiStruct['dpiNumber'], + $this->_stringTable); + + // Now the real fun begins. + // From Sections 5.2 and 5.8 + + // Default content handler. + $this->_ch = &new XML_WBXML_ContentHandler(); + + // Default content handler. + $this->_dtdManager = &new XML_WBXML_DTDManager(); + + // Get the starting DTD. + $this->_tagDTD = $this->_dtdManager->getInstance($this->_dpi); + if (!$this->_tagDTD) { + return $this->raiseError('No DTD found for ' . $this->_dpi); + } + + $this->_attributeDTD = $this->_tagDTD; + + while ($this->_strpos < strlen($wbxml)) { + $this->_decode($wbxml); + } + + return $this->_ch->getOutput(); + } + } + + function getVersionNumber($input) + { + return $this->getByte($input); + } + + function getDocumentPublicIdentifier($input) + { + // 'dpiType' 'dpiNumber' + $dpistruct = array(); + + $i = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + + if ($i == 0) { + $dpiStruct['dpiType'] = 2; + $dpiStruct['dpiNumber'] = $this->getByte($input); + } else { + $dpiStruct['dpiType'] = 1; + $dpiStruct['dpiNumber'] = $i; + } + + return $dpiStruct; + } + + function getDocumentPublicIdentifierImpl($dpiType, $dpiNumber, $st) + { + if ($dpiType == 1) { + return XML_WBXML::getDPIString($dpiNumber); + } else { + return isset($st[$dpiNumber]) ? $st[$dpiNumber] : null; + } + } + + /** + * Returns the character encoding. Only default character + * encodings from J2SE are supported. From + * http://www.iana.org/assignments/character-sets and + * http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/Charset.html + */ + function getCharset($input) + { + $cs = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + return $charset = XML_WBXML::getCharsetString($cs); + } + + /** + * @TODO needs to be fixed. Does this still really need to be + * fixed? + */ + function getStringTable($input, $cs) + { + $stringTable = array(); + $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + + // A hack to make it work with arrays. + // How/why is this necessary? + $str = 'j'; + + $numstr = 0; + $start = 0; + $j = 0; + for ($i = 0; $i < $size; $i++ ) { + /* May need to fix the null detector for more than single + * byte charsets like ASCII, UTF-8, etc. */ + $ch = $input[$this->_strpos++]; + if (ord($ch) == 0) { + $stringTable[$numstr++] = $str; + $str = '#'; + $start = $i + 1; + } else { + $str[$j++] = $ch; + } + } + + if ($start < $size) { + $stringTable[$numstr++] = $str; + } + + return $stringTable; + } + + function _decode($input) + { + $token = $this->getByte($input); + $str = ''; + + switch ($token) { + case XML_WBXML_GLOBAL_TOKEN_STR_I: + // Section 5.8.4.1 + $str = $this->termstr($input); + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_STR_T: + // Section 5.8.4.1 + $str = $this->_stringTable[XML_WBXML::MBUInt32ToInt($intput)]; + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_I_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_2: + // Section 5.8.4.2 + $str = $this->termstr($input); + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_T_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_2: + // Section 5.8.4.2 + $str = $this->_stringTable[XML_WBXML::MBUInt32ToInt($intput)]; + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_2: + // Section 5.8.4.2 + $extension = $this->getByte($input); + $this->_ch->characters($extension); + break; + + case XML_WBXML_GLOBAL_TOKEN_ENTITY: + // Section 5.8.4.3 + // UCS-4 chracter encoding? + $entity = $this->entity(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + + $this->_ch->characters('&#' . $entity . ';'); + break; + + case XML_WBXML_GLOBAL_TOKEN_PI: + // Section 5.8.4.4 + // throw new IOException("WBXML global token processing instruction(PI, " + token + ") is unsupported!"); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL: + // Section 5.8.4.5 + $str = $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + $this->parseTag($input, $str, false, false); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL_A: + // Section 5.8.4.5 + $str = $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + $this->parseTag($input, $str, true, false); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL_AC: + // Section 5.8.4.5 + $str = $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + $this->parseTag($input, $string, true, true); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL_C: + // Section 5.8.4.5 + $str = $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + $this->parseTag($input, $str, false, true); + break; + + case XML_WBXML_GLOBAL_TOKEN_OPAQUE: + // Section 5.8.4.6 + $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + $b = substr($input, $this->_strpos, $this->_strpos + $size); + $this->_strpos += $size; + $this->_ch->opaque($b); + + // FIXME Opaque is used by SYNCML. Opaque data that depends on the context + // if (contentHandler instanceof OpaqueContentHandler) { + // ((OpaqueContentHandler)contentHandler).opaque(b); + // } else { + // String str = new String(b, 0, size, charset); + // char[] chars = str.toCharArray(); + + // contentHandler.characters(chars, 0, chars.length); + // } + + // This can cause some problems. We may have to use a + // event based decoder. + break; + + case XML_WBXML_GLOBAL_TOKEN_END: + // Section 5.8.4.7.1 + $str = $this->endTag(); + break; + + case XML_WBXML_GLOBAL_TOKEN_SWITCH_PAGE: + // Section 5.8.4.7.2 + $codePage = $this->getByte($input); + $this->switchElementCodePage($codePage); + break; + + default: + // Section 5.8.2 + // Section 5.8.3 + $hasAttributes = (($token & 0x80) != 0); + $hasContent = (($token & 0x40) != 0); + $realToken = $token & 0x3F; + $str = $this->getTag($realToken); + + $this->parseTag($input, $str, $hasAttributes, $hasContent); + + if ($realToken == 0x0f) { + // FIXME Don't remember this one. + $this->_isData = true; + } + break; + } + } + + function parseTag($input, $tag, $hasAttributes, $hasContent) + { + $attrs = array(); + if ($hasAttributes) { + $attrs = $this->getAttributes($input); + } + + $this->_ch->startElement($this->getCurrentURI(), $tag, $attrs); + + if ($hasContent) { + // FIXME I forgot what does this does. Not sure if this is + // right? + $this->_tagStack[] = $tag; + } else { + $this->_ch->endElement($this->getCurrentURI(), $tag); + } + } + + function endTag() + { + if (count($this->_tagStack)) { + $tag = array_pop($this->_tagStack); + } else { + $tag = 'Unknown'; + } + + if ($tag == 'Data') { + $this->_isData = false; + } + + $this->_ch->endElement($this->getCurrentURI(), $tag); + + return $tag; + } + + function getAttributes($input) + { + $this->startGetAttributes(); + $hasMoreAttributes = true; + + $attrs = array(); + $attr = null; + $value = null; + $token = null; + + while ($hasMoreAttributes) { + $token = $this->getByte($input); + + switch ($token) { + // Attribute specified. + case XML_WBXML_GLOBAL_TOKEN_LITERAL: + // Section 5.8.4.5 + if (isset($attr)) { + $attrs[] = array('attribute' => $attr, + 'value' => $value); + } + + $attr = $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + break; + + // Value specified. + case XML_WBXML_GLOBAL_TOKEN_EXT_I_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_2: + // Section 5.8.4.2 + $value .= $this->termstr($input); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_T_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_2: + // Section 5.8.4.2 + $value .= $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_2: + // Section 5.8.4.2 + $value .= $input[$this->_strpos++]; + break; + + case XML_WBXML_GLOBAL_TOKEN_ENTITY: + // Section 5.8.4.3 + $value .= $this->entity(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + break; + + case XML_WBXML_GLOBAL_TOKEN_STR_I: + // Section 5.8.4.1 + $value .= $this->termstr($input); + break; + + case XML_WBXML_GLOBAL_TOKEN_STR_T: + // Section 5.8.4.1 + $value .= $this->_stringTable[XML_WBXML::MBUInt32ToInt($input, $this->_strpos)]; + break; + + case XML_WBXML_GLOBAL_TOKEN_OPAQUE: + // Section 5.8.4.6 + $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + $b = substr($input, $this->_strpos, $this->_strpos + $size); + $this->_strpos += $size; + + $value .= $b; + break; + + case XML_WBXML_GLOBAL_TOKEN_END: + // Section 5.8.4.7.1 + $hasMoreAttributes = false; + if (isset($attr)) { + $attrs[] = array('attribute' => $attr, + 'value' => $value); + } + break; + + case XML_WBXML_GLOBAL_TOKEN_SWITCH_PAGE: + // Section 5.8.4.7.2 + $codePage = $this->getByte($input); + if (!$this->_prevAttributeDTD) { + $this->_prevAttributeDTD = $this->_attributeDTD; + } + + $this->switchAttributeCodePage($codePage); + break; + + default: + if ($token > 128) { + if (isset($attr)) { + $attrs[] = array('attribute' => $attr, + 'value' => $value); + } + $attr = $this->_attributeDTD->toAttribute($token); + } else { + // Value. + $value .= $this->_attributeDTD->toAttribute($token); + } + break; + } + } + + if (!$this->_prevAttributeDTD) { + $this->_attributeDTD = $this->_prevAttributeDTD; + $this->_prevAttributeDTD = false; + } + + $this->stopGetAttributes(); + } + + function startGetAttributes() + { + $this->_isAttribute = true; + } + + function stopGetAttributes() + { + $this->_isAttribute = false; + } + + function getCurrentURI() + { + if ($this->_isAttribute) { + return $this->_tagDTD->getURI(); + } else { + return $this->_attributeDTD->getURI(); + } + } + + function writeString($str) + { + $this->_ch->characters($str); + } + + function getTag($tag) + { + // Should know which state it is in. + return $this->_tagDTD->toTagStr($tag); + } + + function getAttribute($attribute) + { + // Should know which state it is in. + $this->_attributeDTD->toAttributeInt($attribute); + } + + function switchElementCodePage($codePage) + { + $this->_tagDTD = &$this->_dtdManager->getInstance($this->_tagDTD->toCodePageStr($codePage)); + $this->switchAttributeCodePage($codePage); + } + + function switchAttributeCodePage($codePage) + { + $this->_attributeDTD = &$this->_dtdManager->getInstance($this->_attributeDTD->toCodePageStr($codePage)); + } + + /** + * Return the hex version of the base 10 $entity. + */ + function entity($entity) + { + return dechex($entity); + } + + /** + * @TODO FIXME reads a null terminated string. + */ + function termstr($input) + { + $str = '#'; + $i = 0; + $ch = $input[$this->_strpos++]; + while (ord($ch) != 0) { + $str[$i++] = $ch; + $ch = $input[$this->_strpos++]; + } + + return $str; + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/Encoder.php b/phpgwapi/inc/horde/XML/WBXML/Encoder.php new file mode 100644 index 0000000000..2fa11ba183 --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/Encoder.php @@ -0,0 +1,486 @@ + + * + * 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 + * + * @package XML_WBXML + */ +class XML_WBXML_Encoder extends XML_WBXML_ContentHandler { + + var $_strings = array(); + + var $_stringTable; + + var $_hasWrittenHeader = false; + + var $_dtd; + + var $_output = ''; + + var $_uris = array(); + + var $_uriNums = array(); + + var $_currentURI; + + var $_subParser; + var $_subParserStack = 0; + + /** + * These will store the startElement params to see if we should + * call startElementImp or startEndElementImp. + */ + var $_storeURI; + var $_storeName; + var $_storeAttributes; + + /** + * The XML parser. + * @var resource $_parser + */ + var $_parser; + + /** + * The DTD Manager. + * @var object XML_WBXML_DTDManager $dtdManager + */ + var $_dtdManager; + + var $_indent = 0; + + /** + * Use wbxml2xml from libwbxml. + * @var string $_xml2wbxml + */ + var $_xml2wbxml = '/usr/bin/xml2wbxml'; + + /** + * Arguments to pass to xml2wbxml. + * @var string $_xml2wbxml_args + */ + var $_xml2wbxml_args = '-k -n -v 1.2 -o - -'; + + /** + * Constructor. + */ + function XML_WBXML_Encoder() + { + if (empty($this->_xml2wbxml) || !is_executable($this->_xml2wbxml)) { + $this->_stringTable = &new XML_WBXML_HashTable(); + $this->_dtdManager = &new XML_WBXML_DTDManager(); + } + } + + /** + * Take the input $xml and turn it into WBXML. + */ + function encode($xml) + { + if (!empty($this->_xml2wbxml) && is_executable($this->_xml2wbxml)) { + $descriptorspec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + ); + + $xml2wbxml = proc_open($this->_xml2wbxml . ' ' . $this->_xml2wbxml_args, + $descriptorspec, $pipes); + if (is_resource($xml2wbxml)) { + fwrite($pipes[0], $xml); + fclose($pipes[0]); + + // Grab the output of xml2wbxml. + $wbxml = ''; + while (!feof($pipes[1])) { + $wbxml .= fread($pipes[1], 8192); + } + fclose($pipes[1]); + + $rv = proc_close($xml2wbxml); + + return $wbxml; + } else { + return PEAR::raiseError('xml2wbxml failed'); + } + } else { + // Create the XML parser and set method references. + $this->_parser = xml_parser_create_ns($this->_charset); + xml_set_object($this->_parser, $this); + xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($this->_parser, '_startElement', '_endElement'); + xml_set_character_data_handler($this->_parser, '_characters'); + xml_set_default_handler($this->_parser, 'defaultHandler'); + xml_set_processing_instruction_handler($this->_parser, ''); + xml_set_external_entity_ref_handler($this->_parser, ''); + + if (!xml_parse($this->_parser, $xml)) { + return $this->raiseError(sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); + } + + xml_parser_free($this->_parser); + + return $this->_output; + } + } + + /** + * This will write the correct headers. + */ + function writeHeader($uri) + { + $this->_dtd = &$this->_dtdManager->getInstanceURI($uri); + + $dpiString = $this->_dtd->getDPI(); + + // Set Version Number from Section 5.4 + // version = u_int8 + // currently 1, 2 or 3 + $this->writeVersionNumber($this->_wbxmlVersion); + + // Set Document Public Idetifier from Section 5.5 + // publicid = mb_u_int32 | ( zero index ) + // zero = u_int8 + // containing the value zero (0) + // The actual DPI is determined after the String Table is read. + $this->writeDocumentPublicIdentifier($dpiString, $this->_strings); + + // Set Charset from 5.6 + // charset = mb_u_int32 + $this->writeCharset($this->_charset); + + // Set String Table from 5.7 + // strb1 = length *byte + $this->writeStringTable($this->_strings, $this->_charset, $this->_stringTable); + + $this->_currentURI = $uri; + + $this->_hasWrittenHeader = true; + } + + function writeVersionNumber($version) + { + $this->_output .= chr($version); + } + + function writeDocumentPublicIdentifier($dpiString, &$strings) + { + $i = XML_WBXML::getDPIInt($dpiString); + + if ($i == 0) { + $strings[0] = $dpiString; + $this->_output .= chr(0); + $this->_output .= chr(0); + } else { + XML_WBXML::intToMBUInt32($this->_output, $i); + } + } + + function writeCharset($charset) + { + $cs = XML_WBXML::getCharsetInt($charset); + + if ($cs == 0) { + return $this->raiseError('Unsupported Charset: ' . $charset); + } else { + XML_WBXML::intToMBUInt32($this->_output, $cs); + } + } + + function writeStringTable($strings, $charset, $stringTable) + { + $stringBytes = array(); + $count = 0; + foreach ($strings as $str) { + $bytes = $this->_getBytes($str, $charset); + $stringBytes = array_merge($stringBytes, $bytes); + $nullLength = $this->_addNullByte($bytes); + $this->_stringTable->set($str, $count); + $count += count($bytes) + $nullLength; + } + + XML_WBXML::intToMBUInt32($this->_output, count($stringBytes)); + $this->_output .= implode('', $stringBytes); + } + + function writeString($str, $cs) + { + $bytes = $this->_getBytes($str, $cs); + $this->_output .= implode('', $bytes); + $this->writeNull($cs); + } + + function writeNull($charset) + { + $this->_output .= chr(0); + return 1; + } + + function _addNullByte(&$bytes) + { + $bytes[] = chr(0); + return 1; + } + + function _getBytes($string, $cs) + { + $string = String::convertCharset($string, $cs, 'utf-8'); + $nbytes = strlen($string); + + $bytes = array(); + for ($i = 0; $i < $nbytes; $i++) { + $bytes[] = $string{$i}; + } + + return $bytes; + } + + function _splitURI($tag) + { + $parts = explode(':', $tag); + $name = array_pop($parts); + $uri = implode(':', $parts); + return array($uri, $name); + } + + /** + * Has no content, 64. + */ + function startEndElementImp($uri, $name, $attributes) + { + if (!$this->_hasWrittenHeader) { + $this->writeHeader($uri); + } + + $this->writeTag($name, $attributes, false, $this->_charset); + } + + function startElementImp($uri, $name, $attributes) + { + if (!$this->_hasWrittenHeader) { + $this->writeHeader($uri); + } + + if ($this->_currentURI != $uri) { + $this->changecodepage($uri); + + $this->_currentURI != $uri; + } + + $this->writeTag($name, $attributes, true, $this->_charset); + } + + function writeStartElement($isEnd) + { + if ($this->_storeName != null) { + if ($isEnd) { + $this->startEndElementImp($this->_storeURI, $this->_storeName, $this->_storeAttributes); + } else { + $this->startElementImp($this->_storeURI, $this->_storeName, $this->_storeAttributes); + } + + $this->_storeURI = null; + $this->_storeName = null; + $this->_storeAttributes = null; + } + } + + function startElement($uri, $name, $attributes) + { + if ($this->_subParser == null) { + $this->writeStartElement(false); + $this->_storeURI = $uri; + $this->_storeName = $name; + $this->_storeAttributes = $attributes; + } else { + $this->_subParserStack++; + } + } + + function _startElement($parser, $tag, $attributes) + { + list($uri, $name) = $this->_splitURI($tag); + + $this->startElement($uri, $name, $attributes); + } + + function opaque($bytes) + { + if ($this->_subParser == null) { + $this->writeStartElement(false); + + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_OPAQUE); + XML_WBXML::intToMBUInt32($this->_output, count($bytes)); + $this->_output .= $bytes; + } + } + + function characters($chars) + { + $chars = trim($chars); + + if (strlen($chars)) { + /* We definitely don't want any whitespace. */ + if ($this->_subParser == null) { + $this->writeStartElement(false); + + $i = $this->_stringTable->get($chars); + if ($i != null) { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_STR_T); + XML_WBXML::intToMBUInt32($this->_output, $i); + } else { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_STR_I); + $this->writeString($chars, $this->_charset); + } + } + } + } + + function _characters($parser, $chars) + { + $this->characters($chars); + } + + function defaultHandler($parser, $data) + { + } + + function writeTag($name, $attrs, $hasContent, $cs) + { + if ($attrs != null && !count($attrs)) { + $attrs = null; + } + + $t = $this->_dtd->toTagInt($name); + if ($t == -1) { + $i = $this->_stringTable->get($name); + if ($i == null) { + return $this->raiseError($name . ' is not found in String Table or DTD'); + } else { + if ($attrs == null && !$hasContent) { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_LITERAL); + } elseif ($attrs == null && $hasContent) { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_LITERAL_A); + } elseif ($attrs != null && $hasContent) { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_LITERAL_C); + } elseif ($attrs != null && !$hasContent) { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_LITERAL_AC); + } + + XML_WBXML::intToMBUInt32($this->_output, $i); + } + } else { + if ($attrs == null && !$hasContent) { + $this->_output .= chr($t); + } elseif ($attrs == null && $hasContent) { + $this->_output .= chr($t | 64); + } elseif ($attrs != null && $hasContent) { + $this->_output .= chr($t | 128); + } elseif ($attrs != null && !$hasContent) { + $this->_output .= chr($t | 192); + } + } + + if ($attrs != null) { + $this->writeAttributes($attrs, $cs); + } + } + + function writeAttributes($attrs, $cs) + { + foreach ($attrs as $name => $value) { + $this->writeAttribute($name, $value, $cs); + } + + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_END); + } + + function writeAttribute($name, $value, $cs) + { + $a = $this->_dtd->toAttribute($name); + if ($a == -1) { + $i = $this->_stringTable->get($name); + if ($i == null) { + return $this->raiseError($name . ' is not found in String Table or DTD'); + } else { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_LITERAL); + XML_WBXML::intToMBUInt32($this->_output, $i); + } + } else { + $this->_output .= $a; + } + + $i = $this->_stringTable->get($name); + if ($i != null) { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_STR_T); + XML_WBXML::intToMBUInt32($this->_output, $i); + } else { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_STR_I); + $this->writeString($value, $cs); + } + } + + function endElement($uri, $name) + { + if ($this->_subParser == null) { + $this->writeStartElement(false); + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_END); + } else { + $this->_subParserStack--; + if ($this->_subParserStack == 0) { + unset($this->_subParser); + } + } + } + + function _endElement($parser, $tag) + { + list($uri, $name) = $this->_splitURI($tag); + $this->endElement($uri, $name); + } + + function changecodepage($uri) + { + $cp = $this->_dtd->toCodePageURI($uri); + + if (strlen($cp)) { + $this->_dtd = &$this->_dtdManager->getInstanceURI($uri); + + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_SWITCH_PAGE); + $this->_output .= chr($cp); + } else { + $this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_OPAQUE); + + $this->_subParser = &new XML_WBXML_Encoder($this->_output); + $this->startElement($this->_storeURI, $this->_storeName, $this->_storeAttributes); + + $this->_subParserStack = 2; + + $this->_storeURI = null; + $this->_storeName = null; + $this->_storeAttributes = null; + } + } + + /** + * Getter for property output. + */ + function getOutput($output) + { + return $this->_output; + } + +} diff --git a/phpgwapi/inc/horde/config/conf.php b/phpgwapi/inc/horde/config/conf.php new file mode 100644 index 0000000000..038ec99f90 --- /dev/null +++ b/phpgwapi/inc/horde/config/conf.php @@ -0,0 +1,44 @@ +applications['horde'] = array( + 'fileroot' => dirname(__FILE__) . '/..', + 'webroot' => $webroot, + 'initial_page' => 'login.php', + 'icon' => $webroot . '/graphics/horde.png', + 'name' => _("Horde"), + 'status' => 'active', + 'templates' => dirname(__FILE__) . '/../templates', + 'provides' => 'horde' +); + +#$this->applications['mnemo'] = array( +# 'fileroot' => dirname(__FILE__) . '/../mnemo', +# 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', +# 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', +# 'name' => _("Notes"), +# 'status' => 'active', +# 'provides' => 'notes', +# 'menu_parent' => 'organizing' +#); + +$this->applications['egwnotessync'] = array( + 'fileroot' => EGW_SERVER_ROOT.'/syncml/notes', + 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', + 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', + 'name' => _("Notes"), + 'status' => 'active', + 'provides' => 'notes', + 'menu_parent' => 'organizing' +); + +$this->applications['egwcontactssync'] = array( + 'fileroot' => EGW_SERVER_ROOT.'/syncml/contacts', + 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', + 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', + 'name' => _("Contacts"), + 'status' => 'active', + 'provides' => 'contacts', + 'menu_parent' => 'organizing' +); + +$this->applications['egwcalendarsync'] = array( + 'fileroot' => EGW_SERVER_ROOT.'/syncml/calendar', + 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', + 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', + 'name' => _("Calendar"), + 'status' => 'active', + 'provides' => 'calendar', + 'menu_parent' => 'organizing' +); + diff --git a/phpgwapi/inc/horde/lib/base.php b/phpgwapi/inc/horde/lib/base.php new file mode 100644 index 0000000000..b658f5f03c --- /dev/null +++ b/phpgwapi/inc/horde/lib/base.php @@ -0,0 +1,54 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + */ + +// 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', dirname(__FILE__) . '/..'); +#} + +// Load the Horde Framework core, and set up inclusion paths. +#require_once HORDE_BASE . '/lib/core.php'; + +// Registry. +if (Util::nonInputVar('session_control') == 'none') { + $registry = &Registry::singleton(HORDE_SESSION_NONE); +} else { + $registry = &Registry::singleton(); +} +#if (is_a(($pushed = $registry->pushApp('horde', !defined('AUTH_HANDLER'))), 'PEAR_Error')) { +# if ($pushed->getCode() == 'permission_denied') { +# Horde::authenticationFailureRedirect(); +# } +# Horde::fatal($pushed, __FILE__, __LINE__, false); +#} +$conf = &$GLOBALS['conf']; +#@define('HORDE_TEMPLATES', $registry->get('templates')); + +// Notification System. +#$notification = &Notification::singleton(); +#$notification->attach('status'); + +/* Set up the menu. */ +#require_once 'Horde/Menu.php'; +#$menu = &new Menu(); + +// Compress output +#Horde::compressOutput(); diff --git a/phpgwapi/inc/horde/lib/core.php b/phpgwapi/inc/horde/lib/core.php new file mode 100644 index 0000000000..53527b9798 --- /dev/null +++ b/phpgwapi/inc/horde/lib/core.php @@ -0,0 +1,43 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + */ + +/* Turn PHP stuff off that can really screw things up. */ +ini_set('magic_quotes_sybase', 0); +ini_set('magic_quotes_runtime', 0); + +/* If the Horde Framework packages are not installed in PHP's global + * 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 . get_include_path()); + +/* PEAR base class. */ +include_once 'PEAR.php'; + +/* Horde core classes. */ +include_once 'Horde.php'; +include_once 'Horde/Registry.php'; +#include_once 'Horde/DataTree.php'; +#include_once 'Horde/String.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'; + +#/* Browser detection object. */ +#if (class_exists('Browser')) { +# $browser = &Browser::singleton(); +#}