diff --git a/.gitignore b/.gitignore index 68c48822e9..ac58f3e6a6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ data owncloud config/config.php +config/mount.php +apps/inc.php # just sane ignores .*.sw[po] diff --git a/.htaccess b/.htaccess index 96ecb685d5..69dddc9abb 100644 --- a/.htaccess +++ b/.htaccess @@ -1,5 +1,9 @@ ErrorDocument 403 /core/templates/403.php ErrorDocument 404 /core/templates/404.php +Redirect 301 /apps/calendar/caldav.php /remote.php/caldav/ +Redirect 301 /apps/contacts/carddav.php /remote.php/carddav/ +Redirect 301 /apps/files/webdav.php /remote.php/webdav/ +Redirect 301 /files/webdav.php /remote.php/webdav/ php_value upload_max_filesize 512M php_value post_max_size 512M @@ -10,8 +14,11 @@ php_value memory_limit 512M RewriteEngine on -RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last] -RewriteRule ^.well-known/carddav /apps/contacts/carddav.php [R] -RewriteRule ^.well-known/caldav /apps/calendar/caldav.php [R] +RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}] +RewriteRule ^.well-known/host-meta /public.php?service=host-meta [QSA,L] +RewriteRule ^.well-known/carddav /remote.php/carddav/ [R] +RewriteRule ^.well-known/caldav /remote.php/caldav/ [R] +RewriteRule ^apps/([^/]*)/(.*\.(css|php))$ index.php?app=$1&getfile=$2 [QSA,L] +RewriteRule ^remote/(.*) remote.php [QSA,L] Options -Indexes diff --git a/3rdparty/MDB2.php b/3rdparty/MDB2.php index aa7ec6ba09..2814f88ac0 100644 --- a/3rdparty/MDB2.php +++ b/3rdparty/MDB2.php @@ -1,4316 +1,4270 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: MDB2.php,v 1.335 2008/11/29 14:57:01 afz Exp $ -// - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - */ - -require_once('PEAR.php'); - -// {{{ Error constants - -/** - * The method mapErrorCode in each MDB2_dbtype implementation maps - * native error codes to one of these. - * - * If you add an error code here, make sure you also add a textual - * version of it in MDB2::errorMessage(). - */ - -define('MDB2_OK', true); -define('MDB2_ERROR', -1); -define('MDB2_ERROR_SYNTAX', -2); -define('MDB2_ERROR_CONSTRAINT', -3); -define('MDB2_ERROR_NOT_FOUND', -4); -define('MDB2_ERROR_ALREADY_EXISTS', -5); -define('MDB2_ERROR_UNSUPPORTED', -6); -define('MDB2_ERROR_MISMATCH', -7); -define('MDB2_ERROR_INVALID', -8); -define('MDB2_ERROR_NOT_CAPABLE', -9); -define('MDB2_ERROR_TRUNCATED', -10); -define('MDB2_ERROR_INVALID_NUMBER', -11); -define('MDB2_ERROR_INVALID_DATE', -12); -define('MDB2_ERROR_DIVZERO', -13); -define('MDB2_ERROR_NODBSELECTED', -14); -define('MDB2_ERROR_CANNOT_CREATE', -15); -define('MDB2_ERROR_CANNOT_DELETE', -16); -define('MDB2_ERROR_CANNOT_DROP', -17); -define('MDB2_ERROR_NOSUCHTABLE', -18); -define('MDB2_ERROR_NOSUCHFIELD', -19); -define('MDB2_ERROR_NEED_MORE_DATA', -20); -define('MDB2_ERROR_NOT_LOCKED', -21); -define('MDB2_ERROR_VALUE_COUNT_ON_ROW', -22); -define('MDB2_ERROR_INVALID_DSN', -23); -define('MDB2_ERROR_CONNECT_FAILED', -24); -define('MDB2_ERROR_EXTENSION_NOT_FOUND',-25); -define('MDB2_ERROR_NOSUCHDB', -26); -define('MDB2_ERROR_ACCESS_VIOLATION', -27); -define('MDB2_ERROR_CANNOT_REPLACE', -28); -define('MDB2_ERROR_CONSTRAINT_NOT_NULL',-29); -define('MDB2_ERROR_DEADLOCK', -30); -define('MDB2_ERROR_CANNOT_ALTER', -31); -define('MDB2_ERROR_MANAGER', -32); -define('MDB2_ERROR_MANAGER_PARSE', -33); -define('MDB2_ERROR_LOADMODULE', -34); -define('MDB2_ERROR_INSUFFICIENT_DATA', -35); -define('MDB2_ERROR_NO_PERMISSION', -36); -define('MDB2_ERROR_DISCONNECT_FAILED', -37); - -// }}} -// {{{ Verbose constants -/** - * These are just helper constants to more verbosely express parameters to prepare() - */ - -define('MDB2_PREPARE_MANIP', false); -define('MDB2_PREPARE_RESULT', null); - -// }}} -// {{{ Fetchmode constants - -/** - * This is a special constant that tells MDB2 the user hasn't specified - * any particular get mode, so the default should be used. - */ -define('MDB2_FETCHMODE_DEFAULT', 0); - -/** - * Column data indexed by numbers, ordered from 0 and up - */ -define('MDB2_FETCHMODE_ORDERED', 1); - -/** - * Column data indexed by column names - */ -define('MDB2_FETCHMODE_ASSOC', 2); - -/** - * Column data as object properties - */ -define('MDB2_FETCHMODE_OBJECT', 3); - -/** - * For multi-dimensional results: normally the first level of arrays - * is the row number, and the second level indexed by column number or name. - * MDB2_FETCHMODE_FLIPPED switches this order, so the first level of arrays - * is the column name, and the second level the row number. - */ -define('MDB2_FETCHMODE_FLIPPED', 4); - -// }}} -// {{{ Portability mode constants - -/** - * Portability: turn off all portability features. - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_NONE', 0); - -/** - * Portability: convert names of tables and fields to case defined in the - * "field_case" option when using the query*(), fetch*() and tableInfo() methods. - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_FIX_CASE', 1); - -/** - * Portability: right trim the data output by query*() and fetch*(). - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_RTRIM', 2); - -/** - * Portability: force reporting the number of rows deleted. - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_DELETE_COUNT', 4); - -/** - * Portability: not needed in MDB2 (just left here for compatibility to DB) - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_NUMROWS', 8); - -/** - * Portability: makes certain error messages in certain drivers compatible - * with those from other DBMS's. - * - * + mysql, mysqli: change unique/primary key constraints - * MDB2_ERROR_ALREADY_EXISTS -> MDB2_ERROR_CONSTRAINT - * - * + odbc(access): MS's ODBC driver reports 'no such field' as code - * 07001, which means 'too few parameters.' When this option is on - * that code gets mapped to MDB2_ERROR_NOSUCHFIELD. - * - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_ERRORS', 16); - -/** - * Portability: convert empty values to null strings in data output by - * query*() and fetch*(). - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_EMPTY_TO_NULL', 32); - -/** - * Portability: removes database/table qualifiers from associative indexes - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES', 64); - -/** - * Portability: turn on all portability features. - * @see MDB2_Driver_Common::setOption() - */ -define('MDB2_PORTABILITY_ALL', 127); - -// }}} -// {{{ Globals for class instance tracking - -/** - * These are global variables that are used to track the various class instances - */ - -$GLOBALS['_MDB2_databases'] = array(); -$GLOBALS['_MDB2_dsninfo_default'] = array( - 'phptype' => false, - 'dbsyntax' => false, - 'username' => false, - 'password' => false, - 'protocol' => false, - 'hostspec' => false, - 'port' => false, - 'socket' => false, - 'database' => false, - 'mode' => false, -); - -// }}} -// {{{ class MDB2 - -/** - * The main 'MDB2' class is simply a container class with some static - * methods for creating DB objects as well as some utility functions - * common to all parts of DB. - * - * The object model of MDB2 is as follows (indentation means inheritance): - * - * MDB2 The main MDB2 class. This is simply a utility class - * with some 'static' methods for creating MDB2 objects as - * well as common utility functions for other MDB2 classes. - * - * MDB2_Driver_Common The base for each MDB2 implementation. Provides default - * | implementations (in OO lingo virtual methods) for - * | the actual DB implementations as well as a bunch of - * | query utility functions. - * | - * +-MDB2_Driver_mysql The MDB2 implementation for MySQL. Inherits MDB2_Driver_Common. - * When calling MDB2::factory or MDB2::connect for MySQL - * connections, the object returned is an instance of this - * class. - * +-MDB2_Driver_pgsql The MDB2 implementation for PostGreSQL. Inherits MDB2_Driver_Common. - * When calling MDB2::factory or MDB2::connect for PostGreSQL - * connections, the object returned is an instance of this - * class. - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2 -{ - // {{{ function setOptions(&$db, $options) - - /** - * set option array in an exiting database object - * - * @param MDB2_Driver_Common MDB2 object - * @param array An associative array of option names and their values. - * - * @return mixed MDB2_OK or a PEAR Error object - * - * @access public - */ - static function setOptions(&$db, $options) - { - if (is_array($options)) { - foreach ($options as $option => $value) { - $test = $db->setOption($option, $value); - if (PEAR::isError($test)) { - return $test; - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ function classExists($classname) - - /** - * Checks if a class exists without triggering __autoload - * - * @param string classname - * - * @return bool true success and false on error - * @static - * @access public - */ - static function classExists($classname) - { - if (version_compare(phpversion(), "5.0", ">=")) { - return class_exists($classname, false); - } - return class_exists($classname); - } - - // }}} - // {{{ function loadClass($class_name, $debug) - - /** - * Loads a PEAR class. - * - * @param string classname to load - * @param bool if errors should be suppressed - * - * @return mixed true success or PEAR_Error on failure - * - * @access public - */ - static function loadClass($class_name, $debug) - { - if (!MDB2::classExists($class_name)) { - $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; - if ($debug) { - $include = include_once($file_name); - } else { - $include = include_once($file_name); - } - if (!$include) { - if (!MDB2::fileExists($file_name)) { - $msg = "unable to find package '$class_name' file '$file_name'"; - } else { - $msg = "unable to load class '$class_name' from file '$file_name'"; - } - $err =MDB2::raiseErrorStatic(MDB2_ERROR_NOT_FOUND, null, null, $msg); - return $err; - } - } - return MDB2_OK; - } - - // }}} - // {{{ function &factory($dsn, $options = false) - - /** - * Create a new MDB2 object for the specified database type - * - * IMPORTANT: In order for MDB2 to work properly it is necessary that - * you make sure that you work with a reference of the original - * object instead of a copy (this is a PHP4 quirk). - * - * For example: - * $db =& MDB2::factory($dsn); - * ^^ - * And not: - * $db = MDB2::factory($dsn); - * - * @param mixed 'data source name', see the MDB2::parseDSN - * method for a description of the dsn format. - * Can also be specified as an array of the - * format returned by MDB2::parseDSN. - * @param array An associative array of option names and - * their values. - * - * @return mixed a newly created MDB2 object, or false on error - * - * @access public - */ - static function factory($dsn, $options = false) - { - $dsninfo = MDB2::parseDSN($dsn); - if (empty($dsninfo['phptype'])) { - $err =MDB2::raiseErrorStatic(MDB2_ERROR_NOT_FOUND, - null, null, 'no RDBMS driver specified'); - return $err; - } - $class_name = 'MDB2_Driver_'.$dsninfo['phptype']; - - $debug = (!empty($options['debug'])); - $err = MDB2::loadClass($class_name, $debug); - if (PEAR::isError($err)) { - return $err; - } - - $db =new $class_name(); - $db->setDSN($dsninfo); - $err = MDB2::setOptions($db, $options); - if (PEAR::isError($err)) { - return $err; - } - - return $db; - } - - // }}} - // {{{ function &connect($dsn, $options = false) - - /** - * Create a new MDB2_Driver_* connection object and connect to the specified - * database - * - * IMPORTANT: In order for MDB2 to work properly it is necessary that - * you make sure that you work with a reference of the original - * object instead of a copy (this is a PHP4 quirk). - * - * For example: - * $db =& MDB2::connect($dsn); - * ^^ - * And not: - * $db = MDB2::connect($dsn); - * ^^ - * - * @param mixed $dsn 'data source name', see the MDB2::parseDSN - * method for a description of the dsn format. - * Can also be specified as an array of the - * format returned by MDB2::parseDSN. - * @param array $options An associative array of option names and - * their values. - * - * @return mixed a newly created MDB2 connection object, or a MDB2 - * error object on error - * - * @access public - * @see MDB2::parseDSN - */ - function &connect($dsn, $options = false) - { - $db =MDB2::factory($dsn, $options); - if (PEAR::isError($db)) { - return $db; - } - - $err = $db->connect(); - if (PEAR::isError($err)) { - $dsn = $db->getDSN('string', 'xxx'); - $db->disconnect(); - $err->addUserInfo($dsn); - return $err; - } - - return $db; - } - - // }}} - // {{{ function &singleton($dsn = null, $options = false) - - /** - * Returns a MDB2 connection with the requested DSN. - * A new MDB2 connection object is only created if no object with the - * requested DSN exists yet. - * - * IMPORTANT: In order for MDB2 to work properly it is necessary that - * you make sure that you work with a reference of the original - * object instead of a copy (this is a PHP4 quirk). - * - * For example: - * $db =& MDB2::singleton($dsn); - * ^^ - * And not: - * $db = MDB2::singleton($dsn); - * ^^ - * - * @param mixed 'data source name', see the MDB2::parseDSN - * method for a description of the dsn format. - * Can also be specified as an array of the - * format returned by MDB2::parseDSN. - * @param array An associative array of option names and - * their values. - * - * @return mixed a newly created MDB2 connection object, or a MDB2 - * error object on error - * - * @access public - * @see MDB2::parseDSN - */ - function &singleton($dsn = null, $options = false) - { - if ($dsn) { - $dsninfo = MDB2::parseDSN($dsn); - $dsninfo = array_merge($GLOBALS['_MDB2_dsninfo_default'], $dsninfo); - $keys = array_keys($GLOBALS['_MDB2_databases']); - for ($i=0, $j=count($keys); $i<$j; ++$i) { - if (isset($GLOBALS['_MDB2_databases'][$keys[$i]])) { - $tmp_dsn = $GLOBALS['_MDB2_databases'][$keys[$i]]->getDSN('array'); - if (count(array_diff_assoc($tmp_dsn, $dsninfo)) == 0) { - MDB2::setOptions($GLOBALS['_MDB2_databases'][$keys[$i]], $options); - return $GLOBALS['_MDB2_databases'][$keys[$i]]; - } - } - } - } elseif (is_array($GLOBALS['_MDB2_databases']) && reset($GLOBALS['_MDB2_databases'])) { - $db =$GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])]; - return $db; - } - $db =MDB2::factory($dsn, $options); - return $db; - } - - // }}} - // {{{ function areEquals() - - /** - * It looks like there's a memory leak in array_diff() in PHP 5.1.x, - * so use this method instead. - * @see http://pear.php.net/bugs/bug.php?id=11790 - * - * @param array $arr1 - * @param array $arr2 - * @return boolean - */ - static function areEquals($arr1, $arr2) - { - if (count($arr1) != count($arr2)) { - return false; - } - foreach (array_keys($arr1) as $k) { - if (!array_key_exists($k, $arr2) || $arr1[$k] != $arr2[$k]) { - return false; - } - } - return true; - } - - // }}} - // {{{ function loadFile($file) - - /** - * load a file (like 'Date') - * - * @param string name of the file in the MDB2 directory (without '.php') - * - * @return string name of the file that was included - * - * @access public - */ - function loadFile($file) - { - $file_name = 'MDB2'.DIRECTORY_SEPARATOR.$file.'.php'; - if (!MDB2::fileExists($file_name)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'unable to find: '.$file_name); - } - if (!include_once($file_name)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'unable to load driver class: '.$file_name); - } - return $file_name; - } - - // }}} - // {{{ function apiVersion() - - /** - * Return the MDB2 API version - * - * @return string the MDB2 API version number - * - * @access public - */ - function apiVersion() - { - return '2.5.0b2'; - } - - // }}} - // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) - - /** - * This method is used to communicate an error and invoke error - * callbacks etc. Basically a wrapper for PEAR::raiseError - * without the message string. - * - * @param mixed int error code - * - * @param int error mode, see PEAR_Error docs - * - * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the - * error level (E_USER_NOTICE etc). If error mode is - * PEAR_ERROR_CALLBACK, this is the callback function, - * either as a function name, or as an array of an - * object and method name. For other error modes this - * parameter is ignored. - * - * @param string Extra debug information. Defaults to the last - * query and native error code. - * - * @return PEAR_Error instance of a PEAR Error object - * - * @access private - * @see PEAR_Error - */ - function raiseError($code = null, - $mode = null, - $options = null, - $userinfo = null, - $dummy1 = null, - $dummy2 = null, - $dummy3 = false) - { - $err =PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); - return $err; - } - static function raiseErrorStatic($code = null, - $mode = null, - $options = null, - $userinfo = null, - $dummy1 = null, - $dummy2 = null, - $dummy3 = false) - { - $pear=new PEAR(); - $err =$pear->raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); - return $err; - } - - // }}} - // {{{ function isError($data, $code = null) - - /** - * Tell whether a value is a MDB2 error. - * - * @param mixed the value to test - * @param int if is an error object, return true - * only if $code is a string and - * $db->getMessage() == $code or - * $code is an integer and $db->getCode() == $code - * - * @return bool true if parameter is an error - * - * @access public - */ - static function isError($data, $code = null) - { - if ($data instanceof MDB2_Error) { - if (is_null($code)) { - return true; - } elseif (is_string($code)) { - return $data->getMessage() === $code; - } else { - $code = (array)$code; - return in_array($data->getCode(), $code); - } - } - return false; - } - - // }}} - // {{{ function isConnection($value) - - /** - * Tell whether a value is a MDB2 connection - * - * @param mixed value to test - * - * @return bool whether $value is a MDB2 connection - * - * @access public - */ - static function isConnection($value) - { - return ($value instanceof MDB2_Driver_Common); - } - - // }}} - // {{{ function isResult($value) - - /** - * Tell whether a value is a MDB2 result - * - * @param mixed value to test - * - * @return bool whether $value is a MDB2 result - * - * @access public - */ - static function isResult($value) - { - return $value instanceof MDB2_Result; - } - - // }}} - // {{{ function isResultCommon($value) - - /** - * Tell whether a value is a MDB2 result implementing the common interface - * - * @param mixed value to test - * - * @return bool whether $value is a MDB2 result implementing the common interface - * - * @access public - */ - static function isResultCommon($value) - { - return ($value instanceof MDB2_Result_Common); - } - - // }}} - // {{{ function isStatement($value) - - /** - * Tell whether a value is a MDB2 statement interface - * - * @param mixed value to test - * - * @return bool whether $value is a MDB2 statement interface - * - * @access public - */ - static function isStatement($value) - { - return $value instanceof MDB2_Statement_Common; - } - - // }}} - // {{{ function errorMessage($value = null) - - /** - * Return a textual error message for a MDB2 error code - * - * @param int|array integer error code, - null to get the current error code-message map, - or an array with a new error code-message map - * - * @return string error message, or false if the error code was - * not recognized - * - * @access public - */ - static function errorMessage($value = null) - { - static $errorMessages; - - if (is_array($value)) { - $errorMessages = $value; - return MDB2_OK; - } - - if (!isset($errorMessages)) { - $errorMessages = array( - MDB2_OK => 'no error', - MDB2_ERROR => 'unknown error', - MDB2_ERROR_ALREADY_EXISTS => 'already exists', - MDB2_ERROR_CANNOT_CREATE => 'can not create', - MDB2_ERROR_CANNOT_ALTER => 'can not alter', - MDB2_ERROR_CANNOT_REPLACE => 'can not replace', - MDB2_ERROR_CANNOT_DELETE => 'can not delete', - MDB2_ERROR_CANNOT_DROP => 'can not drop', - MDB2_ERROR_CONSTRAINT => 'constraint violation', - MDB2_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', - MDB2_ERROR_DIVZERO => 'division by zero', - MDB2_ERROR_INVALID => 'invalid', - MDB2_ERROR_INVALID_DATE => 'invalid date or time', - MDB2_ERROR_INVALID_NUMBER => 'invalid number', - MDB2_ERROR_MISMATCH => 'mismatch', - MDB2_ERROR_NODBSELECTED => 'no database selected', - MDB2_ERROR_NOSUCHFIELD => 'no such field', - MDB2_ERROR_NOSUCHTABLE => 'no such table', - MDB2_ERROR_NOT_CAPABLE => 'MDB2 backend not capable', - MDB2_ERROR_NOT_FOUND => 'not found', - MDB2_ERROR_NOT_LOCKED => 'not locked', - MDB2_ERROR_SYNTAX => 'syntax error', - MDB2_ERROR_UNSUPPORTED => 'not supported', - MDB2_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', - MDB2_ERROR_INVALID_DSN => 'invalid DSN', - MDB2_ERROR_CONNECT_FAILED => 'connect failed', - MDB2_ERROR_NEED_MORE_DATA => 'insufficient data supplied', - MDB2_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', - MDB2_ERROR_NOSUCHDB => 'no such database', - MDB2_ERROR_ACCESS_VIOLATION => 'insufficient permissions', - MDB2_ERROR_LOADMODULE => 'error while including on demand module', - MDB2_ERROR_TRUNCATED => 'truncated', - MDB2_ERROR_DEADLOCK => 'deadlock detected', - MDB2_ERROR_NO_PERMISSION => 'no permission', - MDB2_ERROR_DISCONNECT_FAILED => 'disconnect failed', - ); - } - - if (is_null($value)) { - return $errorMessages; - } - - if (PEAR::isError($value)) { - $value = $value->getCode(); - } - - return isset($errorMessages[$value]) ? - $errorMessages[$value] : $errorMessages[MDB2_ERROR]; - } - - // }}} - // {{{ function parseDSN($dsn) - - /** - * Parse a data source name. - * - * Additional keys can be added by appending a URI query string to the - * end of the DSN. - * - * The format of the supplied DSN is in its fullest form: - * - * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true - * - * - * Most variations are allowed: - * - * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 - * phptype://username:password@hostspec/database_name - * phptype://username:password@hostspec - * phptype://username@hostspec - * phptype://hostspec/database - * phptype://hostspec - * phptype(dbsyntax) - * phptype - * - * - * @param string Data Source Name to be parsed - * - * @return array an associative array with the following keys: - * + phptype: Database backend used in PHP (mysql, odbc etc.) - * + dbsyntax: Database used with regards to SQL syntax etc. - * + protocol: Communication protocol to use (tcp, unix etc.) - * + hostspec: Host specification (hostname[:port]) - * + database: Database to use on the DBMS server - * + username: User name for login - * + password: Password for login - * - * @access public - * @author Tomas V.V.Cox - */ - static function parseDSN($dsn) - { - $parsed = $GLOBALS['_MDB2_dsninfo_default']; - - if (is_array($dsn)) { - $dsn = array_merge($parsed, $dsn); - if (!$dsn['dbsyntax']) { - $dsn['dbsyntax'] = $dsn['phptype']; - } - return $dsn; - } - - // Find phptype and dbsyntax - if (($pos = strpos($dsn, '://')) !== false) { - $str = substr($dsn, 0, $pos); - $dsn = substr($dsn, $pos + 3); - } else { - $str = $dsn; - $dsn = null; - } - - // Get phptype and dbsyntax - // $str => phptype(dbsyntax) - if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { - $parsed['phptype'] = $arr[1]; - $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; - } else { - $parsed['phptype'] = $str; - $parsed['dbsyntax'] = $str; - } - - if (!count($dsn)) { - return $parsed; - } - - // Get (if found): username and password - // $dsn => username:password@protocol+hostspec/database - if (($at = strrpos($dsn,'@')) !== false) { - $str = substr($dsn, 0, $at); - $dsn = substr($dsn, $at + 1); - if (($pos = strpos($str, ':')) !== false) { - $parsed['username'] = rawurldecode(substr($str, 0, $pos)); - $parsed['password'] = rawurldecode(substr($str, $pos + 1)); - } else { - $parsed['username'] = rawurldecode($str); - } - } - - // Find protocol and hostspec - - // $dsn => proto(proto_opts)/database - if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { - $proto = $match[1]; - $proto_opts = $match[2] ? $match[2] : false; - $dsn = $match[3]; - - // $dsn => protocol+hostspec/database (old format) - } else { - if (strpos($dsn, '+') !== false) { - list($proto, $dsn) = explode('+', $dsn, 2); - } - if ( strpos($dsn, '//') === 0 - && strpos($dsn, '/', 2) !== false - && $parsed['phptype'] == 'oci8' - ) { - //oracle's "Easy Connect" syntax: - //"username/password@[//]host[:port][/service_name]" - //e.g. "scott/tiger@//mymachine:1521/oracle" - $proto_opts = $dsn; - $dsn = substr($proto_opts, strrpos($proto_opts, '/') + 1); - } elseif (strpos($dsn, '/') !== false) { - list($proto_opts, $dsn) = explode('/', $dsn, 2); - } else { - $proto_opts = $dsn; - $dsn = null; - } - } - - // process the different protocol options - $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; - $proto_opts = rawurldecode($proto_opts); - if (strpos($proto_opts, ':') !== false) { - list($proto_opts, $parsed['port']) = explode(':', $proto_opts); - } - if ($parsed['protocol'] == 'tcp') { - $parsed['hostspec'] = $proto_opts; - } elseif ($parsed['protocol'] == 'unix') { - $parsed['socket'] = $proto_opts; - } - - // Get dabase if any - // $dsn => database - if ($dsn) { - // /database - if (($pos = strpos($dsn, '?')) === false) { - $parsed['database'] = $dsn; - // /database?param1=value1¶m2=value2 - } else { - $parsed['database'] = substr($dsn, 0, $pos); - $dsn = substr($dsn, $pos + 1); - if (strpos($dsn, '&') !== false) { - $opts = explode('&', $dsn); - } else { // database?param1=value1 - $opts = array($dsn); - } - foreach ($opts as $opt) { - list($key, $value) = explode('=', $opt); - if (!isset($parsed[$key])) { - // don't allow params overwrite - $parsed[$key] = rawurldecode($value); - } - } - } - } - - return $parsed; - } - - // }}} - // {{{ function fileExists($file) - - /** - * Checks if a file exists in the include path - * - * @param string filename - * - * @return bool true success and false on error - * - * @access public - */ - static function fileExists($file) - { - // safe_mode does notwork with is_readable() - if (!@ini_get('safe_mode')) { - $dirs = explode(PATH_SEPARATOR, ini_get('include_path')); - array_unshift($dirs,OC::$SERVERROOT); - array_unshift($dirs,OC::$SERVERROOT. DIRECTORY_SEPARATOR .'inc'); -// print_r($dirs);die(); - foreach ($dirs as $dir) { - if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) { - return true; - } - } - } else { - $fp = @fopen($file, 'r', true); - if (is_resource($fp)) { - @fclose($fp); - return true; - } - } - return false; - } - // }}} -} - -// }}} -// {{{ class MDB2_Error extends PEAR_Error - -/** - * MDB2_Error implements a class for reporting portable database error - * messages. - * - * @package MDB2 - * @category Database - * @author Stig Bakken - */ -class MDB2_Error extends PEAR_Error -{ - // {{{ constructor: function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null) - - /** - * MDB2_Error constructor. - * - * @param mixed MDB2 error code, or string with error message. - * @param int what 'error mode' to operate in - * @param int what error level to use for $mode raPEAR_ERROR_TRIGGER - * @param mixed additional debug info, such as the last query - */ - function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, - $level = E_USER_NOTICE, $debuginfo = null, $dummy = null) - { - if (is_null($code)) { - $code = MDB2_ERROR; - } - $this->PEAR_Error('MDB2 Error: '.MDB2::errorMessage($code), $code, - $mode, $level, $debuginfo); - } - - // }}} -} - -// }}} -// {{{ class MDB2_Driver_Common extends PEAR - -/** - * MDB2_Driver_Common: Base class that is extended by each MDB2 driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Common extends PEAR -{ - // {{{ Variables (Properties) - - /** - * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array - * @var int - * @access public - */ - var $db_index = 0; - - /** - * DSN used for the next query - * @var array - * @access protected - */ - var $dsn = array(); - - /** - * DSN that was used to create the current connection - * @var array - * @access protected - */ - var $connected_dsn = array(); - - /** - * connection resource - * @var mixed - * @access protected - */ - var $connection = 0; - - /** - * if the current opened connection is a persistent connection - * @var bool - * @access protected - */ - var $opened_persistent; - - /** - * the name of the database for the next query - * @var string - * @access protected - */ - var $database_name = ''; - - /** - * the name of the database currently selected - * @var string - * @access protected - */ - var $connected_database_name = ''; - - /** - * server version information - * @var string - * @access protected - */ - var $connected_server_info = ''; - - /** - * list of all supported features of the given driver - * @var array - * @access public - */ - var $supported = array( - 'sequences' => false, - 'indexes' => false, - 'affected_rows' => false, - 'summary_functions' => false, - 'order_by_text' => false, - 'transactions' => false, - 'savepoints' => false, - 'current_id' => false, - 'limit_queries' => false, - 'LOBs' => false, - 'replace' => false, - 'sub_selects' => false, - 'triggers' => false, - 'auto_increment' => false, - 'primary_key' => false, - 'result_introspection' => false, - 'prepared_statements' => false, - 'identifier_quoting' => false, - 'pattern_escaping' => false, - 'new_link' => false, - ); - - /** - * Array of supported options that can be passed to the MDB2 instance. - * - * The options can be set during object creation, using - * MDB2::connect(), MDB2::factory() or MDB2::singleton(). The options can - * also be set after the object is created, using MDB2::setOptions() or - * MDB2_Driver_Common::setOption(). - * The list of available option includes: - *
    - *
  • $options['ssl'] -> boolean: determines if ssl should be used for connections
  • - *
  • $options['field_case'] -> CASE_LOWER|CASE_UPPER: determines what case to force on field/table names
  • - *
  • $options['disable_query'] -> boolean: determines if queries should be executed
  • - *
  • $options['result_class'] -> string: class used for result sets
  • - *
  • $options['buffered_result_class'] -> string: class used for buffered result sets
  • - *
  • $options['result_wrap_class'] -> string: class used to wrap result sets into
  • - *
  • $options['result_buffering'] -> boolean should results be buffered or not?
  • - *
  • $options['fetch_class'] -> string: class to use when fetch mode object is used
  • - *
  • $options['persistent'] -> boolean: persistent connection?
  • - *
  • $options['debug'] -> integer: numeric debug level
  • - *
  • $options['debug_handler'] -> string: function/method that captures debug messages
  • - *
  • $options['debug_expanded_output'] -> bool: BC option to determine if more context information should be send to the debug handler
  • - *
  • $options['default_text_field_length'] -> integer: default text field length to use
  • - *
  • $options['lob_buffer_length'] -> integer: LOB buffer length
  • - *
  • $options['log_line_break'] -> string: line-break format
  • - *
  • $options['idxname_format'] -> string: pattern for index name
  • - *
  • $options['seqname_format'] -> string: pattern for sequence name
  • - *
  • $options['savepoint_format'] -> string: pattern for auto generated savepoint names
  • - *
  • $options['statement_format'] -> string: pattern for prepared statement names
  • - *
  • $options['seqcol_name'] -> string: sequence column name
  • - *
  • $options['quote_identifier'] -> boolean: if identifier quoting should be done when check_option is used
  • - *
  • $options['use_transactions'] -> boolean: if transaction use should be enabled
  • - *
  • $options['decimal_places'] -> integer: number of decimal places to handle
  • - *
  • $options['portability'] -> integer: portability constant
  • - *
  • $options['modules'] -> array: short to long module name mapping for __call()
  • - *
  • $options['emulate_prepared'] -> boolean: force prepared statements to be emulated
  • - *
  • $options['datatype_map'] -> array: map user defined datatypes to other primitive datatypes
  • - *
  • $options['datatype_map_callback'] -> array: callback function/method that should be called
  • - *
  • $options['bindname_format'] -> string: regular expression pattern for named parameters
  • - *
  • $options['multi_query'] -> boolean: determines if queries returning multiple result sets should be executed
  • - *
  • $options['max_identifiers_length'] -> integer: max identifier length
  • - *
  • $options['default_fk_action_onupdate'] -> string: default FOREIGN KEY ON UPDATE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']
  • - *
  • $options['default_fk_action_ondelete'] -> string: default FOREIGN KEY ON DELETE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']
  • - *
- * - * @var array - * @access public - * @see MDB2::connect() - * @see MDB2::factory() - * @see MDB2::singleton() - * @see MDB2_Driver_Common::setOption() - */ - var $options = array( - 'ssl' => false, - 'field_case' => CASE_LOWER, - 'disable_query' => false, - 'result_class' => 'MDB2_Result_%s', - 'buffered_result_class' => 'MDB2_BufferedResult_%s', - 'result_wrap_class' => false, - 'result_buffering' => true, - 'fetch_class' => 'stdClass', - 'persistent' => false, - 'debug' => 0, - 'debug_handler' => 'MDB2_defaultDebugOutput', - 'debug_expanded_output' => false, - 'default_text_field_length' => 4096, - 'lob_buffer_length' => 8192, - 'log_line_break' => "\n", - 'idxname_format' => '%s_idx', - 'seqname_format' => '%s_seq', - 'savepoint_format' => 'MDB2_SAVEPOINT_%s', - 'statement_format' => 'MDB2_STATEMENT_%1$s_%2$s', - 'seqcol_name' => 'sequence', - 'quote_identifier' => false, - 'use_transactions' => true, - 'decimal_places' => 2, - 'portability' => MDB2_PORTABILITY_ALL, - 'modules' => array( - 'ex' => 'Extended', - 'dt' => 'Datatype', - 'mg' => 'Manager', - 'rv' => 'Reverse', - 'na' => 'Native', - 'fc' => 'Function', - ), - 'emulate_prepared' => false, - 'datatype_map' => array(), - 'datatype_map_callback' => array(), - 'nativetype_map_callback' => array(), - 'lob_allow_url_include' => false, - 'bindname_format' => '(?:\d+)|(?:[a-zA-Z][a-zA-Z0-9_]*)', - 'max_identifiers_length' => 30, - 'default_fk_action_onupdate' => 'RESTRICT', - 'default_fk_action_ondelete' => 'RESTRICT', - ); - - /** - * string array - * @var string - * @access protected - */ - var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => false, 'escape_pattern' => false); - - /** - * identifier quoting - * @var array - * @access protected - */ - var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); - - /** - * sql comments - * @var array - * @access protected - */ - var $sql_comments = array( - array('start' => '--', 'end' => "\n", 'escape' => false), - array('start' => '/*', 'end' => '*/', 'escape' => false), - ); - - /** - * comparision wildcards - * @var array - * @access protected - */ - var $wildcards = array('%', '_'); - - /** - * column alias keyword - * @var string - * @access protected - */ - var $as_keyword = ' AS '; - - /** - * warnings - * @var array - * @access protected - */ - var $warnings = array(); - - /** - * string with the debugging information - * @var string - * @access public - */ - var $debug_output = ''; - - /** - * determine if there is an open transaction - * @var bool - * @access protected - */ - var $in_transaction = false; - - /** - * the smart transaction nesting depth - * @var int - * @access protected - */ - var $nested_transaction_counter = null; - - /** - * the first error that occured inside a nested transaction - * @var MDB2_Error|bool - * @access protected - */ - var $has_transaction_error = false; - - /** - * result offset used in the next query - * @var int - * @access protected - */ - var $offset = 0; - - /** - * result limit used in the next query - * @var int - * @access protected - */ - var $limit = 0; - - /** - * Database backend used in PHP (mysql, odbc etc.) - * @var string - * @access public - */ - var $phptype; - - /** - * Database used with regards to SQL syntax etc. - * @var string - * @access public - */ - var $dbsyntax; - - /** - * the last query sent to the driver - * @var string - * @access public - */ - var $last_query; - - /** - * the default fetchmode used - * @var int - * @access protected - */ - var $fetchmode = MDB2_FETCHMODE_ORDERED; - - /** - * array of module instances - * @var array - * @access protected - */ - var $modules = array(); - - /** - * determines of the PHP4 destructor emulation has been enabled yet - * @var array - * @access protected - */ - var $destructor_registered = true; - - // }}} - // {{{ constructor: function __construct() - - /** - * Constructor - */ - function __construct() - { - end($GLOBALS['_MDB2_databases']); - $db_index = key($GLOBALS['_MDB2_databases']) + 1; - $GLOBALS['_MDB2_databases'][$db_index] = &$this; - $this->db_index = $db_index; - } - - - // }}} - // {{{ destructor: function __destruct() - - /** - * Destructor - */ - function __destruct() - { - $this->disconnect(false); - } - - // }}} - // {{{ function free() - - /** - * Free the internal references so that the instance can be destroyed - * - * @return bool true on success, false if result is invalid - * - * @access public - */ - function free() - { - unset($GLOBALS['_MDB2_databases'][$this->db_index]); - unset($this->db_index); - return MDB2_OK; - } - - // }}} - // {{{ function __toString() - - /** - * String conversation - * - * @return string representation of the object - * - * @access public - */ - function __toString() - { - $info = get_class($this); - $info.= ': (phptype = '.$this->phptype.', dbsyntax = '.$this->dbsyntax.')'; - if ($this->connection) { - $info.= ' [connected]'; - } - return $info; - } - - // }}} - // {{{ function errorInfo($error = null) - - /** - * This method is used to collect information about an error - * - * @param mixed error code or resource - * - * @return array with MDB2 errorcode, native error code, native message - * - * @access public - */ - function errorInfo($error = null) - { - return array($error, null, null); - } - - // }}} - // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) - - /** - * This method is used to communicate an error and invoke error - * callbacks etc. Basically a wrapper for PEAR::raiseError - * without the message string. - * - * @param mixed $code integer error code, or a PEAR error object (all - * other parameters are ignored if this parameter is - * an object - * @param int $mode error mode, see PEAR_Error docs - * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the - * error level (E_USER_NOTICE etc). If error mode is - * PEAR_ERROR_CALLBACK, this is the callback function, - * either as a function name, or as an array of an - * object and method name. For other error modes this - * parameter is ignored. - * @param string $userinfo Extra debug information. Defaults to the last - * query and native error code. - * @param string $method name of the method that triggered the error - * @param string $dummy1 not used - * @param bool $dummy2 not used - * - * @return PEAR_Error instance of a PEAR Error object - * @access public - * @see PEAR_Error - */ - function raiseError($code = null, - $mode = null, - $options = null, - $userinfo = null, - $method = null, - $dummy1 = null, - $dummy2 = false - ) { - $userinfo = "[Error message: $userinfo]\n"; - // The error is yet a MDB2 error object - if (PEAR::isError($code)) { - // because we use the static PEAR::raiseError, our global - // handler should be used if it is set - if (is_null($mode) && !empty($this->_default_error_mode)) { - $mode = $this->_default_error_mode; - $options = $this->_default_error_options; - } - if (is_null($userinfo)) { - $userinfo = $code->getUserinfo(); - } - $code = $code->getCode(); - } elseif ($code == MDB2_ERROR_NOT_FOUND) { - // extension not loaded: don't call $this->errorInfo() or the script - // will die - } elseif (isset($this->connection)) { - if (!empty($this->last_query)) { - $userinfo.= "[Last executed query: {$this->last_query}]\n"; - } - $native_errno = $native_msg = null; - list($code, $native_errno, $native_msg) = $this->errorInfo($code); - if (!is_null($native_errno) && $native_errno !== '') { - $userinfo.= "[Native code: $native_errno]\n"; - } - if (!is_null($native_msg) && $native_msg !== '') { - $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n"; - } - echo $userinfo; - if (!is_null($method)) { - $userinfo = $method.': '.$userinfo; - } - } - - $err = PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); - if ($err->getMode() !== PEAR_ERROR_RETURN - && isset($this->nested_transaction_counter) && !$this->has_transaction_error) { - $this->has_transaction_error =$err; - } - return $err; - } - - // }}} - // {{{ function resetWarnings() - - /** - * reset the warning array - * - * @return void - * - * @access public - */ - function resetWarnings() - { - $this->warnings = array(); - } - - // }}} - // {{{ function getWarnings() - - /** - * Get all warnings in reverse order. - * This means that the last warning is the first element in the array - * - * @return array with warnings - * - * @access public - * @see resetWarnings() - */ - function getWarnings() - { - return array_reverse($this->warnings); - } - - // }}} - // {{{ function setFetchMode($fetchmode, $object_class = 'stdClass') - - /** - * Sets which fetch mode should be used by default on queries - * on this connection - * - * @param int MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC - * or MDB2_FETCHMODE_OBJECT - * @param string the class name of the object to be returned - * by the fetch methods when the - * MDB2_FETCHMODE_OBJECT mode is selected. - * If no class is specified by default a cast - * to object from the assoc array row will be - * done. There is also the possibility to use - * and extend the 'MDB2_row' class. - * - * @return mixed MDB2_OK or MDB2 Error Object - * - * @access public - * @see MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT - */ - function setFetchMode($fetchmode, $object_class = 'stdClass') - { - switch ($fetchmode) { - case MDB2_FETCHMODE_OBJECT: - $this->options['fetch_class'] = $object_class; - case MDB2_FETCHMODE_ORDERED: - case MDB2_FETCHMODE_ASSOC: - $this->fetchmode = $fetchmode; - break; - default: - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'invalid fetchmode mode', __FUNCTION__); - } - - return MDB2_OK; - } - - // }}} - // {{{ function setOption($option, $value) - - /** - * set the option for the db class - * - * @param string option name - * @param mixed value for the option - * - * @return mixed MDB2_OK or MDB2 Error Object - * - * @access public - */ - function setOption($option, $value) - { - if (array_key_exists($option, $this->options)) { - $this->options[$option] = $value; - return MDB2_OK; - } - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - "unknown option $option", __FUNCTION__); - } - - // }}} - // {{{ function getOption($option) - - /** - * Returns the value of an option - * - * @param string option name - * - * @return mixed the option value or error object - * - * @access public - */ - function getOption($option) - { - if (array_key_exists($option, $this->options)) { - return $this->options[$option]; - } - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - "unknown option $option", __FUNCTION__); - } - - // }}} - // {{{ function debug($message, $scope = '', $is_manip = null) - - /** - * set a debug message - * - * @param string message that should be appended to the debug variable - * @param string usually the method name that triggered the debug call: - * for example 'query', 'prepare', 'execute', 'parameters', - * 'beginTransaction', 'commit', 'rollback' - * @param array contains context information about the debug() call - * common keys are: is_manip, time, result etc. - * - * @return void - * - * @access public - */ - function debug($message, $scope = '', $context = array()) - { - if ($this->options['debug'] && $this->options['debug_handler']) { - if (!$this->options['debug_expanded_output']) { - if (!empty($context['when']) && $context['when'] !== 'pre') { - return null; - } - $context = empty($context['is_manip']) ? false : $context['is_manip']; - } - return call_user_func_array($this->options['debug_handler'], array(&$this, $scope, $message, $context)); - } - return null; - } - - // }}} - // {{{ function getDebugOutput() - - /** - * output debug info - * - * @return string content of the debug_output class variable - * - * @access public - */ - function getDebugOutput() - { - return $this->debug_output; - } - - // }}} - // {{{ function escape($text) - - /** - * Quotes a string so it can be safely used in a query. It will quote - * the text so it can safely be used within a query. - * - * @param string the input string to quote - * @param bool escape wildcards - * - * @return string quoted string - * - * @access public - */ - function escape($text, $escape_wildcards = false) - { - if ($escape_wildcards) { - $text = $this->escapePattern($text); - } - - $text = str_replace($this->string_quoting['end'], $this->string_quoting['escape'] . $this->string_quoting['end'], $text); - return $text; - } - - // }}} - // {{{ function escapePattern($text) - - /** - * Quotes pattern (% and _) characters in a string) - * - * @param string the input string to quote - * - * @return string quoted string - * - * @access public - */ - function escapePattern($text) - { - if ($this->string_quoting['escape_pattern']) { - $text = str_replace($this->string_quoting['escape_pattern'], $this->string_quoting['escape_pattern'] . $this->string_quoting['escape_pattern'], $text); - foreach ($this->wildcards as $wildcard) { - $text = str_replace($wildcard, $this->string_quoting['escape_pattern'] . $wildcard, $text); - } - } - return $text; - } - - // }}} - // {{{ function quoteIdentifier($str, $check_option = false) - - /** - * Quote a string so it can be safely used as a table or column name - * - * Delimiting style depends on which database driver is being used. - * - * NOTE: just because you CAN use delimited identifiers doesn't mean - * you SHOULD use them. In general, they end up causing way more - * problems than they solve. - * - * NOTE: if you have table names containing periods, don't use this method - * (@see bug #11906) - * - * Portability is broken by using the following characters inside - * delimited identifiers: - * + backtick (`) -- due to MySQL - * + double quote (") -- due to Oracle - * + brackets ([ or ]) -- due to Access - * - * Delimited identifiers are known to generally work correctly under - * the following drivers: - * + mssql - * + mysql - * + mysqli - * + oci8 - * + pgsql - * + sqlite - * - * InterBase doesn't seem to be able to use delimited identifiers - * via PHP 4. They work fine under PHP 5. - * - * @param string identifier name to be quoted - * @param bool check the 'quote_identifier' option - * - * @return string quoted identifier string - * - * @access public - */ - function quoteIdentifier($str, $check_option = false) - { - if ($check_option && !$this->options['quote_identifier']) { - return $str; - } - $str = str_replace($this->identifier_quoting['end'], $this->identifier_quoting['escape'] . $this->identifier_quoting['end'], $str); - $parts = explode('.', $str); - foreach (array_keys($parts) as $k) { - $parts[$k] = $this->identifier_quoting['start'] . $parts[$k] . $this->identifier_quoting['end']; - } - return implode('.', $parts); - } - - // }}} - // {{{ function getAsKeyword() - - /** - * Gets the string to alias column - * - * @return string to use when aliasing a column - */ - function getAsKeyword() - { - return $this->as_keyword; - } - - // }}} - // {{{ function getConnection() - - /** - * Returns a native connection - * - * @return mixed a valid MDB2 connection object, - * or a MDB2 error object on error - * - * @access public - */ - function getConnection() - { - $result = $this->connect(); - if (PEAR::isError($result)) { - return $result; - } - return $this->connection; - } - - // }}} - // {{{ function _fixResultArrayValues(&$row, $mode) - - /** - * Do all necessary conversions on result arrays to fix DBMS quirks - * - * @param array the array to be fixed (passed by reference) - * @param array bit-wise addition of the required portability modes - * - * @return void - * - * @access protected - */ - function _fixResultArrayValues(&$row, $mode) - { - switch ($mode) { - case MDB2_PORTABILITY_EMPTY_TO_NULL: - foreach ($row as $key => $value) { - if ($value === '') { - $row[$key] = null; - } - } - break; - case MDB2_PORTABILITY_RTRIM: - foreach ($row as $key => $value) { - if (is_string($value)) { - $row[$key] = rtrim($value); - } - } - break; - case MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES: - $tmp_row = array(); - foreach ($row as $key => $value) { - $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; - } - $row = $tmp_row; - break; - case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL): - foreach ($row as $key => $value) { - if ($value === '') { - $row[$key] = null; - } elseif (is_string($value)) { - $row[$key] = rtrim($value); - } - } - break; - case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): - $tmp_row = array(); - foreach ($row as $key => $value) { - if (is_string($value)) { - $value = rtrim($value); - } - $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; - } - $row = $tmp_row; - break; - case (MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): - $tmp_row = array(); - foreach ($row as $key => $value) { - if ($value === '') { - $value = null; - } - $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; - } - $row = $tmp_row; - break; - case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): - $tmp_row = array(); - foreach ($row as $key => $value) { - if ($value === '') { - $value = null; - } elseif (is_string($value)) { - $value = rtrim($value); - } - $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; - } - $row = $tmp_row; - break; - } - } - - // }}} - // {{{ function &loadModule($module, $property = null, $phptype_specific = null) - - /** - * loads a module - * - * @param string name of the module that should be loaded - * (only used for error messages) - * @param string name of the property into which the class will be loaded - * @param bool if the class to load for the module is specific to the - * phptype - * - * @return object on success a reference to the given module is returned - * and on failure a PEAR error - * - * @access public - */ - function &loadModule($module, $property = null, $phptype_specific = null) - { - if (!$property) { - $property = strtolower($module); - } - - if (!isset($this->{$property})) { - $version = $phptype_specific; - if ($phptype_specific !== false) { - $version = true; - $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype; - $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; - } - if ($phptype_specific === false - || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name)) - ) { - $version = false; - $class_name = 'MDB2_'.$module; - $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; - } - - $err = MDB2::loadClass($class_name, $this->getOption('debug')); - if (PEAR::isError($err)) { - return $err; - } - // load module in a specific version - if ($version) { - if (method_exists($class_name, 'getClassName')) { - $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index); - if ($class_name != $class_name_new) { - $class_name = $class_name_new; - $err = MDB2::loadClass($class_name, $this->getOption('debug')); - if (PEAR::isError($err)) { - return $err; - } - } - } - } - - if (!MDB2::classExists($class_name)) { - $err =$this->raiseError(MDB2_ERROR_LOADMODULE, null, null, - "unable to load module '$module' into property '$property'", __FUNCTION__); - return $err; - } - $this->{$property} = new $class_name($this->db_index); - $this->modules[$module] =$this->{$property}; - if ($version) { - // this will be used in the connect method to determine if the module - // needs to be loaded with a different version if the server - // version changed in between connects - $this->loaded_version_modules[] = $property; - } - } - - return $this->{$property}; - } - - // }}} - // {{{ function __call($method, $params) - - /** - * Calls a module method using the __call magic method - * - * @param string Method name. - * @param array Arguments. - * - * @return mixed Returned value. - */ - function __call($method, $params) - { - $module = null; - if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match) - && isset($this->options['modules'][$match[1]]) - ) { - $module = $this->options['modules'][$match[1]]; - $method = strtolower($match[2]).$match[3]; - if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) { - $result =& $this->loadModule($module); - if (PEAR::isError($result)) { - return $result; - } - } - } else { - foreach ($this->modules as $key => $foo) { - if (is_object($this->modules[$key]) - && method_exists($this->modules[$key], $method) - ) { - $module = $key; - break; - } - } - } - if (!is_null($module)) { - return call_user_func_array(array(&$this->modules[$module], $method), $params); - } - trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $method), E_USER_ERROR); - } - - // }}} - // {{{ function beginTransaction($savepoint = null) - - /** - * Start a transaction or set a savepoint. - * - * @param string name of a savepoint to set - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function beginTransaction($savepoint = null) - { - $this->debug('Starting transaction', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'transactions are not supported', __FUNCTION__); - } - - // }}} - // {{{ function commit($savepoint = null) - - /** - * Commit the database changes done during a transaction that is in - * progress or release a savepoint. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after committing the pending changes. - * - * @param string name of a savepoint to release - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function commit($savepoint = null) - { - $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'commiting transactions is not supported', __FUNCTION__); - } - - // }}} - // {{{ function rollback($savepoint = null) - - /** - * Cancel any database changes done during a transaction or since a specific - * savepoint that is in progress. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after canceling the pending changes. - * - * @param string name of a savepoint to rollback to - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function rollback($savepoint = null) - { - $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'rolling back transactions is not supported', __FUNCTION__); - } - - // }}} - // {{{ function inTransaction($ignore_nested = false) - - /** - * If a transaction is currently open. - * - * @param bool if the nested transaction count should be ignored - * @return int|bool - an integer with the nesting depth is returned if a - * nested transaction is open - * - true is returned for a normal open transaction - * - false is returned if no transaction is open - * - * @access public - */ - function inTransaction($ignore_nested = false) - { - if (!$ignore_nested && isset($this->nested_transaction_counter)) { - return $this->nested_transaction_counter; - } - return $this->in_transaction; - } - - // }}} - // {{{ function setTransactionIsolation($isolation) - - /** - * Set the transacton isolation level. - * - * @param string standard isolation level - * READ UNCOMMITTED (allows dirty reads) - * READ COMMITTED (prevents dirty reads) - * REPEATABLE READ (prevents nonrepeatable reads) - * SERIALIZABLE (prevents phantom reads) - * @param array some transaction options: - * 'wait' => 'WAIT' | 'NO WAIT' - * 'rw' => 'READ WRITE' | 'READ ONLY' - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - static function setTransactionIsolation($isolation, $options = array()) - { - $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'isolation level setting is not supported', __FUNCTION__); - } - - // }}} - // {{{ function beginNestedTransaction($savepoint = false) - - /** - * Start a nested transaction. - * - * @return mixed MDB2_OK on success/savepoint name, a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - function beginNestedTransaction() - { - if ($this->in_transaction) { - ++$this->nested_transaction_counter; - $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); - if ($this->supports('savepoints') && $savepoint) { - return $this->beginTransaction($savepoint); - } - return MDB2_OK; - } - $this->has_transaction_error = false; - $result = $this->beginTransaction(); - $this->nested_transaction_counter = 1; - return $result; - } - - // }}} - // {{{ function completeNestedTransaction($force_rollback = false, $release = false) - - /** - * Finish a nested transaction by rolling back if an error occured or - * committing otherwise. - * - * @param bool if the transaction should be rolled back regardless - * even if no error was set within the nested transaction - * @return mixed MDB_OK on commit/counter decrementing, false on rollback - * and a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - function completeNestedTransaction($force_rollback = false) - { - if ($this->nested_transaction_counter > 1) { - $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); - if ($this->supports('savepoints') && $savepoint) { - if ($force_rollback || $this->has_transaction_error) { - $result = $this->rollback($savepoint); - if (!PEAR::isError($result)) { - $result = false; - $this->has_transaction_error = false; - } - } else { - $result = $this->commit($savepoint); - } - } else { - $result = MDB2_OK; - } - --$this->nested_transaction_counter; - return $result; - } - - $this->nested_transaction_counter = null; - $result = MDB2_OK; - - // transaction has not yet been rolled back - if ($this->in_transaction) { - if ($force_rollback || $this->has_transaction_error) { - $result = $this->rollback(); - if (!PEAR::isError($result)) { - $result = false; - } - } else { - $result = $this->commit(); - } - } - $this->has_transaction_error = false; - return $result; - } - - // }}} - // {{{ function failNestedTransaction($error = null, $immediately = false) - - /** - * Force setting nested transaction to failed. - * - * @param mixed value to return in getNestededTransactionError() - * @param bool if the transaction should be rolled back immediately - * @return bool MDB2_OK - * - * @access public - * @since 2.1.1 - */ - function failNestedTransaction($error = null, $immediately = false) - { - if (is_null($error)) { - $error = $this->has_transaction_error ? $this->has_transaction_error : true; - } elseif (!$error) { - $error = true; - } - $this->has_transaction_error = $error; - if (!$immediately) { - return MDB2_OK; - } - return $this->rollback(); - } - - // }}} - // {{{ function getNestedTransactionError() - - /** - * The first error that occured since the transaction start. - * - * @return MDB2_Error|bool MDB2 error object if an error occured or false. - * - * @access public - * @since 2.1.1 - */ - function getNestedTransactionError() - { - return $this->has_transaction_error; - } - - // }}} - // {{{ connect() - - /** - * Connect to the database - * - * @return true on success, MDB2 Error Object on failure - */ - function connect() - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ databaseExists() - - /** - * check if given database name is exists? - * - * @param string $name name of the database that should be checked - * - * @return mixed true/false on success, a MDB2 error on failure - * @access public - */ - function databaseExists($name) - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ setCharset($charset, $connection = null) - - /** - * Set the charset on the current connection - * - * @param string charset - * @param resource connection handle - * - * @return true on success, MDB2 Error Object on failure - */ - function setCharset($charset, $connection = null) - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function disconnect($force = true) - - /** - * Log out and disconnect from the database. - * - * @param boolean $force whether the disconnect should be forced even if the - * connection is opened persistently - * - * @return mixed true on success, false if not connected and error object on error - * - * @access public - */ - function disconnect($force = true) - { - $this->connection = 0; - $this->connected_dsn = array(); - $this->connected_database_name = ''; - $this->opened_persistent = null; - $this->connected_server_info = ''; - $this->in_transaction = null; - $this->nested_transaction_counter = null; - return MDB2_OK; - } - - // }}} - // {{{ function setDatabase($name) - - /** - * Select a different database - * - * @param string name of the database that should be selected - * - * @return string name of the database previously connected to - * - * @access public - */ - function setDatabase($name) - { - $previous_database_name = (isset($this->database_name)) ? $this->database_name : ''; - $this->database_name = $name; - if (!empty($this->connected_database_name) && ($this->connected_database_name != $this->database_name)) { - $this->disconnect(false); - } - return $previous_database_name; - } - - // }}} - // {{{ function getDatabase() - - /** - * Get the current database - * - * @return string name of the database - * - * @access public - */ - function getDatabase() - { - return $this->database_name; - } - - // }}} - // {{{ function setDSN($dsn) - - /** - * set the DSN - * - * @param mixed DSN string or array - * - * @return MDB2_OK - * - * @access public - */ - function setDSN($dsn) - { - $dsn_default = $GLOBALS['_MDB2_dsninfo_default']; - $dsn = MDB2::parseDSN($dsn); - if (array_key_exists('database', $dsn)) { - $this->database_name = $dsn['database']; - unset($dsn['database']); - } - $this->dsn = array_merge($dsn_default, $dsn); - return $this->disconnect(false); - } - - // }}} - // {{{ function getDSN($type = 'string', $hidepw = false) - - /** - * return the DSN as a string - * - * @param string format to return ("array", "string") - * @param string string to hide the password with - * - * @return mixed DSN in the chosen type - * - * @access public - */ - function getDSN($type = 'string', $hidepw = false) - { - $dsn = array_merge($GLOBALS['_MDB2_dsninfo_default'], $this->dsn); - $dsn['phptype'] = $this->phptype; - $dsn['database'] = $this->database_name; - if ($hidepw) { - $dsn['password'] = $hidepw; - } - switch ($type) { - // expand to include all possible options - case 'string': - $dsn = $dsn['phptype']. - ($dsn['dbsyntax'] ? ('('.$dsn['dbsyntax'].')') : ''). - '://'.$dsn['username'].':'. - $dsn['password'].'@'.$dsn['hostspec']. - ($dsn['port'] ? (':'.$dsn['port']) : ''). - '/'.$dsn['database']; - break; - case 'array': - default: - break; - } - return $dsn; - } - - // }}} - // {{{ _isNewLinkSet() - - /** - * Check if the 'new_link' option is set - * - * @return boolean - * - * @access protected - */ - function _isNewLinkSet() - { - return (isset($this->dsn['new_link']) - && ($this->dsn['new_link'] === true - || (is_string($this->dsn['new_link']) && preg_match('/^true$/i', $this->dsn['new_link'])) - || (is_numeric($this->dsn['new_link']) && 0 != (int)$this->dsn['new_link']) - ) - ); - } - - // }}} - // {{{ function &standaloneQuery($query, $types = null, $is_manip = false) - - /** - * execute a query as database administrator - * - * @param string the SQL query - * @param mixed array that contains the types of the columns in - * the result set - * @param bool if the query is a manipulation query - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function &standaloneQuery($query, $types = null, $is_manip = false) - { - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $result =$this->_doQuery($query, $is_manip, $connection, false); - if (PEAR::isError($result)) { - return $result; - } - - if ($is_manip) { - $affected_rows = $this->_affectedRows($connection, $result); - return $affected_rows; - } - $result =$this->_wrapResult($result, $types, true, false, $limit, $offset); - return $result; - } - - // }}} - // {{{ function _modifyQuery($query, $is_manip, $limit, $offset) - - /** - * Changes a query string for various DBMS specific reasons - * - * @param string query to modify - * @param bool if it is a DML query - * @param int limit the number of rows - * @param int start reading from given offset - * - * @return string modified query - * - * @access protected - */ - function _modifyQuery($query, $is_manip, $limit, $offset) - { - return $query; - } - - // }}} - // {{{ function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) - - /** - * Execute a query - * @param string query - * @param bool if the query is a manipulation query - * @param resource connection handle - * @param string database name - * - * @return result or error object - * - * @access protected - */ - function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) - { - $this->last_query = $query; - $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - $err =$this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - return $err; - } - - // }}} - // {{{ function _affectedRows($connection, $result = null) - - /** - * Returns the number of rows affected - * - * @param resource result handle - * @param resource connection handle - * - * @return mixed MDB2 Error Object or the number of rows affected - * - * @access private - */ - function _affectedRows($connection, $result = null) - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function &exec($query) - - /** - * Execute a manipulation query to the database and return the number of affected rows - * - * @param string the SQL query - * - * @return mixed number of affected rows on success, a MDB2 error on failure - * - * @access public - */ - function &exec($query) - { - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, true, $limit, $offset); - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $result =$this->_doQuery($query, true, $connection, $this->database_name); - if (PEAR::isError($result)) { - return $result; - } - - $affectedRows = $this->_affectedRows($connection, $result); - return $affectedRows; - } - - // }}} - // {{{ function &query($query, $types = null, $result_class = true, $result_wrap_class = false) - - /** - * Send a query to the database and return any results - * - * @param string the SQL query - * @param mixed array that contains the types of the columns in - * the result set - * @param mixed string which specifies which result class to use - * @param mixed string which specifies which class to wrap results in - * - * @return mixed an MDB2_Result handle on success, a MDB2 error on failure - * - * @access public - */ - function &query($query, $types = null, $result_class = true, $result_wrap_class = false) - { - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, false, $limit, $offset); - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $result =$this->_doQuery($query, false, $connection, $this->database_name); - if (PEAR::isError($result)) { - return $result; - } - - $result =$this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset); - return $result; - } - - // }}} - // {{{ function &_wrapResult($result, $types = array(), $result_class = true, $result_wrap_class = false, $limit = null, $offset = null) - - /** - * wrap a result set into the correct class - * - * @param resource result handle - * @param mixed array that contains the types of the columns in - * the result set - * @param mixed string which specifies which result class to use - * @param mixed string which specifies which class to wrap results in - * @param string number of rows to select - * @param string first row to select - * - * @return mixed an MDB2_Result, a MDB2 error on failure - * - * @access protected - */ - function &_wrapResult($result, $types = array(), $result_class = true, - $result_wrap_class = false, $limit = null, $offset = null) - { - if ($types === true) { - if ($this->supports('result_introspection')) { - $this->loadModule('Reverse', null, true); - $tableInfo = $this->reverse->tableInfo($result); - if (PEAR::isError($tableInfo)) { - return $tableInfo; - } - $types = array(); - foreach ($tableInfo as $field) { - $types[] = $field['mdb2type']; - } - } else { - $types = null; - } - } - - if ($result_class === true) { - $result_class = $this->options['result_buffering'] - ? $this->options['buffered_result_class'] : $this->options['result_class']; - } - - if ($result_class) { - $class_name = sprintf($result_class, $this->phptype); - if (!MDB2::classExists($class_name)) { - $err =$this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'result class does not exist '.$class_name, __FUNCTION__); - return $err; - } - $result =new $class_name($this, $result, $limit, $offset); - if (!MDB2::isResultCommon($result)) { - $err =$this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'result class is not extended from MDB2_Result_Common', __FUNCTION__); - return $err; - } - if (!empty($types)) { - $err = $result->setResultTypes($types); - if (PEAR::isError($err)) { - $result->free(); - return $err; - } - } - } - if ($result_wrap_class === true) { - $result_wrap_class = $this->options['result_wrap_class']; - } - if ($result_wrap_class) { - if (!MDB2::classExists($result_wrap_class)) { - $err =$this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'result wrap class does not exist '.$result_wrap_class, __FUNCTION__); - return $err; - } - $result = new $result_wrap_class($result, $this->fetchmode); - } - return $result; - } - - // }}} - // {{{ function getServerVersion($native = false) - - /** - * return version information about the server - * - * @param bool determines if the raw version string should be returned - * - * @return mixed array with version information or row string - * - * @access public - */ - function getServerVersion($native = false) - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function setLimit($limit, $offset = null) - - /** - * set the range of the next query - * - * @param string number of rows to select - * @param string first row to select - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function setLimit($limit, $offset = null) - { - if (!$this->supports('limit_queries')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'limit is not supported by this driver', __FUNCTION__); - } - $limit = (int)$limit; - if ($limit < 0) { - return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'it was not specified a valid selected range row limit', __FUNCTION__); - } - $this->limit = $limit; - if (!is_null($offset)) { - $offset = (int)$offset; - if ($offset < 0) { - return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'it was not specified a valid first selected range row', __FUNCTION__); - } - $this->offset = $offset; - } - return MDB2_OK; - } - - // }}} - // {{{ function subSelect($query, $type = false) - - /** - * simple subselect emulation: leaves the query untouched for all RDBMS - * that support subselects - * - * @param string the SQL query for the subselect that may only - * return a column - * @param string determines type of the field - * - * @return string the query - * - * @access public - */ - function subSelect($query, $type = false) - { - if ($this->supports('sub_selects') === true) { - return $query; - } - - if (!$this->supports('sub_selects')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - $col = $this->queryCol($query, $type); - if (PEAR::isError($col)) { - return $col; - } - if (!is_array($col) || count($col) == 0) { - return 'NULL'; - } - if ($type) { - $this->loadModule('Datatype', null, true); - return $this->datatype->implodeArray($col, $type); - } - return implode(', ', $col); - } - - // }}} - // {{{ function replace($table, $fields) - - /** - * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT - * query, except that if there is already a row in the table with the same - * key field values, the old row is deleted before the new row is inserted. - * - * The REPLACE type of query does not make part of the SQL standards. Since - * practically only MySQL and SQLite implement it natively, this type of - * query isemulated through this method for other DBMS using standard types - * of queries inside a transaction to assure the atomicity of the operation. - * - * @param string name of the table on which the REPLACE query will - * be executed. - * @param array associative array that describes the fields and the - * values that will be inserted or updated in the specified table. The - * indexes of the array are the names of all the fields of the table. - * The values of the array are also associative arrays that describe - * the values and other properties of the table fields. - * - * Here follows a list of field properties that need to be specified: - * - * value - * Value to be assigned to the specified field. This value may be - * of specified in database independent type format as this - * function can perform the necessary datatype conversions. - * - * Default: this property is required unless the Null property is - * set to 1. - * - * type - * Name of the type of the field. Currently, all types MDB2 - * are supported except for clob and blob. - * - * Default: no type conversion - * - * null - * bool property that indicates that the value for this field - * should be set to null. - * - * The default value for fields missing in INSERT queries may be - * specified the definition of a table. Often, the default value - * is already null, but since the REPLACE may be emulated using - * an UPDATE query, make sure that all fields of the table are - * listed in this function argument array. - * - * Default: 0 - * - * key - * bool property that indicates that this field should be - * handled as a primary key or at least as part of the compound - * unique index of the table that will determine the row that will - * updated if it exists or inserted a new row otherwise. - * - * This function will fail if no key field is specified or if the - * value of a key field is set to null because fields that are - * part of unique index they may not be null. - * - * Default: 0 - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function replace($table, $fields) - { - if (!$this->supports('replace')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'replace query is not supported', __FUNCTION__); - } - $count = count($fields); - $condition = $values = array(); - for ($colnum = 0, reset($fields); $colnum < $count; next($fields), $colnum++) { - $name = key($fields); - if (isset($fields[$name]['null']) && $fields[$name]['null']) { - $value = 'NULL'; - } else { - $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; - $value = $this->quote($fields[$name]['value'], $type); - } - $values[$name] = $value; - if (isset($fields[$name]['key']) && $fields[$name]['key']) { - if ($value === 'NULL') { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'key value '.$name.' may not be NULL', __FUNCTION__); - } - $condition[] = $this->quoteIdentifier($name, true) . '=' . $value; - } - } - if (empty($condition)) { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'not specified which fields are keys', __FUNCTION__); - } - - $result = null; - $in_transaction = $this->in_transaction; - if (!$in_transaction && PEAR::isError($result = $this->beginTransaction())) { - return $result; - } - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $condition = ' WHERE '.implode(' AND ', $condition); - $query = 'DELETE FROM ' . $this->quoteIdentifier($table, true) . $condition; - $result =$this->_doQuery($query, true, $connection); - if (!PEAR::isError($result)) { - $affected_rows = $this->_affectedRows($connection, $result); - $insert = ''; - foreach ($values as $key => $value) { - $insert .= ($insert?', ':'') . $this->quoteIdentifier($key, true); - } - $values = implode(', ', $values); - $query = 'INSERT INTO '. $this->quoteIdentifier($table, true) . "($insert) VALUES ($values)"; - $result =$this->_doQuery($query, true, $connection); - if (!PEAR::isError($result)) { - $affected_rows += $this->_affectedRows($connection, $result);; - } - } - - if (!$in_transaction) { - if (PEAR::isError($result)) { - $this->rollback(); - } else { - $result = $this->commit(); - } - } - - if (PEAR::isError($result)) { - return $result; - } - - return $affected_rows; - } - - // }}} - // {{{ function &prepare($query, $types = null, $result_types = null, $lobs = array()) - - /** - * Prepares a query for multiple execution with execute(). - * With some database backends, this is emulated. - * prepare() requires a generic query as string like - * 'INSERT INTO numbers VALUES(?,?)' or - * 'INSERT INTO numbers VALUES(:foo,:bar)'. - * The ? and :name and are placeholders which can be set using - * bindParam() and the query can be sent off using the execute() method. - * The allowed format for :name can be set with the 'bindname_format' option. - * - * @param string the query to prepare - * @param mixed array that contains the types of the placeholders - * @param mixed array that contains the types of the columns in - * the result set or MDB2_PREPARE_RESULT, if set to - * MDB2_PREPARE_MANIP the query is handled as a manipulation query - * @param mixed key (field) value (parameter) pair for all lob placeholders - * - * @return mixed resource handle for the prepared query on success, - * a MDB2 error on failure - * - * @access public - * @see bindParam, execute - */ - function &prepare($query, $types = null, $result_types = null, $lobs = array()) - { - $is_manip = ($result_types === MDB2_PREPARE_MANIP); - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - $placeholder_type_guess = $placeholder_type = null; - $question = '?'; - $colon = ':'; - $positions = array(); - $position = 0; - while ($position < strlen($query)) { - $q_position = strpos($query, $question, $position); - $c_position = strpos($query, $colon, $position); - if ($q_position && $c_position) { - $p_position = min($q_position, $c_position); - } elseif ($q_position) { - $p_position = $q_position; - } elseif ($c_position) { - $p_position = $c_position; - } else { - break; - } - if (is_null($placeholder_type)) { - $placeholder_type_guess = $query[$p_position]; - } - - $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); - if (PEAR::isError($new_pos)) { - return $new_pos; - } - if ($new_pos != $position) { - $position = $new_pos; - continue; //evaluate again starting from the new position - } - - if ($query[$position] == $placeholder_type_guess) { - if (is_null($placeholder_type)) { - $placeholder_type = $query[$p_position]; - $question = $colon = $placeholder_type; - if (!empty($types) && is_array($types)) { - if ($placeholder_type == ':') { - if (is_int(key($types))) { - $types_tmp = $types; - $types = array(); - $count = -1; - } - } else { - $types = array_values($types); - } - } - } - if ($placeholder_type == ':') { - $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; - $parameter = preg_replace($regexp, '\\1', $query); - if ($parameter === '') { - $err =$this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'named parameter name must match "bindname_format" option', __FUNCTION__); - return $err; - } - $positions[$p_position] = $parameter; - $query = substr_replace($query, '?', $position, strlen($parameter)+1); - // use parameter name in type array - if (isset($count) && isset($types_tmp[++$count])) { - $types[$parameter] = $types_tmp[$count]; - } - } else { - $positions[$p_position] = count($positions); - } - $position = $p_position + 1; - } else { - $position = $p_position; - } - } - $class_name = 'MDB2_Statement_'.$this->phptype; - $statement = null; - $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); - $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); - return $obj; - } - - // }}} - // {{{ function _skipDelimitedStrings($query, $position, $p_position) - - /** - * Utility method, used by prepare() to avoid replacing placeholders within delimited strings. - * Check if the placeholder is contained within a delimited string. - * If so, skip it and advance the position, otherwise return the current position, - * which is valid - * - * @param string $query - * @param integer $position current string cursor position - * @param integer $p_position placeholder position - * - * @return mixed integer $new_position on success - * MDB2_Error on failure - * - * @access protected - */ - function _skipDelimitedStrings($query, $position, $p_position) - { - $ignores = $this->string_quoting; - $ignores[] = $this->identifier_quoting; - $ignores = array_merge($ignores, $this->sql_comments); - - foreach ($ignores as $ignore) { - if (!empty($ignore['start'])) { - if (is_int($start_quote = strpos($query, $ignore['start'], $position)) && $start_quote < $p_position) { - $end_quote = $start_quote; - do { - if (!is_int($end_quote = strpos($query, $ignore['end'], $end_quote + 1))) { - if ($ignore['end'] === "\n") { - $end_quote = strlen($query) - 1; - } else { - $err =$this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'query with an unterminated text string specified', __FUNCTION__); - return $err; - } - } - } while ($ignore['escape'] - && $end_quote-1 != $start_quote - && $query[($end_quote - 1)] == $ignore['escape'] - && ( $ignore['escape_pattern'] !== $ignore['escape'] - || $query[($end_quote - 2)] != $ignore['escape']) - ); - - $position = $end_quote + 1; - return $position; - } - } - } - return $position; - } - - // }}} - // {{{ function quote($value, $type = null, $quote = true) - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string text string value that is intended to be converted. - * @param string type to which the value should be converted to - * @param bool quote - * @param bool escape wildcards - * - * @return string text string that represents the given argument value in - * a DBMS specific format. - * - * @access public - */ - function quote($value, $type = null, $quote = true, $escape_wildcards = false) - { - $result = $this->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - - return $this->datatype->quote($value, $type, $quote, $escape_wildcards); - } - - // }}} - // {{{ function getDeclaration($type, $name, $field) - - /** - * Obtain DBMS specific SQL code portion needed to declare - * of the given type - * - * @param string type to which the value should be converted to - * @param string name the field to be declared. - * @param string definition of the field - * - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * - * @access public - */ - function getDeclaration($type, $name, $field) - { - $result = $this->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - return $this->datatype->getDeclaration($type, $name, $field); - } - - // }}} - // {{{ function compareDefinition($current, $previous) - - /** - * Obtain an array of changes that may need to applied - * - * @param array new definition - * @param array old definition - * - * @return array containing all changes that will need to be applied - * - * @access public - */ - function compareDefinition($current, $previous) - { - $result = $this->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - return $this->datatype->compareDefinition($current, $previous); - } - - // }}} - // {{{ function supports($feature) - - /** - * Tell whether a DB implementation or its backend extension - * supports a given feature. - * - * @param string name of the feature (see the MDB2 class doc) - * - * @return bool|string if this DB implementation supports a given feature - * false means no, true means native, - * 'emulated' means emulated - * - * @access public - */ - function supports($feature) - { - if (array_key_exists($feature, $this->supported)) { - return $this->supported[$feature]; - } - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - "unknown support feature $feature", __FUNCTION__); - } - - // }}} - // {{{ function getSequenceName($sqn) - - /** - * adds sequence name formatting to a sequence name - * - * @param string name of the sequence - * - * @return string formatted sequence name - * - * @access public - */ - function getSequenceName($sqn) - { - return sprintf($this->options['seqname_format'], - preg_replace('/[^a-z0-9_\-\$.]/i', '_', $sqn)); - } - - // }}} - // {{{ function getIndexName($idx) - - /** - * adds index name formatting to a index name - * - * @param string name of the index - * - * @return string formatted index name - * - * @access public - */ - function getIndexName($idx) - { - return sprintf($this->options['idxname_format'], - preg_replace('/[^a-z0-9_\-\$.]/i', '_', $idx)); - } - - // }}} - // {{{ function nextID($seq_name, $ondemand = true) - - /** - * Returns the next free id of a sequence - * - * @param string name of the sequence - * @param bool when true missing sequences are automatic created - * - * @return mixed MDB2 Error Object or id - * - * @access public - */ - function nextID($seq_name, $ondemand = true) - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function lastInsertID($table = null, $field = null) - - /** - * Returns the autoincrement ID if supported or $id or fetches the current - * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) - * - * @param string name of the table into which a new row was inserted - * @param string name of the field into which a new row was inserted - * - * @return mixed MDB2 Error Object or id - * - * @access public - */ - function lastInsertID($table = null, $field = null) - { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function currID($seq_name) - - /** - * Returns the current id of a sequence - * - * @param string name of the sequence - * - * @return mixed MDB2 Error Object or id - * - * @access public - */ - function currID($seq_name) - { - $this->warnings[] = 'database does not support getting current - sequence value, the sequence value was incremented'; - return $this->nextID($seq_name); - } - - // }}} - // {{{ function queryOne($query, $type = null, $colnum = 0) - - /** - * Execute the specified query, fetch the value from the first column of - * the first row of the result set and then frees - * the result set. - * - * @param string $query the SELECT query statement to be executed. - * @param string $type optional argument that specifies the expected - * datatype of the result set field, so that an eventual - * conversion may be performed. The default datatype is - * text, meaning that no conversion is performed - * @param mixed $colnum the column number (or name) to fetch - * - * @return mixed MDB2_OK or field value on success, a MDB2 error on failure - * - * @access public - */ - function queryOne($query, $type = null, $colnum = 0) - { - $result = $this->query($query, $type); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $one = $result->fetchOne($colnum); - $result->free(); - return $one; - } - - // }}} - // {{{ function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) - - /** - * Execute the specified query, fetch the values from the first - * row of the result set into an array and then frees - * the result set. - * - * @param string the SELECT query statement to be executed. - * @param array optional array argument that specifies a list of - * expected datatypes of the result set columns, so that the eventual - * conversions may be performed. The default list of datatypes is - * empty, meaning that no conversion is performed. - * @param int how the array data should be indexed - * - * @return mixed MDB2_OK or data array on success, a MDB2 error on failure - * - * @access public - */ - function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) - { - $result = $this->query($query, $types); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $row = $result->fetchRow($fetchmode); - $result->free(); - return $row; - } - - // }}} - // {{{ function queryCol($query, $type = null, $colnum = 0) - - /** - * Execute the specified query, fetch the value from the first column of - * each row of the result set into an array and then frees the result set. - * - * @param string $query the SELECT query statement to be executed. - * @param string $type optional argument that specifies the expected - * datatype of the result set field, so that an eventual - * conversion may be performed. The default datatype is text, - * meaning that no conversion is performed - * @param mixed $colnum the column number (or name) to fetch - * - * @return mixed MDB2_OK or data array on success, a MDB2 error on failure - * @access public - */ - function queryCol($query, $type = null, $colnum = 0) - { - $result = $this->query($query, $type); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $col = $result->fetchCol($colnum); - $result->free(); - return $col; - } - - // }}} - // {{{ function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) - - /** - * Execute the specified query, fetch all the rows of the result set into - * a two dimensional array and then frees the result set. - * - * @param string the SELECT query statement to be executed. - * @param array optional array argument that specifies a list of - * expected datatypes of the result set columns, so that the eventual - * conversions may be performed. The default list of datatypes is - * empty, meaning that no conversion is performed. - * @param int how the array data should be indexed - * @param bool if set to true, the $all will have the first - * column as its first dimension - * @param bool used only when the query returns exactly - * two columns. If true, the values of the returned array will be - * one-element arrays instead of scalars. - * @param bool if true, the values of the returned array is - * wrapped in another array. If the same key value (in the first - * column) repeats itself, the values will be appended to this array - * instead of overwriting the existing values. - * - * @return mixed MDB2_OK or data array on success, a MDB2 error on failure - * - * @access public - */ - function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, - $rekey = false, $force_array = false, $group = false) - { - $result = $this->query($query, $types); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); - $result->free(); - return $all; - } - - // }}} -} - -// }}} -// {{{ class MDB2_Result - -/** - * The dummy class that all user space result classes should extend from - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Result -{ -} - -// }}} -// {{{ class MDB2_Result_Common extends MDB2_Result - -/** - * The common result class for MDB2 result objects - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Result_Common extends MDB2_Result -{ - // {{{ Variables (Properties) - - var $db; - var $result; - var $rownum = -1; - var $types = array(); - var $values = array(); - var $offset; - var $offset_count = 0; - var $limit; - var $column_names; - - // }}} - // {{{ constructor: function __construct(&$db, &$result, $limit = 0, $offset = 0) - - /** - * Constructor - */ - function __construct(&$db, &$result, $limit = 0, $offset = 0) - { - $this->db =$db; - $this->result =$result; - $this->offset = $offset; - $this->limit = max(0, $limit - 1); - } - - - // }}} - // {{{ function setResultTypes($types) - - /** - * Define the list of types to be associated with the columns of a given - * result set. - * - * This function may be called before invoking fetchRow(), fetchOne(), - * fetchCol() and fetchAll() so that the necessary data type - * conversions are performed on the data to be retrieved by them. If this - * function is not called, the type of all result set columns is assumed - * to be text, thus leading to not perform any conversions. - * - * @param array variable that lists the - * data types to be expected in the result set columns. If this array - * contains less types than the number of columns that are returned - * in the result set, the remaining columns are assumed to be of the - * type text. Currently, the types clob and blob are not fully - * supported. - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function setResultTypes($types) - { - $load = $this->db->loadModule('Datatype', null, true); - if (PEAR::isError($load)) { - return $load; - } - $types = $this->db->datatype->checkResultTypes($types); - if (PEAR::isError($types)) { - return $types; - } - $this->types = $types; - return MDB2_OK; - } - - // }}} - // {{{ function seek($rownum = 0) - - /** - * Seek to a specific row in a result set - * - * @param int number of the row where the data can be found - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function seek($rownum = 0) - { - $target_rownum = $rownum - 1; - if ($this->rownum > $target_rownum) { - return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'seeking to previous rows not implemented', __FUNCTION__); - } - while ($this->rownum < $target_rownum) { - $this->fetchRow(); - } - return MDB2_OK; - } - - // }}} - // {{{ function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) - - /** - * Fetch and return a row of data - * - * @param int how the array data should be indexed - * @param int number of the row where the data can be found - * - * @return int data array on success, a MDB2 error on failure - * - * @access public - */ - function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) - { - $err =$this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - return $err; - } - - // }}} - // {{{ function fetchOne($colnum = 0) - - /** - * fetch single column from the next row from a result set - * - * @param int|string the column number (or name) to fetch - * @param int number of the row where the data can be found - * - * @return string data on success, a MDB2 error on failure - * @access public - */ - function fetchOne($colnum = 0, $rownum = null) - { - $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; - $row = $this->fetchRow($fetchmode, $rownum); - if (!is_array($row) || PEAR::isError($row)) { - return $row; - } - if (!array_key_exists($colnum, $row)) { - return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, - 'column is not defined in the result set: '.$colnum, __FUNCTION__); - } - return $row[$colnum]; - } - - // }}} - // {{{ function fetchCol($colnum = 0) - - /** - * Fetch and return a column from the current row pointer position - * - * @param int|string the column number (or name) to fetch - * - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function fetchCol($colnum = 0) - { - $column = array(); - $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; - $row = $this->fetchRow($fetchmode); - if (is_array($row)) { - if (!array_key_exists($colnum, $row)) { - return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, - 'column is not defined in the result set: '.$colnum, __FUNCTION__); - } - do { - $column[] = $row[$colnum]; - } while (is_array($row = $this->fetchRow($fetchmode))); - } - if (PEAR::isError($row)) { - return $row; - } - return $column; - } - - // }}} - // {{{ function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) - - /** - * Fetch and return all rows from the current row pointer position - * - * @param int $fetchmode the fetch mode to use: - * + MDB2_FETCHMODE_ORDERED - * + MDB2_FETCHMODE_ASSOC - * + MDB2_FETCHMODE_ORDERED | MDB2_FETCHMODE_FLIPPED - * + MDB2_FETCHMODE_ASSOC | MDB2_FETCHMODE_FLIPPED - * @param bool if set to true, the $all will have the first - * column as its first dimension - * @param bool used only when the query returns exactly - * two columns. If true, the values of the returned array will be - * one-element arrays instead of scalars. - * @param bool if true, the values of the returned array is - * wrapped in another array. If the same key value (in the first - * column) repeats itself, the values will be appended to this array - * instead of overwriting the existing values. - * - * @return mixed data array on success, a MDB2 error on failure - * - * @access public - * @see getAssoc() - */ - function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, - $force_array = false, $group = false) - { - $all = array(); - $row = $this->fetchRow($fetchmode); - if (PEAR::isError($row)) { - return $row; - } elseif (!$row) { - return $all; - } - - $shift_array = $rekey ? false : null; - if (!is_null($shift_array)) { - if (is_object($row)) { - $colnum = count(get_object_vars($row)); - } else { - $colnum = count($row); - } - if ($colnum < 2) { - return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, - 'rekey feature requires atleast 2 column', __FUNCTION__); - } - $shift_array = (!$force_array && $colnum == 2); - } - - if ($rekey) { - do { - if (is_object($row)) { - $arr = get_object_vars($row); - $key = reset($arr); - unset($row->{$key}); - } else { - if ($fetchmode & MDB2_FETCHMODE_ASSOC) { - $key = reset($row); - unset($row[key($row)]); - } else { - $key = array_shift($row); - } - if ($shift_array) { - $row = array_shift($row); - } - } - if ($group) { - $all[$key][] = $row; - } else { - $all[$key] = $row; - } - } while (($row = $this->fetchRow($fetchmode))); - } elseif ($fetchmode & MDB2_FETCHMODE_FLIPPED) { - do { - foreach ($row as $key => $val) { - $all[$key][] = $val; - } - } while (($row = $this->fetchRow($fetchmode))); - } else { - do { - $all[] = $row; - } while (($row = $this->fetchRow($fetchmode))); - } - - return $all; - } - - // }}} - // {{{ function rowCount() - /** - * Returns the actual row number that was last fetched (count from 0) - * @return int - * - * @access public - */ - function rowCount() - { - return $this->rownum + 1; - } - - // }}} - // {{{ function numRows() - - /** - * Returns the number of rows in a result object - * - * @return mixed MDB2 Error Object or the number of rows - * - * @access public - */ - function numRows() - { - return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function nextResult() - - /** - * Move the internal result pointer to the next available result - * - * @return true on success, false if there is no more result set or an error object on failure - * - * @access public - */ - function nextResult() - { - return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function getColumnNames() - - /** - * Retrieve the names of columns returned by the DBMS in a query result or - * from the cache. - * - * @param bool If set to true the values are the column names, - * otherwise the names of the columns are the keys. - * @return mixed Array variable that holds the names of columns or an - * MDB2 error on failure. - * Some DBMS may not return any columns when the result set - * does not contain any rows. - * - * @access public - */ - function getColumnNames($flip = false) - { - if (!isset($this->column_names)) { - $result = $this->_getColumnNames(); - if (PEAR::isError($result)) { - return $result; - } - $this->column_names = $result; - } - if ($flip) { - return array_flip($this->column_names); - } - return $this->column_names; - } - - // }}} - // {{{ function _getColumnNames() - - /** - * Retrieve the names of columns returned by the DBMS in a query result. - * - * @return mixed Array variable that holds the names of columns as keys - * or an MDB2 error on failure. - * Some DBMS may not return any columns when the result set - * does not contain any rows. - * - * @access private - */ - function _getColumnNames() - { - return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function numCols() - - /** - * Count the number of columns returned by the DBMS in a query result. - * - * @return mixed integer value with the number of columns, a MDB2 error - * on failure - * - * @access public - */ - function numCols() - { - return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ function getResource() - - /** - * return the resource associated with the result object - * - * @return resource - * - * @access public - */ - function getResource() - { - return $this->result; - } - - // }}} - // {{{ function bindColumn($column, &$value, $type = null) - - /** - * Set bind variable to a column. - * - * @param int column number or name - * @param mixed variable reference - * @param string specifies the type of the field - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function bindColumn($column, &$value, $type = null) - { - if (!is_numeric($column)) { - $column_names = $this->getColumnNames(); - if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($this->db->options['field_case'] == CASE_LOWER) { - $column = strtolower($column); - } else { - $column = strtoupper($column); - } - } - $column = $column_names[$column]; - } - $this->values[$column] =$value; - if (!is_null($type)) { - $this->types[$column] = $type; - } - return MDB2_OK; - } - - // }}} - // {{{ function _assignBindColumns($row) - - /** - * Bind a variable to a value in the result row. - * - * @param array row data - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access private - */ - function _assignBindColumns($row) - { - $row = array_values($row); - foreach ($row as $column => $value) { - if (array_key_exists($column, $this->values)) { - $this->values[$column] = $value; - } - } - return MDB2_OK; - } - - // }}} - // {{{ function free() - - /** - * Free the internal resources associated with result. - * - * @return bool true on success, false if result is invalid - * - * @access public - */ - function free() - { - $this->result = false; - return MDB2_OK; - } - - // }}} -} - -// }}} -// {{{ class MDB2_Row - -/** - * The simple class that accepts row data as an array - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Row -{ - // {{{ constructor: function __construct(&$row) - - /** - * constructor - * - * @param resource row data as array - */ - function __construct(&$row) - { - foreach ($row as $key => $value) { - $this->$key = &$row[$key]; - } - } -} - -// }}} -// {{{ class MDB2_Statement_Common - -/** - * The common statement class for MDB2 statement objects - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Statement_Common -{ - // {{{ Variables (Properties) - - var $db; - var $statement; - var $query; - var $result_types; - var $types; - var $values = array(); - var $limit; - var $offset; - var $is_manip; - - // }}} - // {{{ constructor: function __construct(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) - - /** - * Constructor - */ - function __construct(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) - { - $this->db =$db; - $this->statement =$statement; - $this->positions = $positions; - $this->query = $query; - $this->types = (array)$types; - $this->result_types = (array)$result_types; - $this->limit = $limit; - $this->is_manip = $is_manip; - $this->offset = $offset; - } - - - // }}} - // {{{ function bindValue($parameter, &$value, $type = null) - - /** - * Set the value of a parameter of a prepared query. - * - * @param int the order number of the parameter in the query - * statement. The order number of the first parameter is 1. - * @param mixed value that is meant to be assigned to specified - * parameter. The type of the value depends on the $type argument. - * @param string specifies the type of the field - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function bindValue($parameter, $value, $type = null) - { - if (!is_numeric($parameter)) { - $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); - } - if (!in_array($parameter, $this->positions)) { - return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); - } - $this->values[$parameter] = $value; - if (!is_null($type)) { - $this->types[$parameter] = $type; - } - return MDB2_OK; - } - - // }}} - // {{{ function bindValueArray($values, $types = null) - - /** - * Set the values of multiple a parameter of a prepared query in bulk. - * - * @param array specifies all necessary information - * for bindValue() the array elements must use keys corresponding to - * the number of the position of the parameter. - * @param array specifies the types of the fields - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @see bindParam() - */ - function bindValueArray($values, $types = null) - { - $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); - $parameters = array_keys($values); - foreach ($parameters as $key => $parameter) { - $this->db->pushErrorHandling(PEAR_ERROR_RETURN); - $this->db->expectError(MDB2_ERROR_NOT_FOUND); - $err = $this->bindValue($parameter, $values[$parameter], $types[$key]); - $this->db->popExpect(); - $this->db->popErrorHandling(); - if (PEAR::isError($err)) { - if ($err->getCode() == MDB2_ERROR_NOT_FOUND) { - //ignore (extra value for missing placeholder) - continue; - } - return $err; - } - } - return MDB2_OK; - } - - // }}} - // {{{ function bindParam($parameter, &$value, $type = null) - - /** - * Bind a variable to a parameter of a prepared query. - * - * @param int the order number of the parameter in the query - * statement. The order number of the first parameter is 1. - * @param mixed variable that is meant to be bound to specified - * parameter. The type of the value depends on the $type argument. - * @param string specifies the type of the field - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function bindParam($parameter, &$value, $type = null) - { - if (!is_numeric($parameter)) { - $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); - } - if (!in_array($parameter, $this->positions)) { - return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); - } - $this->values[$parameter] =$value; - if (!is_null($type)) { - $this->types[$parameter] = $type; - } - return MDB2_OK; - } - - // }}} - // {{{ function bindParamArray(&$values, $types = null) - - /** - * Bind the variables of multiple a parameter of a prepared query in bulk. - * - * @param array specifies all necessary information - * for bindParam() the array elements must use keys corresponding to - * the number of the position of the parameter. - * @param array specifies the types of the fields - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @see bindParam() - */ - function bindParamArray(&$values, $types = null) - { - $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); - $parameters = array_keys($values); - foreach ($parameters as $key => $parameter) { - $err = $this->bindParam($parameter, $values[$parameter], $types[$key]); - if (PEAR::isError($err)) { - return $err; - } - } - return MDB2_OK; - } - - // }}} - // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false) - - /** - * Execute a prepared query statement. - * - * @param array specifies all necessary information - * for bindParam() the array elements must use keys corresponding - * to the number of the position of the parameter. - * @param mixed specifies which result class to use - * @param mixed specifies which class to wrap results in - * - * @return mixed MDB2_Result or integer (affected rows) on success, - * a MDB2 error on failure - * @access public - */ - function &execute($values = null, $result_class = true, $result_wrap_class = false) - { - if (is_null($this->positions)) { - return $this->db->raiseError(MDB2_ERROR, null, null, - 'Prepared statement has already been freed', __FUNCTION__); - } - - $values = (array)$values; - if (!empty($values)) { - $err = $this->bindValueArray($values); - if (PEAR::isError($err)) { - return $this->db->raiseError(MDB2_ERROR, null, null, - 'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__); - } - } - $result =$this->_execute($result_class, $result_wrap_class); - return $result; - } - - // }}} - // {{{ function &_execute($result_class = true, $result_wrap_class = false) - - /** - * Execute a prepared query statement helper method. - * - * @param mixed specifies which result class to use - * @param mixed specifies which class to wrap results in - * - * @return mixed MDB2_Result or integer (affected rows) on success, - * a MDB2 error on failure - * @access private - */ - function &_execute($result_class = true, $result_wrap_class = false) - { - $this->last_query = $this->query; - $query = ''; - $last_position = 0; - foreach ($this->positions as $current_position => $parameter) { - if (!array_key_exists($parameter, $this->values)) { - return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); - } - $value = $this->values[$parameter]; - $query.= substr($this->query, $last_position, $current_position - $last_position); - if (!isset($value)) { - $value_quoted = 'NULL'; - } else { - $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null; - $value_quoted = $this->db->quote($value, $type); - if (PEAR::isError($value_quoted)) { - return $value_quoted; - } - } - $query.= $value_quoted; - $last_position = $current_position + 1; - } - $query.= substr($this->query, $last_position); - - $this->db->offset = $this->offset; - $this->db->limit = $this->limit; - if ($this->is_manip) { - $result = $this->db->exec($query); - } else { - $result =$this->db->query($query, $this->result_types, $result_class, $result_wrap_class); - } - return $result; - } - - // }}} - // {{{ function free() - - /** - * Release resources allocated for the specified prepared query. - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function free() - { - if (is_null($this->positions)) { - return $this->db->raiseError(MDB2_ERROR, null, null, - 'Prepared statement has already been freed', __FUNCTION__); - } - - $this->statement = null; - $this->positions = null; - $this->query = null; - $this->types = null; - $this->result_types = null; - $this->limit = null; - $this->is_manip = null; - $this->offset = null; - $this->values = null; - - return MDB2_OK; - } - - // }}} -} - -// }}} -// {{{ class MDB2_Module_Common - -/** - * The common modules class for MDB2 module objects - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Module_Common -{ - // {{{ Variables (Properties) - - /** - * contains the key to the global MDB2 instance array of the associated - * MDB2 instance - * - * @var int - * @access protected - */ - var $db_index; - - // }}} - // {{{ constructor: function __construct($db_index) - - /** - * Constructor - */ - function __construct($db_index) - { - $this->db_index = $db_index; - } - - // }}} - // {{{ function &getDBInstance() - - /** - * Get the instance of MDB2 associated with the module instance - * - * @return object MDB2 instance or a MDB2 error on failure - * - * @access public - */ - function getDBInstance() - { - if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - $result =$GLOBALS['_MDB2_databases'][$this->db_index]; - } else { - $result =$this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'could not find MDB2 instance'); - } - return $result; - } - - // }}} -} - -// }}} -// {{{ function MDB2_closeOpenTransactions() - -/** - * Close any open transactions form persistent connections - * - * @return void - * - * @access public - */ - -function MDB2_closeOpenTransactions() -{ - reset($GLOBALS['_MDB2_databases']); - while (next($GLOBALS['_MDB2_databases'])) { - $key = key($GLOBALS['_MDB2_databases']); - if ($GLOBALS['_MDB2_databases'][$key]->opened_persistent - && $GLOBALS['_MDB2_databases'][$key]->in_transaction - ) { - $GLOBALS['_MDB2_databases'][$key]->rollback(); - } - } -} - -// }}} -// {{{ function MDB2_defaultDebugOutput(&$db, $scope, $message, $is_manip = null) - -/** - * default debug output handler - * - * @param object reference to an MDB2 database object - * @param string usually the method name that triggered the debug call: - * for example 'query', 'prepare', 'execute', 'parameters', - * 'beginTransaction', 'commit', 'rollback' - * @param string message that should be appended to the debug variable - * @param array contains context information about the debug() call - * common keys are: is_manip, time, result etc. - * - * @return void|string optionally return a modified message, this allows - * rewriting a query before being issued or prepared - * - * @access public - */ -function MDB2_defaultDebugOutput(&$db, $scope, $message, $context = array()) -{ - $db->debug_output.= $scope.'('.$db->db_index.'): '; - $db->debug_output.= $message.$db->getOption('log_line_break'); - return $message; -} - -// }}} -?> + | +// +----------------------------------------------------------------------+ +// +// $Id: MDB2.php 295587 2010-02-28 17:16:38Z quipo $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +require_once 'PEAR.php'; + +// {{{ Error constants + +/** + * The method mapErrorCode in each MDB2_dbtype implementation maps + * native error codes to one of these. + * + * If you add an error code here, make sure you also add a textual + * version of it in MDB2::errorMessage(). + */ + +define('MDB2_OK', true); +define('MDB2_ERROR', -1); +define('MDB2_ERROR_SYNTAX', -2); +define('MDB2_ERROR_CONSTRAINT', -3); +define('MDB2_ERROR_NOT_FOUND', -4); +define('MDB2_ERROR_ALREADY_EXISTS', -5); +define('MDB2_ERROR_UNSUPPORTED', -6); +define('MDB2_ERROR_MISMATCH', -7); +define('MDB2_ERROR_INVALID', -8); +define('MDB2_ERROR_NOT_CAPABLE', -9); +define('MDB2_ERROR_TRUNCATED', -10); +define('MDB2_ERROR_INVALID_NUMBER', -11); +define('MDB2_ERROR_INVALID_DATE', -12); +define('MDB2_ERROR_DIVZERO', -13); +define('MDB2_ERROR_NODBSELECTED', -14); +define('MDB2_ERROR_CANNOT_CREATE', -15); +define('MDB2_ERROR_CANNOT_DELETE', -16); +define('MDB2_ERROR_CANNOT_DROP', -17); +define('MDB2_ERROR_NOSUCHTABLE', -18); +define('MDB2_ERROR_NOSUCHFIELD', -19); +define('MDB2_ERROR_NEED_MORE_DATA', -20); +define('MDB2_ERROR_NOT_LOCKED', -21); +define('MDB2_ERROR_VALUE_COUNT_ON_ROW', -22); +define('MDB2_ERROR_INVALID_DSN', -23); +define('MDB2_ERROR_CONNECT_FAILED', -24); +define('MDB2_ERROR_EXTENSION_NOT_FOUND',-25); +define('MDB2_ERROR_NOSUCHDB', -26); +define('MDB2_ERROR_ACCESS_VIOLATION', -27); +define('MDB2_ERROR_CANNOT_REPLACE', -28); +define('MDB2_ERROR_CONSTRAINT_NOT_NULL',-29); +define('MDB2_ERROR_DEADLOCK', -30); +define('MDB2_ERROR_CANNOT_ALTER', -31); +define('MDB2_ERROR_MANAGER', -32); +define('MDB2_ERROR_MANAGER_PARSE', -33); +define('MDB2_ERROR_LOADMODULE', -34); +define('MDB2_ERROR_INSUFFICIENT_DATA', -35); +define('MDB2_ERROR_NO_PERMISSION', -36); +define('MDB2_ERROR_DISCONNECT_FAILED', -37); + +// }}} +// {{{ Verbose constants +/** + * These are just helper constants to more verbosely express parameters to prepare() + */ + +define('MDB2_PREPARE_MANIP', false); +define('MDB2_PREPARE_RESULT', null); + +// }}} +// {{{ Fetchmode constants + +/** + * This is a special constant that tells MDB2 the user hasn't specified + * any particular get mode, so the default should be used. + */ +define('MDB2_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('MDB2_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('MDB2_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('MDB2_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results: normally the first level of arrays + * is the row number, and the second level indexed by column number or name. + * MDB2_FETCHMODE_FLIPPED switches this order, so the first level of arrays + * is the column name, and the second level the row number. + */ +define('MDB2_FETCHMODE_FLIPPED', 4); + +// }}} +// {{{ Portability mode constants + +/** + * Portability: turn off all portability features. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_NONE', 0); + +/** + * Portability: convert names of tables and fields to case defined in the + * "field_case" option when using the query*(), fetch*() and tableInfo() methods. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_FIX_CASE', 1); + +/** + * Portability: right trim the data output by query*() and fetch*(). + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_RTRIM', 2); + +/** + * Portability: force reporting the number of rows deleted. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_DELETE_COUNT', 4); + +/** + * Portability: not needed in MDB2 (just left here for compatibility to DB) + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_NUMROWS', 8); + +/** + * Portability: makes certain error messages in certain drivers compatible + * with those from other DBMS's. + * + * + mysql, mysqli: change unique/primary key constraints + * MDB2_ERROR_ALREADY_EXISTS -> MDB2_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to MDB2_ERROR_NOSUCHFIELD. + * + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_ERRORS', 16); + +/** + * Portability: convert empty values to null strings in data output by + * query*() and fetch*(). + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_EMPTY_TO_NULL', 32); + +/** + * Portability: removes database/table qualifiers from associative indexes + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES', 64); + +/** + * Portability: turn on all portability features. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_ALL', 127); + +// }}} +// {{{ Globals for class instance tracking + +/** + * These are global variables that are used to track the various class instances + */ + +$GLOBALS['_MDB2_databases'] = array(); +$GLOBALS['_MDB2_dsninfo_default'] = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + 'mode' => false, +); + +// }}} +// {{{ class MDB2 + +/** + * The main 'MDB2' class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of MDB2 is as follows (indentation means inheritance): + * + * MDB2 The main MDB2 class. This is simply a utility class + * with some 'static' methods for creating MDB2 objects as + * well as common utility functions for other MDB2 classes. + * + * MDB2_Driver_Common The base for each MDB2 implementation. Provides default + * | implementations (in OO lingo virtual methods) for + * | the actual DB implementations as well as a bunch of + * | query utility functions. + * | + * +-MDB2_Driver_mysql The MDB2 implementation for MySQL. Inherits MDB2_Driver_Common. + * When calling MDB2::factory or MDB2::connect for MySQL + * connections, the object returned is an instance of this + * class. + * +-MDB2_Driver_pgsql The MDB2 implementation for PostGreSQL. Inherits MDB2_Driver_Common. + * When calling MDB2::factory or MDB2::connect for PostGreSQL + * connections, the object returned is an instance of this + * class. + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2 +{ + // {{{ function setOptions($db, $options) + + /** + * set option array in an exiting database object + * + * @param MDB2_Driver_Common MDB2 object + * @param array An associative array of option names and their values. + * + * @return mixed MDB2_OK or a PEAR Error object + * + * @access public + */ + static function setOptions($db, $options) + { + if (is_array($options)) { + foreach ($options as $option => $value) { + $test = $db->setOption($option, $value); + if (PEAR::isError($test)) { + return $test; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ function classExists($classname) + + /** + * Checks if a class exists without triggering __autoload + * + * @param string classname + * + * @return bool true success and false on error + * @static + * @access public + */ + static function classExists($classname) + { + return class_exists($classname, false); + } + + // }}} + // {{{ function loadClass($class_name, $debug) + + /** + * Loads a PEAR class. + * + * @param string classname to load + * @param bool if errors should be suppressed + * + * @return mixed true success or PEAR_Error on failure + * + * @access public + */ + static function loadClass($class_name, $debug) + { + if (!MDB2::classExists($class_name)) { + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + if ($debug) { + $include = include_once($file_name); + } else { + $include = @include_once($file_name); + } + if (!$include) { + if (!MDB2::fileExists($file_name)) { + $msg = "unable to find package '$class_name' file '$file_name'"; + } else { + $msg = "unable to load class '$class_name' from file '$file_name'"; + } + $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg); + return $err; + } + if (!MDB2::classExists($class_name)) { + $msg = "unable to load class '$class_name' from file '$file_name'"; + $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg); + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function factory($dsn, $options = false) + + /** + * Create a new MDB2 object for the specified database type + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 object, or false on error + * + * @access public + */ + static function factory($dsn, $options = false) + { + $dsninfo = MDB2::parseDSN($dsn); + if (empty($dsninfo['phptype'])) { + $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, + null, null, 'no RDBMS driver specified'); + return $err; + } + $class_name = 'MDB2_Driver_'.$dsninfo['phptype']; + + $debug = (!empty($options['debug'])); + $err = MDB2::loadClass($class_name, $debug); + if (PEAR::isError($err)) { + return $err; + } + + $db = new $class_name(); + $db->setDSN($dsninfo); + $err = MDB2::setOptions($db, $options); + if (PEAR::isError($err)) { + return $err; + } + + return $db; + } + + // }}} + // {{{ function connect($dsn, $options = false) + + /** + * Create a new MDB2_Driver_* connection object and connect to the specified + * database + * + * @param mixed $dsn 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array $options An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 connection object, or a MDB2 + * error object on error + * + * @access public + * @see MDB2::parseDSN + */ + static function connect($dsn, $options = false) + { + $db = MDB2::factory($dsn, $options); + if (PEAR::isError($db)) { + return $db; + } + + $err = $db->connect(); + if (PEAR::isError($err)) { + $dsn = $db->getDSN('string', 'xxx'); + $db->disconnect(); + $err->addUserInfo($dsn); + return $err; + } + + return $db; + } + + // }}} + // {{{ function singleton($dsn = null, $options = false) + + /** + * Returns a MDB2 connection with the requested DSN. + * A new MDB2 connection object is only created if no object with the + * requested DSN exists yet. + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 connection object, or a MDB2 + * error object on error + * + * @access public + * @see MDB2::parseDSN + */ + static function singleton($dsn = null, $options = false) + { + if ($dsn) { + $dsninfo = MDB2::parseDSN($dsn); + $dsninfo = array_merge($GLOBALS['_MDB2_dsninfo_default'], $dsninfo); + $keys = array_keys($GLOBALS['_MDB2_databases']); + for ($i=0, $j=count($keys); $i<$j; ++$i) { + if (isset($GLOBALS['_MDB2_databases'][$keys[$i]])) { + $tmp_dsn = $GLOBALS['_MDB2_databases'][$keys[$i]]->getDSN('array'); + if (count(array_diff_assoc($tmp_dsn, $dsninfo)) == 0) { + MDB2::setOptions($GLOBALS['_MDB2_databases'][$keys[$i]], $options); + return $GLOBALS['_MDB2_databases'][$keys[$i]]; + } + } + } + } elseif (is_array($GLOBALS['_MDB2_databases']) && reset($GLOBALS['_MDB2_databases'])) { + return $GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])]; + } + $db = MDB2::factory($dsn, $options); + return $db; + } + + // }}} + // {{{ function areEquals() + + /** + * It looks like there's a memory leak in array_diff() in PHP 5.1.x, + * so use this method instead. + * @see http://pear.php.net/bugs/bug.php?id=11790 + * + * @param array $arr1 + * @param array $arr2 + * @return boolean + */ + static function areEquals($arr1, $arr2) + { + if (count($arr1) != count($arr2)) { + return false; + } + foreach (array_keys($arr1) as $k) { + if (!array_key_exists($k, $arr2) || $arr1[$k] != $arr2[$k]) { + return false; + } + } + return true; + } + + // }}} + // {{{ function loadFile($file) + + /** + * load a file (like 'Date') + * + * @param string $file name of the file in the MDB2 directory (without '.php') + * + * @return string name of the file that was included + * + * @access public + */ + static function loadFile($file) + { + $file_name = 'MDB2'.DIRECTORY_SEPARATOR.$file.'.php'; + if (!MDB2::fileExists($file_name)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to find: '.$file_name); + } + if (!include_once($file_name)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to load driver class: '.$file_name); + } + return $file_name; + } + + // }}} + // {{{ function apiVersion() + + /** + * Return the MDB2 API version + * + * @return string the MDB2 API version number + * + * @access public + */ + function apiVersion() + { + return '2.5.0b3'; + } + + // }}} + // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed int error code + * + * @param int error mode, see PEAR_Error docs + * + * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * + * @param string Extra debug information. Defaults to the last + * query and native error code. + * + * @return PEAR_Error instance of a PEAR Error object + * + * @access private + * @see PEAR_Error + */ + function &raiseError($code = null, + $mode = null, + $options = null, + $userinfo = null, + $dummy1 = null, + $dummy2 = null, + $dummy3 = false) + { + $err =& PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); + return $err; + } + + // }}} + // {{{ function isError($data, $code = null) + + /** + * Tell whether a value is a MDB2 error. + * + * @param mixed the value to test + * @param int if is an error object, return true + * only if $code is a string and + * $db->getMessage() == $code or + * $code is an integer and $db->getCode() == $code + * + * @return bool true if parameter is an error + * + * @access public + */ + static function isError($data, $code = null) + { + if ($data instanceof MDB2_Error) { + if (null === $code) { + return true; + } + if (is_string($code)) { + return $data->getMessage() === $code; + } + return in_array($data->getCode(), (array)$code); + } + return false; + } + + // }}} + // {{{ function isConnection($value) + + /** + * Tell whether a value is a MDB2 connection + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 connection + * @access public + */ + function isConnection($value) + { + return ($value instanceof MDB2_Driver_Common); + } + + // }}} + // {{{ function isResult($value) + + /** + * Tell whether a value is a MDB2 result + * + * @param mixed $value value to test + * + * @return bool whether $value is a MDB2 result + * + * @access public + */ + function isResult($value) + { + return ($value instanceof MDB2_Result); + } + + // }}} + // {{{ function isResultCommon($value) + + /** + * Tell whether a value is a MDB2 result implementing the common interface + * + * @param mixed $value value to test + * + * @return bool whether $value is a MDB2 result implementing the common interface + * + * @access public + */ + static function isResultCommon($value) + { + return ($value instanceof MDB2_Result_Common); + } + + // }}} + // {{{ function isStatement($value) + + /** + * Tell whether a value is a MDB2 statement interface + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 statement interface + * + * @access public + */ + function isStatement($value) + { + return ($value instanceof MDB2_Statement_Common); + } + + // }}} + // {{{ function errorMessage($value = null) + + /** + * Return a textual error message for a MDB2 error code + * + * @param int|array integer error code, + null to get the current error code-message map, + or an array with a new error code-message map + * + * @return string error message, or false if the error code was + * not recognized + * + * @access public + */ + function errorMessage($value = null) + { + static $errorMessages; + + if (is_array($value)) { + $errorMessages = $value; + return MDB2_OK; + } + + if (!isset($errorMessages)) { + $errorMessages = array( + MDB2_OK => 'no error', + MDB2_ERROR => 'unknown error', + MDB2_ERROR_ALREADY_EXISTS => 'already exists', + MDB2_ERROR_CANNOT_CREATE => 'can not create', + MDB2_ERROR_CANNOT_ALTER => 'can not alter', + MDB2_ERROR_CANNOT_REPLACE => 'can not replace', + MDB2_ERROR_CANNOT_DELETE => 'can not delete', + MDB2_ERROR_CANNOT_DROP => 'can not drop', + MDB2_ERROR_CONSTRAINT => 'constraint violation', + MDB2_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + MDB2_ERROR_DIVZERO => 'division by zero', + MDB2_ERROR_INVALID => 'invalid', + MDB2_ERROR_INVALID_DATE => 'invalid date or time', + MDB2_ERROR_INVALID_NUMBER => 'invalid number', + MDB2_ERROR_MISMATCH => 'mismatch', + MDB2_ERROR_NODBSELECTED => 'no database selected', + MDB2_ERROR_NOSUCHFIELD => 'no such field', + MDB2_ERROR_NOSUCHTABLE => 'no such table', + MDB2_ERROR_NOT_CAPABLE => 'MDB2 backend not capable', + MDB2_ERROR_NOT_FOUND => 'not found', + MDB2_ERROR_NOT_LOCKED => 'not locked', + MDB2_ERROR_SYNTAX => 'syntax error', + MDB2_ERROR_UNSUPPORTED => 'not supported', + MDB2_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + MDB2_ERROR_INVALID_DSN => 'invalid DSN', + MDB2_ERROR_CONNECT_FAILED => 'connect failed', + MDB2_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + MDB2_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + MDB2_ERROR_NOSUCHDB => 'no such database', + MDB2_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + MDB2_ERROR_LOADMODULE => 'error while including on demand module', + MDB2_ERROR_TRUNCATED => 'truncated', + MDB2_ERROR_DEADLOCK => 'deadlock detected', + MDB2_ERROR_NO_PERMISSION => 'no permission', + MDB2_ERROR_DISCONNECT_FAILED => 'disconnect failed', + ); + } + + if (null === $value) { + return $errorMessages; + } + + if (PEAR::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? + $errorMessages[$value] : $errorMessages[MDB2_ERROR]; + } + + // }}} + // {{{ function parseDSN($dsn) + + /** + * Parse a data source name. + * + * Additional keys can be added by appending a URI query string to the + * end of the DSN. + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * + * @param string Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + phptype: Database backend used in PHP (mysql, odbc etc.) + * + dbsyntax: Database used with regards to SQL syntax etc. + * + protocol: Communication protocol to use (tcp, unix etc.) + * + hostspec: Host specification (hostname[:port]) + * + database: Database to use on the DBMS server + * + username: User name for login + * + password: Password for login + * + * @access public + * @author Tomas V.V.Cox + */ + static function parseDSN($dsn) + { + $parsed = $GLOBALS['_MDB2_dsninfo_default']; + + if (is_array($dsn)) { + $dsn = array_merge($parsed, $dsn); + if (!$dsn['dbsyntax']) { + $dsn['dbsyntax'] = $dsn['phptype']; + } + return $dsn; + } + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (!count($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + // $dsn => proto(proto_opts)/database + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + // $dsn => protocol+hostspec/database (old format) + } else { + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if ( strpos($dsn, '//') === 0 + && strpos($dsn, '/', 2) !== false + && $parsed['phptype'] == 'oci8' + ) { + //oracle's "Easy Connect" syntax: + //"username/password@[//]host[:port][/service_name]" + //e.g. "scott/tiger@//mymachine:1521/oracle" + $proto_opts = $dsn; + $pos = strrpos($proto_opts, '/'); + $dsn = substr($proto_opts, $pos + 1); + $proto_opts = substr($proto_opts, 0, $pos); + } elseif (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if (strpos($proto_opts, ':') !== false) { + list($proto_opts, $parsed['port']) = explode(':', $proto_opts); + } + if ($parsed['protocol'] == 'tcp') { + $parsed['hostspec'] = $proto_opts; + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + // /database + if (($pos = strpos($dsn, '?')) === false) { + $parsed['database'] = $dsn; + // /database?param1=value1¶m2=value2 + } else { + $parsed['database'] = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!array_key_exists($key, $parsed) || false === $parsed[$key]) { + // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + // }}} + // {{{ function fileExists($file) + + /** + * Checks if a file exists in the include path + * + * @param string filename + * + * @return bool true success and false on error + * + * @access public + */ + static function fileExists($file) + { + // safe_mode does notwork with is_readable() + if (!@ini_get('safe_mode')) { + $dirs = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($dirs as $dir) { + if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) { + return true; + } + } + } else { + $fp = @fopen($file, 'r', true); + if (is_resource($fp)) { + @fclose($fp); + return true; + } + } + return false; + } + // }}} +} + +// }}} +// {{{ class MDB2_Error extends PEAR_Error + +/** + * MDB2_Error implements a class for reporting portable database error + * messages. + * + * @package MDB2 + * @category Database + * @author Stig Bakken + */ +class MDB2_Error extends PEAR_Error +{ + // {{{ constructor: function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null) + + /** + * MDB2_Error constructor. + * + * @param mixed MDB2 error code, or string with error message. + * @param int what 'error mode' to operate in + * @param int what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param mixed additional debug info, such as the last query + */ + function __construct($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null, $dummy = null) + { + if (null === $code) { + $code = MDB2_ERROR; + } + $this->PEAR_Error('MDB2 Error: '.MDB2::errorMessage($code), $code, + $mode, $level, $debuginfo); + } + + // }}} +} + +// }}} +// {{{ class MDB2_Driver_Common extends PEAR + +/** + * MDB2_Driver_Common: Base class that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Common extends PEAR +{ + // {{{ Variables (Properties) + + /** + * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array + * @var int + * @access public + */ + var $db_index = 0; + + /** + * DSN used for the next query + * @var array + * @access protected + */ + var $dsn = array(); + + /** + * DSN that was used to create the current connection + * @var array + * @access protected + */ + var $connected_dsn = array(); + + /** + * connection resource + * @var mixed + * @access protected + */ + var $connection = 0; + + /** + * if the current opened connection is a persistent connection + * @var bool + * @access protected + */ + var $opened_persistent; + + /** + * the name of the database for the next query + * @var string + * @access protected + */ + var $database_name = ''; + + /** + * the name of the database currently selected + * @var string + * @access protected + */ + var $connected_database_name = ''; + + /** + * server version information + * @var string + * @access protected + */ + var $connected_server_info = ''; + + /** + * list of all supported features of the given driver + * @var array + * @access public + */ + var $supported = array( + 'sequences' => false, + 'indexes' => false, + 'affected_rows' => false, + 'summary_functions' => false, + 'order_by_text' => false, + 'transactions' => false, + 'savepoints' => false, + 'current_id' => false, + 'limit_queries' => false, + 'LOBs' => false, + 'replace' => false, + 'sub_selects' => false, + 'triggers' => false, + 'auto_increment' => false, + 'primary_key' => false, + 'result_introspection' => false, + 'prepared_statements' => false, + 'identifier_quoting' => false, + 'pattern_escaping' => false, + 'new_link' => false, + ); + + /** + * Array of supported options that can be passed to the MDB2 instance. + * + * The options can be set during object creation, using + * MDB2::connect(), MDB2::factory() or MDB2::singleton(). The options can + * also be set after the object is created, using MDB2::setOptions() or + * MDB2_Driver_Common::setOption(). + * The list of available option includes: + *
    + *
  • $options['ssl'] -> boolean: determines if ssl should be used for connections
  • + *
  • $options['field_case'] -> CASE_LOWER|CASE_UPPER: determines what case to force on field/table names
  • + *
  • $options['disable_query'] -> boolean: determines if queries should be executed
  • + *
  • $options['result_class'] -> string: class used for result sets
  • + *
  • $options['buffered_result_class'] -> string: class used for buffered result sets
  • + *
  • $options['result_wrap_class'] -> string: class used to wrap result sets into
  • + *
  • $options['result_buffering'] -> boolean should results be buffered or not?
  • + *
  • $options['fetch_class'] -> string: class to use when fetch mode object is used
  • + *
  • $options['persistent'] -> boolean: persistent connection?
  • + *
  • $options['debug'] -> integer: numeric debug level
  • + *
  • $options['debug_handler'] -> string: function/method that captures debug messages
  • + *
  • $options['debug_expanded_output'] -> bool: BC option to determine if more context information should be send to the debug handler
  • + *
  • $options['default_text_field_length'] -> integer: default text field length to use
  • + *
  • $options['lob_buffer_length'] -> integer: LOB buffer length
  • + *
  • $options['log_line_break'] -> string: line-break format
  • + *
  • $options['idxname_format'] -> string: pattern for index name
  • + *
  • $options['seqname_format'] -> string: pattern for sequence name
  • + *
  • $options['savepoint_format'] -> string: pattern for auto generated savepoint names
  • + *
  • $options['statement_format'] -> string: pattern for prepared statement names
  • + *
  • $options['seqcol_name'] -> string: sequence column name
  • + *
  • $options['quote_identifier'] -> boolean: if identifier quoting should be done when check_option is used
  • + *
  • $options['use_transactions'] -> boolean: if transaction use should be enabled
  • + *
  • $options['decimal_places'] -> integer: number of decimal places to handle
  • + *
  • $options['portability'] -> integer: portability constant
  • + *
  • $options['modules'] -> array: short to long module name mapping for __call()
  • + *
  • $options['emulate_prepared'] -> boolean: force prepared statements to be emulated
  • + *
  • $options['datatype_map'] -> array: map user defined datatypes to other primitive datatypes
  • + *
  • $options['datatype_map_callback'] -> array: callback function/method that should be called
  • + *
  • $options['bindname_format'] -> string: regular expression pattern for named parameters
  • + *
  • $options['multi_query'] -> boolean: determines if queries returning multiple result sets should be executed
  • + *
  • $options['max_identifiers_length'] -> integer: max identifier length
  • + *
  • $options['default_fk_action_onupdate'] -> string: default FOREIGN KEY ON UPDATE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']
  • + *
  • $options['default_fk_action_ondelete'] -> string: default FOREIGN KEY ON DELETE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']
  • + *
+ * + * @var array + * @access public + * @see MDB2::connect() + * @see MDB2::factory() + * @see MDB2::singleton() + * @see MDB2_Driver_Common::setOption() + */ + var $options = array( + 'ssl' => false, + 'field_case' => CASE_LOWER, + 'disable_query' => false, + 'result_class' => 'MDB2_Result_%s', + 'buffered_result_class' => 'MDB2_BufferedResult_%s', + 'result_wrap_class' => false, + 'result_buffering' => true, + 'fetch_class' => 'stdClass', + 'persistent' => false, + 'debug' => 0, + 'debug_handler' => 'MDB2_defaultDebugOutput', + 'debug_expanded_output' => false, + 'default_text_field_length' => 4096, + 'lob_buffer_length' => 8192, + 'log_line_break' => "\n", + 'idxname_format' => '%s_idx', + 'seqname_format' => '%s_seq', + 'savepoint_format' => 'MDB2_SAVEPOINT_%s', + 'statement_format' => 'MDB2_STATEMENT_%1$s_%2$s', + 'seqcol_name' => 'sequence', + 'quote_identifier' => false, + 'use_transactions' => true, + 'decimal_places' => 2, + 'portability' => MDB2_PORTABILITY_ALL, + 'modules' => array( + 'ex' => 'Extended', + 'dt' => 'Datatype', + 'mg' => 'Manager', + 'rv' => 'Reverse', + 'na' => 'Native', + 'fc' => 'Function', + ), + 'emulate_prepared' => false, + 'datatype_map' => array(), + 'datatype_map_callback' => array(), + 'nativetype_map_callback' => array(), + 'lob_allow_url_include' => false, + 'bindname_format' => '(?:\d+)|(?:[a-zA-Z][a-zA-Z0-9_]*)', + 'max_identifiers_length' => 30, + 'default_fk_action_onupdate' => 'RESTRICT', + 'default_fk_action_ondelete' => 'RESTRICT', + ); + + /** + * string array + * @var string + * @access protected + */ + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => false, 'escape_pattern' => false); + + /** + * identifier quoting + * @var array + * @access protected + */ + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + /** + * sql comments + * @var array + * @access protected + */ + var $sql_comments = array( + array('start' => '--', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + /** + * comparision wildcards + * @var array + * @access protected + */ + var $wildcards = array('%', '_'); + + /** + * column alias keyword + * @var string + * @access protected + */ + var $as_keyword = ' AS '; + + /** + * warnings + * @var array + * @access protected + */ + var $warnings = array(); + + /** + * string with the debugging information + * @var string + * @access public + */ + var $debug_output = ''; + + /** + * determine if there is an open transaction + * @var bool + * @access protected + */ + var $in_transaction = false; + + /** + * the smart transaction nesting depth + * @var int + * @access protected + */ + var $nested_transaction_counter = null; + + /** + * the first error that occured inside a nested transaction + * @var MDB2_Error|bool + * @access protected + */ + var $has_transaction_error = false; + + /** + * result offset used in the next query + * @var int + * @access protected + */ + var $offset = 0; + + /** + * result limit used in the next query + * @var int + * @access protected + */ + var $limit = 0; + + /** + * Database backend used in PHP (mysql, odbc etc.) + * @var string + * @access public + */ + var $phptype; + + /** + * Database used with regards to SQL syntax etc. + * @var string + * @access public + */ + var $dbsyntax; + + /** + * the last query sent to the driver + * @var string + * @access public + */ + var $last_query; + + /** + * the default fetchmode used + * @var int + * @access protected + */ + var $fetchmode = MDB2_FETCHMODE_ORDERED; + + /** + * array of module instances + * @var array + * @access protected + */ + var $modules = array(); + + /** + * determines of the PHP4 destructor emulation has been enabled yet + * @var array + * @access protected + */ + var $destructor_registered = true; + + // }}} + // {{{ constructor: function __construct() + + /** + * Constructor + */ + function __construct() + { + end($GLOBALS['_MDB2_databases']); + $db_index = key($GLOBALS['_MDB2_databases']) + 1; + $GLOBALS['_MDB2_databases'][$db_index] = &$this; + $this->db_index = $db_index; + } + + // }}} + // {{{ destructor: function __destruct() + + /** + * Destructor + */ + function __destruct() + { + $this->disconnect(false); + } + + // }}} + // {{{ function free() + + /** + * Free the internal references so that the instance can be destroyed + * + * @return bool true on success, false if result is invalid + * + * @access public + */ + function free() + { + unset($GLOBALS['_MDB2_databases'][$this->db_index]); + unset($this->db_index); + return MDB2_OK; + } + + // }}} + // {{{ function __toString() + + /** + * String conversation + * + * @return string representation of the object + * + * @access public + */ + function __toString() + { + $info = get_class($this); + $info.= ': (phptype = '.$this->phptype.', dbsyntax = '.$this->dbsyntax.')'; + if ($this->connection) { + $info.= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ function errorInfo($error = null) + + /** + * This method is used to collect information about an error + * + * @param mixed error code or resource + * + * @return array with MDB2 errorcode, native error code, native message + * + * @access public + */ + function errorInfo($error = null) + { + return array($error, null, null); + } + + // }}} + // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed $code integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int $mode error mode, see PEAR_Error docs + * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string $userinfo Extra debug information. Defaults to the last + * query and native error code. + * @param string $method name of the method that triggered the error + * @param string $dummy1 not used + * @param bool $dummy2 not used + * + * @return PEAR_Error instance of a PEAR Error object + * @access public + * @see PEAR_Error + */ + function &raiseError($code = null, + $mode = null, + $options = null, + $userinfo = null, + $method = null, + $dummy1 = null, + $dummy2 = false + ) { + $userinfo = "[Error message: $userinfo]\n"; + // The error is yet a MDB2 error object + if (PEAR::isError($code)) { + // because we use the static PEAR::raiseError, our global + // handler should be used if it is set + if ((null === $mode) && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + if (null === $userinfo) { + $userinfo = $code->getUserinfo(); + } + $code = $code->getCode(); + } elseif ($code == MDB2_ERROR_NOT_FOUND) { + // extension not loaded: don't call $this->errorInfo() or the script + // will die + } elseif (isset($this->connection)) { + if (!empty($this->last_query)) { + $userinfo.= "[Last executed query: {$this->last_query}]\n"; + } + $native_errno = $native_msg = null; + list($code, $native_errno, $native_msg) = $this->errorInfo($code); + if ((null !== $native_errno) && $native_errno !== '') { + $userinfo.= "[Native code: $native_errno]\n"; + } + if ((null !== $native_msg) && $native_msg !== '') { + $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n"; + } + if (null !== $method) { + $userinfo = $method.': '.$userinfo; + } + } + + $err = PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); + if ($err->getMode() !== PEAR_ERROR_RETURN + && isset($this->nested_transaction_counter) && !$this->has_transaction_error) { + $this->has_transaction_error = $err; + } + return $err; + } + + // }}} + // {{{ function resetWarnings() + + /** + * reset the warning array + * + * @return void + * + * @access public + */ + function resetWarnings() + { + $this->warnings = array(); + } + + // }}} + // {{{ function getWarnings() + + /** + * Get all warnings in reverse order. + * This means that the last warning is the first element in the array + * + * @return array with warnings + * + * @access public + * @see resetWarnings() + */ + function getWarnings() + { + return array_reverse($this->warnings); + } + + // }}} + // {{{ function setFetchMode($fetchmode, $object_class = 'stdClass') + + /** + * Sets which fetch mode should be used by default on queries + * on this connection + * + * @param int MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC + * or MDB2_FETCHMODE_OBJECT + * @param string the class name of the object to be returned + * by the fetch methods when the + * MDB2_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the possibility to use + * and extend the 'MDB2_row' class. + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + * @see MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case MDB2_FETCHMODE_OBJECT: + $this->options['fetch_class'] = $object_class; + case MDB2_FETCHMODE_ORDERED: + case MDB2_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'invalid fetchmode mode', __FUNCTION__); + } + + return MDB2_OK; + } + + // }}} + // {{{ function setOption($option, $value) + + /** + * set the option for the db class + * + * @param string option name + * @param mixed value for the option + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + */ + function setOption($option, $value) + { + if (array_key_exists($option, $this->options)) { + $this->options[$option] = $value; + return MDB2_OK; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown option $option", __FUNCTION__); + } + + // }}} + // {{{ function getOption($option) + + /** + * Returns the value of an option + * + * @param string option name + * + * @return mixed the option value or error object + * + * @access public + */ + function getOption($option) + { + if (array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown option $option", __FUNCTION__); + } + + // }}} + // {{{ function debug($message, $scope = '', $is_manip = null) + + /** + * set a debug message + * + * @param string message that should be appended to the debug variable + * @param string usually the method name that triggered the debug call: + * for example 'query', 'prepare', 'execute', 'parameters', + * 'beginTransaction', 'commit', 'rollback' + * @param array contains context information about the debug() call + * common keys are: is_manip, time, result etc. + * + * @return void + * + * @access public + */ + function debug($message, $scope = '', $context = array()) + { + if ($this->options['debug'] && $this->options['debug_handler']) { + if (!$this->options['debug_expanded_output']) { + if (!empty($context['when']) && $context['when'] !== 'pre') { + return null; + } + $context = empty($context['is_manip']) ? false : $context['is_manip']; + } + return call_user_func_array($this->options['debug_handler'], array(&$this, $scope, $message, $context)); + } + return null; + } + + // }}} + // {{{ function getDebugOutput() + + /** + * output debug info + * + * @return string content of the debug_output class variable + * + * @access public + */ + function getDebugOutput() + { + return $this->debug_output; + } + + // }}} + // {{{ function escape($text) + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + + $text = str_replace($this->string_quoting['end'], $this->string_quoting['escape'] . $this->string_quoting['end'], $text); + return $text; + } + + // }}} + // {{{ function escapePattern($text) + + /** + * Quotes pattern (% and _) characters in a string) + * + * @param string the input string to quote + * + * @return string quoted string + * + * @access public + */ + function escapePattern($text) + { + if ($this->string_quoting['escape_pattern']) { + $text = str_replace($this->string_quoting['escape_pattern'], $this->string_quoting['escape_pattern'] . $this->string_quoting['escape_pattern'], $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, $this->string_quoting['escape_pattern'] . $wildcard, $text); + } + } + return $text; + } + + // }}} + // {{{ function quoteIdentifier($str, $check_option = false) + + /** + * Quote a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * NOTE: if you have table names containing periods, don't use this method + * (@see bug #11906) + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + pgsql + * + sqlite + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string identifier name to be quoted + * @param bool check the 'quote_identifier' option + * + * @return string quoted identifier string + * + * @access public + */ + function quoteIdentifier($str, $check_option = false) + { + if ($check_option && !$this->options['quote_identifier']) { + return $str; + } + $str = str_replace($this->identifier_quoting['end'], $this->identifier_quoting['escape'] . $this->identifier_quoting['end'], $str); + $parts = explode('.', $str); + foreach (array_keys($parts) as $k) { + $parts[$k] = $this->identifier_quoting['start'] . $parts[$k] . $this->identifier_quoting['end']; + } + return implode('.', $parts); + } + + // }}} + // {{{ function getAsKeyword() + + /** + * Gets the string to alias column + * + * @return string to use when aliasing a column + */ + function getAsKeyword() + { + return $this->as_keyword; + } + + // }}} + // {{{ function getConnection() + + /** + * Returns a native connection + * + * @return mixed a valid MDB2 connection object, + * or a MDB2 error object on error + * + * @access public + */ + function getConnection() + { + $result = $this->connect(); + if (PEAR::isError($result)) { + return $result; + } + return $this->connection; + } + + // }}} + // {{{ function _fixResultArrayValues(&$row, $mode) + + /** + * Do all necessary conversions on result arrays to fix DBMS quirks + * + * @param array the array to be fixed (passed by reference) + * @param array bit-wise addition of the required portability modes + * + * @return void + * + * @access protected + */ + function _fixResultArrayValues(&$row, $mode) + { + switch ($mode) { + case MDB2_PORTABILITY_EMPTY_TO_NULL: + foreach ($row as $key => $value) { + if ($value === '') { + $row[$key] = null; + } + } + break; + case MDB2_PORTABILITY_RTRIM: + foreach ($row as $key => $value) { + if (is_string($value)) { + $row[$key] = rtrim($value); + } + } + break; + case MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES: + $tmp_row = array(); + foreach ($row as $key => $value) { + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL): + foreach ($row as $key => $value) { + if ($value === '') { + $row[$key] = null; + } elseif (is_string($value)) { + $row[$key] = rtrim($value); + } + } + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if (is_string($value)) { + $value = rtrim($value); + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if ($value === '') { + $value = null; + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if ($value === '') { + $value = null; + } elseif (is_string($value)) { + $value = rtrim($value); + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + } + } + + // }}} + // {{{ function loadModule($module, $property = null, $phptype_specific = null) + + /** + * loads a module + * + * @param string name of the module that should be loaded + * (only used for error messages) + * @param string name of the property into which the class will be loaded + * @param bool if the class to load for the module is specific to the + * phptype + * + * @return object on success a reference to the given module is returned + * and on failure a PEAR error + * + * @access public + */ + function loadModule($module, $property = null, $phptype_specific = null) + { + if (!$property) { + $property = strtolower($module); + } + + if (!isset($this->{$property})) { + $version = $phptype_specific; + if ($phptype_specific !== false) { + $version = true; + $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype; + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + } + if ($phptype_specific === false + || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name)) + ) { + $version = false; + $class_name = 'MDB2_'.$module; + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + } + + $err = MDB2::loadClass($class_name, $this->getOption('debug')); + if (PEAR::isError($err)) { + return $err; + } + + // load module in a specific version + if ($version) { + if (method_exists($class_name, 'getClassName')) { + $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index); + if ($class_name != $class_name_new) { + $class_name = $class_name_new; + $err = MDB2::loadClass($class_name, $this->getOption('debug')); + if (PEAR::isError($err)) { + return $err; + } + } + } + } + + if (!MDB2::classExists($class_name)) { + $err = $this->raiseError(MDB2_ERROR_LOADMODULE, null, null, + "unable to load module '$module' into property '$property'", __FUNCTION__); + return $err; + } + $this->{$property} = new $class_name($this->db_index); + $this->modules[$module] = $this->{$property}; + if ($version) { + // this will be used in the connect method to determine if the module + // needs to be loaded with a different version if the server + // version changed in between connects + $this->loaded_version_modules[] = $property; + } + } + + return $this->{$property}; + } + + // }}} + // {{{ function __call($method, $params) + + /** + * Calls a module method using the __call magic method + * + * @param string Method name. + * @param array Arguments. + * + * @return mixed Returned value. + */ + function __call($method, $params) + { + $module = null; + if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match) + && isset($this->options['modules'][$match[1]]) + ) { + $module = $this->options['modules'][$match[1]]; + $method = strtolower($match[2]).$match[3]; + if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) { + $result = $this->loadModule($module); + if (PEAR::isError($result)) { + return $result; + } + } + } else { + foreach ($this->modules as $key => $foo) { + if (is_object($this->modules[$key]) + && method_exists($this->modules[$key], $method) + ) { + $module = $key; + break; + } + } + } + if (null !== $module) { + return call_user_func_array(array(&$this->modules[$module], $method), $params); + } + trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $method), E_USER_ERROR); + } + + // }}} + // {{{ function beginTransaction($savepoint = null) + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + // }}} + // {{{ function commit($savepoint = null) + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'commiting transactions is not supported', __FUNCTION__); + } + + // }}} + // {{{ function rollback($savepoint = null) + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'rolling back transactions is not supported', __FUNCTION__); + } + + // }}} + // {{{ function inTransaction($ignore_nested = false) + + /** + * If a transaction is currently open. + * + * @param bool if the nested transaction count should be ignored + * @return int|bool - an integer with the nesting depth is returned if a + * nested transaction is open + * - true is returned for a normal open transaction + * - false is returned if no transaction is open + * + * @access public + */ + function inTransaction($ignore_nested = false) + { + if (!$ignore_nested && isset($this->nested_transaction_counter)) { + return $this->nested_transaction_counter; + } + return $this->in_transaction; + } + + // }}} + // {{{ function setTransactionIsolation($isolation) + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @param array some transaction options: + * 'wait' => 'WAIT' | 'NO WAIT' + * 'rw' => 'READ WRITE' | 'READ ONLY' + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation, $options = array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level setting is not supported', __FUNCTION__); + } + + // }}} + // {{{ function beginNestedTransaction($savepoint = false) + + /** + * Start a nested transaction. + * + * @return mixed MDB2_OK on success/savepoint name, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function beginNestedTransaction() + { + if ($this->in_transaction) { + ++$this->nested_transaction_counter; + $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); + if ($this->supports('savepoints') && $savepoint) { + return $this->beginTransaction($savepoint); + } + return MDB2_OK; + } + $this->has_transaction_error = false; + $result = $this->beginTransaction(); + $this->nested_transaction_counter = 1; + return $result; + } + + // }}} + // {{{ function completeNestedTransaction($force_rollback = false, $release = false) + + /** + * Finish a nested transaction by rolling back if an error occured or + * committing otherwise. + * + * @param bool if the transaction should be rolled back regardless + * even if no error was set within the nested transaction + * @return mixed MDB_OK on commit/counter decrementing, false on rollback + * and a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function completeNestedTransaction($force_rollback = false) + { + if ($this->nested_transaction_counter > 1) { + $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); + if ($this->supports('savepoints') && $savepoint) { + if ($force_rollback || $this->has_transaction_error) { + $result = $this->rollback($savepoint); + if (!PEAR::isError($result)) { + $result = false; + $this->has_transaction_error = false; + } + } else { + $result = $this->commit($savepoint); + } + } else { + $result = MDB2_OK; + } + --$this->nested_transaction_counter; + return $result; + } + + $this->nested_transaction_counter = null; + $result = MDB2_OK; + + // transaction has not yet been rolled back + if ($this->in_transaction) { + if ($force_rollback || $this->has_transaction_error) { + $result = $this->rollback(); + if (!PEAR::isError($result)) { + $result = false; + } + } else { + $result = $this->commit(); + } + } + $this->has_transaction_error = false; + return $result; + } + + // }}} + // {{{ function failNestedTransaction($error = null, $immediately = false) + + /** + * Force setting nested transaction to failed. + * + * @param mixed value to return in getNestededTransactionError() + * @param bool if the transaction should be rolled back immediately + * @return bool MDB2_OK + * + * @access public + * @since 2.1.1 + */ + function failNestedTransaction($error = null, $immediately = false) + { + if (null !== $error) { + $error = $this->has_transaction_error ? $this->has_transaction_error : true; + } elseif (!$error) { + $error = true; + } + $this->has_transaction_error = $error; + if (!$immediately) { + return MDB2_OK; + } + return $this->rollback(); + } + + // }}} + // {{{ function getNestedTransactionError() + + /** + * The first error that occured since the transaction start. + * + * @return MDB2_Error|bool MDB2 error object if an error occured or false. + * + * @access public + * @since 2.1.1 + */ + function getNestedTransactionError() + { + return $this->has_transaction_error; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name is exists? + * + * @param string $name name of the database that should be checked + * + * @return mixed true/false on success, a MDB2 error on failure + * @access public + */ + function databaseExists($name) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ setCharset($charset, $connection = null) + + /** + * Set the charset on the current connection + * + * @param string charset + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function disconnect($force = true) + + /** + * Log out and disconnect from the database. + * + * @param boolean $force whether the disconnect should be forced even if the + * connection is opened persistently + * + * @return mixed true on success, false if not connected and error object on error + * + * @access public + */ + function disconnect($force = true) + { + $this->connection = 0; + $this->connected_dsn = array(); + $this->connected_database_name = ''; + $this->opened_persistent = null; + $this->connected_server_info = ''; + $this->in_transaction = null; + $this->nested_transaction_counter = null; + return MDB2_OK; + } + + // }}} + // {{{ function setDatabase($name) + + /** + * Select a different database + * + * @param string name of the database that should be selected + * + * @return string name of the database previously connected to + * + * @access public + */ + function setDatabase($name) + { + $previous_database_name = (isset($this->database_name)) ? $this->database_name : ''; + $this->database_name = $name; + if (!empty($this->connected_database_name) && ($this->connected_database_name != $this->database_name)) { + $this->disconnect(false); + } + return $previous_database_name; + } + + // }}} + // {{{ function getDatabase() + + /** + * Get the current database + * + * @return string name of the database + * + * @access public + */ + function getDatabase() + { + return $this->database_name; + } + + // }}} + // {{{ function setDSN($dsn) + + /** + * set the DSN + * + * @param mixed DSN string or array + * + * @return MDB2_OK + * + * @access public + */ + function setDSN($dsn) + { + $dsn_default = $GLOBALS['_MDB2_dsninfo_default']; + $dsn = MDB2::parseDSN($dsn); + if (array_key_exists('database', $dsn)) { + $this->database_name = $dsn['database']; + unset($dsn['database']); + } + $this->dsn = array_merge($dsn_default, $dsn); + return $this->disconnect(false); + } + + // }}} + // {{{ function getDSN($type = 'string', $hidepw = false) + + /** + * return the DSN as a string + * + * @param string format to return ("array", "string") + * @param string string to hide the password with + * + * @return mixed DSN in the chosen type + * + * @access public + */ + function getDSN($type = 'string', $hidepw = false) + { + $dsn = array_merge($GLOBALS['_MDB2_dsninfo_default'], $this->dsn); + $dsn['phptype'] = $this->phptype; + $dsn['database'] = $this->database_name; + if ($hidepw) { + $dsn['password'] = $hidepw; + } + switch ($type) { + // expand to include all possible options + case 'string': + $dsn = $dsn['phptype']. + ($dsn['dbsyntax'] ? ('('.$dsn['dbsyntax'].')') : ''). + '://'.$dsn['username'].':'. + $dsn['password'].'@'.$dsn['hostspec']. + ($dsn['port'] ? (':'.$dsn['port']) : ''). + '/'.$dsn['database']; + break; + case 'array': + default: + break; + } + return $dsn; + } + + // }}} + // {{{ _isNewLinkSet() + + /** + * Check if the 'new_link' option is set + * + * @return boolean + * + * @access protected + */ + function _isNewLinkSet() + { + return (isset($this->dsn['new_link']) + && ($this->dsn['new_link'] === true + || (is_string($this->dsn['new_link']) && preg_match('/^true$/i', $this->dsn['new_link'])) + || (is_numeric($this->dsn['new_link']) && 0 != (int)$this->dsn['new_link']) + ) + ); + } + + // }}} + // {{{ function &standaloneQuery($query, $types = null, $is_manip = false) + + /** + * execute a query as database administrator + * + * @param string the SQL query + * @param mixed array that contains the types of the columns in + * the result set + * @param bool if the query is a manipulation query + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function standaloneQuery($query, $types = null, $is_manip = false) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result = $this->_doQuery($query, $is_manip, $connection, false); + if (PEAR::isError($result)) { + return $result; + } + + if ($is_manip) { + $affected_rows = $this->_affectedRows($connection, $result); + return $affected_rows; + } + $result = $this->_wrapResult($result, $types, true, false, $limit, $offset); + return $result; + } + + // }}} + // {{{ function _modifyQuery($query, $is_manip, $limit, $offset) + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string query to modify + * @param bool if it is a DML query + * @param int limit the number of rows + * @param int start reading from given offset + * + * @return string modified query + * + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + return $query; + } + + // }}} + // {{{ function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + + /** + * Execute a query + * @param string query + * @param bool if the query is a manipulation query + * @param resource connection handle + * @param string database name + * + * @return result or error object + * + * @access protected + */ + function _doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $err = $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $err; + } + + // }}} + // {{{ function _affectedRows($connection, $result = null) + + /** + * Returns the number of rows affected + * + * @param resource result handle + * @param resource connection handle + * + * @return mixed MDB2 Error Object or the number of rows affected + * + * @access private + */ + function _affectedRows($connection, $result = null) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function &exec($query) + + /** + * Execute a manipulation query to the database and return the number of affected rows + * + * @param string the SQL query + * + * @return mixed number of affected rows on success, a MDB2 error on failure + * + * @access public + */ + function exec($query) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, true, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result = $this->_doQuery($query, true, $connection, $this->database_name); + if (PEAR::isError($result)) { + return $result; + } + + $affectedRows = $this->_affectedRows($connection, $result); + return $affectedRows; + } + + // }}} + // {{{ function &query($query, $types = null, $result_class = true, $result_wrap_class = false) + + /** + * Send a query to the database and return any results + * + * @param string the SQL query + * @param mixed array that contains the types of the columns in + * the result set + * @param mixed string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * + * @return mixed an MDB2_Result handle on success, a MDB2 error on failure + * + * @access public + */ + function query($query, $types = null, $result_class = true, $result_wrap_class = false) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, false, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result = $this->_doQuery($query, false, $connection, $this->database_name); + if (PEAR::isError($result)) { + return $result; + } + + $result = $this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset); + return $result; + } + + // }}} + // {{{ function _wrapResult($result_resource, $types = array(), $result_class = true, $result_wrap_class = false, $limit = null, $offset = null) + + /** + * wrap a result set into the correct class + * + * @param resource result handle + * @param mixed array that contains the types of the columns in + * the result set + * @param mixed string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * @param string number of rows to select + * @param string first row to select + * + * @return mixed an MDB2_Result, a MDB2 error on failure + * + * @access protected + */ + function _wrapResult($result_resource, $types = array(), $result_class = true, + $result_wrap_class = false, $limit = null, $offset = null) + { + if ($types === true) { + if ($this->supports('result_introspection')) { + $this->loadModule('Reverse', null, true); + $tableInfo = $this->reverse->tableInfo($result_resource); + if (PEAR::isError($tableInfo)) { + return $tableInfo; + } + $types = array(); + foreach ($tableInfo as $field) { + $types[] = $field['mdb2type']; + } + } else { + $types = null; + } + } + + if ($result_class === true) { + $result_class = $this->options['result_buffering'] + ? $this->options['buffered_result_class'] : $this->options['result_class']; + } + + if ($result_class) { + $class_name = sprintf($result_class, $this->phptype); + if (!MDB2::classExists($class_name)) { + $err = $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class does not exist '.$class_name, __FUNCTION__); + return $err; + } + $result = new $class_name($this, $result_resource, $limit, $offset); + if (!MDB2::isResultCommon($result)) { + $err = $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class is not extended from MDB2_Result_Common', __FUNCTION__); + return $err; + } + if (!empty($types)) { + $err = $result->setResultTypes($types); + if (PEAR::isError($err)) { + $result->free(); + return $err; + } + } + } + if ($result_wrap_class === true) { + $result_wrap_class = $this->options['result_wrap_class']; + } + if ($result_wrap_class) { + if (!MDB2::classExists($result_wrap_class)) { + $err = $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result wrap class does not exist '.$result_wrap_class, __FUNCTION__); + return $err; + } + $result = new $result_wrap_class($result_resource, $this->fetchmode); + } + return $result; + } + + // }}} + // {{{ function getServerVersion($native = false) + + /** + * return version information about the server + * + * @param bool determines if the raw version string should be returned + * + * @return mixed array with version information or row string + * + * @access public + */ + function getServerVersion($native = false) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function setLimit($limit, $offset = null) + + /** + * set the range of the next query + * + * @param string number of rows to select + * @param string first row to select + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function setLimit($limit, $offset = null) + { + if (!$this->supports('limit_queries')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'limit is not supported by this driver', __FUNCTION__); + } + $limit = (int)$limit; + if ($limit < 0) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid selected range row limit', __FUNCTION__); + } + $this->limit = $limit; + if (null !== $offset) { + $offset = (int)$offset; + if ($offset < 0) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid first selected range row', __FUNCTION__); + } + $this->offset = $offset; + } + return MDB2_OK; + } + + // }}} + // {{{ function subSelect($query, $type = false) + + /** + * simple subselect emulation: leaves the query untouched for all RDBMS + * that support subselects + * + * @param string the SQL query for the subselect that may only + * return a column + * @param string determines type of the field + * + * @return string the query + * + * @access public + */ + function subSelect($query, $type = false) + { + if ($this->supports('sub_selects') === true) { + return $query; + } + + if (!$this->supports('sub_selects')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $col = $this->queryCol($query, $type); + if (PEAR::isError($col)) { + return $col; + } + if (!is_array($col) || count($col) == 0) { + return 'NULL'; + } + if ($type) { + $this->loadModule('Datatype', null, true); + return $this->datatype->implodeArray($col, $type); + } + return implode(', ', $col); + } + + // }}} + // {{{ function replace($table, $fields) + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the old row is deleted before the new row is inserted. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL and SQLite implement it natively, this type of + * query isemulated through this method for other DBMS using standard types + * of queries inside a transaction to assure the atomicity of the operation. + * + * @param string name of the table on which the REPLACE query will + * be executed. + * @param array associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. + * The values of the array are also associative arrays that describe + * the values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: this property is required unless the Null property is + * set to 1. + * + * type + * Name of the type of the field. Currently, all types MDB2 + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * bool property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * bool property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function replace($table, $fields) + { + if (!$this->supports('replace')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'replace query is not supported', __FUNCTION__); + } + $count = count($fields); + $condition = $values = array(); + for ($colnum = 0, reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + } + $values[$name] = $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $condition[] = $this->quoteIdentifier($name, true) . '=' . $value; + } + } + if (empty($condition)) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $result = null; + $in_transaction = $this->in_transaction; + if (!$in_transaction && PEAR::isError($result = $this->beginTransaction())) { + return $result; + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $condition = ' WHERE '.implode(' AND ', $condition); + $query = 'DELETE FROM ' . $this->quoteIdentifier($table, true) . $condition; + $result = $this->_doQuery($query, true, $connection); + if (!PEAR::isError($result)) { + $affected_rows = $this->_affectedRows($connection, $result); + $insert = ''; + foreach ($values as $key => $value) { + $insert .= ($insert?', ':'') . $this->quoteIdentifier($key, true); + } + $values = implode(', ', $values); + $query = 'INSERT INTO '. $this->quoteIdentifier($table, true) . "($insert) VALUES ($values)"; + $result = $this->_doQuery($query, true, $connection); + if (!PEAR::isError($result)) { + $affected_rows += $this->_affectedRows($connection, $result);; + } + } + + if (!$in_transaction) { + if (PEAR::isError($result)) { + $this->rollback(); + } else { + $result = $this->commit(); + } + } + + if (PEAR::isError($result)) { + return $result; + } + + return $affected_rows; + } + + // }}} + // {{{ function &prepare($query, $types = null, $result_types = null, $lobs = array()) + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string the query to prepare + * @param mixed array that contains the types of the placeholders + * @param mixed array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed key (field) value (parameter) pair for all lob placeholders + * + * @return mixed resource handle for the prepared query on success, + * a MDB2 error on failure + * + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (null === $placeholder_type) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (null === $placeholder_type) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + if (is_int(key($types))) { + $types_tmp = $types; + $types = array(); + $count = -1; + } + } else { + $types = array_values($types); + } + } + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $positions[$p_position] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + // use parameter name in type array + if (isset($count) && isset($types_tmp[++$count])) { + $types[$parameter] = $types_tmp[$count]; + } + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $class_name = 'MDB2_Statement_'.$this->phptype; + $statement = null; + $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ function _skipDelimitedStrings($query, $position, $p_position) + + /** + * Utility method, used by prepare() to avoid replacing placeholders within delimited strings. + * Check if the placeholder is contained within a delimited string. + * If so, skip it and advance the position, otherwise return the current position, + * which is valid + * + * @param string $query + * @param integer $position current string cursor position + * @param integer $p_position placeholder position + * + * @return mixed integer $new_position on success + * MDB2_Error on failure + * + * @access protected + */ + function _skipDelimitedStrings($query, $position, $p_position) + { + $ignores = array(); + $ignores[] = $this->string_quoting; + $ignores[] = $this->identifier_quoting; + $ignores = array_merge($ignores, $this->sql_comments); + + foreach ($ignores as $ignore) { + if (!empty($ignore['start'])) { + if (is_int($start_quote = strpos($query, $ignore['start'], $position)) && $start_quote < $p_position) { + $end_quote = $start_quote; + do { + if (!is_int($end_quote = strpos($query, $ignore['end'], $end_quote + 1))) { + if ($ignore['end'] === "\n") { + $end_quote = strlen($query) - 1; + } else { + $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'query with an unterminated text string specified', __FUNCTION__); + return $err; + } + } + } while ($ignore['escape'] + && $end_quote-1 != $start_quote + && $query[($end_quote - 1)] == $ignore['escape'] + && ( $ignore['escape_pattern'] !== $ignore['escape'] + || $query[($end_quote - 2)] != $ignore['escape']) + ); + + $position = $end_quote + 1; + return $position; + } + } + } + return $position; + } + + // }}} + // {{{ function quote($value, $type = null, $quote = true) + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string text string value that is intended to be converted. + * @param string type to which the value should be converted to + * @param bool quote + * @param bool escape wildcards + * + * @return string text string that represents the given argument value in + * a DBMS specific format. + * + * @access public + */ + function quote($value, $type = null, $quote = true, $escape_wildcards = false) + { + $result = $this->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + return $this->datatype->quote($value, $type, $quote, $escape_wildcards); + } + + // }}} + // {{{ function getDeclaration($type, $name, $field) + + /** + * Obtain DBMS specific SQL code portion needed to declare + * of the given type + * + * @param string type to which the value should be converted to + * @param string name the field to be declared. + * @param string definition of the field + * + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * + * @access public + */ + function getDeclaration($type, $name, $field) + { + $result = $this->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + return $this->datatype->getDeclaration($type, $name, $field); + } + + // }}} + // {{{ function compareDefinition($current, $previous) + + /** + * Obtain an array of changes that may need to applied + * + * @param array new definition + * @param array old definition + * + * @return array containing all changes that will need to be applied + * + * @access public + */ + function compareDefinition($current, $previous) + { + $result = $this->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + return $this->datatype->compareDefinition($current, $previous); + } + + // }}} + // {{{ function supports($feature) + + /** + * Tell whether a DB implementation or its backend extension + * supports a given feature. + * + * @param string name of the feature (see the MDB2 class doc) + * + * @return bool|string if this DB implementation supports a given feature + * false means no, true means native, + * 'emulated' means emulated + * + * @access public + */ + function supports($feature) + { + if (array_key_exists($feature, $this->supported)) { + return $this->supported[$feature]; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown support feature $feature", __FUNCTION__); + } + + // }}} + // {{{ function getSequenceName($sqn) + + /** + * adds sequence name formatting to a sequence name + * + * @param string name of the sequence + * + * @return string formatted sequence name + * + * @access public + */ + function getSequenceName($sqn) + { + return sprintf($this->options['seqname_format'], + preg_replace('/[^a-z0-9_\-\$.]/i', '_', $sqn)); + } + + // }}} + // {{{ function getIndexName($idx) + + /** + * adds index name formatting to a index name + * + * @param string name of the index + * + * @return string formatted index name + * + * @access public + */ + function getIndexName($idx) + { + return sprintf($this->options['idxname_format'], + preg_replace('/[^a-z0-9_\-\$.]/i', '_', $idx)); + } + + // }}} + // {{{ function nextID($seq_name, $ondemand = true) + + /** + * Returns the next free id of a sequence + * + * @param string name of the sequence + * @param bool when true missing sequences are automatic created + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function lastInsertID($table = null, $field = null) + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function lastInsertID($table = null, $field = null) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function currID($seq_name) + + /** + * Returns the current id of a sequence + * + * @param string name of the sequence + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function currID($seq_name) + { + $this->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + return $this->nextID($seq_name); + } + + // }}} + // {{{ function queryOne($query, $type = null, $colnum = 0) + + /** + * Execute the specified query, fetch the value from the first column of + * the first row of the result set and then frees + * the result set. + * + * @param string $query the SELECT query statement to be executed. + * @param string $type optional argument that specifies the expected + * datatype of the result set field, so that an eventual + * conversion may be performed. The default datatype is + * text, meaning that no conversion is performed + * @param mixed $colnum the column number (or name) to fetch + * + * @return mixed MDB2_OK or field value on success, a MDB2 error on failure + * + * @access public + */ + function queryOne($query, $type = null, $colnum = 0) + { + $result = $this->query($query, $type); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $one = $result->fetchOne($colnum); + $result->free(); + return $one; + } + + // }}} + // {{{ function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + + /** + * Execute the specified query, fetch the values from the first + * row of the result set into an array and then frees + * the result set. + * + * @param string the SELECT query statement to be executed. + * @param array optional array argument that specifies a list of + * expected datatypes of the result set columns, so that the eventual + * conversions may be performed. The default list of datatypes is + * empty, meaning that no conversion is performed. + * @param int how the array data should be indexed + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $result = $this->query($query, $types); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $row = $result->fetchRow($fetchmode); + $result->free(); + return $row; + } + + // }}} + // {{{ function queryCol($query, $type = null, $colnum = 0) + + /** + * Execute the specified query, fetch the value from the first column of + * each row of the result set into an array and then frees the result set. + * + * @param string $query the SELECT query statement to be executed. + * @param string $type optional argument that specifies the expected + * datatype of the result set field, so that an eventual + * conversion may be performed. The default datatype is text, + * meaning that no conversion is performed + * @param mixed $colnum the column number (or name) to fetch + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * @access public + */ + function queryCol($query, $type = null, $colnum = 0) + { + $result = $this->query($query, $type); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $col = $result->fetchCol($colnum); + $result->free(); + return $col; + } + + // }}} + // {{{ function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) + + /** + * Execute the specified query, fetch all the rows of the result set into + * a two dimensional array and then frees the result set. + * + * @param string the SELECT query statement to be executed. + * @param array optional array argument that specifies a list of + * expected datatypes of the result set columns, so that the eventual + * conversions may be performed. The default list of datatypes is + * empty, meaning that no conversion is performed. + * @param int how the array data should be indexed + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, + $rekey = false, $force_array = false, $group = false) + { + $result = $this->query($query, $types); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); + $result->free(); + return $all; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Result + +/** + * The dummy class that all user space result classes should extend from + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result +{ +} + +// }}} +// {{{ class MDB2_Result_Common extends MDB2_Result + +/** + * The common result class for MDB2 result objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_Common extends MDB2_Result +{ + // {{{ Variables (Properties) + + var $db; + var $result; + var $rownum = -1; + var $types = array(); + var $values = array(); + var $offset; + var $offset_count = 0; + var $limit; + var $column_names; + + // }}} + // {{{ constructor: function __construct($db, &$result, $limit = 0, $offset = 0) + + /** + * Constructor + */ + function __construct($db, &$result, $limit = 0, $offset = 0) + { + $this->db = $db; + $this->result = $result; + $this->offset = $offset; + $this->limit = max(0, $limit - 1); + } + + // }}} + // {{{ function setResultTypes($types) + + /** + * Define the list of types to be associated with the columns of a given + * result set. + * + * This function may be called before invoking fetchRow(), fetchOne(), + * fetchCol() and fetchAll() so that the necessary data type + * conversions are performed on the data to be retrieved by them. If this + * function is not called, the type of all result set columns is assumed + * to be text, thus leading to not perform any conversions. + * + * @param array variable that lists the + * data types to be expected in the result set columns. If this array + * contains less types than the number of columns that are returned + * in the result set, the remaining columns are assumed to be of the + * type text. Currently, the types clob and blob are not fully + * supported. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function setResultTypes($types) + { + $load = $this->db->loadModule('Datatype', null, true); + if (PEAR::isError($load)) { + return $load; + } + $types = $this->db->datatype->checkResultTypes($types); + if (PEAR::isError($types)) { + return $types; + } + $this->types = $types; + return MDB2_OK; + } + + // }}} + // {{{ function seek($rownum = 0) + + /** + * Seek to a specific row in a result set + * + * @param int number of the row where the data can be found + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function seek($rownum = 0) + { + $target_rownum = $rownum - 1; + if ($this->rownum > $target_rownum) { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'seeking to previous rows not implemented', __FUNCTION__); + } + while ($this->rownum < $target_rownum) { + $this->fetchRow(); + } + return MDB2_OK; + } + + // }}} + // {{{ function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + + /** + * Fetch and return a row of data + * + * @param int how the array data should be indexed + * @param int number of the row where the data can be found + * + * @return int data array on success, a MDB2 error on failure + * + * @access public + */ + function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + $err = $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $err; + } + + // }}} + // {{{ function fetchOne($colnum = 0) + + /** + * fetch single column from the next row from a result set + * + * @param int|string the column number (or name) to fetch + * @param int number of the row where the data can be found + * + * @return string data on success, a MDB2 error on failure + * @access public + */ + function fetchOne($colnum = 0, $rownum = null) + { + $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; + $row = $this->fetchRow($fetchmode, $rownum); + if (!is_array($row) || PEAR::isError($row)) { + return $row; + } + if (!array_key_exists($colnum, $row)) { + return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'column is not defined in the result set: '.$colnum, __FUNCTION__); + } + return $row[$colnum]; + } + + // }}} + // {{{ function fetchCol($colnum = 0) + + /** + * Fetch and return a column from the current row pointer position + * + * @param int|string the column number (or name) to fetch + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function fetchCol($colnum = 0) + { + $column = array(); + $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; + $row = $this->fetchRow($fetchmode); + if (is_array($row)) { + if (!array_key_exists($colnum, $row)) { + return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'column is not defined in the result set: '.$colnum, __FUNCTION__); + } + do { + $column[] = $row[$colnum]; + } while (is_array($row = $this->fetchRow($fetchmode))); + } + if (PEAR::isError($row)) { + return $row; + } + return $column; + } + + // }}} + // {{{ function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) + + /** + * Fetch and return all rows from the current row pointer position + * + * @param int $fetchmode the fetch mode to use: + * + MDB2_FETCHMODE_ORDERED + * + MDB2_FETCHMODE_ASSOC + * + MDB2_FETCHMODE_ORDERED | MDB2_FETCHMODE_FLIPPED + * + MDB2_FETCHMODE_ASSOC | MDB2_FETCHMODE_FLIPPED + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return mixed data array on success, a MDB2 error on failure + * + * @access public + * @see getAssoc() + */ + function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, + $force_array = false, $group = false) + { + $all = array(); + $row = $this->fetchRow($fetchmode); + if (PEAR::isError($row)) { + return $row; + } elseif (!$row) { + return $all; + } + + $shift_array = $rekey ? false : null; + if (null !== $shift_array) { + if (is_object($row)) { + $colnum = count(get_object_vars($row)); + } else { + $colnum = count($row); + } + if ($colnum < 2) { + return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'rekey feature requires atleast 2 column', __FUNCTION__); + } + $shift_array = (!$force_array && $colnum == 2); + } + + if ($rekey) { + do { + if (is_object($row)) { + $arr = get_object_vars($row); + $key = reset($arr); + unset($row->{$key}); + } else { + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $key = reset($row); + unset($row[key($row)]); + } else { + $key = array_shift($row); + } + if ($shift_array) { + $row = array_shift($row); + } + } + if ($group) { + $all[$key][] = $row; + } else { + $all[$key] = $row; + } + } while (($row = $this->fetchRow($fetchmode))); + } elseif ($fetchmode & MDB2_FETCHMODE_FLIPPED) { + do { + foreach ($row as $key => $val) { + $all[$key][] = $val; + } + } while (($row = $this->fetchRow($fetchmode))); + } else { + do { + $all[] = $row; + } while (($row = $this->fetchRow($fetchmode))); + } + + return $all; + } + + // }}} + // {{{ function rowCount() + /** + * Returns the actual row number that was last fetched (count from 0) + * @return int + * + * @access public + */ + function rowCount() + { + return $this->rownum + 1; + } + + // }}} + // {{{ function numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * + * @access public + */ + function numRows() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * + * @access public + */ + function nextResult() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result or + * from the cache. + * + * @param bool If set to true the values are the column names, + * otherwise the names of the columns are the keys. + * @return mixed Array variable that holds the names of columns or an + * MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * + * @access public + */ + function getColumnNames($flip = false) + { + if (!isset($this->column_names)) { + $result = $this->_getColumnNames(); + if (PEAR::isError($result)) { + return $result; + } + $this->column_names = $result; + } + if ($flip) { + return array_flip($this->column_names); + } + return $this->column_names; + } + + // }}} + // {{{ function _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * + * @access private + */ + function _getColumnNames() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * + * @access public + */ + function numCols() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function getResource() + + /** + * return the resource associated with the result object + * + * @return resource + * + * @access public + */ + function getResource() + { + return $this->result; + } + + // }}} + // {{{ function bindColumn($column, &$value, $type = null) + + /** + * Set bind variable to a column. + * + * @param int column number or name + * @param mixed variable reference + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindColumn($column, &$value, $type = null) + { + if (!is_numeric($column)) { + $column_names = $this->getColumnNames(); + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($this->db->options['field_case'] == CASE_LOWER) { + $column = strtolower($column); + } else { + $column = strtoupper($column); + } + } + $column = $column_names[$column]; + } + $this->values[$column] =& $value; + if (null !== $type) { + $this->types[$column] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function _assignBindColumns($row) + + /** + * Bind a variable to a value in the result row. + * + * @param array row data + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access private + */ + function _assignBindColumns($row) + { + $row = array_values($row); + foreach ($row as $column => $value) { + if (array_key_exists($column, $this->values)) { + $this->values[$column] = $value; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function free() + + /** + * Free the internal resources associated with result. + * + * @return bool true on success, false if result is invalid + * + * @access public + */ + function free() + { + $this->result = false; + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Row + +/** + * The simple class that accepts row data as an array + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Row +{ + // {{{ constructor: function __construct(&$row) + + /** + * constructor + * + * @param resource row data as array + */ + function __construct(&$row) + { + foreach ($row as $key => $value) { + $this->$key = &$row[$key]; + } + } + + // }}} +} + +// }}} +// {{{ class MDB2_Statement_Common + +/** + * The common statement class for MDB2 statement objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_Common +{ + // {{{ Variables (Properties) + + var $db; + var $statement; + var $query; + var $result_types; + var $types; + var $values = array(); + var $limit; + var $offset; + var $is_manip; + + // }}} + // {{{ constructor: function __construct($db, $statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + + /** + * Constructor + */ + function __construct($db, $statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + { + $this->db = $db; + $this->statement = $statement; + $this->positions = $positions; + $this->query = $query; + $this->types = (array)$types; + $this->result_types = (array)$result_types; + $this->limit = $limit; + $this->is_manip = $is_manip; + $this->offset = $offset; + } + + // }}} + // {{{ function bindValue($parameter, &$value, $type = null) + + /** + * Set the value of a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed value that is meant to be assigned to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindValue($parameter, $value, $type = null) + { + if (!is_numeric($parameter)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (!in_array($parameter, $this->positions)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] = $value; + if (null !== $type) { + $this->types[$parameter] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function bindValueArray($values, $types = null) + + /** + * Set the values of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindValue() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindValueArray($values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $this->db->pushErrorHandling(PEAR_ERROR_RETURN); + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + $err = $this->bindValue($parameter, $values[$parameter], $types[$key]); + $this->db->popExpect(); + $this->db->popErrorHandling(); + if (PEAR::isError($err)) { + if ($err->getCode() == MDB2_ERROR_NOT_FOUND) { + //ignore (extra value for missing placeholder) + continue; + } + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function bindParam($parameter, &$value, $type = null) + + /** + * Bind a variable to a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed variable that is meant to be bound to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindParam($parameter, &$value, $type = null) + { + if (!is_numeric($parameter)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (!in_array($parameter, $this->positions)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] =& $value; + if (null !== $type) { + $this->types[$parameter] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function bindParamArray(&$values, $types = null) + + /** + * Bind the variables of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindParamArray(&$values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $err = $this->bindParam($parameter, $values[$parameter], $types[$key]); + if (PEAR::isError($err)) { + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding + * to the number of the position of the parameter. + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access public + */ + function execute($values = null, $result_class = true, $result_wrap_class = false) + { + if (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + $values = (array)$values; + if (!empty($values)) { + $err = $this->bindValueArray($values); + if (PEAR::isError($err)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__); + } + } + $result = $this->_execute($result_class, $result_wrap_class); + return $result; + } + + // }}} + // {{{ function _execute($result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement helper method. + * + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access private + */ + function _execute($result_class = true, $result_wrap_class = false) + { + $this->last_query = $this->query; + $query = ''; + $last_position = 0; + foreach ($this->positions as $current_position => $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $query.= substr($this->query, $last_position, $current_position - $last_position); + if (!isset($value)) { + $value_quoted = 'NULL'; + } else { + $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null; + $value_quoted = $this->db->quote($value, $type); + if (PEAR::isError($value_quoted)) { + return $value_quoted; + } + } + $query.= $value_quoted; + $last_position = $current_position + 1; + } + $query.= substr($this->query, $last_position); + + $this->db->offset = $this->offset; + $this->db->limit = $this->limit; + if ($this->is_manip) { + $result = $this->db->exec($query); + } else { + $result = $this->db->query($query, $this->result_types, $result_class, $result_wrap_class); + } + return $result; + } + + // }}} + // {{{ function free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function free() + { + if (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + $this->statement = null; + $this->positions = null; + $this->query = null; + $this->types = null; + $this->result_types = null; + $this->limit = null; + $this->is_manip = null; + $this->offset = null; + $this->values = null; + + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Module_Common + +/** + * The common modules class for MDB2 module objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Module_Common +{ + // {{{ Variables (Properties) + + /** + * contains the key to the global MDB2 instance array of the associated + * MDB2 instance + * + * @var int + * @access protected + */ + var $db_index; + + // }}} + // {{{ constructor: function __construct($db_index) + + /** + * Constructor + */ + function __construct($db_index) + { + $this->db_index = $db_index; + } + + // }}} + // {{{ function getDBInstance() + + /** + * Get the instance of MDB2 associated with the module instance + * + * @return object MDB2 instance or a MDB2 error on failure + * + * @access public + */ + function getDBInstance() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $result = $GLOBALS['_MDB2_databases'][$this->db_index]; + } else { + $result = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'could not find MDB2 instance'); + } + return $result; + } + + // }}} +} + +// }}} +// {{{ function MDB2_closeOpenTransactions() + +/** + * Close any open transactions form persistent connections + * + * @return void + * + * @access public + */ + +function MDB2_closeOpenTransactions() +{ + reset($GLOBALS['_MDB2_databases']); + while (next($GLOBALS['_MDB2_databases'])) { + $key = key($GLOBALS['_MDB2_databases']); + if ($GLOBALS['_MDB2_databases'][$key]->opened_persistent + && $GLOBALS['_MDB2_databases'][$key]->in_transaction + ) { + $GLOBALS['_MDB2_databases'][$key]->rollback(); + } + } +} + +// }}} +// {{{ function MDB2_defaultDebugOutput(&$db, $scope, $message, $is_manip = null) + +/** + * default debug output handler + * + * @param object reference to an MDB2 database object + * @param string usually the method name that triggered the debug call: + * for example 'query', 'prepare', 'execute', 'parameters', + * 'beginTransaction', 'commit', 'rollback' + * @param string message that should be appended to the debug variable + * @param array contains context information about the debug() call + * common keys are: is_manip, time, result etc. + * + * @return void|string optionally return a modified message, this allows + * rewriting a query before being issued or prepared + * + * @access public + */ +function MDB2_defaultDebugOutput(&$db, $scope, $message, $context = array()) +{ + $db->debug_output.= $scope.'('.$db->db_index.'): '; + $db->debug_output.= $message.$db->getOption('log_line_break'); + return $message; +} + +// }}} +?> \ No newline at end of file diff --git a/3rdparty/MDB2/Date.php b/3rdparty/MDB2/Date.php index ce846543a5..d874531150 100644 --- a/3rdparty/MDB2/Date.php +++ b/3rdparty/MDB2/Date.php @@ -1,183 +1,183 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Date.php,v 1.10 2006/03/01 12:15:32 lsmith Exp $ -// - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - */ - -/** - * Several methods to convert the MDB2 native timestamp format (ISO based) - * to and from data structures that are convenient to worth with in side of php. - * For more complex date arithmetic please take a look at the Date package in PEAR - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Date -{ - // {{{ mdbNow() - - /** - * return the current datetime - * - * @return string current datetime in the MDB2 format - * @access public - */ - function mdbNow() - { - return date('Y-m-d H:i:s'); - } - // }}} - - // {{{ mdbToday() - - /** - * return the current date - * - * @return string current date in the MDB2 format - * @access public - */ - function mdbToday() - { - return date('Y-m-d'); - } - // }}} - - // {{{ mdbTime() - - /** - * return the current time - * - * @return string current time in the MDB2 format - * @access public - */ - function mdbTime() - { - return date('H:i:s'); - } - // }}} - - // {{{ date2Mdbstamp() - - /** - * convert a date into a MDB2 timestamp - * - * @param int hour of the date - * @param int minute of the date - * @param int second of the date - * @param int month of the date - * @param int day of the date - * @param int year of the date - * - * @return string a valid MDB2 timestamp - * @access public - */ - function date2Mdbstamp($hour = null, $minute = null, $second = null, - $month = null, $day = null, $year = null) - { - return MDB2_Date::unix2Mdbstamp(mktime($hour, $minute, $second, $month, $day, $year, -1)); - } - // }}} - - // {{{ unix2Mdbstamp() - - /** - * convert a unix timestamp into a MDB2 timestamp - * - * @param int a valid unix timestamp - * - * @return string a valid MDB2 timestamp - * @access public - */ - function unix2Mdbstamp($unix_timestamp) - { - return date('Y-m-d H:i:s', $unix_timestamp); - } - // }}} - - // {{{ mdbstamp2Unix() - - /** - * convert a MDB2 timestamp into a unix timestamp - * - * @param int a valid MDB2 timestamp - * @return string unix timestamp with the time stored in the MDB2 format - * - * @access public - */ - function mdbstamp2Unix($mdb_timestamp) - { - $arr = MDB2_Date::mdbstamp2Date($mdb_timestamp); - - return mktime($arr['hour'], $arr['minute'], $arr['second'], $arr['month'], $arr['day'], $arr['year'], -1); - } - // }}} - - // {{{ mdbstamp2Date() - - /** - * convert a MDB2 timestamp into an array containing all - * values necessary to pass to php's date() function - * - * @param int a valid MDB2 timestamp - * - * @return array with the time split - * @access public - */ - function mdbstamp2Date($mdb_timestamp) - { - list($arr['year'], $arr['month'], $arr['day'], $arr['hour'], $arr['minute'], $arr['second']) = - sscanf($mdb_timestamp, "%04u-%02u-%02u %02u:%02u:%02u"); - return $arr; - } - // }}} -} - -?> + | +// +----------------------------------------------------------------------+ +// +// $Id: Date.php 208329 2006-03-01 12:15:38Z lsmith $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Several methods to convert the MDB2 native timestamp format (ISO based) + * to and from data structures that are convenient to worth with in side of php. + * For more complex date arithmetic please take a look at the Date package in PEAR + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Date +{ + // {{{ mdbNow() + + /** + * return the current datetime + * + * @return string current datetime in the MDB2 format + * @access public + */ + function mdbNow() + { + return date('Y-m-d H:i:s'); + } + // }}} + + // {{{ mdbToday() + + /** + * return the current date + * + * @return string current date in the MDB2 format + * @access public + */ + function mdbToday() + { + return date('Y-m-d'); + } + // }}} + + // {{{ mdbTime() + + /** + * return the current time + * + * @return string current time in the MDB2 format + * @access public + */ + function mdbTime() + { + return date('H:i:s'); + } + // }}} + + // {{{ date2Mdbstamp() + + /** + * convert a date into a MDB2 timestamp + * + * @param int hour of the date + * @param int minute of the date + * @param int second of the date + * @param int month of the date + * @param int day of the date + * @param int year of the date + * + * @return string a valid MDB2 timestamp + * @access public + */ + function date2Mdbstamp($hour = null, $minute = null, $second = null, + $month = null, $day = null, $year = null) + { + return MDB2_Date::unix2Mdbstamp(mktime($hour, $minute, $second, $month, $day, $year, -1)); + } + // }}} + + // {{{ unix2Mdbstamp() + + /** + * convert a unix timestamp into a MDB2 timestamp + * + * @param int a valid unix timestamp + * + * @return string a valid MDB2 timestamp + * @access public + */ + function unix2Mdbstamp($unix_timestamp) + { + return date('Y-m-d H:i:s', $unix_timestamp); + } + // }}} + + // {{{ mdbstamp2Unix() + + /** + * convert a MDB2 timestamp into a unix timestamp + * + * @param int a valid MDB2 timestamp + * @return string unix timestamp with the time stored in the MDB2 format + * + * @access public + */ + function mdbstamp2Unix($mdb_timestamp) + { + $arr = MDB2_Date::mdbstamp2Date($mdb_timestamp); + + return mktime($arr['hour'], $arr['minute'], $arr['second'], $arr['month'], $arr['day'], $arr['year'], -1); + } + // }}} + + // {{{ mdbstamp2Date() + + /** + * convert a MDB2 timestamp into an array containing all + * values necessary to pass to php's date() function + * + * @param int a valid MDB2 timestamp + * + * @return array with the time split + * @access public + */ + function mdbstamp2Date($mdb_timestamp) + { + list($arr['year'], $arr['month'], $arr['day'], $arr['hour'], $arr['minute'], $arr['second']) = + sscanf($mdb_timestamp, "%04u-%02u-%02u %02u:%02u:%02u"); + return $arr; + } + // }}} +} + +?> diff --git a/3rdparty/MDB2/Driver/Datatype/Common.php b/3rdparty/MDB2/Driver/Datatype/Common.php index 85a74f64fc..db0cb45dd8 100644 --- a/3rdparty/MDB2/Driver/Datatype/Common.php +++ b/3rdparty/MDB2/Driver/Datatype/Common.php @@ -1,1824 +1,1841 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.139 2008/12/04 11:50:42 afz Exp $ - -require_once('MDB2/LOB.php'); - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - */ - -/** - * MDB2_Driver_Common: Base class that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Datatype'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Datatype_Common extends MDB2_Module_Common -{ - var $valid_default_values = array( - 'text' => '', - 'boolean' => true, - 'integer' => 0, - 'decimal' => 0.0, - 'float' => 0.0, - 'timestamp' => '1970-01-01 00:00:00', - 'time' => '00:00:00', - 'date' => '1970-01-01', - 'clob' => '', - 'blob' => '', - ); - - /** - * contains all LOB objects created with this MDB2 instance - * @var array - * @access protected - */ - var $lobs = array(); - - // }}} - // {{{ getValidTypes() - - /** - * Get the list of valid types - * - * This function returns an array of valid types as keys with the values - * being possible default values for all native datatypes and mapped types - * for custom datatypes. - * - * @return mixed array on success, a MDB2 error on failure - * @access public - */ - function getValidTypes() - { - $types = $this->valid_default_values; - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (!empty($db->options['datatype_map'])) { - foreach ($db->options['datatype_map'] as $type => $mapped_type) { - if (array_key_exists($mapped_type, $types)) { - $types[$type] = $types[$mapped_type]; - } elseif (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('type' => $type, 'mapped_type' => $mapped_type); - $default = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - $types[$type] = $default; - } - } - } - return $types; - } - - // }}} - // {{{ checkResultTypes() - - /** - * Define the list of types to be associated with the columns of a given - * result set. - * - * This function may be called before invoking fetchRow(), fetchOne() - * fetchCole() and fetchAll() so that the necessary data type - * conversions are performed on the data to be retrieved by them. If this - * function is not called, the type of all result set columns is assumed - * to be text, thus leading to not perform any conversions. - * - * @param array $types array variable that lists the - * data types to be expected in the result set columns. If this array - * contains less types than the number of columns that are returned - * in the result set, the remaining columns are assumed to be of the - * type text. Currently, the types clob and blob are not fully - * supported. - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function checkResultTypes($types) - { - $types = is_array($types) ? $types : array($types); - foreach ($types as $key => $type) { - if (!isset($this->valid_default_values[$type])) { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (empty($db->options['datatype_map'][$type])) { - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - $type.' for '.$key.' is not a supported column type', __FUNCTION__); - } - } - } - return $types; - } - - // }}} - // {{{ _baseConvertResult() - - /** - * General type conversion method - * - * @param mixed $value reference to a value to be converted - * @param string $type specifies which type to convert to - * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text - * @return object an MDB2 error on failure - * @access protected - */ - function _baseConvertResult($value, $type, $rtrim = true) - { - switch ($type) { - case 'text': - if ($rtrim) { - $value = rtrim($value); - } - return $value; - case 'integer': - return intval($value); - case 'boolean': - return !empty($value); - case 'decimal': - return $value; - case 'float': - return doubleval($value); - case 'date': - return $value; - case 'time': - return $value; - case 'timestamp': - return $value; - case 'clob': - case 'blob': - $this->lobs[] = array( - 'buffer' => null, - 'position' => 0, - 'lob_index' => null, - 'endOfLOB' => false, - 'resource' => $value, - 'value' => null, - 'loaded' => false, - ); - end($this->lobs); - $lob_index = key($this->lobs); - $this->lobs[$lob_index]['lob_index'] = $lob_index; - return fopen('MDB2LOB://'.$lob_index.'@'.$this->db_index, 'r+'); - } - - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_INVALID, null, null, - 'attempt to convert result value to an unknown type :' . $type, __FUNCTION__); - } - - // }}} - // {{{ convertResult() - - /** - * Convert a value to a RDBMS indipendent MDB2 type - * - * @param mixed $value value to be converted - * @param string $type specifies which type to convert to - * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text - * @return mixed converted value - * @access public - */ - function convertResult($value, $type, $rtrim = true) - { - if (is_null($value)) { - return null; - } - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (!empty($db->options['datatype_map'][$type])) { - $type = $db->options['datatype_map'][$type]; - if (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('type' => $type, 'value' => $value, 'rtrim' => $rtrim); - return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - } - } - return $this->_baseConvertResult($value, $type, $rtrim); - } - - // }}} - // {{{ convertResultRow() - - /** - * Convert a result row - * - * @param array $types - * @param array $row specifies the types to convert to - * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text - * @return mixed MDB2_OK on success, an MDB2 error on failure - * @access public - */ - function convertResultRow($types, $row, $rtrim = true) - { - $types = $this->_sortResultFieldTypes(array_keys($row), $types); - foreach ($row as $key => $value) { - if (empty($types[$key])) { - continue; - } - $value = $this->convertResult($row[$key], $types[$key], $rtrim); - if (PEAR::isError($value)) { - return $value; - } - $row[$key] = $value; - } - return $row; - } - - // }}} - // {{{ _sortResultFieldTypes() - - /** - * convert a result row - * - * @param array $types - * @param array $row specifies the types to convert to - * @param bool $rtrim if to rtrim text values or not - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function _sortResultFieldTypes($columns, $types) - { - $n_cols = count($columns); - $n_types = count($types); - if ($n_cols > $n_types) { - for ($i= $n_cols - $n_types; $i >= 0; $i--) { - $types[] = null; - } - } - $sorted_types = array(); - foreach ($columns as $col) { - $sorted_types[$col] = null; - } - foreach ($types as $name => $type) { - if (array_key_exists($name, $sorted_types)) { - $sorted_types[$name] = $type; - unset($types[$name]); - } - } - // if there are left types in the array, fill the null values of the - // sorted array with them, in order. - if (count($types)) { - reset($types); - foreach (array_keys($sorted_types) as $k) { - if (is_null($sorted_types[$k])) { - $sorted_types[$k] = current($types); - next($types); - } - } - } - return $sorted_types; - } - - // }}} - // {{{ getDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare - * of the given type - * - * @param string $type type to which the value should be converted to - * @param string $name name the field to be declared. - * @param string $field definition of the field - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access public - */ - function getDeclaration($type, $name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!empty($db->options['datatype_map'][$type])) { - $type = $db->options['datatype_map'][$type]; - if (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('type' => $type, 'name' => $name, 'field' => $field); - return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - } - $field['type'] = $type; - } - - if (!method_exists($this, "_get{$type}Declaration")) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'type not defined: '.$type, __FUNCTION__); - } - return $this->{"_get{$type}Declaration"}($name, $field); - } - - // }}} - // {{{ getTypeDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an text type - * field to be used in statements like CREATE TABLE. - * - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access public - */ - function getTypeDeclaration($field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - switch ($field['type']) { - case 'text': - $length = !empty($field['length']) ? $field['length'] : $db->options['default_text_field_length']; - $fixed = !empty($field['fixed']) ? $field['fixed'] : false; - return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') - : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); - case 'clob': - return 'TEXT'; - case 'blob': - return 'TEXT'; - case 'integer': - return 'INT'; - case 'boolean': - return 'INT'; - case 'date': - return 'CHAR ('.strlen('YYYY-MM-DD').')'; - case 'time': - return 'CHAR ('.strlen('HH:MM:SS').')'; - case 'timestamp': - return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; - case 'float': - return 'TEXT'; - case 'decimal': - return 'TEXT'; - } - return ''; - } - - // }}} - // {{{ _getDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a generic type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * charset - * Text value with the default CHARACTER SET for this field. - * collation - * Text value with the default COLLATION for this field. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field, or a MDB2_Error on failure - * @access protected - */ - function _getDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $declaration_options = $db->datatype->_getDeclarationOptions($field); - if (PEAR::isError($declaration_options)) { - return $declaration_options; - } - return $name.' '.$this->getTypeDeclaration($field).$declaration_options; - } - - // }}} - // {{{ _getDeclarationOptions() - - /** - * Obtain DBMS specific SQL code portion needed to declare a generic type - * field to be used in statement like CREATE TABLE, without the field name - * and type values (ie. just the character set, default value, if the - * field is permitted to be NULL or not, and the collation options). - * - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Text value to be used as default for this field. - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * charset - * Text value with the default CHARACTER SET for this field. - * collation - * Text value with the default COLLATION for this field. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field's options. - * @access protected - */ - function _getDeclarationOptions($field) - { - $charset = empty($field['charset']) ? '' : - ' '.$this->_getCharsetFieldDeclaration($field['charset']); - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $default = ''; - if (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $valid_default_values = $this->getValidTypes(); - $field['default'] = $valid_default_values[$field['type']]; - if ($field['default'] === ''&& ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL)) { - $field['default'] = ' '; - } - } - if (!is_null($field['default'])) { - $default = ' DEFAULT ' . $this->quote($field['default'], $field['type']); - } - } - - $collation = empty($field['collation']) ? '' : - ' '.$this->_getCollationFieldDeclaration($field['collation']); - - return $charset.$default.$notnull.$collation; - } - - // }}} - // {{{ _getCharsetFieldDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $charset name of the charset - * @return string DBMS specific SQL code portion needed to set the CHARACTER SET - * of a field declaration. - */ - function _getCharsetFieldDeclaration($charset) - { - return ''; - } - - // }}} - // {{{ _getCollationFieldDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $collation name of the collation - * @return string DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration. - */ - function _getCollationFieldDeclaration($collation) - { - return ''; - } - - // }}} - // {{{ _getIntegerDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an integer type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field should be - * declared as unsigned integer if possible. - * - * default - * Integer value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getIntegerDeclaration($name, $field) - { - if (!empty($field['unsigned'])) { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; - } - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getTextDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an text type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getTextDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getCLOBDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an character - * large object type field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the large - * object field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access public - */ - function _getCLOBDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field).$notnull; - } - - // }}} - // {{{ _getBLOBDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an binary large - * object type field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the large - * object field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getBLOBDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field).$notnull; - } - - // }}} - // {{{ _getBooleanDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a boolean type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Boolean value to be used as default for this field. - * - * notnullL - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getBooleanDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getDateDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a date type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Date value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getDateDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getTimestampDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a timestamp - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Timestamp value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getTimestampDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getTimeDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a time - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Time value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getTimeDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getFloatDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a float type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Float value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getFloatDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ _getDecimalDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare a decimal type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Decimal value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getDecimalDeclaration($name, $field) - { - return $this->_getDeclaration($name, $field); - } - - // }}} - // {{{ compareDefinition() - - /** - * Obtain an array of changes that may need to applied - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access public - */ - function compareDefinition($current, $previous) - { - $type = !empty($current['type']) ? $current['type'] : null; - - if (!method_exists($this, "_compare{$type}Definition")) { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('current' => $current, 'previous' => $previous); - $change = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - return $change; - } - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'type "'.$current['type'].'" is not yet supported', __FUNCTION__); - } - - if (empty($previous['type']) || $previous['type'] != $type) { - return $current; - } - - $change = $this->{"_compare{$type}Definition"}($current, $previous); - - if ($previous['type'] != $type) { - $change['type'] = true; - } - - $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false; - $notnull = !empty($current['notnull']) ? $current['notnull'] : false; - if ($previous_notnull != $notnull) { - $change['notnull'] = true; - } - - $previous_default = array_key_exists('default', $previous) ? $previous['default'] : - ($previous_notnull ? '' : null); - $default = array_key_exists('default', $current) ? $current['default'] : - ($notnull ? '' : null); - if ($previous_default !== $default) { - $change['default'] = true; - } - - return $change; - } - - // }}} - // {{{ _compareIntegerDefinition() - - /** - * Obtain an array of changes that may need to applied to an integer field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareIntegerDefinition($current, $previous) - { - $change = array(); - $previous_unsigned = !empty($previous['unsigned']) ? $previous['unsigned'] : false; - $unsigned = !empty($current['unsigned']) ? $current['unsigned'] : false; - if ($previous_unsigned != $unsigned) { - $change['unsigned'] = true; - } - $previous_autoincrement = !empty($previous['autoincrement']) ? $previous['autoincrement'] : false; - $autoincrement = !empty($current['autoincrement']) ? $current['autoincrement'] : false; - if ($previous_autoincrement != $autoincrement) { - $change['autoincrement'] = true; - } - return $change; - } - - // }}} - // {{{ _compareTextDefinition() - - /** - * Obtain an array of changes that may need to applied to an text field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareTextDefinition($current, $previous) - { - $change = array(); - $previous_length = !empty($previous['length']) ? $previous['length'] : 0; - $length = !empty($current['length']) ? $current['length'] : 0; - if ($previous_length != $length) { - $change['length'] = true; - } - $previous_fixed = !empty($previous['fixed']) ? $previous['fixed'] : 0; - $fixed = !empty($current['fixed']) ? $current['fixed'] : 0; - if ($previous_fixed != $fixed) { - $change['fixed'] = true; - } - return $change; - } - - // }}} - // {{{ _compareCLOBDefinition() - - /** - * Obtain an array of changes that may need to applied to an CLOB field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareCLOBDefinition($current, $previous) - { - return $this->_compareTextDefinition($current, $previous); - } - - // }}} - // {{{ _compareBLOBDefinition() - - /** - * Obtain an array of changes that may need to applied to an BLOB field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareBLOBDefinition($current, $previous) - { - return $this->_compareTextDefinition($current, $previous); - } - - // }}} - // {{{ _compareDateDefinition() - - /** - * Obtain an array of changes that may need to applied to an date field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareDateDefinition($current, $previous) - { - return array(); - } - - // }}} - // {{{ _compareTimeDefinition() - - /** - * Obtain an array of changes that may need to applied to an time field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareTimeDefinition($current, $previous) - { - return array(); - } - - // }}} - // {{{ _compareTimestampDefinition() - - /** - * Obtain an array of changes that may need to applied to an timestamp field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareTimestampDefinition($current, $previous) - { - return array(); - } - - // }}} - // {{{ _compareBooleanDefinition() - - /** - * Obtain an array of changes that may need to applied to an boolean field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareBooleanDefinition($current, $previous) - { - return array(); - } - - // }}} - // {{{ _compareFloatDefinition() - - /** - * Obtain an array of changes that may need to applied to an float field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareFloatDefinition($current, $previous) - { - return array(); - } - - // }}} - // {{{ _compareDecimalDefinition() - - /** - * Obtain an array of changes that may need to applied to an decimal field - * - * @param array $current new definition - * @param array $previous old definition - * @return array containing all changes that will need to be applied - * @access protected - */ - function _compareDecimalDefinition($current, $previous) - { - return array(); - } - - // }}} - // {{{ quote() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param string $type type to which the value should be converted to - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access public - */ - function quote($value, $type = null, $quote = true, $escape_wildcards = false) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (is_null($value) - || ($value === '' && $db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) - ) { - if (!$quote) { - return null; - } - return 'NULL'; - } - - if (is_null($type)) { - switch (gettype($value)) { - case 'integer': - $type = 'integer'; - break; - case 'double': - // todo: default to decimal as float is quite unusual - // $type = 'float'; - $type = 'decimal'; - break; - case 'boolean': - $type = 'boolean'; - break; - case 'array': - $value = serialize($value); - case 'object': - $type = 'text'; - break; - default: - if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value)) { - $type = 'timestamp'; - } elseif (preg_match('/^\d{2}:\d{2}$/', $value)) { - $type = 'time'; - } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { - $type = 'date'; - } else { - $type = 'text'; - } - break; - } - } elseif (!empty($db->options['datatype_map'][$type])) { - $type = $db->options['datatype_map'][$type]; - if (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('type' => $type, 'value' => $value, 'quote' => $quote, 'escape_wildcards' => $escape_wildcards); - return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - } - } - - if (!method_exists($this, "_quote{$type}")) { - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'type not defined: '.$type, __FUNCTION__); - } - $value = $this->{"_quote{$type}"}($value, $quote, $escape_wildcards); - if ($quote && $escape_wildcards && $db->string_quoting['escape_pattern'] - && $db->string_quoting['escape'] !== $db->string_quoting['escape_pattern'] - ) { - $value.= $this->patternEscapeString(); - } - return $value; - } - - // }}} - // {{{ _quoteInteger() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteInteger($value, $quote, $escape_wildcards) - { - return (int)$value; - } - - // }}} - // {{{ _quoteText() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that already contains any DBMS specific - * escaped character sequences. - * @access protected - */ - function _quoteText($value, $quote, $escape_wildcards) - { - if (!$quote) { - return $value; - } - - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $value = $db->escape($value, $escape_wildcards); - if (PEAR::isError($value)) { - return $value; - } - return "'".$value."'"; - } - - // }}} - // {{{ _readFile() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _readFile($value) - { - $close = false; - if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { - $close = true; - if ($match[1] == 'file://') { - $value = $match[2]; - } - $value = @fopen($value, 'r'); - } - - if (is_resource($value)) { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $fp = $value; - $value = ''; - while (!@feof($fp)) { - $value.= @fread($fp, $db->options['lob_buffer_length']); - } - if ($close) { - @fclose($fp); - } - } - - return $value; - } - - // }}} - // {{{ _quoteLOB() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteLOB($value, $quote, $escape_wildcards) - { - $value = $this->_readFile($value); - if (PEAR::isError($value)) { - return $value; - } - return $this->_quoteText($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteCLOB() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteCLOB($value, $quote, $escape_wildcards) - { - return $this->_quoteLOB($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteBLOB() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteBLOB($value, $quote, $escape_wildcards) - { - return $this->_quoteLOB($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteBoolean() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteBoolean($value, $quote, $escape_wildcards) - { - return ($value ? 1 : 0); - } - - // }}} - // {{{ _quoteDate() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteDate($value, $quote, $escape_wildcards) - { - if ($value === 'CURRENT_DATE') { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { - return $db->function->now('date'); - } - return 'CURRENT_DATE'; - } - return $this->_quoteText($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteTimestamp() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteTimestamp($value, $quote, $escape_wildcards) - { - if ($value === 'CURRENT_TIMESTAMP') { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { - return $db->function->now('timestamp'); - } - return 'CURRENT_TIMESTAMP'; - } - return $this->_quoteText($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteTime() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteTime($value, $quote, $escape_wildcards) - { - if ($value === 'CURRENT_TIME') { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { - return $db->function->now('time'); - } - return 'CURRENT_TIME'; - } - return $this->_quoteText($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteFloat() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteFloat($value, $quote, $escape_wildcards) - { - if (preg_match('/^(.*)e([-+])(\d+)$/i', $value, $matches)) { - $decimal = $this->_quoteDecimal($matches[1], $quote, $escape_wildcards); - $sign = $matches[2]; - $exponent = str_pad($matches[3], 2, '0', STR_PAD_LEFT); - $value = $decimal.'E'.$sign.$exponent; - } else { - $value = $this->_quoteDecimal($value, $quote, $escape_wildcards); - } - return $value; - } - - // }}} - // {{{ _quoteDecimal() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteDecimal($value, $quote, $escape_wildcards) - { - $value = (string)$value; - $value = preg_replace('/[^\d\.,\-+eE]/', '', $value); - if (preg_match('/[^\.\d]/', $value)) { - if (strpos($value, ',')) { - // 1000,00 - if (!strpos($value, '.')) { - // convert the last "," to a "." - $value = strrev(str_replace(',', '.', strrev($value))); - // 1.000,00 - } elseif (strpos($value, '.') && strpos($value, '.') < strpos($value, ',')) { - $value = str_replace('.', '', $value); - // convert the last "," to a "." - $value = strrev(str_replace(',', '.', strrev($value))); - // 1,000.00 - } else { - $value = str_replace(',', '', $value); - } - } - } - return $value; - } - - // }}} - // {{{ writeLOBToFile() - - /** - * retrieve LOB from the database - * - * @param resource $lob stream handle - * @param string $file name of the file into which the LOb should be fetched - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access protected - */ - function writeLOBToFile($lob, $file) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) { - if ($match[1] == 'file://') { - $file = $match[2]; - } - } - - $fp = @fopen($file, 'wb'); - while (!@feof($lob)) { - $result = @fread($lob, $db->options['lob_buffer_length']); - $read = strlen($result); - if (@fwrite($fp, $result, $read) != $read) { - @fclose($fp); - return $db->raiseError(MDB2_ERROR, null, null, - 'could not write to the output file', __FUNCTION__); - } - } - @fclose($fp); - return MDB2_OK; - } - - // }}} - // {{{ _retrieveLOB() - - /** - * retrieve LOB from the database - * - * @param array $lob array - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access protected - */ - function _retrieveLOB(&$lob) - { - if (is_null($lob['value'])) { - $lob['value'] = $lob['resource']; - } - $lob['loaded'] = true; - return MDB2_OK; - } - - // }}} - // {{{ readLOB() - - /** - * Read data from large object input stream. - * - * @param resource $lob stream handle - * @param string $data reference to a variable that will hold data - * to be read from the large object input stream - * @param integer $length value that indicates the largest ammount ofdata - * to be read from the large object input stream. - * @return mixed the effective number of bytes read from the large object - * input stream on sucess or an MDB2 error object. - * @access public - * @see endOfLOB() - */ - function _readLOB($lob, $length) - { - return substr($lob['value'], $lob['position'], $length); - } - - // }}} - // {{{ _endOfLOB() - - /** - * Determine whether it was reached the end of the large object and - * therefore there is no more data to be read for the its input stream. - * - * @param array $lob array - * @return mixed true or false on success, a MDB2 error on failure - * @access protected - */ - function _endOfLOB($lob) - { - return $lob['endOfLOB']; - } - - // }}} - // {{{ destroyLOB() - - /** - * Free any resources allocated during the lifetime of the large object - * handler object. - * - * @param resource $lob stream handle - * @access public - */ - function destroyLOB($lob) - { - $lob_data = stream_get_meta_data($lob); - $lob_index = $lob_data['wrapper_data']->lob_index; - fclose($lob); - if (isset($this->lobs[$lob_index])) { - $this->_destroyLOB($this->lobs[$lob_index]); - unset($this->lobs[$lob_index]); - } - return MDB2_OK; - } - - // }}} - // {{{ _destroyLOB() - - /** - * Free any resources allocated during the lifetime of the large object - * handler object. - * - * @param array $lob array - * @access private - */ - function _destroyLOB(&$lob) - { - return MDB2_OK; - } - - // }}} - // {{{ implodeArray() - - /** - * apply a type to all values of an array and return as a comma seperated string - * useful for generating IN statements - * - * @access public - * - * @param array $array data array - * @param string $type determines type of the field - * - * @return string comma seperated values - */ - function implodeArray($array, $type = false) - { - if (!is_array($array) || empty($array)) { - return 'NULL'; - } - if ($type) { - foreach ($array as $value) { - $return[] = $this->quote($value, $type); - } - } else { - $return = $array; - } - return implode(', ', $return); - } - - // }}} - // {{{ matchPattern() - - /** - * build a pattern matching string - * - * @access public - * - * @param array $pattern even keys are strings, odd are patterns (% and _) - * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) - * @param string $field optional field name that is being matched against - * (might be required when emulating ILIKE) - * - * @return string SQL pattern - */ - function matchPattern($pattern, $operator = null, $field = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $match = ''; - if (!is_null($operator)) { - $operator = strtoupper($operator); - switch ($operator) { - // case insensitive - case 'ILIKE': - if (is_null($field)) { - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'case insensitive LIKE matching requires passing the field name', __FUNCTION__); - } - $db->loadModule('Function', null, true); - $match = $db->function->lower($field).' LIKE '; - break; - // case sensitive - case 'LIKE': - $match = is_null($field) ? 'LIKE ' : $field.' LIKE '; - break; - default: - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'not a supported operator type:'. $operator, __FUNCTION__); - } - } - $match.= "'"; - foreach ($pattern as $key => $value) { - if ($key % 2) { - $match.= $value; - } else { - if ($operator === 'ILIKE') { - $value = strtolower($value); - } - $escaped = $db->escape($value); - if (PEAR::isError($escaped)) { - return $escaped; - } - $match.= $db->escapePattern($escaped); - } - } - $match.= "'"; - $match.= $this->patternEscapeString(); - return $match; - } - - // }}} - // {{{ patternEscapeString() - - /** - * build string to define pattern escape character - * - * @access public - * - * @return string define pattern escape character - */ - function patternEscapeString() - { - return ''; - } - - // }}} - // {{{ mapNativeDatatype() - - /** - * Maps a native array description of a field to a MDB2 datatype and length - * - * @param array $field native field description - * @return array containing the various possible types, length, sign, fixed - * @access public - */ - function mapNativeDatatype($field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - // If the user has specified an option to map the native field - // type to a custom MDB2 datatype... - $db_type = strtok($field['type'], '(), '); - if (!empty($db->options['nativetype_map_callback'][$db_type])) { - return call_user_func_array($db->options['nativetype_map_callback'][$db_type], array($db, $field)); - } - - // Otherwise perform the built-in (i.e. normal) MDB2 native type to - // MDB2 datatype conversion - return $this->_mapNativeDatatype($field); - } - - // }}} - // {{{ _mapNativeDatatype() - - /** - * Maps a native array description of a field to a MDB2 datatype and length - * - * @param array $field native field description - * @return array containing the various possible types, length, sign, fixed - * @access public - */ - function _mapNativeDatatype($field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ mapPrepareDatatype() - - /** - * Maps an mdb2 datatype to mysqli prepare type - * - * @param string $type - * @return string - * @access public - */ - function mapPrepareDatatype($type) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!empty($db->options['datatype_map'][$type])) { - $type = $db->options['datatype_map'][$type]; - if (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('type' => $type); - return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - } - } - - return $type; - } -} -?> + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php 300551 2010-06-17 21:54:16Z quipo $ + +require_once 'MDB2/LOB.php'; + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * MDB2_Driver_Common: Base class that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Datatype'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_Common extends MDB2_Module_Common +{ + var $valid_default_values = array( + 'text' => '', + 'boolean' => true, + 'integer' => 0, + 'decimal' => 0.0, + 'float' => 0.0, + 'timestamp' => '1970-01-01 00:00:00', + 'time' => '00:00:00', + 'date' => '1970-01-01', + 'clob' => '', + 'blob' => '', + ); + + /** + * contains all LOB objects created with this MDB2 instance + * @var array + * @access protected + */ + var $lobs = array(); + + // }}} + // {{{ getValidTypes() + + /** + * Get the list of valid types + * + * This function returns an array of valid types as keys with the values + * being possible default values for all native datatypes and mapped types + * for custom datatypes. + * + * @return mixed array on success, a MDB2 error on failure + * @access public + */ + function getValidTypes() + { + $types = $this->valid_default_values; + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map'])) { + foreach ($db->options['datatype_map'] as $type => $mapped_type) { + if (array_key_exists($mapped_type, $types)) { + $types[$type] = $types[$mapped_type]; + } elseif (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'mapped_type' => $mapped_type); + $default = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + $types[$type] = $default; + } + } + } + return $types; + } + + // }}} + // {{{ checkResultTypes() + + /** + * Define the list of types to be associated with the columns of a given + * result set. + * + * This function may be called before invoking fetchRow(), fetchOne() + * fetchCole() and fetchAll() so that the necessary data type + * conversions are performed on the data to be retrieved by them. If this + * function is not called, the type of all result set columns is assumed + * to be text, thus leading to not perform any conversions. + * + * @param array $types array variable that lists the + * data types to be expected in the result set columns. If this array + * contains less types than the number of columns that are returned + * in the result set, the remaining columns are assumed to be of the + * type text. Currently, the types clob and blob are not fully + * supported. + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function checkResultTypes($types) + { + $types = is_array($types) ? $types : array($types); + foreach ($types as $key => $type) { + if (!isset($this->valid_default_values[$type])) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (empty($db->options['datatype_map'][$type])) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + $type.' for '.$key.' is not a supported column type', __FUNCTION__); + } + } + } + return $types; + } + + // }}} + // {{{ _baseConvertResult() + + /** + * General type conversion method + * + * @param mixed $value reference to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object an MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + switch ($type) { + case 'text': + if ($rtrim) { + $value = rtrim($value); + } + return $value; + case 'integer': + return intval($value); + case 'boolean': + return !empty($value); + case 'decimal': + return $value; + case 'float': + return doubleval($value); + case 'date': + return $value; + case 'time': + return $value; + case 'timestamp': + return $value; + case 'clob': + case 'blob': + $this->lobs[] = array( + 'buffer' => null, + 'position' => 0, + 'lob_index' => null, + 'endOfLOB' => false, + 'resource' => $value, + 'value' => null, + 'loaded' => false, + ); + end($this->lobs); + $lob_index = key($this->lobs); + $this->lobs[$lob_index]['lob_index'] = $lob_index; + return fopen('MDB2LOB://'.$lob_index.'@'.$this->db_index, 'r+'); + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_INVALID, null, null, + 'attempt to convert result value to an unknown type :' . $type, __FUNCTION__); + } + + // }}} + // {{{ convertResult() + + /** + * Convert a value to a RDBMS indipendent MDB2 type + * + * @param mixed $value value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return mixed converted value + * @access public + */ + function convertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'value' => $value, 'rtrim' => $rtrim); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + return $this->_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ convertResultRow() + + /** + * Convert a result row + * + * @param array $types + * @param array $row specifies the types to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return mixed MDB2_OK on success, an MDB2 error on failure + * @access public + */ + function convertResultRow($types, $row, $rtrim = true) + { + $types = $this->_sortResultFieldTypes(array_keys($row), $types); + foreach ($row as $key => $value) { + if (empty($types[$key])) { + continue; + } + $value = $this->convertResult($row[$key], $types[$key], $rtrim); + if (PEAR::isError($value)) { + return $value; + } + $row[$key] = $value; + } + return $row; + } + + // }}} + // {{{ _sortResultFieldTypes() + + /** + * convert a result row + * + * @param array $types + * @param array $row specifies the types to convert to + * @param bool $rtrim if to rtrim text values or not + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function _sortResultFieldTypes($columns, $types) + { + $n_cols = count($columns); + $n_types = count($types); + if ($n_cols > $n_types) { + for ($i= $n_cols - $n_types; $i >= 0; $i--) { + $types[] = null; + } + } + $sorted_types = array(); + foreach ($columns as $col) { + $sorted_types[$col] = null; + } + foreach ($types as $name => $type) { + if (array_key_exists($name, $sorted_types)) { + $sorted_types[$name] = $type; + unset($types[$name]); + } + } + // if there are left types in the array, fill the null values of the + // sorted array with them, in order. + if (count($types)) { + reset($types); + foreach (array_keys($sorted_types) as $k) { + if (null === $sorted_types[$k]) { + $sorted_types[$k] = current($types); + next($types); + } + } + } + return $sorted_types; + } + + // }}} + // {{{ getDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare + * of the given type + * + * @param string $type type to which the value should be converted to + * @param string $name name the field to be declared. + * @param string $field definition of the field + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getDeclaration($type, $name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'name' => $name, 'field' => $field); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + $field['type'] = $type; + } + + if (!method_exists($this, "_get{$type}Declaration")) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'type not defined: '.$type, __FUNCTION__); + } + return $this->{"_get{$type}Declaration"}($name, $field); + } + + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + $length = !empty($field['length']) ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + return 'TEXT'; + case 'blob': + return 'TEXT'; + case 'integer': + return 'INT'; + case 'boolean': + return 'INT'; + case 'date': + return 'CHAR ('.strlen('YYYY-MM-DD').')'; + case 'time': + return 'CHAR ('.strlen('HH:MM:SS').')'; + case 'timestamp': + return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; + case 'float': + return 'TEXT'; + case 'decimal': + return 'TEXT'; + } + return ''; + } + + // }}} + // {{{ _getDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field, or a MDB2_Error on failure + * @access protected + */ + function _getDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $declaration_options = $db->datatype->_getDeclarationOptions($field); + if (PEAR::isError($declaration_options)) { + return $declaration_options; + } + return $name.' '.$this->getTypeDeclaration($field).$declaration_options; + } + + // }}} + // {{{ _getDeclarationOptions() + + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statement like CREATE TABLE, without the field name + * and type values (ie. just the character set, default value, if the + * field is permitted to be NULL or not, and the collation options). + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Text value to be used as default for this field. + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field's options. + * @access protected + */ + function _getDeclarationOptions($field) + { + $charset = empty($field['charset']) ? '' : + ' '.$this->_getCharsetFieldDeclaration($field['charset']); + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $valid_default_values = $this->getValidTypes(); + $field['default'] = $valid_default_values[$field['type']]; + if ($field['default'] === '' && ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL)) { + $field['default'] = ' '; + } + } + if (null !== $field['default']) { + $default = ' DEFAULT ' . $this->quote($field['default'], $field['type']); + } + } + if (empty($default) && empty($notnull)) { + $default = ' DEFAULT NULL'; + } + + $collation = empty($field['collation']) ? '' : + ' '.$this->_getCollationFieldDeclaration($field['collation']); + + return $charset.$default.$notnull.$collation; + } + + // }}} + // {{{ _getCharsetFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the charset + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + function _getCharsetFieldDeclaration($charset) + { + return ''; + } + + // }}} + // {{{ _getCollationFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + function _getCollationFieldDeclaration($collation) + { + return ''; + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field should be + * declared as unsigned integer if possible. + * + * default + * Integer value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + if (!empty($field['unsigned'])) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTextDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTextDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function _getCLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getBLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBooleanDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a boolean type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnullL + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getBooleanDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getDateDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a date type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Date value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDateDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTimestampDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a timestamp + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Timestamp value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTimestampDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTimeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a time + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Time value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTimeDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getFloatDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a float type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Float value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getFloatDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getDecimalDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a decimal type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Decimal value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDecimalDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ compareDefinition() + + /** + * Obtain an array of changes that may need to applied + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access public + */ + function compareDefinition($current, $previous) + { + $type = !empty($current['type']) ? $current['type'] : null; + + if (!method_exists($this, "_compare{$type}Definition")) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('current' => $current, 'previous' => $previous); + $change = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + return $change; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type "'.$current['type'].'" is not yet supported', __FUNCTION__); + } + + if (empty($previous['type']) || $previous['type'] != $type) { + return $current; + } + + $change = $this->{"_compare{$type}Definition"}($current, $previous); + + if ($previous['type'] != $type) { + $change['type'] = true; + } + + $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false; + $notnull = !empty($current['notnull']) ? $current['notnull'] : false; + if ($previous_notnull != $notnull) { + $change['notnull'] = true; + } + + $previous_default = array_key_exists('default', $previous) ? $previous['default'] : + ($previous_notnull ? '' : null); + $default = array_key_exists('default', $current) ? $current['default'] : + ($notnull ? '' : null); + if ($previous_default !== $default) { + $change['default'] = true; + } + + return $change; + } + + // }}} + // {{{ _compareIntegerDefinition() + + /** + * Obtain an array of changes that may need to applied to an integer field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareIntegerDefinition($current, $previous) + { + $change = array(); + $previous_unsigned = !empty($previous['unsigned']) ? $previous['unsigned'] : false; + $unsigned = !empty($current['unsigned']) ? $current['unsigned'] : false; + if ($previous_unsigned != $unsigned) { + $change['unsigned'] = true; + } + $previous_autoincrement = !empty($previous['autoincrement']) ? $previous['autoincrement'] : false; + $autoincrement = !empty($current['autoincrement']) ? $current['autoincrement'] : false; + if ($previous_autoincrement != $autoincrement) { + $change['autoincrement'] = true; + } + return $change; + } + + // }}} + // {{{ _compareTextDefinition() + + /** + * Obtain an array of changes that may need to applied to an text field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTextDefinition($current, $previous) + { + $change = array(); + $previous_length = !empty($previous['length']) ? $previous['length'] : 0; + $length = !empty($current['length']) ? $current['length'] : 0; + if ($previous_length != $length) { + $change['length'] = true; + } + $previous_fixed = !empty($previous['fixed']) ? $previous['fixed'] : 0; + $fixed = !empty($current['fixed']) ? $current['fixed'] : 0; + if ($previous_fixed != $fixed) { + $change['fixed'] = true; + } + return $change; + } + + // }}} + // {{{ _compareCLOBDefinition() + + /** + * Obtain an array of changes that may need to applied to an CLOB field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareCLOBDefinition($current, $previous) + { + return $this->_compareTextDefinition($current, $previous); + } + + // }}} + // {{{ _compareBLOBDefinition() + + /** + * Obtain an array of changes that may need to applied to an BLOB field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareBLOBDefinition($current, $previous) + { + return $this->_compareTextDefinition($current, $previous); + } + + // }}} + // {{{ _compareDateDefinition() + + /** + * Obtain an array of changes that may need to applied to an date field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareDateDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareTimeDefinition() + + /** + * Obtain an array of changes that may need to applied to an time field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTimeDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareTimestampDefinition() + + /** + * Obtain an array of changes that may need to applied to an timestamp field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTimestampDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareBooleanDefinition() + + /** + * Obtain an array of changes that may need to applied to an boolean field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareBooleanDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareFloatDefinition() + + /** + * Obtain an array of changes that may need to applied to an float field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareFloatDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareDecimalDefinition() + + /** + * Obtain an array of changes that may need to applied to an decimal field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareDecimalDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ quote() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param string $type type to which the value should be converted to + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access public + */ + function quote($value, $type = null, $quote = true, $escape_wildcards = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ((null === $value) + || ($value === '' && $db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) + ) { + if (!$quote) { + return null; + } + return 'NULL'; + } + + if (null === $type) { + switch (gettype($value)) { + case 'integer': + $type = 'integer'; + break; + case 'double': + // todo: default to decimal as float is quite unusual + // $type = 'float'; + $type = 'decimal'; + break; + case 'boolean': + $type = 'boolean'; + break; + case 'array': + $value = serialize($value); + case 'object': + $type = 'text'; + break; + default: + if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value)) { + $type = 'timestamp'; + } elseif (preg_match('/^\d{2}:\d{2}$/', $value)) { + $type = 'time'; + } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + $type = 'date'; + } else { + $type = 'text'; + } + break; + } + } elseif (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'value' => $value, 'quote' => $quote, 'escape_wildcards' => $escape_wildcards); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + if (!method_exists($this, "_quote{$type}")) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type not defined: '.$type, __FUNCTION__); + } + $value = $this->{"_quote{$type}"}($value, $quote, $escape_wildcards); + if ($quote && $escape_wildcards && $db->string_quoting['escape_pattern'] + && $db->string_quoting['escape'] !== $db->string_quoting['escape_pattern'] + ) { + $value.= $this->patternEscapeString(); + } + return $value; + } + + // }}} + // {{{ _quoteInteger() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteInteger($value, $quote, $escape_wildcards) + { + return (int)$value; + } + + // }}} + // {{{ _quoteText() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that already contains any DBMS specific + * escaped character sequences. + * @access protected + */ + function _quoteText($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $value = $db->escape($value, $escape_wildcards); + if (PEAR::isError($value)) { + return $value; + } + return "'".$value."'"; + } + + // }}} + // {{{ _readFile() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _readFile($value) + { + $close = false; + if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + $close = true; + if (strtolower($match[1]) == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + } + + if (is_resource($value)) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $fp = $value; + $value = ''; + while (!@feof($fp)) { + $value.= @fread($fp, $db->options['lob_buffer_length']); + } + if ($close) { + @fclose($fp); + } + } + + return $value; + } + + // }}} + // {{{ _quoteLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteLOB($value, $quote, $escape_wildcards) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if ($db->options['lob_allow_url_include']) { + $value = $this->_readFile($value); + if (PEAR::isError($value)) { + return $value; + } + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + return $this->_quoteLOB($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + return $this->_quoteLOB($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBoolean() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBoolean($value, $quote, $escape_wildcards) + { + return ($value ? 1 : 0); + } + + // }}} + // {{{ _quoteDate() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDate($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_DATE') { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('date'); + } + return 'CURRENT_DATE'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTimestamp() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTimestamp($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_TIMESTAMP') { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('timestamp'); + } + return 'CURRENT_TIMESTAMP'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTime() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTime($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_TIME') { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('time'); + } + return 'CURRENT_TIME'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteFloat() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteFloat($value, $quote, $escape_wildcards) + { + if (preg_match('/^(.*)e([-+])(\d+)$/i', $value, $matches)) { + $decimal = $this->_quoteDecimal($matches[1], $quote, $escape_wildcards); + $sign = $matches[2]; + $exponent = str_pad($matches[3], 2, '0', STR_PAD_LEFT); + $value = $decimal.'E'.$sign.$exponent; + } else { + $value = $this->_quoteDecimal($value, $quote, $escape_wildcards); + } + return $value; + } + + // }}} + // {{{ _quoteDecimal() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDecimal($value, $quote, $escape_wildcards) + { + $value = (string)$value; + $value = preg_replace('/[^\d\.,\-+eE]/', '', $value); + if (preg_match('/[^\.\d]/', $value)) { + if (strpos($value, ',')) { + // 1000,00 + if (!strpos($value, '.')) { + // convert the last "," to a "." + $value = strrev(str_replace(',', '.', strrev($value))); + // 1.000,00 + } elseif (strpos($value, '.') && strpos($value, '.') < strpos($value, ',')) { + $value = str_replace('.', '', $value); + // convert the last "," to a "." + $value = strrev(str_replace(',', '.', strrev($value))); + // 1,000.00 + } else { + $value = str_replace(',', '', $value); + } + } + } + return $value; + } + + // }}} + // {{{ writeLOBToFile() + + /** + * retrieve LOB from the database + * + * @param resource $lob stream handle + * @param string $file name of the file into which the LOb should be fetched + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function writeLOBToFile($lob, $file) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) { + if ($match[1] == 'file://') { + $file = $match[2]; + } + } + + $fp = @fopen($file, 'wb'); + while (!@feof($lob)) { + $result = @fread($lob, $db->options['lob_buffer_length']); + $read = strlen($result); + if (@fwrite($fp, $result, $read) != $read) { + @fclose($fp); + return $db->raiseError(MDB2_ERROR, null, null, + 'could not write to the output file', __FUNCTION__); + } + } + @fclose($fp); + return MDB2_OK; + } + + // }}} + // {{{ _retrieveLOB() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _retrieveLOB(&$lob) + { + if (null === $lob['value']) { + $lob['value'] = $lob['resource']; + } + $lob['loaded'] = true; + return MDB2_OK; + } + + // }}} + // {{{ readLOB() + + /** + * Read data from large object input stream. + * + * @param resource $lob stream handle + * @param string $data reference to a variable that will hold data + * to be read from the large object input stream + * @param integer $length value that indicates the largest ammount ofdata + * to be read from the large object input stream. + * @return mixed the effective number of bytes read from the large object + * input stream on sucess or an MDB2 error object. + * @access public + * @see endOfLOB() + */ + function _readLOB($lob, $length) + { + return substr($lob['value'], $lob['position'], $length); + } + + // }}} + // {{{ _endOfLOB() + + /** + * Determine whether it was reached the end of the large object and + * therefore there is no more data to be read for the its input stream. + * + * @param array $lob array + * @return mixed true or false on success, a MDB2 error on failure + * @access protected + */ + function _endOfLOB($lob) + { + return $lob['endOfLOB']; + } + + // }}} + // {{{ destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param resource $lob stream handle + * @access public + */ + function destroyLOB($lob) + { + $lob_data = stream_get_meta_data($lob); + $lob_index = $lob_data['wrapper_data']->lob_index; + fclose($lob); + if (isset($this->lobs[$lob_index])) { + $this->_destroyLOB($this->lobs[$lob_index]); + unset($this->lobs[$lob_index]); + } + return MDB2_OK; + } + + // }}} + // {{{ _destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param array $lob array + * @access private + */ + function _destroyLOB(&$lob) + { + return MDB2_OK; + } + + // }}} + // {{{ implodeArray() + + /** + * apply a type to all values of an array and return as a comma seperated string + * useful for generating IN statements + * + * @access public + * + * @param array $array data array + * @param string $type determines type of the field + * + * @return string comma seperated values + */ + function implodeArray($array, $type = false) + { + if (!is_array($array) || empty($array)) { + return 'NULL'; + } + if ($type) { + foreach ($array as $value) { + $return[] = $this->quote($value, $type); + } + } else { + $return = $array; + } + return implode(', ', $return); + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (null !== $operator) { + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + if (null === $field) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'case insensitive LIKE matching requires passing the field name', __FUNCTION__); + } + $db->loadModule('Function', null, true); + $match = $db->function->lower($field).' LIKE '; + break; + case 'NOT ILIKE': + if (null === $field) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'case insensitive NOT ILIKE matching requires passing the field name', __FUNCTION__); + } + $db->loadModule('Function', null, true); + $match = $db->function->lower($field).' NOT LIKE '; + break; + // case sensitive + case 'LIKE': + $match = (null === $field) ? 'LIKE ' : ($field.' LIKE '); + break; + case 'NOT LIKE': + $match = (null === $field) ? 'NOT LIKE ' : ($field.' NOT LIKE '); + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + $escaped = $db->escape($value); + if (PEAR::isError($escaped)) { + return $escaped; + } + $match.= $db->escapePattern($escaped); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define pattern escape character + * + * @access public + * + * @return string define pattern escape character + */ + function patternEscapeString() + { + return ''; + } + + // }}} + // {{{ mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function mapNativeDatatype($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + // If the user has specified an option to map the native field + // type to a custom MDB2 datatype... + $db_type = strtok($field['type'], '(), '); + if (!empty($db->options['nativetype_map_callback'][$db_type])) { + return call_user_func_array($db->options['nativetype_map_callback'][$db_type], array($db, $field)); + } + + // Otherwise perform the built-in (i.e. normal) MDB2 native type to + // MDB2 datatype conversion + return $this->_mapNativeDatatype($field); + } + + // }}} + // {{{ _mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function _mapNativeDatatype($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ mapPrepareDatatype() + + /** + * Maps an mdb2 datatype to mysqli prepare type + * + * @param string $type + * @return string + * @access public + */ + function mapPrepareDatatype($type) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + return $type; + } +} +?> diff --git a/3rdparty/MDB2/Driver/Datatype/mysql.php b/3rdparty/MDB2/Driver/Datatype/mysql.php index 93dd40524e..299901ae6e 100644 --- a/3rdparty/MDB2/Driver/Datatype/mysql.php +++ b/3rdparty/MDB2/Driver/Datatype/mysql.php @@ -1,553 +1,562 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.65 2008/02/22 19:23:49 quipo Exp $ -// - -require_once('MDB2/Driver/Datatype/Common.php'); - -/** - * MDB2 MySQL driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common -{ - // {{{ _getCharsetFieldDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $charset name of the charset - * @return string DBMS specific SQL code portion needed to set the CHARACTER SET - * of a field declaration. - */ - function _getCharsetFieldDeclaration($charset) - { - return 'CHARACTER SET '.$charset; - } - - // }}} - // {{{ _getCollationFieldDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $collation name of the collation - * @return string DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration. - */ - function _getCollationFieldDeclaration($collation) - { - return 'COLLATE '.$collation; - } - - // }}} - // {{{ getTypeDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an text type - * field to be used in statements like CREATE TABLE. - * - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access public - */ - function getTypeDeclaration($field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - switch ($field['type']) { - case 'text': - if (empty($field['length']) && array_key_exists('default', $field)) { - $field['length'] = $db->varchar_max_length; - } - $length = !empty($field['length']) ? $field['length'] : false; - $fixed = !empty($field['fixed']) ? $field['fixed'] : false; - return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') - : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); - case 'clob': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYTEXT'; - } elseif ($length <= 65532) { - return 'TEXT'; - } elseif ($length <= 16777215) { - return 'MEDIUMTEXT'; - } - } - return 'LONGTEXT'; - case 'blob': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYBLOB'; - } elseif ($length <= 65532) { - return 'BLOB'; - } elseif ($length <= 16777215) { - return 'MEDIUMBLOB'; - } - } - return 'LONGBLOB'; - case 'integer': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 1) { - return 'TINYINT'; - } elseif ($length == 2) { - return 'SMALLINT'; - } elseif ($length == 3) { - return 'MEDIUMINT'; - } elseif ($length == 4) { - return 'INT'; - } elseif ($length > 4) { - return 'BIGINT'; - } - } - return 'INT'; - case 'boolean': - return 'TINYINT(1)'; - case 'date': - return 'DATE'; - case 'time': - return 'TIME'; - case 'timestamp': - return 'DATETIME'; - case 'float': - return 'DOUBLE'; - case 'decimal': - $length = !empty($field['length']) ? $field['length'] : 18; - $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; - return 'DECIMAL('.$length.','.$scale.')'; - } - return ''; - } - - // }}} - // {{{ _getIntegerDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an integer type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param string $field associative array with the name of the properties - * of the field being declared as array indexes. - * Currently, the types of supported field - * properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field - * should be declared as unsigned integer if - * possible. - * - * default - * Integer value to be used as default for this - * field. - * - * notnull - * Boolean flag that indicates whether this field is - * constrained to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getIntegerDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $default = $autoinc = ''; - if (!empty($field['autoincrement'])) { - $autoinc = ' AUTO_INCREMENT PRIMARY KEY'; - } elseif (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : 0; - } - $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; - } - - // }}} - // {{{ _getFloatDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an float type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param string $field associative array with the name of the properties - * of the field being declared as array indexes. - * Currently, the types of supported field - * properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field - * should be declared as unsigned float if - * possible. - * - * default - * float value to be used as default for this - * field. - * - * notnull - * Boolean flag that indicates whether this field is - * constrained to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getFloatDeclaration($name, $field) - { - // Since AUTO_INCREMENT can be used for integer or floating-point types, - // reuse the INTEGER declaration - // @see http://bugs.mysql.com/bug.php?id=31032 - return $this->_getIntegerDeclaration($name, $field); - } - - // }}} - // {{{ _getDecimalDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an decimal type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param string $field associative array with the name of the properties - * of the field being declared as array indexes. - * Currently, the types of supported field - * properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field - * should be declared as unsigned integer if - * possible. - * - * default - * Decimal value to be used as default for this - * field. - * - * notnull - * Boolean flag that indicates whether this field is - * constrained to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getDecimalDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $default = ''; - if (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : 0; - } - $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); - } elseif (empty($field['notnull'])) { - $default = ' DEFAULT NULL'; - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull; - } - - // }}} - // {{{ matchPattern() - - /** - * build a pattern matching string - * - * @access public - * - * @param array $pattern even keys are strings, odd are patterns (% and _) - * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) - * @param string $field optional field name that is being matched against - * (might be required when emulating ILIKE) - * - * @return string SQL pattern - */ - function matchPattern($pattern, $operator = null, $field = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $match = ''; - if (!is_null($operator)) { - $field = is_null($field) ? '' : $field.' '; - $operator = strtoupper($operator); - switch ($operator) { - // case insensitive - case 'ILIKE': - $match = $field.'LIKE '; - break; - // case sensitive - case 'LIKE': - $match = $field.'LIKE BINARY '; - break; - default: - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'not a supported operator type:'. $operator, __FUNCTION__); - } - } - $match.= "'"; - foreach ($pattern as $key => $value) { - if ($key % 2) { - $match.= $value; - } else { - $match.= $db->escapePattern($db->escape($value)); - } - } - $match.= "'"; - $match.= $this->patternEscapeString(); - return $match; - } - - // }}} - // {{{ _mapNativeDatatype() - - /** - * Maps a native array description of a field to a MDB2 datatype and length - * - * @param array $field native field description - * @return array containing the various possible types, length, sign, fixed - * @access public - */ - function _mapNativeDatatype($field) - { - $db_type = strtolower($field['type']); - $db_type = strtok($db_type, '(), '); - if ($db_type == 'national') { - $db_type = strtok('(), '); - } - if (!empty($field['length'])) { - $length = strtok($field['length'], ', '); - $decimal = strtok(', '); - } else { - $length = strtok('(), '); - $decimal = strtok('(), '); - } - $type = array(); - $unsigned = $fixed = null; - switch ($db_type) { - case 'tinyint': - $type[] = 'integer'; - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 1; - break; - case 'smallint': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 2; - break; - case 'mediumint': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 3; - break; - case 'int': - case 'integer': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 4; - break; - case 'bigint': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 8; - break; - case 'tinytext': - case 'mediumtext': - case 'longtext': - case 'text': - case 'varchar': - $fixed = false; - case 'string': - case 'char': - $type[] = 'text'; - if ($length == '1') { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } elseif (strstr($db_type, 'text')) { - $type[] = 'clob'; - if ($decimal == 'binary') { - $type[] = 'blob'; - } - $type = array_reverse($type); - } - if ($fixed !== false) { - $fixed = true; - } - break; - case 'enum': - $type[] = 'text'; - preg_match_all('/\'.+\'/U', $field['type'], $matches); - $length = 0; - $fixed = false; - if (is_array($matches)) { - foreach ($matches[0] as $value) { - $length = max($length, strlen($value)-2); - } - if ($length == '1' && count($matches[0]) == 2) { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } - } - $type[] = 'integer'; - case 'set': - $fixed = false; - $type[] = 'text'; - $type[] = 'integer'; - break; - case 'date': - $type[] = 'date'; - $length = null; - break; - case 'datetime': - case 'timestamp': - $type[] = 'timestamp'; - $length = null; - break; - case 'time': - $type[] = 'time'; - $length = null; - break; - case 'float': - case 'double': - case 'real': - $type[] = 'float'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - break; - case 'unknown': - case 'decimal': - case 'numeric': - $type[] = 'decimal'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - if ($decimal !== false) { - $length = $length.','.$decimal; - } - break; - case 'tinyblob': - case 'mediumblob': - case 'longblob': - case 'blob': - $type[] = 'blob'; - $length = null; - break; - case 'binary': - case 'varbinary': - $type[] = 'blob'; - break; - case 'year': - $type[] = 'integer'; - $type[] = 'date'; - $length = null; - break; - default: - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'unknown database attribute type: '.$db_type, __FUNCTION__); - } - - if ((int)$length <= 0) { - $length = null; - } - - return array($type, $length, $unsigned, $fixed); - } - - // }}} -} - + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common +{ + // {{{ _getCharsetFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the charset + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + function _getCharsetFieldDeclaration($charset) + { + return 'CHARACTER SET '.$charset; + } + + // }}} + // {{{ _getCollationFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + function _getCollationFieldDeclaration($collation) + { + return 'COLLATE '.$collation; + } + + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + if (empty($field['length']) && array_key_exists('default', $field)) { + $field['length'] = $db->varchar_max_length; + } + $length = !empty($field['length']) ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYTEXT'; + } elseif ($length <= 65532) { + return 'TEXT'; + } elseif ($length <= 16777215) { + return 'MEDIUMTEXT'; + } + } + return 'LONGTEXT'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYBLOB'; + } elseif ($length <= 65532) { + return 'BLOB'; + } elseif ($length <= 16777215) { + return 'MEDIUMBLOB'; + } + } + return 'LONGBLOB'; + case 'integer': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 1) { + return 'TINYINT'; + } elseif ($length == 2) { + return 'SMALLINT'; + } elseif ($length == 3) { + return 'MEDIUMINT'; + } elseif ($length == 4) { + return 'INT'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INT'; + case 'boolean': + return 'TINYINT(1)'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'DOUBLE'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'DECIMAL('.$length.','.$scale.')'; + } + return ''; + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned integer if + * possible. + * + * default + * Integer value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $default = $autoinc = ''; + if (!empty($field['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT PRIMARY KEY'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; + if (empty($default) && empty($notnull)) { + $default = ' DEFAULT NULL'; + } + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; + } + + // }}} + // {{{ _getFloatDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an float type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned float if + * possible. + * + * default + * float value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getFloatDeclaration($name, $field) + { + // Since AUTO_INCREMENT can be used for integer or floating-point types, + // reuse the INTEGER declaration + // @see http://bugs.mysql.com/bug.php?id=31032 + return $this->_getIntegerDeclaration($name, $field); + } + + // }}} + // {{{ _getDecimalDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an decimal type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned integer if + * possible. + * + * default + * Decimal value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDecimalDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull; + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (null !== $operator) { + $field = (null === $field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + case 'NOT ILIKE': + $match = $field.'NOT LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'LIKE BINARY '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE BINARY '; + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + $match.= $db->escapePattern($db->escape($value)); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ _mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function _mapNativeDatatype($field) + { + $db_type = strtolower($field['type']); + $db_type = strtok($db_type, '(), '); + if ($db_type == 'national') { + $db_type = strtok('(), '); + } + if (!empty($field['length'])) { + $length = strtok($field['length'], ', '); + $decimal = strtok(', '); + } else { + $length = strtok('(), '); + $decimal = strtok('(), '); + } + $type = array(); + $unsigned = $fixed = null; + switch ($db_type) { + case 'tinyint': + $type[] = 'integer'; + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 1; + break; + case 'smallint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 2; + break; + case 'mediumint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 3; + break; + case 'int': + case 'integer': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 4; + break; + case 'bigint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 8; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'varchar': + $fixed = false; + case 'string': + case 'char': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + if ($decimal == 'binary') { + $type[] = 'blob'; + } + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'enum': + $type[] = 'text'; + preg_match_all('/\'.+\'/U', $field['type'], $matches); + $length = 0; + $fixed = false; + if (is_array($matches)) { + foreach ($matches[0] as $value) { + $length = max($length, strlen($value)-2); + } + if ($length == '1' && count($matches[0]) == 2) { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + } + $type[] = 'integer'; + case 'set': + $fixed = false; + $type[] = 'text'; + $type[] = 'integer'; + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type[] = 'float'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + break; + case 'unknown': + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + if ($decimal !== false) { + $length = $length.','.$decimal; + } + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $length = null; + break; + case 'binary': + case 'varbinary': + $type[] = 'blob'; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + default: + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unknown database attribute type: '.$db_type, __FUNCTION__); + } + + if ((int)$length <= 0) { + $length = null; + } + + return array($type, $length, $unsigned, $fixed); + } + + // }}} +} + ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Datatype/pgsql.php b/3rdparty/MDB2/Driver/Datatype/pgsql.php index e72aa743b7..fec2c9799f 100644 --- a/3rdparty/MDB2/Driver/Datatype/pgsql.php +++ b/3rdparty/MDB2/Driver/Datatype/pgsql.php @@ -1,554 +1,575 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.93 2008/08/28 20:32:57 afz Exp $ - -require_once('MDB2/Driver/Datatype/Common.php'); - -/** - * MDB2 PostGreSQL driver - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_Driver_Datatype_pgsql extends MDB2_Driver_Datatype_Common -{ - // {{{ _baseConvertResult() - - /** - * General type conversion method - * - * @param mixed $value refernce to a value to be converted - * @param string $type specifies which type to convert to - * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text - * @return object a MDB2 error on failure - * @access protected - */ - function _baseConvertResult($value, $type, $rtrim = true) - { - if (is_null($value)) { - return null; - } - switch ($type) { - case 'boolean': - return $value == 't'; - case 'float': - return doubleval($value); - case 'date': - return $value; - case 'time': - return substr($value, 0, strlen('HH:MM:SS')); - case 'timestamp': - return substr($value, 0, strlen('YYYY-MM-DD HH:MM:SS')); - case 'blob': - $value = pg_unescape_bytea($value); - return parent::_baseConvertResult($value, $type, $rtrim); - } - return parent::_baseConvertResult($value, $type, $rtrim); - } - - // }}} - // {{{ getTypeDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an text type - * field to be used in statements like CREATE TABLE. - * - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access public - */ - function getTypeDeclaration($field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - switch ($field['type']) { - case 'text': - $length = !empty($field['length']) ? $field['length'] : false; - $fixed = !empty($field['fixed']) ? $field['fixed'] : false; - return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') - : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); - case 'clob': - return 'TEXT'; - case 'blob': - return 'BYTEA'; - case 'integer': - if (!empty($field['autoincrement'])) { - if (!empty($field['length'])) { - $length = $field['length']; - if ($length > 4) { - return 'BIGSERIAL PRIMARY KEY'; - } - } - return 'SERIAL PRIMARY KEY'; - } - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 2) { - return 'SMALLINT'; - } elseif ($length == 3 || $length == 4) { - return 'INT'; - } elseif ($length > 4) { - return 'BIGINT'; - } - } - return 'INT'; - case 'boolean': - return 'BOOLEAN'; - case 'date': - return 'DATE'; - case 'time': - return 'TIME without time zone'; - case 'timestamp': - return 'TIMESTAMP without time zone'; - case 'float': - return 'FLOAT8'; - case 'decimal': - $length = !empty($field['length']) ? $field['length'] : 18; - $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; - return 'NUMERIC('.$length.','.$scale.')'; - } - } - - // }}} - // {{{ _getIntegerDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an integer type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field should be - * declared as unsigned integer if possible. - * - * default - * Integer value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getIntegerDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!empty($field['unsigned'])) { - $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; - } - if (!empty($field['autoincrement'])) { - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field); - } - $default = ''; - if (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : 0; - } - $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field).$default.$notnull; - } - - // }}} - // {{{ _quoteCLOB() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteCLOB($value, $quote, $escape_wildcards) - { - return $this->_quoteText($value, $quote, $escape_wildcards); - } - - // }}} - // {{{ _quoteBLOB() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteBLOB($value, $quote, $escape_wildcards) - { - if (!$quote) { - return $value; - } - if (version_compare(PHP_VERSION, '5.2.0RC6', '>=')) { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $connection = $db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $value = @pg_escape_bytea($connection, $value); - } else { - $value = @pg_escape_bytea($value); - } - return "'".$value."'"; - } - - // }}} - // {{{ _quoteBoolean() - - /** - * Convert a text value into a DBMS specific format that is suitable to - * compose query statements. - * - * @param string $value text string value that is intended to be converted. - * @param bool $quote determines if the value should be quoted and escaped - * @param bool $escape_wildcards if to escape escape wildcards - * @return string text string that represents the given argument value in - * a DBMS specific format. - * @access protected - */ - function _quoteBoolean($value, $quote, $escape_wildcards) - { - $value = $value ? 't' : 'f'; - if (!$quote) { - return $value; - } - return "'".$value."'"; - } - - // }}} - // {{{ matchPattern() - - /** - * build a pattern matching string - * - * @access public - * - * @param array $pattern even keys are strings, odd are patterns (% and _) - * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) - * @param string $field optional field name that is being matched against - * (might be required when emulating ILIKE) - * - * @return string SQL pattern - */ - function matchPattern($pattern, $operator = null, $field = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $match = ''; - if (!is_null($operator)) { - $field = is_null($field) ? '' : $field.' '; - $operator = strtoupper($operator); - switch ($operator) { - // case insensitive - case 'ILIKE': - $match = $field.'ILIKE '; - break; - // case sensitive - case 'LIKE': - $match = $field.'LIKE '; - break; - default: - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'not a supported operator type:'. $operator, __FUNCTION__); - } - } - $match.= "'"; - foreach ($pattern as $key => $value) { - if ($key % 2) { - $match.= $value; - } else { - $match.= $db->escapePattern($db->escape($value)); - } - } - $match.= "'"; - $match.= $this->patternEscapeString(); - return $match; - } - - // }}} - // {{{ patternEscapeString() - - /** - * build string to define escape pattern string - * - * @access public - * - * - * @return string define escape pattern - */ - function patternEscapeString() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - return ' ESCAPE '.$this->quote($db->string_quoting['escape_pattern']); - } - - // }}} - // {{{ _mapNativeDatatype() - - /** - * Maps a native array description of a field to a MDB2 datatype and length - * - * @param array $field native field description - * @return array containing the various possible types, length, sign, fixed - * @access public - */ - function _mapNativeDatatype($field) - { - $db_type = strtolower($field['type']); - $length = $field['length']; - $type = array(); - $unsigned = $fixed = null; - switch ($db_type) { - case 'smallint': - case 'int2': - $type[] = 'integer'; - $unsigned = false; - $length = 2; - if ($length == '2') { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } - break; - case 'int': - case 'int4': - case 'integer': - case 'serial': - case 'serial4': - $type[] = 'integer'; - $unsigned = false; - $length = 4; - break; - case 'bigint': - case 'int8': - case 'bigserial': - case 'serial8': - $type[] = 'integer'; - $unsigned = false; - $length = 8; - break; - case 'bool': - case 'boolean': - $type[] = 'boolean'; - $length = null; - break; - case 'text': - case 'varchar': - $fixed = false; - case 'unknown': - case 'char': - case 'bpchar': - $type[] = 'text'; - if ($length == '1') { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } elseif (strstr($db_type, 'text')) { - $type[] = 'clob'; - $type = array_reverse($type); - } - if ($fixed !== false) { - $fixed = true; - } - break; - case 'date': - $type[] = 'date'; - $length = null; - break; - case 'datetime': - case 'timestamp': - case 'timestamptz': - $type[] = 'timestamp'; - $length = null; - break; - case 'time': - $type[] = 'time'; - $length = null; - break; - case 'float': - case 'float4': - case 'float8': - case 'double': - case 'real': - $type[] = 'float'; - break; - case 'decimal': - case 'money': - case 'numeric': - $type[] = 'decimal'; - if (isset($field['scale'])) { - $length = $length.','.$field['scale']; - } - break; - case 'tinyblob': - case 'mediumblob': - case 'longblob': - case 'blob': - case 'bytea': - $type[] = 'blob'; - $length = null; - break; - case 'oid': - $type[] = 'blob'; - $type[] = 'clob'; - $length = null; - break; - case 'year': - $type[] = 'integer'; - $type[] = 'date'; - $length = null; - break; - default: - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'unknown database attribute type: '.$db_type, __FUNCTION__); - } - - if ((int)$length <= 0) { - $length = null; - } - - return array($type, $length, $unsigned, $fixed); - } - - // }}} - // {{{ mapPrepareDatatype() - - /** - * Maps an mdb2 datatype to native prepare type - * - * @param string $type - * @return string - * @access public - */ - function mapPrepareDatatype($type) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!empty($db->options['datatype_map'][$type])) { - $type = $db->options['datatype_map'][$type]; - if (!empty($db->options['datatype_map_callback'][$type])) { - $parameter = array('type' => $type); - return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); - } - } - - switch ($type) { - case 'integer': - return 'int'; - case 'boolean': - return 'bool'; - case 'decimal': - case 'float': - return 'numeric'; - case 'clob': - return 'text'; - case 'blob': - return 'bytea'; - default: - break; - } - return $type; - } - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php 298763 2010-04-29 08:49:41Z afz $ + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 PostGreSQL driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_Datatype_pgsql extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * General type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'boolean': + return $value == 't'; + case 'float': + return doubleval($value); + case 'date': + return $value; + case 'time': + return substr($value, 0, strlen('HH:MM:SS')); + case 'timestamp': + return substr($value, 0, strlen('YYYY-MM-DD HH:MM:SS')); + case 'blob': + $value = pg_unescape_bytea($value); + return parent::_baseConvertResult($value, $type, $rtrim); + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + $length = !empty($field['length']) ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + return 'TEXT'; + case 'blob': + return 'BYTEA'; + case 'integer': + if (!empty($field['autoincrement'])) { + if (!empty($field['length'])) { + $length = $field['length']; + if ($length > 4) { + return 'BIGSERIAL PRIMARY KEY'; + } + } + return 'SERIAL PRIMARY KEY'; + } + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INT'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INT'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME without time zone'; + case 'timestamp': + return 'TIMESTAMP without time zone'; + case 'float': + return 'FLOAT8'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'NUMERIC('.$length.','.$scale.')'; + } + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field should be + * declared as unsigned integer if possible. + * + * default + * Integer value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($field['unsigned'])) { + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + if (!empty($field['autoincrement'])) { + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field); + } + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + if (empty($default) && empty($notnull)) { + $default = ' DEFAULT NULL'; + } + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$default.$notnull; + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $value = $this->_readFile($value, $db->options['lob_allow_url_include']); + if (PEAR::isError($value)) { + return $value; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $value = $this->_readFile($value, $db->options['lob_allow_url_include']); + if (PEAR::isError($value)) { + return $value; + } + if (version_compare(PHP_VERSION, '5.2.0RC6', '>=')) { + $connection = $db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $value = @pg_escape_bytea($connection, $value); + } else { + $value = @pg_escape_bytea($value); + } + return "'".$value."'"; + } + + // }}} + // {{{ _quoteBoolean() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBoolean($value, $quote, $escape_wildcards) + { + $value = $value ? 't' : 'f'; + if (!$quote) { + return $value; + } + return "'".$value."'"; + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (null !== $operator) { + $field = (null === $field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'ILIKE '; + break; + case 'NOT ILIKE': + $match = $field.'NOT ILIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'LIKE '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE '; + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + $match.= $db->escapePattern($db->escape($value)); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define escape pattern string + * + * @access public + * + * + * @return string define escape pattern + */ + function patternEscapeString() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + return ' ESCAPE '.$this->quote($db->string_quoting['escape_pattern']); + } + + // }}} + // {{{ _mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function _mapNativeDatatype($field) + { + $db_type = strtolower($field['type']); + $length = $field['length']; + $type = array(); + $unsigned = $fixed = null; + switch ($db_type) { + case 'smallint': + case 'int2': + $type[] = 'integer'; + $unsigned = false; + $length = 2; + if ($length == '2') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'int': + case 'int4': + case 'integer': + case 'serial': + case 'serial4': + $type[] = 'integer'; + $unsigned = false; + $length = 4; + break; + case 'bigint': + case 'int8': + case 'bigserial': + case 'serial8': + $type[] = 'integer'; + $unsigned = false; + $length = 8; + break; + case 'bool': + case 'boolean': + $type[] = 'boolean'; + $length = null; + break; + case 'text': + case 'varchar': + $fixed = false; + case 'unknown': + case 'char': + case 'bpchar': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + case 'timestamptz': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'float4': + case 'float8': + case 'double': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'money': + case 'numeric': + $type[] = 'decimal'; + if (isset($field['scale'])) { + $length = $length.','.$field['scale']; + } + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + case 'bytea': + $type[] = 'blob'; + $length = null; + break; + case 'oid': + $type[] = 'blob'; + $type[] = 'clob'; + $length = null; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + default: + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unknown database attribute type: '.$db_type, __FUNCTION__); + } + + if ((int)$length <= 0) { + $length = null; + } + + return array($type, $length, $unsigned, $fixed); + } + + // }}} + // {{{ mapPrepareDatatype() + + /** + * Maps an mdb2 datatype to native prepare type + * + * @param string $type + * @return string + * @access public + */ + function mapPrepareDatatype($type) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + switch ($type) { + case 'integer': + return 'int'; + case 'boolean': + return 'bool'; + case 'decimal': + case 'float': + return 'numeric'; + case 'clob': + return 'text'; + case 'blob': + return 'bytea'; + default: + break; + } + return $type; + } + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Datatype/sqlite.php b/3rdparty/MDB2/Driver/Datatype/sqlite.php index 9e57e7e567..e4711e46b3 100644 --- a/3rdparty/MDB2/Driver/Datatype/sqlite.php +++ b/3rdparty/MDB2/Driver/Datatype/sqlite.php @@ -1,409 +1,418 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: sqlite.php,v 1.67 2008/02/22 19:58:06 quipo Exp $ -// - -require_once('MDB2/Driver/Datatype/Common.php'); - -/** - * MDB2 SQLite driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Datatype_sqlite extends MDB2_Driver_Datatype_Common -{ - // {{{ _getCollationFieldDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $collation name of the collation - * - * @return string DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration. - */ - function _getCollationFieldDeclaration($collation) - { - return 'COLLATE '.$collation; - } - - // }}} - // {{{ getTypeDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an text type - * field to be used in statements like CREATE TABLE. - * - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access public - */ - function getTypeDeclaration($field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - switch ($field['type']) { - case 'text': - $length = !empty($field['length']) - ? $field['length'] : false; - $fixed = !empty($field['fixed']) ? $field['fixed'] : false; - return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') - : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); - case 'clob': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYTEXT'; - } elseif ($length <= 65532) { - return 'TEXT'; - } elseif ($length <= 16777215) { - return 'MEDIUMTEXT'; - } - } - return 'LONGTEXT'; - case 'blob': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYBLOB'; - } elseif ($length <= 65532) { - return 'BLOB'; - } elseif ($length <= 16777215) { - return 'MEDIUMBLOB'; - } - } - return 'LONGBLOB'; - case 'integer': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 2) { - return 'SMALLINT'; - } elseif ($length == 3 || $length == 4) { - return 'INTEGER'; - } elseif ($length > 4) { - return 'BIGINT'; - } - } - return 'INTEGER'; - case 'boolean': - return 'BOOLEAN'; - case 'date': - return 'DATE'; - case 'time': - return 'TIME'; - case 'timestamp': - return 'DATETIME'; - case 'float': - return 'DOUBLE'.($db->options['fixed_float'] ? '('. - ($db->options['fixed_float']+2).','.$db->options['fixed_float'].')' : ''); - case 'decimal': - $length = !empty($field['length']) ? $field['length'] : 18; - $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; - return 'DECIMAL('.$length.','.$scale.')'; - } - return ''; - } - - // }}} - // {{{ _getIntegerDeclaration() - - /** - * Obtain DBMS specific SQL code portion needed to declare an integer type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param string $field associative array with the name of the properties - * of the field being declared as array indexes. - * Currently, the types of supported field - * properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field - * should be declared as unsigned integer if - * possible. - * - * default - * Integer value to be used as default for this - * field. - * - * notnull - * Boolean flag that indicates whether this field is - * constrained to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - * @access protected - */ - function _getIntegerDeclaration($name, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $default = $autoinc = ''; - if (!empty($field['autoincrement'])) { - $autoinc = ' PRIMARY KEY'; - } elseif (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : 0; - } - $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; - $name = $db->quoteIdentifier($name, true); - return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; - } - - // }}} - // {{{ matchPattern() - - /** - * build a pattern matching string - * - * @access public - * - * @param array $pattern even keys are strings, odd are patterns (% and _) - * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) - * @param string $field optional field name that is being matched against - * (might be required when emulating ILIKE) - * - * @return string SQL pattern - */ - function matchPattern($pattern, $operator = null, $field = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $match = ''; - if (!is_null($operator)) { - $field = is_null($field) ? '' : $field.' '; - $operator = strtoupper($operator); - switch ($operator) { - // case insensitive - case 'ILIKE': - $match = $field.'LIKE '; - break; - // case sensitive - case 'LIKE': - $match = $field.'LIKE '; - break; - default: - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'not a supported operator type:'. $operator, __FUNCTION__); - } - } - $match.= "'"; - foreach ($pattern as $key => $value) { - if ($key % 2) { - $match.= $value; - } else { - $match.= $db->escapePattern($db->escape($value)); - } - } - $match.= "'"; - $match.= $this->patternEscapeString(); - return $match; - } - - // }}} - // {{{ _mapNativeDatatype() - - /** - * Maps a native array description of a field to a MDB2 datatype and length - * - * @param array $field native field description - * @return array containing the various possible types, length, sign, fixed - * @access public - */ - function _mapNativeDatatype($field) - { - $db_type = strtolower($field['type']); - $length = !empty($field['length']) ? $field['length'] : null; - $unsigned = !empty($field['unsigned']) ? $field['unsigned'] : null; - $fixed = null; - $type = array(); - switch ($db_type) { - case 'boolean': - $type[] = 'boolean'; - break; - case 'tinyint': - $type[] = 'integer'; - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 1; - break; - case 'smallint': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 2; - break; - case 'mediumint': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 3; - break; - case 'int': - case 'integer': - case 'serial': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 4; - break; - case 'bigint': - case 'bigserial': - $type[] = 'integer'; - $unsigned = preg_match('/ unsigned/i', $field['type']); - $length = 8; - break; - case 'clob': - $type[] = 'clob'; - $fixed = false; - break; - case 'tinytext': - case 'mediumtext': - case 'longtext': - case 'text': - case 'varchar': - case 'varchar2': - $fixed = false; - case 'char': - $type[] = 'text'; - if ($length == '1') { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } elseif (strstr($db_type, 'text')) { - $type[] = 'clob'; - $type = array_reverse($type); - } - if ($fixed !== false) { - $fixed = true; - } - break; - case 'date': - $type[] = 'date'; - $length = null; - break; - case 'datetime': - case 'timestamp': - $type[] = 'timestamp'; - $length = null; - break; - case 'time': - $type[] = 'time'; - $length = null; - break; - case 'float': - case 'double': - case 'real': - $type[] = 'float'; - break; - case 'decimal': - case 'numeric': - $type[] = 'decimal'; - $length = $length.','.$field['decimal']; - break; - case 'tinyblob': - case 'mediumblob': - case 'longblob': - case 'blob': - $type[] = 'blob'; - $length = null; - break; - case 'year': - $type[] = 'integer'; - $type[] = 'date'; - $length = null; - break; - default: - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'unknown database attribute type: '.$db_type, __FUNCTION__); - } - - if ((int)$length <= 0) { - $length = null; - } - - return array($type, $length, $unsigned, $fixed); - } - - // }}} -} - + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 SQLite driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_sqlite extends MDB2_Driver_Datatype_Common +{ + // {{{ _getCollationFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + function _getCollationFieldDeclaration($collation) + { + return 'COLLATE '.$collation; + } + + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + $length = !empty($field['length']) + ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYTEXT'; + } elseif ($length <= 65532) { + return 'TEXT'; + } elseif ($length <= 16777215) { + return 'MEDIUMTEXT'; + } + } + return 'LONGTEXT'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYBLOB'; + } elseif ($length <= 65532) { + return 'BLOB'; + } elseif ($length <= 16777215) { + return 'MEDIUMBLOB'; + } + } + return 'LONGBLOB'; + case 'integer': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INTEGER'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INTEGER'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'DOUBLE'.($db->options['fixed_float'] ? '('. + ($db->options['fixed_float']+2).','.$db->options['fixed_float'].')' : ''); + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'DECIMAL('.$length.','.$scale.')'; + } + return ''; + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned integer if + * possible. + * + * default + * Integer value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $default = $autoinc = ''; + if (!empty($field['autoincrement'])) { + $autoinc = ' PRIMARY KEY'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; + if (empty($default) && empty($notnull)) { + $default = ' DEFAULT NULL'; + } + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (null !== $operator) { + $field = (null === $field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + case 'NOT ILIKE': + $match = $field.'NOT LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'LIKE '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE '; + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + $match.= $db->escapePattern($db->escape($value)); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ _mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function _mapNativeDatatype($field) + { + $db_type = strtolower($field['type']); + $length = !empty($field['length']) ? $field['length'] : null; + $unsigned = !empty($field['unsigned']) ? $field['unsigned'] : null; + $fixed = null; + $type = array(); + switch ($db_type) { + case 'boolean': + $type[] = 'boolean'; + break; + case 'tinyint': + $type[] = 'integer'; + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 1; + break; + case 'smallint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 2; + break; + case 'mediumint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 3; + break; + case 'int': + case 'integer': + case 'serial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 4; + break; + case 'bigint': + case 'bigserial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 8; + break; + case 'clob': + $type[] = 'clob'; + $fixed = false; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'varchar': + case 'varchar2': + $fixed = false; + case 'char': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $length = $length.','.$field['decimal']; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $length = null; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + default: + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unknown database attribute type: '.$db_type, __FUNCTION__); + } + + if ((int)$length <= 0) { + $length = null; + } + + return array($type, $length, $unsigned, $fixed); + } + + // }}} +} + ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Function/Common.php b/3rdparty/MDB2/Driver/Function/Common.php index 731f06882c..5d247bec35 100644 --- a/3rdparty/MDB2/Driver/Function/Common.php +++ b/3rdparty/MDB2/Driver/Function/Common.php @@ -1,293 +1,293 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.21 2008/02/17 18:51:39 quipo Exp $ -// - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - */ - -/** - * Base class for the function modules that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Function'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Function_Common extends MDB2_Module_Common -{ - // {{{ executeStoredProc() - - /** - * Execute a stored procedure and return any results - * - * @param string $name string that identifies the function to execute - * @param mixed $params array that contains the paramaters to pass the stored proc - * @param mixed $types array that contains the types of the columns in - * the result set - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * - * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - return $error; - } - - // }}} - // {{{ functionTable() - - /** - * return string for internal table used when calling only a function - * - * @return string for internal table used when calling only a function - * @access public - */ - function functionTable() - { - return ''; - } - - // }}} - // {{{ now() - - /** - * Return string to call a variable with the current timestamp inside an SQL statement - * There are three special variables for current date and time: - * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) - * - CURRENT_DATE (date, DATE type) - * - CURRENT_TIME (time, TIME type) - * - * @param string $type 'timestamp' | 'time' | 'date' - * - * @return string to call a variable with the current timestamp - * @access public - */ - function now($type = 'timestamp') - { - switch ($type) { - case 'time': - return 'CURRENT_TIME'; - case 'date': - return 'CURRENT_DATE'; - case 'timestamp': - default: - return 'CURRENT_TIMESTAMP'; - } - } - - // }}} - // {{{ unixtimestamp() - - /** - * return string to call a function to get the unix timestamp from a iso timestamp - * - * @param string $expression - * - * @return string to call a variable with the timestamp - * @access public - */ - function unixtimestamp($expression) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - return $error; - } - - // }}} - // {{{ substring() - - /** - * return string to call a function to get a substring inside an SQL statement - * - * @return string to call a function to get a substring - * @access public - */ - function substring($value, $position = 1, $length = null) - { - if (!is_null($length)) { - return "SUBSTRING($value FROM $position FOR $length)"; - } - return "SUBSTRING($value FROM $position)"; - } - - // }}} - // {{{ replace() - - /** - * return string to call a function to get replace inside an SQL statement. - * - * @return string to call a function to get a replace - * @access public - */ - function replace($str, $from_str, $to_str) - { - return "REPLACE($str, $from_str , $to_str)"; - } - - // }}} - // {{{ concat() - - /** - * Returns string to concatenate two or more string parameters - * - * @param string $value1 - * @param string $value2 - * @param string $values... - * - * @return string to concatenate two strings - * @access public - */ - function concat($value1, $value2) - { - $args = func_get_args(); - return "(".implode(' || ', $args).")"; - } - - // }}} - // {{{ random() - - /** - * return string to call a function to get random value inside an SQL statement - * - * @return return string to generate float between 0 and 1 - * @access public - */ - function random() - { - return 'RAND()'; - } - - // }}} - // {{{ lower() - - /** - * return string to call a function to lower the case of an expression - * - * @param string $expression - * - * @return return string to lower case of an expression - * @access public - */ - function lower($expression) - { - return "LOWER($expression)"; - } - - // }}} - // {{{ upper() - - /** - * return string to call a function to upper the case of an expression - * - * @param string $expression - * - * @return return string to upper case of an expression - * @access public - */ - function upper($expression) - { - return "UPPER($expression)"; - } - - // }}} - // {{{ length() - - /** - * return string to call a function to get the length of a string expression - * - * @param string $expression - * - * @return return string to get the string expression length - * @access public - */ - function length($expression) - { - return "LENGTH($expression)"; - } - - // }}} - // {{{ guid() - - /** - * Returns global unique identifier - * - * @return string to get global unique identifier - * @access public - */ - function guid() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - return $error; - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php 295587 2010-02-28 17:16:38Z quipo $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Base class for the function modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Function'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_Common extends MDB2_Module_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} + // {{{ functionTable() + + /** + * return string for internal table used when calling only a function + * + * @return string for internal table used when calling only a function + * @access public + */ + function functionTable() + { + return ''; + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @param string $type 'timestamp' | 'time' | 'date' + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'CURRENT_TIME'; + case 'date': + return 'CURRENT_DATE'; + case 'timestamp': + default: + return 'CURRENT_TIMESTAMP'; + } + } + + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTRING($value FROM $position FOR $length)"; + } + return "SUBSTRING($value FROM $position)"; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get replace inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + return "REPLACE($str, $from_str , $to_str)"; + } + + // }}} + // {{{ concat() + + /** + * Returns string to concatenate two or more string parameters + * + * @param string $value1 + * @param string $value2 + * @param string $values... + * + * @return string to concatenate two strings + * @access public + */ + function concat($value1, $value2) + { + $args = func_get_args(); + return "(".implode(' || ', $args).")"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'RAND()'; + } + + // }}} + // {{{ lower() + + /** + * return string to call a function to lower the case of an expression + * + * @param string $expression + * + * @return return string to lower case of an expression + * @access public + */ + function lower($expression) + { + return "LOWER($expression)"; + } + + // }}} + // {{{ upper() + + /** + * return string to call a function to upper the case of an expression + * + * @param string $expression + * + * @return return string to upper case of an expression + * @access public + */ + function upper($expression) + { + return "UPPER($expression)"; + } + + // }}} + // {{{ length() + + /** + * return string to call a function to get the length of a string expression + * + * @param string $expression + * + * @return return string to get the string expression length + * @access public + */ + function length($expression) + { + return "LENGTH($expression)"; + } + + // }}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Function/mysql.php b/3rdparty/MDB2/Driver/Function/mysql.php index 44183c3aa0..bf91536e1f 100644 --- a/3rdparty/MDB2/Driver/Function/mysql.php +++ b/3rdparty/MDB2/Driver/Function/mysql.php @@ -1,136 +1,136 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.12 2008/02/17 18:54:08 quipo Exp $ -// - -require_once('MDB2/Driver/Function/Common.php'); - -/** - * MDB2 MySQL driver for the function modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Function_mysql extends MDB2_Driver_Function_Common -{ - // }}} - // {{{ executeStoredProc() - - /** - * Execute a stored procedure and return any results - * - * @param string $name string that identifies the function to execute - * @param mixed $params array that contains the paramaters to pass the stored proc - * @param mixed $types array that contains the types of the columns in - * the result set - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'CALL '.$name; - $query .= $params ? '('.implode(', ', $params).')' : '()'; - return $db->query($query, $types, $result_class, $result_wrap_class); - } - - // }}} - // {{{ unixtimestamp() - - /** - * return string to call a function to get the unix timestamp from a iso timestamp - * - * @param string $expression - * - * @return string to call a variable with the timestamp - * @access public - */ - function unixtimestamp($expression) - { - return 'UNIX_TIMESTAMP('. $expression.')'; - } - - // }}} - // {{{ concat() - - /** - * Returns string to concatenate two or more string parameters - * - * @param string $value1 - * @param string $value2 - * @param string $values... - * @return string to concatenate two strings - * @access public - **/ - function concat($value1, $value2) - { - $args = func_get_args(); - return "CONCAT(".implode(', ', $args).")"; - } - - // }}} - // {{{ guid() - - /** - * Returns global unique identifier - * - * @return string to get global unique identifier - * @access public - */ - function guid() - { - return 'UUID()'; - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_mysql extends MDB2_Driver_Function_Common +{ + // }}} + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'CALL '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + return 'UNIX_TIMESTAMP('. $expression.')'; + } + + // }}} + // {{{ concat() + + /** + * Returns string to concatenate two or more string parameters + * + * @param string $value1 + * @param string $value2 + * @param string $values... + * @return string to concatenate two strings + * @access public + **/ + function concat($value1, $value2) + { + $args = func_get_args(); + return "CONCAT(".implode(', ', $args).")"; + } + + // }}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + return 'UUID()'; + } + + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Function/pgsql.php b/3rdparty/MDB2/Driver/Function/pgsql.php index 173bfc9149..e232e4feec 100644 --- a/3rdparty/MDB2/Driver/Function/pgsql.php +++ b/3rdparty/MDB2/Driver/Function/pgsql.php @@ -1,115 +1,132 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.11 2008/11/09 19:46:50 quipo Exp $ - -require_once('MDB2/Driver/Function/Common.php'); - -/** - * MDB2 MySQL driver for the function modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Function_pgsql extends MDB2_Driver_Function_Common -{ - // {{{ executeStoredProc() - - /** - * Execute a stored procedure and return any results - * - * @param string $name string that identifies the function to execute - * @param mixed $params array that contains the paramaters to pass the stored proc - * @param mixed $types array that contains the types of the columns in - * the result set - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT * FROM '.$name; - $query .= $params ? '('.implode(', ', $params).')' : '()'; - return $db->query($query, $types, $result_class, $result_wrap_class); - } - // }}} - // {{{ unixtimestamp() - - /** - * return string to call a function to get the unix timestamp from a iso timestamp - * - * @param string $expression - * - * @return string to call a variable with the timestamp - * @access public - */ - function unixtimestamp($expression) - { - return 'EXTRACT(EPOCH FROM DATE_TRUNC(\'seconds\', CAST ((' . $expression . ') AS TIMESTAMP)))'; - } - - // }}} - // {{{ random() - - /** - * return string to call a function to get random value inside an SQL statement - * - * @return return string to generate float between 0 and 1 - * @access public - */ - function random() - { - return 'RANDOM()'; - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php 296139 2010-03-13 04:15:22Z afz $ + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_pgsql extends MDB2_Driver_Function_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT * FROM '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + return 'EXTRACT(EPOCH FROM DATE_TRUNC(\'seconds\', CAST ((' . $expression . ') AS TIMESTAMP)))'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTRING(CAST($value AS VARCHAR) FROM $position FOR $length)"; + } + return "SUBSTRING(CAST($value AS VARCHAR) FROM $position)"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'RANDOM()'; + } + + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Function/sqlite.php b/3rdparty/MDB2/Driver/Function/sqlite.php index 8a5b7ec8fa..0a46a4f06a 100644 --- a/3rdparty/MDB2/Driver/Function/sqlite.php +++ b/3rdparty/MDB2/Driver/Function/sqlite.php @@ -1,162 +1,162 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: sqlite.php,v 1.10 2008/02/17 18:54:08 quipo Exp $ -// - -require_once('MDB2/Driver/Function/Common.php'); - -/** - * MDB2 SQLite driver for the function modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Function_sqlite extends MDB2_Driver_Function_Common -{ - // {{{ constructor - - /** - * Constructor - */ - function __construct($db_index) - { - parent::__construct($db_index); - // create all sorts of UDFs - } - - // {{{ now() - - /** - * Return string to call a variable with the current timestamp inside an SQL statement - * There are three special variables for current date and time. - * - * @return string to call a variable with the current timestamp - * @access public - */ - function now($type = 'timestamp') - { - switch ($type) { - case 'time': - return 'time(\'now\')'; - case 'date': - return 'date(\'now\')'; - case 'timestamp': - default: - return 'datetime(\'now\')'; - } - } - - // }}} - // {{{ unixtimestamp() - - /** - * return string to call a function to get the unix timestamp from a iso timestamp - * - * @param string $expression - * - * @return string to call a variable with the timestamp - * @access public - */ - function unixtimestamp($expression) - { - return 'strftime("%s",'. $expression.', "utc")'; - } - - // }}} - // {{{ substring() - - /** - * return string to call a function to get a substring inside an SQL statement - * - * @return string to call a function to get a substring - * @access public - */ - function substring($value, $position = 1, $length = null) - { - if (!is_null($length)) { - return "substr($value,$position,$length)"; - } - return "substr($value,$position,length($value))"; - } - - // }}} - // {{{ random() - - /** - * return string to call a function to get random value inside an SQL statement - * - * @return return string to generate float between 0 and 1 - * @access public - */ - function random() - { - return '((RANDOM()+2147483648)/4294967296)'; - } - - // }}} - // {{{ replace() - - /** - * return string to call a function to get a replacement inside an SQL statement. - * - * @return string to call a function to get a replace - * @access public - */ - function replace($str, $from_str, $to_str) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - return $error; - } - - // }}} -} -?> + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 SQLite driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_sqlite extends MDB2_Driver_Function_Common +{ + // {{{ constructor + + /** + * Constructor + */ + function __construct($db_index) + { + parent::__construct($db_index); + // create all sorts of UDFs + } + + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time. + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'time(\'now\')'; + case 'date': + return 'date(\'now\')'; + case 'timestamp': + default: + return 'datetime(\'now\')'; + } + } + + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + return 'strftime("%s",'. $expression.', "utc")'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "substr($value, $position, $length)"; + } + return "substr($value, $position, length($value))"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return '((RANDOM()+2147483648)/4294967296)'; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get a replacement inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> diff --git a/3rdparty/MDB2/Driver/Manager/Common.php b/3rdparty/MDB2/Driver/Manager/Common.php index 9e16749bff..fadd72934d 100644 --- a/3rdparty/MDB2/Driver/Manager/Common.php +++ b/3rdparty/MDB2/Driver/Manager/Common.php @@ -1,1014 +1,1014 @@ - | -// | Lorenzo Alberton | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.72 2009/01/14 15:00:40 quipo Exp $ -// - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - * @author Lorenzo Alberton - */ - -/** - * Base class for the management modules that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Manager'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Manager_Common extends MDB2_Module_Common -{ - // {{{ splitTableSchema() - - /** - * Split the "[owner|schema].table" notation into an array - * - * @param string $table [schema and] table name - * - * @return array array(schema, table) - * @access private - */ - function splitTableSchema($table) - { - $ret = array(); - if (strpos($table, '.') !== false) { - return explode('.', $table); - } - return array(null, $table); - } - - // }}} - // {{{ getFieldDeclarationList() - - /** - * Get declaration of a number of field in bulk - * - * @param array $fields a multidimensional associative array. - * The first dimension determines the field name, while the second - * dimension is keyed with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Boolean value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * - * @return mixed string on success, a MDB2 error on failure - * @access public - */ - function getFieldDeclarationList($fields) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!is_array($fields) || empty($fields)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'missing any fields', __FUNCTION__); - } - foreach ($fields as $field_name => $field) { - $query = $db->getDeclaration($field['type'], $field_name, $field); - if (PEAR::isError($query)) { - return $query; - } - $query_fields[] = $query; - } - return implode(', ', $query_fields); - } - - // }}} - // {{{ _fixSequenceName() - - /** - * Removes any formatting in an sequence name using the 'seqname_format' option - * - * @param string $sqn string that containts name of a potential sequence - * @param bool $check if only formatted sequences should be returned - * @return string name of the sequence with possible formatting removed - * @access protected - */ - function _fixSequenceName($sqn, $check = false) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; - $seq_name = preg_replace($seq_pattern, '\\1', $sqn); - if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { - return $seq_name; - } - if ($check) { - return false; - } - return $sqn; - } - - // }}} - // {{{ _fixIndexName() - - /** - * Removes any formatting in an index name using the 'idxname_format' option - * - * @param string $idx string that containts name of anl index - * @return string name of the index with eventual formatting removed - * @access protected - */ - function _fixIndexName($idx) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; - $idx_name = preg_replace($idx_pattern, '\\1', $idx); - if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { - return $idx_name; - } - return $idx; - } - - // }}} - // {{{ createDatabase() - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createDatabase($database, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ alterDatabase() - - /** - * alter an existing database - * - * @param string $name name of the database that should be created - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function alterDatabase($database, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ dropDatabase() - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropDatabase($database) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ _getCreateTableQuery() - - /** - * Create a basic SQL query for a new table creation - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * @param array $options An associative array of table options - * - * @return mixed string (the SQL query) on success, a MDB2 error on failure - * @see createTable() - */ - function _getCreateTableQuery($name, $fields, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!$name) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - 'no valid table name specified', __FUNCTION__); - } - if (empty($fields)) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - 'no fields specified for table "'.$name.'"', __FUNCTION__); - } - $query_fields = $this->getFieldDeclarationList($fields); - if (PEAR::isError($query_fields)) { - return $query_fields; - } - if (!empty($options['primary'])) { - $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; - } - - $name = $db->quoteIdentifier($name, true); - $result = 'CREATE '; - if (!empty($options['temporary'])) { - $result .= $this->_getTemporaryTableQuery(); - } - $result .= " TABLE $name ($query_fields)"; - return $result; - } - - // }}} - // {{{ _getTemporaryTableQuery() - - /** - * A method to return the required SQL string that fits between CREATE ... TABLE - * to create the table as a temporary table. - * - * Should be overridden in driver classes to return the correct string for the - * specific database type. - * - * The default is to return the string "TEMPORARY" - this will result in a - * SQL error for any database that does not support temporary tables, or that - * requires a different SQL command from "CREATE TEMPORARY TABLE". - * - * @return string The string required to be placed between "CREATE" and "TABLE" - * to generate a temporary table, if possible. - */ - function _getTemporaryTableQuery() - { - return 'TEMPORARY'; - } - - // }}} - // {{{ createTable() - - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * The indexes of the array entries are the names of the fields of the table an - * the array entry values are associative arrays like those that are meant to be - * passed with the field definitions to get[Type]Declaration() functions. - * array( - * 'id' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * 'notnull' => 1 - * 'default' => 0 - * ), - * 'name' => array( - * 'type' => 'text', - * 'length' => 12 - * ), - * 'password' => array( - * 'type' => 'text', - * 'length' => 12 - * ) - * ); - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'temporary' => true|false, - * ); - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createTable($name, $fields, $options = array()) - { - $query = $this->_getCreateTableQuery($name, $fields, $options); - if (PEAR::isError($query)) { - return $query; - } - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $result = $db->exec($query); - if (PEAR::isError($result)) { - return $result; - } - return MDB2_OK; - } - - // }}} - // {{{ dropTable() - - /** - * drop an existing table - * - * @param string $name name of the table that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropTable($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("DROP TABLE $name"); - } - - // }}} - // {{{ truncateTable() - - /** - * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, - * it falls back to a DELETE FROM TABLE query) - * - * @param string $name name of the table that should be truncated - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function truncateTable($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("DELETE FROM $name"); - } - - // }}} - // {{{ vacuum() - - /** - * Optimize (vacuum) all the tables in the db (or only the specified table) - * and optionally run ANALYZE. - * - * @param string $table table name (all the tables if empty) - * @param array $options an array with driver-specific options: - * - timeout [int] (in seconds) [mssql-only] - * - analyze [boolean] [pgsql and mysql] - * - full [boolean] [pgsql-only] - * - freeze [boolean] [pgsql-only] - * - * @return mixed MDB2_OK success, a MDB2 error on failure - * @access public - */ - function vacuum($table = null, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ alterTable() - - /** - * alter an existing table - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @access public - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function alterTable($name, $changes, $check) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listDatabases() - - /** - * list all databases - * - * @return mixed array of database names on success, a MDB2 error on failure - * @access public - */ - function listDatabases() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implementedd', __FUNCTION__); - } - - // }}} - // {{{ listUsers() - - /** - * list all users - * - * @return mixed array of user names on success, a MDB2 error on failure - * @access public - */ - function listUsers() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listViews() - - /** - * list all views in the current database - * - * @param string database, the current is default - * NB: not all the drivers can get the view names from - * a database other than the current one - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listViews($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTableViews() - - /** - * list the views in the database that reference a given table - * - * @param string table for which all referenced views should be found - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listTableViews($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTableTriggers() - - /** - * list all triggers in the database that reference a given table - * - * @param string table for which all referenced triggers should be found - * @return mixed array of trigger names on success, a MDB2 error on failure - * @access public - */ - function listTableTriggers($table = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listFunctions() - - /** - * list all functions in the current database - * - * @return mixed array of function names on success, a MDB2 error on failure - * @access public - */ - function listFunctions() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTables() - - /** - * list all tables in the current database - * - * @param string database, the current is default. - * NB: not all the drivers can get the table names from - * a database other than the current one - * @return mixed array of table names on success, a MDB2 error on failure - * @access public - */ - function listTables($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTableFields() - - /** - * list all fields in a table in the current database - * - * @param string $table name of table that should be used in method - * @return mixed array of field names on success, a MDB2 error on failure - * @access public - */ - function listTableFields($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ createIndex() - - /** - * Get the stucture of a field into an array - * - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the index fields as array - * indexes. Each entry of this array is set to another type of associative - * array that specifies properties of the index that are specific to - * each field. - * - * Currently, only the sorting property is supported. It should be used - * to define the sorting direction of the index. It may be set to either - * ascending or descending. - * - * Not all DBMS support index sorting direction configuration. The DBMS - * drivers of those that do not support it ignore this property. Use the - * function supports() to determine whether the DBMS driver can manage indexes. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array( - * 'sorting' => 'ascending' - * ), - * 'last_login' => array() - * ) - * ) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createIndex($table, $name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "CREATE INDEX $name ON $table"; - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $fields) . ')'; - return $db->exec($query); - } - - // }}} - // {{{ dropIndex() - - /** - * drop existing index - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropIndex($table, $name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($db->getIndexName($name), true); - return $db->exec("DROP INDEX $name"); - } - - // }}} - // {{{ listTableIndexes() - - /** - * list all indexes in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of index names on success, a MDB2 error on failure - * @access public - */ - function listTableIndexes($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ _getAdvancedFKOptions() - - /** - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @access protected - */ - function _getAdvancedFKOptions($definition) - { - return ''; - } - - // }}} - // {{{ createConstraint() - - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * The full structure of the array looks like this: - *
-     *          array (
-     *              [primary] => 0
-     *              [unique]  => 0
-     *              [foreign] => 1
-     *              [check]   => 0
-     *              [fields] => array (
-     *                  [field1name] => array() // one entry per each field covered
-     *                  [field2name] => array() // by the index
-     *                  [field3name] => array(
-     *                      [sorting]  => ascending
-     *                      [position] => 3
-     *                  )
-     *              )
-     *              [references] => array(
-     *                  [table] => name
-     *                  [fields] => array(
-     *                      [field1name] => array(  //one entry per each referenced field
-     *                           [position] => 1
-     *                      )
-     *                  )
-     *              )
-     *              [deferrable] => 0
-     *              [initiallydeferred] => 0
-     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [match] => SIMPLE|PARTIAL|FULL
-     *          );
-     *          
- * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createConstraint($table, $name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "ALTER TABLE $table ADD CONSTRAINT $name"; - if (!empty($definition['primary'])) { - $query.= ' PRIMARY KEY'; - } elseif (!empty($definition['unique'])) { - $query.= ' UNIQUE'; - } elseif (!empty($definition['foreign'])) { - $query.= ' FOREIGN KEY'; - } - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $fields) . ')'; - if (!empty($definition['foreign'])) { - $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); - $referenced_fields = array(); - foreach (array_keys($definition['references']['fields']) as $field) { - $referenced_fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $referenced_fields) . ')'; - $query .= $this->_getAdvancedFKOptions($definition); - } - return $db->exec($query); - } - - // }}} - // {{{ dropConstraint() - - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropConstraint($table, $name, $primary = false) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - return $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); - } - - // }}} - // {{{ listTableConstraints() - - /** - * list all constraints in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of constraint names on success, a MDB2 error on failure - * @access public - */ - function listTableConstraints($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ createSequence() - - /** - * create sequence - * - * @param string $seq_name name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createSequence($seq_name, $start = 1) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ dropSequence() - - /** - * drop existing sequence - * - * @param string $seq_name name of the sequence to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropSequence($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listSequences() - - /** - * list all sequences in the current database - * - * @param string database, the current is default - * NB: not all the drivers can get the sequence names from - * a database other than the current one - * @return mixed array of sequence names on success, a MDB2 error on failure - * @access public - */ - function listSequences($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} -} + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php 295587 2010-02-28 17:16:38Z quipo $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ + +/** + * Base class for the management modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Manager'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * + * @param string $table [schema and] table name + * + * @return array array(schema, table) + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getFieldDeclarationList() + + /** + * Get declaration of a number of field in bulk + * + * @param array $fields a multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return mixed string on success, a MDB2 error on failure + * @access public + */ + function getFieldDeclarationList($fields) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_array($fields) || empty($fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'missing any fields', __FUNCTION__); + } + foreach ($fields as $field_name => $field) { + $query = $db->getDeclaration($field['type'], $field_name, $field); + if (PEAR::isError($query)) { + return $query; + } + $query_fields[] = $query; + } + return implode(', ', $query_fields); + } + + // }}} + // {{{ _fixSequenceName() + + /** + * Removes any formatting in an sequence name using the 'seqname_format' option + * + * @param string $sqn string that containts name of a potential sequence + * @param bool $check if only formatted sequences should be returned + * @return string name of the sequence with possible formatting removed + * @access protected + */ + function _fixSequenceName($sqn, $check = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; + $seq_name = preg_replace($seq_pattern, '\\1', $sqn); + if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { + return $seq_name; + } + if ($check) { + return false; + } + return $sqn; + } + + // }}} + // {{{ _fixIndexName() + + /** + * Removes any formatting in an index name using the 'idxname_format' option + * + * @param string $idx string that containts name of anl index + * @return string name of the index with eventual formatting removed + * @access protected + */ + function _fixIndexName($idx) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; + $idx_name = preg_replace($idx_pattern, '\\1', $idx); + if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { + return $idx_name; + } + return $idx; + } + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($database, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($database, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($database) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'TEMPORARY'; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP TABLE $name"); + } + + // }}} + // {{{ truncateTable() + + /** + * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, + * it falls back to a DELETE FROM TABLE query) + * + * @param string $name name of the table that should be truncated + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function truncateTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DELETE FROM $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implementedd', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the view names from + * a database other than the current one + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default. + * NB: not all the drivers can get the table names from + * a database other than the current one + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + return ''; + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * The full structure of the array looks like this: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table ADD CONSTRAINT $name"; + if (!empty($definition['primary'])) { + $query.= ' PRIMARY KEY'; + } elseif (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $query.= ' FOREIGN KEY'; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the sequence names from + * a database other than the current one + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Manager/mysql.php b/3rdparty/MDB2/Driver/Manager/mysql.php index 099ed48a84..8bcb338360 100644 --- a/3rdparty/MDB2/Driver/Manager/mysql.php +++ b/3rdparty/MDB2/Driver/Manager/mysql.php @@ -1,1432 +1,1436 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.113 2008/11/23 20:30:29 quipo Exp $ -// - -require_once('MDB2/Driver/Manager/Common.php'); - -/** - * MDB2 MySQL driver for the management modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common -{ - - // }}} - // {{{ createDatabase() - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createDatabase($name, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $query = 'CREATE DATABASE ' . $name; - if (!empty($options['charset'])) { - $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); - } - if (!empty($options['collation'])) { - $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); - } - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ alterDatabase() - - /** - * alter an existing database - * - * @param string $name name of the database that is intended to be changed - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function alterDatabase($name, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true); - if (!empty($options['charset'])) { - $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); - } - if (!empty($options['collation'])) { - $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); - } - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ dropDatabase() - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropDatabase($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $query = "DROP DATABASE $name"; - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ _getAdvancedFKOptions() - - /** - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @access protected - */ - function _getAdvancedFKOptions($definition) - { - $query = ''; - if (!empty($definition['match'])) { - $query .= ' MATCH '.$definition['match']; - } - if (!empty($definition['onupdate'])) { - $query .= ' ON UPDATE '.$definition['onupdate']; - } - if (!empty($definition['ondelete'])) { - $query .= ' ON DELETE '.$definition['ondelete']; - } - return $query; - } - - // }}} - // {{{ createTable() - - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * The indexes of the array entries are the names of the fields of the table an - * the array entry values are associative arrays like those that are meant to be - * passed with the field definitions to get[Type]Declaration() functions. - * array( - * 'id' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * 'notnull' => 1 - * 'default' => 0 - * ), - * 'name' => array( - * 'type' => 'text', - * 'length' => 12 - * ), - * 'password' => array( - * 'type' => 'text', - * 'length' => 12 - * ) - * ); - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * 'type' => 'innodb', - * ); - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createTable($name, $fields, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - // if we have an AUTO_INCREMENT column and a PK on more than one field, - // we have to handle it differently... - $autoincrement = null; - if (empty($options['primary'])) { - $pk_fields = array(); - foreach ($fields as $fieldname => $def) { - if (!empty($def['primary'])) { - $pk_fields[$fieldname] = true; - } - if (!empty($def['autoincrement'])) { - $autoincrement = $fieldname; - } - } - if (!is_null($autoincrement) && count($pk_fields) > 1) { - $options['primary'] = $pk_fields; - } else { - // the PK constraint is on max one field => OK - $autoincrement = null; - } - } - - $query = $this->_getCreateTableQuery($name, $fields, $options); - if (PEAR::isError($query)) { - return $query; - } - - if (!is_null($autoincrement)) { - // we have to remove the PK clause added by _getIntegerDeclaration() - $query = str_replace('AUTO_INCREMENT PRIMARY KEY', 'AUTO_INCREMENT', $query); - } - - $options_strings = array(); - - if (!empty($options['comment'])) { - $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); - } - - if (!empty($options['charset'])) { - $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; - if (!empty($options['collate'])) { - $options_strings['charset'].= ' COLLATE '.$options['collate']; - } - } - - $type = false; - if (!empty($options['type'])) { - $type = $options['type']; - } elseif ($db->options['default_table_type']) { - $type = $db->options['default_table_type']; - } - if ($type) { - $options_strings[] = "ENGINE = $type"; - } - - if (!empty($options_strings)) { - $query .= ' '.implode(' ', $options_strings); - } - $result = $db->exec($query); - if (PEAR::isError($result)) { - return $result; - } - return MDB2_OK; - } - - // }}} - // {{{ dropTable() - - /** - * drop an existing table - * - * @param string $name name of the table that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropTable($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - //delete the triggers associated to existing FK constraints - $constraints = $this->listTableConstraints($name); - if (!PEAR::isError($constraints) && !empty($constraints)) { - $db->loadModule('Reverse', null, true); - foreach ($constraints as $constraint) { - $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); - if (!PEAR::isError($definition) && !empty($definition['foreign'])) { - $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - - return parent::dropTable($name); - } - - // }}} - // {{{ truncateTable() - - /** - * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, - * it falls back to a DELETE FROM TABLE query) - * - * @param string $name name of the table that should be truncated - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function truncateTable($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("TRUNCATE TABLE $name"); - } - - // }}} - // {{{ vacuum() - - /** - * Optimize (vacuum) all the tables in the db (or only the specified table) - * and optionally run ANALYZE. - * - * @param string $table table name (all the tables if empty) - * @param array $options an array with driver-specific options: - * - timeout [int] (in seconds) [mssql-only] - * - analyze [boolean] [pgsql and mysql] - * - full [boolean] [pgsql-only] - * - freeze [boolean] [pgsql-only] - * - * @return mixed MDB2_OK success, a MDB2 error on failure - * @access public - */ - function vacuum($table = null, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (empty($table)) { - $table = $this->listTables(); - if (PEAR::isError($table)) { - return $table; - } - } - if (is_array($table)) { - foreach (array_keys($table) as $k) { - $table[$k] = $db->quoteIdentifier($table[$k], true); - } - $table = implode(', ', $table); - } else { - $table = $db->quoteIdentifier($table, true); - } - - $result = $db->exec('OPTIMIZE TABLE '.$table); - if (PEAR::isError($result)) { - return $result; - } - if (!empty($options['analyze'])) { - return $db->exec('ANALYZE TABLE '.$table); - } - return MDB2_OK; - } - - // }}} - // {{{ alterTable() - - /** - * alter an existing table - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @access public - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function alterTable($name, $changes, $check) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - foreach ($changes as $change_name => $change) { - switch ($change_name) { - case 'add': - case 'remove': - case 'change': - case 'rename': - case 'name': - break; - default: - return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, - 'change type "'.$change_name.'" not yet supported', __FUNCTION__); - } - } - - if ($check) { - return MDB2_OK; - } - - $query = ''; - if (!empty($changes['name'])) { - $change_name = $db->quoteIdentifier($changes['name'], true); - $query .= 'RENAME TO ' . $change_name; - } - - if (!empty($changes['add']) && is_array($changes['add'])) { - foreach ($changes['add'] as $field_name => $field) { - if ($query) { - $query.= ', '; - } - $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); - } - } - - if (!empty($changes['remove']) && is_array($changes['remove'])) { - foreach ($changes['remove'] as $field_name => $field) { - if ($query) { - $query.= ', '; - } - $field_name = $db->quoteIdentifier($field_name, true); - $query.= 'DROP ' . $field_name; - } - } - - $rename = array(); - if (!empty($changes['rename']) && is_array($changes['rename'])) { - foreach ($changes['rename'] as $field_name => $field) { - $rename[$field['name']] = $field_name; - } - } - - if (!empty($changes['change']) && is_array($changes['change'])) { - foreach ($changes['change'] as $field_name => $field) { - if ($query) { - $query.= ', '; - } - if (isset($rename[$field_name])) { - $old_field_name = $rename[$field_name]; - unset($rename[$field_name]); - } else { - $old_field_name = $field_name; - } - $old_field_name = $db->quoteIdentifier($old_field_name, true); - $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); - } - } - - if (!empty($rename) && is_array($rename)) { - foreach ($rename as $rename_name => $renamed_field) { - if ($query) { - $query.= ', '; - } - $field = $changes['rename'][$renamed_field]; - $renamed_field = $db->quoteIdentifier($renamed_field, true); - $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); - } - } - - if (!$query) { - return MDB2_OK; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("ALTER TABLE $name $query"); - } - - // }}} - // {{{ listDatabases() - - /** - * list all databases - * - * @return mixed array of database names on success, a MDB2 error on failure - * @access public - */ - function listDatabases() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->queryCol('SHOW DATABASES'); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listUsers() - - /** - * list all users - * - * @return mixed array of user names on success, a MDB2 error on failure - * @access public - */ - function listUsers() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER'); - } - - // }}} - // {{{ listFunctions() - - /** - * list all functions in the current database - * - * @return mixed array of function names on success, a MDB2 error on failure - * @access public - */ - function listFunctions() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name FROM mysql.proc"; - /* - SELECT ROUTINE_NAME - FROM INFORMATION_SCHEMA.ROUTINES - WHERE ROUTINE_TYPE = 'FUNCTION' - */ - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableTriggers() - - /** - * list all triggers in the database that reference a given table - * - * @param string table for which all referenced triggers should be found - * @return mixed array of trigger names on success, a MDB2 error on failure - * @access public - */ - function listTableTriggers($table = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SHOW TRIGGERS'; - if (!is_null($table)) { - $table = $db->quote($table, 'text'); - $query .= " LIKE $table"; - } - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTables() - - /** - * list all tables in the current database - * - * @param string database, the current is default - * @return mixed array of table names on success, a MDB2 error on failure - * @access public - */ - function listTables($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SHOW /*!50002 FULL*/ TABLES"; - if (!is_null($database)) { - $query .= " FROM $database"; - } - $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; - - $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); - if (PEAR::isError($table_names)) { - return $table_names; - } - - $result = array(); - foreach ($table_names as $table) { - if (!$this->_fixSequenceName($table[0], true)) { - $result[] = $table[0]; - } - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listViews() - - /** - * list all views in the current database - * - * @param string database, the current is default - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listViews($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SHOW FULL TABLES'; - if (!is_null($database)) { - $query.= " FROM $database"; - } - $query.= " WHERE Table_type = 'VIEW'"; - - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableFields() - - /** - * list all fields in a table in the current database - * - * @param string $table name of table that should be used in method - * @return mixed array of field names on success, a MDB2 error on failure - * @access public - */ - function listTableFields($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $result = $db->queryCol("SHOW COLUMNS FROM $table"); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ createIndex() - - /** - * Get the stucture of a field into an array - * - * @author Leoncx - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the index fields as array - * indexes. Each entry of this array is set to another type of associative - * array that specifies properties of the index that are specific to - * each field. - * - * Currently, only the sorting property is supported. It should be used - * to define the sorting direction of the index. It may be set to either - * ascending or descending. - * - * Not all DBMS support index sorting direction configuration. The DBMS - * drivers of those that do not support it ignore this property. Use the - * function supports() to determine whether the DBMS driver can manage indexes. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array( - * 'sorting' => 'ascending' - * 'length' => 10 - * ), - * 'last_login' => array() - * ) - * ) - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createIndex($table, $name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "CREATE INDEX $name ON $table"; - $fields = array(); - foreach ($definition['fields'] as $field => $fieldinfo) { - if (!empty($fieldinfo['length'])) { - $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; - } else { - $fields[] = $db->quoteIdentifier($field, true); - } - } - $query .= ' ('. implode(', ', $fields) . ')'; - return $db->exec($query); - } - - // }}} - // {{{ dropIndex() - - /** - * drop existing index - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropIndex($table, $name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - return $db->exec("DROP INDEX $name ON $table"); - } - - // }}} - // {{{ listTableIndexes() - - /** - * list all indexes in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of index names on success, a MDB2 error on failure - * @access public - */ - function listTableIndexes($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $key_name = 'Key_name'; - $non_unique = 'Non_unique'; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $key_name = strtolower($key_name); - $non_unique = strtolower($non_unique); - } else { - $key_name = strtoupper($key_name); - $non_unique = strtoupper($non_unique); - } - } - - $table = $db->quoteIdentifier($table, true); - $query = "SHOW INDEX FROM $table"; - $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $index_data) { - if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { - $result[$index] = true; - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createConstraint() - - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the constraint fields as array - * constraints. Each entry of this array is set to another type of associative - * array that specifies properties of the constraint that are specific to - * each field. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array(), - * 'last_login' => array() - * ) - * ) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createConstraint($table, $name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $type = ''; - $idx_name = $db->quoteIdentifier($db->getIndexName($name), true); - if (!empty($definition['primary'])) { - $type = 'PRIMARY'; - $idx_name = 'KEY'; - } elseif (!empty($definition['unique'])) { - $type = 'UNIQUE'; - } elseif (!empty($definition['foreign'])) { - $type = 'CONSTRAINT'; - } - if (empty($type)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'invalid definition, could not create constraint', __FUNCTION__); - } - - $table_quoted = $db->quoteIdentifier($table, true); - $query = "ALTER TABLE $table_quoted ADD $type $idx_name"; - if (!empty($definition['foreign'])) { - $query .= ' FOREIGN KEY'; - } - $fields = array(); - foreach ($definition['fields'] as $field => $fieldinfo) { - $quoted = $db->quoteIdentifier($field, true); - if (!empty($fieldinfo['length'])) { - $quoted .= '(' . $fieldinfo['length'] . ')'; - } - $fields[] = $quoted; - } - $query .= ' ('. implode(', ', $fields) . ')'; - if (!empty($definition['foreign'])) { - $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); - $referenced_fields = array(); - foreach (array_keys($definition['references']['fields']) as $field) { - $referenced_fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $referenced_fields) . ')'; - $query .= $this->_getAdvancedFKOptions($definition); - - // add index on FK column(s) or we can't add a FK constraint - // @see http://forums.mysql.com/read.php?22,19755,226009 - $result = $this->createIndex($table, $name.'_fkidx', $definition); - if (PEAR::isError($result)) { - return $result; - } - } - $res = $db->exec($query); - if (PEAR::isError($res)) { - return $res; - } - if (!empty($definition['foreign'])) { - return $this->_createFKTriggers($table, array($name => $definition)); - } - return MDB2_OK; - } - - // }}} - // {{{ dropConstraint() - - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropConstraint($table, $name, $primary = false) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if ($primary || strtolower($name) == 'primary') { - $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY'; - return $db->exec($query); - } - - //is it a FK constraint? If so, also delete the associated triggers - $db->loadModule('Reverse', null, true); - $definition = $db->reverse->getTableConstraintDefinition($table, $name); - if (!PEAR::isError($definition) && !empty($definition['foreign'])) { - //first drop the FK enforcing triggers - $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); - if (PEAR::isError($result)) { - return $result; - } - //then drop the constraint itself - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "ALTER TABLE $table DROP FOREIGN KEY $name"; - return $db->exec($query); - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "ALTER TABLE $table DROP INDEX $name"; - return $db->exec($query); - } - - // }}} - // {{{ _createFKTriggers() - - /** - * Create triggers to enforce the FOREIGN KEY constraint on the table - * - * NB: since there's no RAISE_APPLICATION_ERROR facility in mysql, - * we call a non-existent procedure to raise the FK violation message. - * @see http://forums.mysql.com/read.php?99,55108,71877#msg-71877 - * - * @param string $table table name - * @param array $foreign_keys FOREIGN KEY definitions - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access private - */ - function _createFKTriggers($table, $foreign_keys) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - // create triggers to enforce FOREIGN KEY constraints - if ($db->supports('triggers') && !empty($foreign_keys)) { - $table_quoted = $db->quoteIdentifier($table, true); - foreach ($foreign_keys as $fkname => $fkdef) { - if (empty($fkdef)) { - continue; - } - //set actions to default if not set - $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); - $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); - - $trigger_names = array( - 'insert' => $fkname.'_insert_trg', - 'update' => $fkname.'_update_trg', - 'pk_update' => $fkname.'_pk_update_trg', - 'pk_delete' => $fkname.'_pk_delete_trg', - ); - $table_fields = array_keys($fkdef['fields']); - $referenced_fields = array_keys($fkdef['references']['fields']); - - //create the ON [UPDATE|DELETE] triggers on the primary table - $restrict_action = ' IF (SELECT '; - $aliased_fields = array(); - foreach ($table_fields as $field) { - $aliased_fields[] = $table_quoted .'.'.$field .' AS '.$field; - } - $restrict_action .= implode(',', $aliased_fields) - .' FROM '.$table_quoted - .' WHERE '; - $conditions = array(); - $new_values = array(); - $null_values = array(); - for ($i=0; $i OLD.'.$referenced_fields[$i]; - } - $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' - .' AND (' .implode(' OR ', $conditions2) .')' - .' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();' - .' END IF;'; - - $cascade_action_update = 'UPDATE '.$table_quoted.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';'; - $cascade_action_delete = 'DELETE FROM '.$table_quoted.' WHERE '.implode(' AND ', $conditions). ';'; - $setnull_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';'; - - if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { - $db->loadModule('Reverse', null, true); - $default_values = array(); - foreach ($table_fields as $table_field) { - $field_definition = $db->reverse->getTableFieldDefinition($table, $field); - if (PEAR::isError($field_definition)) { - return $field_definition; - } - $default_values[] = $table_field .' = '. $field_definition[0]['default']; - } - $setdefault_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';'; - } - - $query = 'CREATE TRIGGER %s' - .' %s ON '.$fkdef['references']['table'] - .' FOR EACH ROW BEGIN ' - .' SET FOREIGN_KEY_CHECKS = 0; '; //only really needed for ON UPDATE CASCADE - - if ('CASCADE' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $cascade_action_update; - } elseif ('SET NULL' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action; - } elseif ('SET DEFAULT' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action; - } elseif ('NO ACTION' == $fkdef['onupdate']) { - $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update'); - } elseif ('RESTRICT' == $fkdef['onupdate']) { - $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update'); - } - if ('CASCADE' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $cascade_action_delete; - } elseif ('SET NULL' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action; - } elseif ('SET DEFAULT' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action; - } elseif ('NO ACTION' == $fkdef['ondelete']) { - $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete'); - } elseif ('RESTRICT' == $fkdef['ondelete']) { - $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete'); - } - $sql_update .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; - $sql_delete .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; - - $db->pushErrorHandling(PEAR_ERROR_RETURN); - $db->expectError(MDB2_ERROR_CANNOT_CREATE); - $result = $db->exec($sql_delete); - $expected_errmsg = 'This MySQL version doesn\'t support multiple triggers with the same action time and event for one table'; - $db->popExpect(); - $db->popErrorHandling(); - if (PEAR::isError($result)) { - if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { - return $result; - } - $db->warnings[] = $expected_errmsg; - } - $db->pushErrorHandling(PEAR_ERROR_RETURN); - $db->expectError(MDB2_ERROR_CANNOT_CREATE); - $result = $db->exec($sql_update); - $db->popExpect(); - $db->popErrorHandling(); - if (PEAR::isError($result) && $result->getCode() != MDB2_ERROR_CANNOT_CREATE) { - if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { - return $result; - } - $db->warnings[] = $expected_errmsg; - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ _dropFKTriggers() - - /** - * Drop the triggers created to enforce the FOREIGN KEY constraint on the table - * - * @param string $table table name - * @param string $fkname FOREIGN KEY constraint name - * @param string $referenced_table referenced table name - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access private - */ - function _dropFKTriggers($table, $fkname, $referenced_table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $triggers = $this->listTableTriggers($table); - $triggers2 = $this->listTableTriggers($referenced_table); - if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { - $triggers = array_merge($triggers, $triggers2); - $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; - foreach ($triggers as $trigger) { - if (preg_match($pattern, $trigger)) { - $result = $db->exec('DROP TRIGGER '.$trigger); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ listTableConstraints() - - /** - * list all constraints in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of constraint names on success, a MDB2 error on failure - * @access public - */ - function listTableConstraints($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $key_name = 'Key_name'; - $non_unique = 'Non_unique'; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $key_name = strtolower($key_name); - $non_unique = strtolower($non_unique); - } else { - $key_name = strtoupper($key_name); - $non_unique = strtoupper($non_unique); - } - } - - $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true); - $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $index_data) { - if (!$index_data[$non_unique]) { - if ($index_data[$key_name] !== 'PRIMARY') { - $index = $this->_fixIndexName($index_data[$key_name]); - } else { - $index = 'PRIMARY'; - } - if (!empty($index)) { - $result[$index] = true; - } - } - } - - //list FOREIGN KEY constraints... - $query = 'SHOW CREATE TABLE '. $db->escape($table); - $definition = $db->queryOne($query, 'text', 1); - if (!PEAR::isError($definition) && !empty($definition)) { - $pattern = '/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN KEY\b/Uims'; - if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 0) { - foreach ($matches[1] as $constraint) { - $result[$constraint] = true; - } - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createSequence() - - /** - * create sequence - * - * @param string $seq_name name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * 'type' => 'innodb', - * ); - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createSequence($seq_name, $start = 1, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); - - $options_strings = array(); - - if (!empty($options['comment'])) { - $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); - } - - if (!empty($options['charset'])) { - $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; - if (!empty($options['collate'])) { - $options_strings['charset'].= ' COLLATE '.$options['collate']; - } - } - - $type = false; - if (!empty($options['type'])) { - $type = $options['type']; - } elseif ($db->options['default_table_type']) { - $type = $db->options['default_table_type']; - } - if ($type) { - $options_strings[] = "ENGINE = $type"; - } - - $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; - if (!empty($options_strings)) { - $query .= ' '.implode(' ', $options_strings); - } - $res = $db->exec($query); - if (PEAR::isError($res)) { - return $res; - } - - if ($start == 1) { - return MDB2_OK; - } - - $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; - $res = $db->exec($query); - if (!PEAR::isError($res)) { - return MDB2_OK; - } - - // Handle error - $result = $db->exec("DROP TABLE $sequence_name"); - if (PEAR::isError($result)) { - return $db->raiseError($result, null, null, - 'could not drop inconsistent sequence table', __FUNCTION__); - } - - return $db->raiseError($res, null, null, - 'could not create sequence table', __FUNCTION__); - } - - // }}} - // {{{ dropSequence() - - /** - * drop existing sequence - * - * @param string $seq_name name of the sequence to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropSequence($seq_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - return $db->exec("DROP TABLE $sequence_name"); - } - - // }}} - // {{{ listSequences() - - /** - * list all sequences in the current database - * - * @param string database, the current is default - * @return mixed array of sequence names on success, a MDB2 error on failure - * @access public - */ - function listSequences($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SHOW TABLES"; - if (!is_null($database)) { - $query .= " FROM $database"; - } - $table_names = $db->queryCol($query); - if (PEAR::isError($table_names)) { - return $table_names; - } - - $result = array(); - foreach ($table_names as $table_name) { - if ($sqn = $this->_fixSequenceName($table_name, true)) { - $result[] = $sqn; - } - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php 302865 2010-08-29 10:30:55Z quipo $ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common +{ + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = 'CREATE DATABASE ' . $name; + if (!empty($options['charset'])) { + $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); + } + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * @param string $name name of the database that is intended to be changed + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($name, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true); + if (!empty($options['charset'])) { + $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); + } + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "DROP DATABASE $name"; + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + $query = ''; + if (!empty($definition['match'])) { + $query .= ' MATCH '.$definition['match']; + } + if (!empty($definition['onupdate'])) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete'])) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + return $query; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + // if we have an AUTO_INCREMENT column and a PK on more than one field, + // we have to handle it differently... + $autoincrement = null; + if (empty($options['primary'])) { + $pk_fields = array(); + foreach ($fields as $fieldname => $def) { + if (!empty($def['primary'])) { + $pk_fields[$fieldname] = true; + } + if (!empty($def['autoincrement'])) { + $autoincrement = $fieldname; + } + } + if ((null !== $autoincrement) && count($pk_fields) > 1) { + $options['primary'] = $pk_fields; + } else { + // the PK constraint is on max one field => OK + $autoincrement = null; + } + } + + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + + if (null !== $autoincrement) { + // we have to remove the PK clause added by _getIntegerDeclaration() + $query = str_replace('AUTO_INCREMENT PRIMARY KEY', 'AUTO_INCREMENT', $query); + } + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + //delete the triggers associated to existing FK constraints + $constraints = $this->listTableConstraints($name); + if (!PEAR::isError($constraints) && !empty($constraints)) { + $db->loadModule('Reverse', null, true); + foreach ($constraints as $constraint) { + $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + + return parent::dropTable($name); + } + + // }}} + // {{{ truncateTable() + + /** + * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, + * it falls back to a DELETE FROM TABLE query) + * + * @param string $name name of the table that should be truncated + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function truncateTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("TRUNCATE TABLE $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($table)) { + $table = $this->listTables(); + if (PEAR::isError($table)) { + return $table; + } + } + if (is_array($table)) { + foreach (array_keys($table) as $k) { + $table[$k] = $db->quoteIdentifier($table[$k], true); + } + $table = implode(', ', $table); + } else { + $table = $db->quoteIdentifier($table, true); + } + + $result = $db->exec('OPTIMIZE TABLE '.$table); + if (PEAR::isError($result)) { + return $result; + } + if (!empty($options['analyze'])) { + return $db->exec('ANALYZE TABLE '.$table); + } + return MDB2_OK; + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $query = ''; + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $query .= 'RENAME TO ' . $change_name; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'DROP ' . $field_name; + } + } + + $rename = array(); + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $rename[$field['name']] = $field_name; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$field_name])) { + $old_field_name = $rename[$field_name]; + unset($rename[$field_name]); + } else { + $old_field_name = $field_name; + } + $old_field_name = $db->quoteIdentifier($old_field_name, true); + $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + } + + if (!empty($rename) && is_array($rename)) { + foreach ($rename as $rename_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamed_field]; + $renamed_field = $db->quoteIdentifier($renamed_field, true); + $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); + } + } + + if (!$query) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("ALTER TABLE $name $query"); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SHOW DATABASES'); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER'); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM mysql.proc"; + /* + SELECT ROUTINE_NAME + FROM INFORMATION_SCHEMA.ROUTINES + WHERE ROUTINE_TYPE = 'FUNCTION' + */ + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW TRIGGERS'; + if (null !== $table) { + $table = $db->quote($table, 'text'); + $query .= " LIKE $table"; + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW /*!50002 FULL*/ TABLES"; + if (null !== $database) { + $query .= " FROM $database"; + } + $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; + + $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table) { + if (!$this->_fixSequenceName($table[0], true)) { + $result[] = $table[0]; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW FULL TABLES'; + if (null !== $database) { + $query.= " FROM $database"; + } + $query.= " WHERE Table_type = 'VIEW'"; + + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $result = $db->queryCol("SHOW COLUMNS FROM $table"); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @author Leoncx + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * 'length' => 10 + * ), + * 'last_login' => array() + * ) + * ) + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + if (!empty($fieldinfo['length'])) { + $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; + } else { + $fields[] = $db->quoteIdentifier($field, true); + } + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name ON $table"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { + $result[$index] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $type = ''; + $idx_name = $db->quoteIdentifier($db->getIndexName($name), true); + if (!empty($definition['primary'])) { + $type = 'PRIMARY'; + $idx_name = 'KEY'; + } elseif (!empty($definition['unique'])) { + $type = 'UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $type = 'CONSTRAINT'; + } + if (empty($type)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'invalid definition, could not create constraint', __FUNCTION__); + } + + $table_quoted = $db->quoteIdentifier($table, true); + $query = "ALTER TABLE $table_quoted ADD $type $idx_name"; + if (!empty($definition['foreign'])) { + $query .= ' FOREIGN KEY'; + } + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + $quoted = $db->quoteIdentifier($field, true); + if (!empty($fieldinfo['length'])) { + $quoted .= '(' . $fieldinfo['length'] . ')'; + } + $fields[] = $quoted; + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + + // add index on FK column(s) or we can't add a FK constraint + // @see http://forums.mysql.com/read.php?22,19755,226009 + $result = $this->createIndex($table, $name.'_fkidx', $definition); + if (PEAR::isError($result)) { + return $result; + } + } + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + if (!empty($definition['foreign'])) { + return $this->_createFKTriggers($table, array($name => $definition)); + } + return MDB2_OK; + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($primary || strtolower($name) == 'primary') { + $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY'; + return $db->exec($query); + } + + //is it a FK constraint? If so, also delete the associated triggers + $db->loadModule('Reverse', null, true); + $definition = $db->reverse->getTableConstraintDefinition($table, $name); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + //first drop the FK enforcing triggers + $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + //then drop the constraint itself + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP FOREIGN KEY $name"; + return $db->exec($query); + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP INDEX $name"; + return $db->exec($query); + } + + // }}} + // {{{ _createFKTriggers() + + /** + * Create triggers to enforce the FOREIGN KEY constraint on the table + * + * NB: since there's no RAISE_APPLICATION_ERROR facility in mysql, + * we call a non-existent procedure to raise the FK violation message. + * @see http://forums.mysql.com/read.php?99,55108,71877#msg-71877 + * + * @param string $table table name + * @param array $foreign_keys FOREIGN KEY definitions + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _createFKTriggers($table, $foreign_keys) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + // create triggers to enforce FOREIGN KEY constraints + if ($db->supports('triggers') && !empty($foreign_keys)) { + $table_quoted = $db->quoteIdentifier($table, true); + foreach ($foreign_keys as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + //set actions to default if not set + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); + $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); + + $trigger_names = array( + 'insert' => $fkname.'_insert_trg', + 'update' => $fkname.'_update_trg', + 'pk_update' => $fkname.'_pk_update_trg', + 'pk_delete' => $fkname.'_pk_delete_trg', + ); + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + + //create the ON [UPDATE|DELETE] triggers on the primary table + $restrict_action = ' IF (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $table_quoted .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$table_quoted + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $i OLD.'.$referenced_fields[$i]; + } + + $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL'; + $restrict_action2 = empty($conditions2) ? '' : ' AND (' .implode(' OR ', $conditions2) .')'; + $restrict_action3 = ' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();' + .' END IF;'; + + $restrict_action_update = $restrict_action . $restrict_action2 . $restrict_action3; + $restrict_action_delete = $restrict_action . $restrict_action3; // There is no NEW row in on DELETE trigger + + $cascade_action_update = 'UPDATE '.$table_quoted.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';'; + $cascade_action_delete = 'DELETE FROM '.$table_quoted.' WHERE '.implode(' AND ', $conditions). ';'; + $setnull_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';'; + + if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { + $db->loadModule('Reverse', null, true); + $default_values = array(); + foreach ($table_fields as $table_field) { + $field_definition = $db->reverse->getTableFieldDefinition($table, $field); + if (PEAR::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';'; + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW BEGIN ' + .' SET FOREIGN_KEY_CHECKS = 0; '; //only really needed for ON UPDATE CASCADE + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $cascade_action_update; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action; + } elseif ('NO ACTION' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action_update, $trigger_names['pk_update'], 'AFTER UPDATE', 'update'); + } elseif ('RESTRICT' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action_update, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update'); + } + if ('CASCADE' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $cascade_action_delete; + } elseif ('SET NULL' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action; + } elseif ('SET DEFAULT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action; + } elseif ('NO ACTION' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action_delete, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete'); + } elseif ('RESTRICT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action_delete, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete'); + } + $sql_update .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; + $sql_delete .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; + + $db->pushErrorHandling(PEAR_ERROR_RETURN); + $db->expectError(MDB2_ERROR_CANNOT_CREATE); + $result = $db->exec($sql_delete); + $expected_errmsg = 'This MySQL version doesn\'t support multiple triggers with the same action time and event for one table'; + $db->popExpect(); + $db->popErrorHandling(); + if (PEAR::isError($result)) { + if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { + return $result; + } + $db->warnings[] = $expected_errmsg; + } + $db->pushErrorHandling(PEAR_ERROR_RETURN); + $db->expectError(MDB2_ERROR_CANNOT_CREATE); + $result = $db->exec($sql_update); + $db->popExpect(); + $db->popErrorHandling(); + if (PEAR::isError($result) && $result->getCode() != MDB2_ERROR_CANNOT_CREATE) { + if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { + return $result; + } + $db->warnings[] = $expected_errmsg; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ _dropFKTriggers() + + /** + * Drop the triggers created to enforce the FOREIGN KEY constraint on the table + * + * @param string $table table name + * @param string $fkname FOREIGN KEY constraint name + * @param string $referenced_table referenced table name + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropFKTriggers($table, $fkname, $referenced_table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $triggers = $this->listTableTriggers($table); + $triggers2 = $this->listTableTriggers($referenced_table); + if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { + $triggers = array_merge($triggers, $triggers2); + $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; + foreach ($triggers as $trigger) { + if (preg_match($pattern, $trigger)) { + $result = $db->exec('DROP TRIGGER '.$trigger); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true); + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if (!$index_data[$non_unique]) { + if ($index_data[$key_name] !== 'PRIMARY') { + $index = $this->_fixIndexName($index_data[$key_name]); + } else { + $index = 'PRIMARY'; + } + if (!empty($index)) { + $result[$index] = true; + } + } + } + + //list FOREIGN KEY constraints... + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $definition = $db->queryOne($query, 'text', 1); + if (!PEAR::isError($definition) && !empty($definition)) { + $pattern = '/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN KEY\b/Uims'; + if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 0) { + foreach ($matches[1] as $constraint) { + $result[$constraint] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + + if ($start == 1) { + return MDB2_OK; + } + + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; + $res = $db->exec($query); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW TABLES"; + if (null !== $database) { + $query .= " FROM $database"; + } + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Manager/pgsql.php b/3rdparty/MDB2/Driver/Manager/pgsql.php index 44a611d399..ec1b08975f 100644 --- a/3rdparty/MDB2/Driver/Manager/pgsql.php +++ b/3rdparty/MDB2/Driver/Manager/pgsql.php @@ -1,951 +1,954 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.87 2008/11/29 14:09:59 afz Exp $ - -require_once('MDB2/Driver/Manager/Common.php'); - -/** - * MDB2 MySQL driver for the management modules - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_Driver_Manager_pgsql extends MDB2_Driver_Manager_Common -{ - // {{{ createDatabase() - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @param array $options array with charset info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createDatabase($name, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $query = 'CREATE DATABASE ' . $name; - if (!empty($options['charset'])) { - $query .= ' WITH ENCODING ' . $db->quote($options['charset'], 'text'); - } - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ alterDatabase() - - /** - * alter an existing database - * - * @param string $name name of the database that is intended to be changed - * @param array $options array with name, owner info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function alterDatabase($name, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true); - if (!empty($options['name'])) { - $query .= ' RENAME TO ' . $options['name']; - } - if (!empty($options['owner'])) { - $query .= ' OWNER TO ' . $options['owner']; - } - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ dropDatabase() - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropDatabase($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $query = "DROP DATABASE $name"; - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ _getAdvancedFKOptions() - - /** - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @access protected - */ - function _getAdvancedFKOptions($definition) - { - $query = ''; - if (!empty($definition['match'])) { - $query .= ' MATCH '.$definition['match']; - } - if (!empty($definition['onupdate'])) { - $query .= ' ON UPDATE '.$definition['onupdate']; - } - if (!empty($definition['ondelete'])) { - $query .= ' ON DELETE '.$definition['ondelete']; - } - if (!empty($definition['deferrable'])) { - $query .= ' DEFERRABLE'; - } else { - $query .= ' NOT DEFERRABLE'; - } - if (!empty($definition['initiallydeferred'])) { - $query .= ' INITIALLY DEFERRED'; - } else { - $query .= ' INITIALLY IMMEDIATE'; - } - return $query; - } - - // }}} - // {{{ truncateTable() - - /** - * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, - * it falls back to a DELETE FROM TABLE query) - * - * @param string $name name of the table that should be truncated - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function truncateTable($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("TRUNCATE TABLE $name"); - } - - // }}} - // {{{ vacuum() - - /** - * Optimize (vacuum) all the tables in the db (or only the specified table) - * and optionally run ANALYZE. - * - * @param string $table table name (all the tables if empty) - * @param array $options an array with driver-specific options: - * - timeout [int] (in seconds) [mssql-only] - * - analyze [boolean] [pgsql and mysql] - * - full [boolean] [pgsql-only] - * - freeze [boolean] [pgsql-only] - * - * @return mixed MDB2_OK success, a MDB2 error on failure - * @access public - */ - function vacuum($table = null, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $query = 'VACUUM'; - - if (!empty($options['full'])) { - $query .= ' FULL'; - } - if (!empty($options['freeze'])) { - $query .= ' FREEZE'; - } - if (!empty($options['analyze'])) { - $query .= ' ANALYZE'; - } - - if (!empty($table)) { - $query .= ' '.$db->quoteIdentifier($table, true); - } - return $db->exec($query); - } - - // }}} - // {{{ alterTable() - - /** - * alter an existing table - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @access public - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function alterTable($name, $changes, $check) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - foreach ($changes as $change_name => $change) { - switch ($change_name) { - case 'add': - case 'remove': - case 'change': - case 'name': - case 'rename': - break; - default: - return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, - 'change type "'.$change_name.'\" not yet supported', __FUNCTION__); - } - } - - if ($check) { - return MDB2_OK; - } - - $name = $db->quoteIdentifier($name, true); - - if (!empty($changes['remove']) && is_array($changes['remove'])) { - foreach ($changes['remove'] as $field_name => $field) { - $field_name = $db->quoteIdentifier($field_name, true); - $query = 'DROP ' . $field_name; - $result = $db->exec("ALTER TABLE $name $query"); - if (PEAR::isError($result)) { - return $result; - } - } - } - - if (!empty($changes['rename']) && is_array($changes['rename'])) { - foreach ($changes['rename'] as $field_name => $field) { - $field_name = $db->quoteIdentifier($field_name, true); - $result = $db->exec("ALTER TABLE $name RENAME COLUMN $field_name TO ".$db->quoteIdentifier($field['name'], true)); - if (PEAR::isError($result)) { - return $result; - } - } - } - - if (!empty($changes['add']) && is_array($changes['add'])) { - foreach ($changes['add'] as $field_name => $field) { - $query = 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); - $result = $db->exec("ALTER TABLE $name $query"); - if (PEAR::isError($result)) { - return $result; - } - } - } - - if (!empty($changes['change']) && is_array($changes['change'])) { - foreach ($changes['change'] as $field_name => $field) { - $field_name = $db->quoteIdentifier($field_name, true); - if (!empty($field['definition']['type'])) { - $server_info = $db->getServerVersion(); - if (PEAR::isError($server_info)) { - return $server_info; - } - if (is_array($server_info) && $server_info['major'] < 8) { - return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, - 'changing column type for "'.$change_name.'\" requires PostgreSQL 8.0 or above', __FUNCTION__); - } - $db->loadModule('Datatype', null, true); - $type = $db->datatype->getTypeDeclaration($field['definition']); - if($type=='SERIAL PRIMARY KEY'){//not correct when altering a table, since serials arent a real type - $type='INTEGER';//use integer instead - } - $query = "ALTER $field_name TYPE $type USING CAST($field_name AS $type)"; - $result = $db->exec("ALTER TABLE $name $query"); - if (PEAR::isError($result)) { - return $result; - } - } - if (array_key_exists('default', $field['definition'])) { - $query = "ALTER $field_name SET DEFAULT ".$db->quote($field['definition']['default'], $field['definition']['type']); - $result = $db->exec("ALTER TABLE $name $query"); - if (PEAR::isError($result)) { - return $result; - } - } - if (!empty($field['definition']['notnull'])) { - $query = "ALTER $field_name ".($field['definition']['notnull'] ? 'SET' : 'DROP').' NOT NULL'; - $result = $db->exec("ALTER TABLE $name $query"); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - - if (!empty($changes['name'])) { - $change_name = $db->quoteIdentifier($changes['name'], true); - $result = $db->exec("ALTER TABLE $name RENAME TO ".$change_name); - if (PEAR::isError($result)) { - return $result; - } - } - - return MDB2_OK; - } - - // }}} - // {{{ listDatabases() - - /** - * list all databases - * - * @return mixed array of database names on success, a MDB2 error on failure - * @access public - */ - function listDatabases() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT datname FROM pg_database'; - $result2 = $db->standaloneQuery($query, array('text'), false); - if (!MDB2::isResultCommon($result2)) { - return $result2; - } - - $result = $result2->fetchCol(); - $result2->free(); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listUsers() - - /** - * list all users - * - * @return mixed array of user names on success, a MDB2 error on failure - * @access public - */ - function listUsers() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT usename FROM pg_user'; - $result2 = $db->standaloneQuery($query, array('text'), false); - if (!MDB2::isResultCommon($result2)) { - return $result2; - } - - $result = $result2->fetchCol(); - $result2->free(); - return $result; - } - - // }}} - // {{{ listViews() - - /** - * list all views in the current database - * - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listViews($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT viewname - FROM pg_views - WHERE schemaname NOT IN ('pg_catalog', 'information_schema') - AND viewname !~ '^pg_'"; - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableViews() - - /** - * list the views in the database that reference a given table - * - * @param string table for which all referenced views should be found - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listTableViews($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT viewname FROM pg_views NATURAL JOIN pg_tables'; - $query.= ' WHERE tablename ='.$db->quote($table, 'text'); - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listFunctions() - - /** - * list all functions in the current database - * - * @return mixed array of function names on success, a MDB2 error on failure - * @access public - */ - function listFunctions() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = " - SELECT - proname - FROM - pg_proc pr, - pg_type tp - WHERE - tp.oid = pr.prorettype - AND pr.proisagg = FALSE - AND tp.typname <> 'trigger' - AND pr.pronamespace IN - (SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableTriggers() - - /** - * list all triggers in the database that reference a given table - * - * @param string table for which all referenced triggers should be found - * @return mixed array of trigger names on success, a MDB2 error on failure - * @access public - */ - function listTableTriggers($table = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT trg.tgname AS trigger_name - FROM pg_trigger trg, - pg_class tbl - WHERE trg.tgrelid = tbl.oid'; - if (!is_null($table)) { - $table = $db->quote(strtoupper($table), 'text'); - $query .= " AND tbl.relname = $table"; - } - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTables() - - /** - * list all tables in the current database - * - * @return mixed array of table names on success, a MDB2 error on failure - * @access public - */ - function listTables($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - // gratuitously stolen from PEAR DB _getSpecialQuery in pgsql.php - $query = 'SELECT c.relname AS "Name"' - . ' FROM pg_class c, pg_user u' - . ' WHERE c.relowner = u.usesysid' - . " AND c.relkind = 'r'" - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_views' - . ' WHERE viewname = c.relname)' - . " AND c.relname !~ '^(pg_|sql_)'" - . ' UNION' - . ' SELECT c.relname AS "Name"' - . ' FROM pg_class c' - . " WHERE c.relkind = 'r'" - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_views' - . ' WHERE viewname = c.relname)' - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_user' - . ' WHERE usesysid = c.relowner)' - . " AND c.relname !~ '^pg_'"; - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableFields() - - /** - * list all fields in a table in the current database - * - * @param string $table name of table that should be used in method - * @return mixed array of field names on success, a MDB2 error on failure - * @access public - */ - function listTableFields($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table); - - $table = $db->quoteIdentifier($table, true); - if (!empty($schema)) { - $table = $db->quoteIdentifier($schema, true) . '.' .$table; - } - $db->setLimit(1); - $result2 = $db->query("SELECT * FROM $table LIMIT 1"); - if (PEAR::isError($result2)) { - return $result2; - } - $result = $result2->getColumnNames(); - $result2->free(); - if (PEAR::isError($result)) { - return $result; - } - return array_flip($result); - } - - // }}} - // {{{ listTableIndexes() - - /** - * list all indexes in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of index names on success, a MDB2 error on failure - * @access public - */ - function listTableIndexes($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table); - - $table = $db->quote($table, 'text'); - $subquery = "SELECT indexrelid - FROM pg_index - LEFT JOIN pg_class ON pg_class.oid = pg_index.indrelid - LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid - WHERE pg_class.relname = $table - AND indisunique != 't' - AND indisprimary != 't'"; - if (!empty($schema)) { - $subquery .= ' AND pg_namespace.nspname = '.$db->quote($schema, 'text'); - } - $query = "SELECT relname FROM pg_class WHERE oid IN ($subquery)"; - $indexes = $db->queryCol($query, 'text'); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $index) { - $index = $this->_fixIndexName($index); - if (!empty($index)) { - $result[$index] = true; - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ dropConstraint() - - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropConstraint($table, $name, $primary = false) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - // is it an UNIQUE index? - $query = 'SELECT relname - FROM pg_class - WHERE oid IN ( - SELECT indexrelid - FROM pg_index, pg_class - WHERE pg_class.relname = '.$db->quote($table, 'text').' - AND pg_class.oid = pg_index.indrelid - AND indisunique = \'t\') - EXCEPT - SELECT conname - FROM pg_constraint, pg_class - WHERE pg_constraint.conrelid = pg_class.oid - AND relname = '. $db->quote($table, 'text'); - $unique = $db->queryCol($query, 'text'); - if (PEAR::isError($unique) || empty($unique)) { - // not an UNIQUE index, maybe a CONSTRAINT - return parent::dropConstraint($table, $name, $primary); - } - - if (in_array($name, $unique)) { - return $db->exec('DROP INDEX '.$db->quoteIdentifier($name, true)); - } - $idxname = $db->getIndexName($name); - if (in_array($idxname, $unique)) { - return $db->exec('DROP INDEX '.$db->quoteIdentifier($idxname, true)); - } - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $name . ' is not an existing constraint for table ' . $table, __FUNCTION__); - } - - // }}} - // {{{ listTableConstraints() - - /** - * list all constraints in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of constraint names on success, a MDB2 error on failure - * @access public - */ - function listTableConstraints($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table); - - $table = $db->quote($table, 'text'); - $query = 'SELECT conname - FROM pg_constraint - LEFT JOIN pg_class ON pg_constraint.conrelid = pg_class.oid - LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid - WHERE relname = ' .$table; - if (!empty($schema)) { - $query .= ' AND pg_namespace.nspname = ' . $db->quote($schema, 'text'); - } - $query .= ' - UNION DISTINCT - SELECT relname - FROM pg_class - WHERE oid IN ( - SELECT indexrelid - FROM pg_index - LEFT JOIN pg_class ON pg_class.oid = pg_index.indrelid - LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid - WHERE pg_class.relname = '.$table.' - AND indisunique = \'t\''; - if (!empty($schema)) { - $query .= ' AND pg_namespace.nspname = ' . $db->quote($schema, 'text'); - } - $query .= ')'; - $constraints = $db->queryCol($query); - if (PEAR::isError($constraints)) { - return $constraints; - } - - $result = array(); - foreach ($constraints as $constraint) { - $constraint = $this->_fixIndexName($constraint); - if (!empty($constraint)) { - $result[$constraint] = true; - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE - && $db->options['field_case'] == CASE_LOWER - ) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createSequence() - - /** - * create sequence - * - * @param string $seq_name name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createSequence($seq_name, $start = 1) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - return $db->exec("CREATE SEQUENCE $sequence_name INCREMENT 1". - ($start < 1 ? " MINVALUE $start" : '')." START $start"); - } - - // }}} - // {{{ dropSequence() - - /** - * drop existing sequence - * - * @param string $seq_name name of the sequence to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropSequence($seq_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - return $db->exec("DROP SEQUENCE $sequence_name"); - } - - // }}} - // {{{ listSequences() - - /** - * list all sequences in the current database - * - * @return mixed array of sequence names on success, a MDB2 error on failure - * @access public - */ - function listSequences($database = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT relname FROM pg_class WHERE relkind = 'S' AND relnamespace IN"; - $query.= "(SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; - $table_names = $db->queryCol($query); - if (PEAR::isError($table_names)) { - return $table_names; - } - $result = array(); - foreach ($table_names as $table_name) { - $result[] = $this->_fixSequenceName($table_name); - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } -} + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php 299393 2010-05-14 17:49:49Z afz $ + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_Manager_pgsql extends MDB2_Driver_Manager_Common +{ + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = 'CREATE DATABASE ' . $name; + if (!empty($options['charset'])) { + $query .= ' WITH ENCODING ' . $db->quote($options['charset'], 'text'); + } + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * @param string $name name of the database that is intended to be changed + * @param array $options array with name, owner info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($name, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = ''; + if (!empty($options['name'])) { + $query .= ' RENAME TO ' . $options['name']; + } + if (!empty($options['owner'])) { + $query .= ' OWNER TO ' . $options['owner']; + } + + if (empty($query)) { + return MDB2_OK; + } + + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true) . $query; + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "DROP DATABASE $name"; + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + $query = ''; + if (!empty($definition['match'])) { + $query .= ' MATCH '.$definition['match']; + } + if (!empty($definition['onupdate'])) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete'])) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + // }}} + // {{{ truncateTable() + + /** + * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, + * it falls back to a DELETE FROM TABLE query) + * + * @param string $name name of the table that should be truncated + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function truncateTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("TRUNCATE TABLE $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $query = 'VACUUM'; + + if (!empty($options['full'])) { + $query .= ' FULL'; + } + if (!empty($options['freeze'])) { + $query .= ' FREEZE'; + } + if (!empty($options['analyze'])) { + $query .= ' ANALYZE'; + } + + if (!empty($table)) { + $query .= ' '.$db->quoteIdentifier($table, true); + } + return $db->exec($query); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'\" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $query = 'DROP ' . $field_name; + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $result = $db->exec("ALTER TABLE $name RENAME COLUMN $field_name TO ".$db->quoteIdentifier($field['name'], true)); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + $query = 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + if (!empty($field['definition']['type'])) { + $server_info = $db->getServerVersion(); + if (PEAR::isError($server_info)) { + return $server_info; + } + if (is_array($server_info) && $server_info['major'] < 8) { + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'changing column type for "'.$change_name.'\" requires PostgreSQL 8.0 or above', __FUNCTION__); + } + $db->loadModule('Datatype', null, true); + $type = $db->datatype->getTypeDeclaration($field['definition']); + $query = "ALTER $field_name TYPE $type USING CAST($field_name AS $type)"; + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::isError($result)) { + return $result; + } + } + if (array_key_exists('default', $field['definition'])) { + $query = "ALTER $field_name SET DEFAULT ".$db->quote($field['definition']['default'], $field['definition']['type']); + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::isError($result)) { + return $result; + } + } + if (array_key_exists('notnull', $field['definition'])) { + $query = "ALTER $field_name ".($field['definition']['notnull'] ? 'SET' : 'DROP').' NOT NULL'; + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("ALTER TABLE $name RENAME TO ".$change_name); + if (PEAR::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT datname FROM pg_database'; + $result2 = $db->standaloneQuery($query, array('text'), false); + if (!MDB2::isResultCommon($result2)) { + return $result2; + } + + $result = $result2->fetchCol(); + $result2->free(); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT usename FROM pg_user'; + $result2 = $db->standaloneQuery($query, array('text'), false); + if (!MDB2::isResultCommon($result2)) { + return $result2; + } + + $result = $result2->fetchCol(); + $result2->free(); + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT viewname + FROM pg_views + WHERE schemaname NOT IN ('pg_catalog', 'information_schema') + AND viewname !~ '^pg_'"; + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT viewname FROM pg_views NATURAL JOIN pg_tables'; + $query.= ' WHERE tablename ='.$db->quote($table, 'text'); + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = " + SELECT + proname + FROM + pg_proc pr, + pg_type tp + WHERE + tp.oid = pr.prorettype + AND pr.proisagg = FALSE + AND tp.typname <> 'trigger' + AND pr.pronamespace IN + (SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT trg.tgname AS trigger_name + FROM pg_trigger trg, + pg_class tbl + WHERE trg.tgrelid = tbl.oid'; + if (null !== $table) { + $table = $db->quote(strtoupper($table), 'text'); + $query .= " AND UPPER(tbl.relname) = $table"; + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + // gratuitously stolen from PEAR DB _getSpecialQuery in pgsql.php + $query = 'SELECT c.relname AS "Name"' + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table); + + $table = $db->quoteIdentifier($table, true); + if (!empty($schema)) { + $table = $db->quoteIdentifier($schema, true) . '.' .$table; + } + $db->setLimit(1); + $result2 = $db->query("SELECT * FROM $table"); + if (PEAR::isError($result2)) { + return $result2; + } + $result = $result2->getColumnNames(); + $result2->free(); + if (PEAR::isError($result)) { + return $result; + } + return array_flip($result); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table); + + $table = $db->quote($table, 'text'); + $subquery = "SELECT indexrelid + FROM pg_index + LEFT JOIN pg_class ON pg_class.oid = pg_index.indrelid + LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid + WHERE pg_class.relname = $table + AND indisunique != 't' + AND indisprimary != 't'"; + if (!empty($schema)) { + $subquery .= ' AND pg_namespace.nspname = '.$db->quote($schema, 'text'); + } + $query = "SELECT relname FROM pg_class WHERE oid IN ($subquery)"; + $indexes = $db->queryCol($query, 'text'); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index) { + $index = $this->_fixIndexName($index); + if (!empty($index)) { + $result[$index] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + // is it an UNIQUE index? + $query = 'SELECT relname + FROM pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = '.$db->quote($table, 'text').' + AND pg_class.oid = pg_index.indrelid + AND indisunique = \'t\') + EXCEPT + SELECT conname + FROM pg_constraint, pg_class + WHERE pg_constraint.conrelid = pg_class.oid + AND relname = '. $db->quote($table, 'text'); + $unique = $db->queryCol($query, 'text'); + if (PEAR::isError($unique) || empty($unique)) { + // not an UNIQUE index, maybe a CONSTRAINT + return parent::dropConstraint($table, $name, $primary); + } + + if (in_array($name, $unique)) { + return $db->exec('DROP INDEX '.$db->quoteIdentifier($name, true)); + } + $idxname = $db->getIndexName($name); + if (in_array($idxname, $unique)) { + return $db->exec('DROP INDEX '.$db->quoteIdentifier($idxname, true)); + } + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $name . ' is not an existing constraint for table ' . $table, __FUNCTION__); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table); + + $table = $db->quote($table, 'text'); + $query = 'SELECT conname + FROM pg_constraint + LEFT JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid + WHERE relname = ' .$table; + if (!empty($schema)) { + $query .= ' AND pg_namespace.nspname = ' . $db->quote($schema, 'text'); + } + $query .= ' + UNION DISTINCT + SELECT relname + FROM pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index + LEFT JOIN pg_class ON pg_class.oid = pg_index.indrelid + LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid + WHERE pg_class.relname = '.$table.' + AND indisunique = \'t\''; + if (!empty($schema)) { + $query .= ' AND pg_namespace.nspname = ' . $db->quote($schema, 'text'); + } + $query .= ')'; + $constraints = $db->queryCol($query); + if (PEAR::isError($constraints)) { + return $constraints; + } + + $result = array(); + foreach ($constraints as $constraint) { + $constraint = $this->_fixIndexName($constraint); + if (!empty($constraint)) { + $result[$constraint] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + && $db->options['field_case'] == CASE_LOWER + ) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("CREATE SEQUENCE $sequence_name INCREMENT 1". + ($start < 1 ? " MINVALUE $start" : '')." START $start"); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP SEQUENCE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT relname FROM pg_class WHERE relkind = 'S' AND relnamespace IN"; + $query.= "(SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + $result[] = $this->_fixSequenceName($table_name); + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Manager/sqlite.php b/3rdparty/MDB2/Driver/Manager/sqlite.php index 1b7239876f..c4c30c9dda 100644 --- a/3rdparty/MDB2/Driver/Manager/sqlite.php +++ b/3rdparty/MDB2/Driver/Manager/sqlite.php @@ -1,1366 +1,1362 @@ - | -// | Lorenzo Alberton | -// +----------------------------------------------------------------------+ -// -// $Id: sqlite.php,v 1.76 2008/05/31 11:48:48 quipo Exp $ -// - -require_once('MDB2/Driver/Manager/Common.php'); - -/** - * MDB2 SQLite driver for the management modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - * @author Lorenzo Alberton - */ -class MDB2_Driver_Manager_sqlite extends MDB2_Driver_Manager_Common -{ - // {{{ createDatabase() - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @param array $options array with charset info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createDatabase($name, $options = array()) - { - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $database_file = $db->_getDatabaseFile($name); - if (file_exists($database_file)) { - return $db->raiseError(MDB2_ERROR_ALREADY_EXISTS, null, null, - 'database already exists', __FUNCTION__); - } - $php_errormsg = ''; - $database_file="$datadir/$database_file.db"; - $handle = sqlite_open($database_file, $db->dsn['mode'], $php_errormsg); - if (!$handle) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - (isset($php_errormsg) ? $php_errormsg : 'could not create the database file'), __FUNCTION__); - } - if (!empty($options['charset'])) { - $query = 'PRAGMA encoding = ' . $db->quote($options['charset'], 'text'); - @sqlite_query($query, $handle); - } - @sqlite_close($handle); - return MDB2_OK; - } - - // }}} - // {{{ dropDatabase() - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropDatabase($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $database_file = $db->_getDatabaseFile($name); - if (!@file_exists($database_file)) { - return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, - 'database does not exist', __FUNCTION__); - } - $result = @unlink($database_file); - if (!$result) { - return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, - (isset($php_errormsg) ? $php_errormsg : 'could not remove the database file'), __FUNCTION__); - } - return MDB2_OK; - } - - // }}} - // {{{ _getAdvancedFKOptions() - - /** - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @access protected - */ - function _getAdvancedFKOptions($definition) - { - $query = ''; - if (!empty($definition['match'])) { - $query .= ' MATCH '.$definition['match']; - } - if (!empty($definition['onupdate']) && (strtoupper($definition['onupdate']) != 'NO ACTION')) { - $query .= ' ON UPDATE '.$definition['onupdate']; - } - if (!empty($definition['ondelete']) && (strtoupper($definition['ondelete']) != 'NO ACTION')) { - $query .= ' ON DELETE '.$definition['ondelete']; - } - if (!empty($definition['deferrable'])) { - $query .= ' DEFERRABLE'; - } else { - $query .= ' NOT DEFERRABLE'; - } - if (!empty($definition['initiallydeferred'])) { - $query .= ' INITIALLY DEFERRED'; - } else { - $query .= ' INITIALLY IMMEDIATE'; - } - return $query; - } - - // }}} - // {{{ _getCreateTableQuery() - - /** - * Create a basic SQL query for a new table creation - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * @param array $options An associative array of table options - * @return mixed string (the SQL query) on success, a MDB2 error on failure - * @see createTable() - */ - function _getCreateTableQuery($name, $fields, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!$name) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - 'no valid table name specified', __FUNCTION__); - } - if (empty($fields)) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - 'no fields specified for table "'.$name.'"', __FUNCTION__); - } - $query_fields = $this->getFieldDeclarationList($fields); - if (PEAR::isError($query_fields)) { - return $query_fields; - } - if (!empty($options['primary'])) { - $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; - } - if (!empty($options['foreign_keys'])) { - foreach ($options['foreign_keys'] as $fkname => $fkdef) { - if (empty($fkdef)) { - continue; - } - $query_fields.= ', CONSTRAINT '.$fkname.' FOREIGN KEY ('.implode(', ', array_keys($fkdef['fields'])).')'; - $query_fields.= ' REFERENCES '.$fkdef['references']['table'].' ('.implode(', ', array_keys($fkdef['references']['fields'])).')'; - $query_fields.= $this->_getAdvancedFKOptions($fkdef); - } - } - - $name = $db->quoteIdentifier($name, true); - $result = 'CREATE '; - if (!empty($options['temporary'])) { - $result .= $this->_getTemporaryTableQuery(); - } - $result .= " TABLE $name ($query_fields)"; - return $result; - } - - // }}} - // {{{ createTable() - - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition - * of each field of the new table - * @param array $options An associative array of table options - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createTable($name, $fields, $options = array()) - { - $result = parent::createTable($name, $fields, $options); - if (PEAR::isError($result)) { - return $result; - } - // create triggers to enforce FOREIGN KEY constraints - if (!empty($options['foreign_keys'])) { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - foreach ($options['foreign_keys'] as $fkname => $fkdef) { - if (empty($fkdef)) { - continue; - } - //set actions to default if not set - $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); - $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); - - $trigger_names = array( - 'insert' => $fkname.'_insert_trg', - 'update' => $fkname.'_update_trg', - 'pk_update' => $fkname.'_pk_update_trg', - 'pk_delete' => $fkname.'_pk_delete_trg', - ); - - //create the [insert|update] triggers on the FK table - $table_fields = array_keys($fkdef['fields']); - $referenced_fields = array_keys($fkdef['references']['fields']); - $query = 'CREATE TRIGGER %s BEFORE %s ON '.$name - .' FOR EACH ROW BEGIN' - .' SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' - .' WHERE (SELECT '; - $aliased_fields = array(); - foreach ($referenced_fields as $field) { - $aliased_fields[] = $fkdef['references']['table'] .'.'.$field .' AS '.$field; - } - $query .= implode(',', $aliased_fields) - .' FROM '.$fkdef['references']['table'] - .' WHERE '; - $conditions = array(); - for ($i=0; $iexec(sprintf($query, $trigger_names['insert'], 'INSERT', 'insert')); - if (PEAR::isError($result)) { - return $result; - } - - $result = $db->exec(sprintf($query, $trigger_names['update'], 'UPDATE', 'update')); - if (PEAR::isError($result)) { - return $result; - } - - //create the ON [UPDATE|DELETE] triggers on the primary table - $restrict_action = 'SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' - .' WHERE (SELECT '; - $aliased_fields = array(); - foreach ($table_fields as $field) { - $aliased_fields[] = $name .'.'.$field .' AS '.$field; - } - $restrict_action .= implode(',', $aliased_fields) - .' FROM '.$name - .' WHERE '; - $conditions = array(); - $new_values = array(); - $null_values = array(); - for ($i=0; $i OLD.'.$referenced_fields[$i]; - } - $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' - .' AND (' .implode(' OR ', $conditions2) .')'; - - $cascade_action_update = 'UPDATE '.$name.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions); - $cascade_action_delete = 'DELETE FROM '.$name.' WHERE '.implode(' AND ', $conditions); - $setnull_action = 'UPDATE '.$name.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions); - - if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { - $db->loadModule('Reverse', null, true); - $default_values = array(); - foreach ($table_fields as $table_field) { - $field_definition = $db->reverse->getTableFieldDefinition($name, $field); - if (PEAR::isError($field_definition)) { - return $field_definition; - } - $default_values[] = $table_field .' = '. $field_definition[0]['default']; - } - $setdefault_action = 'UPDATE '.$name.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions); - } - - $query = 'CREATE TRIGGER %s' - .' %s ON '.$fkdef['references']['table'] - .' FOR EACH ROW BEGIN '; - - if ('CASCADE' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . $cascade_action_update. '; END;'; - } elseif ('SET NULL' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action. '; END;'; - } elseif ('SET DEFAULT' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action. '; END;'; - } elseif ('NO ACTION' == $fkdef['onupdate']) { - $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . '; END;'; - } elseif ('RESTRICT' == $fkdef['onupdate']) { - $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . '; END;'; - } - if ('CASCADE' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . $cascade_action_delete. '; END;'; - } elseif ('SET NULL' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action. '; END;'; - } elseif ('SET DEFAULT' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action. '; END;'; - } elseif ('NO ACTION' == $fkdef['ondelete']) { - $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . '; END;'; - } elseif ('RESTRICT' == $fkdef['ondelete']) { - $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . '; END;'; - } - - if (PEAR::isError($result)) { - return $result; - } - $result = $db->exec($sql_delete); - if (PEAR::isError($result)) { - return $result; - } - $result = $db->exec($sql_update); - if (PEAR::isError($result)) { - return $result; - } - } - } - if (PEAR::isError($result)) { - return $result; - } - return MDB2_OK; - } - - // }}} - // {{{ dropTable() - - /** - * drop an existing table - * - * @param string $name name of the table that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropTable($name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - //delete the triggers associated to existing FK constraints - $constraints = $this->listTableConstraints($name); - if (!PEAR::isError($constraints) && !empty($constraints)) { - $db->loadModule('Reverse', null, true); - foreach ($constraints as $constraint) { - $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); - if (!PEAR::isError($definition) && !empty($definition['foreign'])) { - $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("DROP TABLE $name"); - } - - // }}} - // {{{ vacuum() - - /** - * Optimize (vacuum) all the tables in the db (or only the specified table) - * and optionally run ANALYZE. - * - * @param string $table table name (all the tables if empty) - * @param array $options an array with driver-specific options: - * - timeout [int] (in seconds) [mssql-only] - * - analyze [boolean] [pgsql and mysql] - * - full [boolean] [pgsql-only] - * - freeze [boolean] [pgsql-only] - * - * @return mixed MDB2_OK success, a MDB2 error on failure - * @access public - */ - function vacuum($table = null, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'VACUUM'; - if (!empty($table)) { - $query .= ' '.$db->quoteIdentifier($table, true); - } - return $db->exec($query); - } - - // }}} - // {{{ alterTable() - - /** - * alter an existing table - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @access public - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function alterTable($name, $changes, $check, $options = array()) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - foreach ($changes as $change_name => $change) { - switch ($change_name) { - case 'add': - case 'remove': - case 'change': - case 'name': - case 'rename': - break; - default: - return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, - 'change type "'.$change_name.'" not yet supported', __FUNCTION__); - } - } - - if ($check) { - return MDB2_OK; - } - - $db->loadModule('Reverse', null, true); - - // actually sqlite 2.x supports no ALTER TABLE at all .. so we emulate it - $fields = $db->manager->listTableFields($name); - if (PEAR::isError($fields)) { - return $fields; - } - - $fields = array_flip($fields); - foreach ($fields as $field => $value) { - $definition = $db->reverse->getTableFieldDefinition($name, $field); - if (PEAR::isError($definition)) { - return $definition; - } - $fields[$field] = $definition[0]; - } - - $indexes = $db->manager->listTableIndexes($name); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $indexes = array_flip($indexes); - foreach ($indexes as $index => $value) { - $definition = $db->reverse->getTableIndexDefinition($name, $index); - if (PEAR::isError($definition)) { - return $definition; - } - $indexes[$index] = $definition; - } - - $constraints = $db->manager->listTableConstraints($name); - if (PEAR::isError($constraints)) { - return $constraints; - } - - if (!array_key_exists('foreign_keys', $options)) { - $options['foreign_keys'] = array(); - } - $constraints = array_flip($constraints); - foreach ($constraints as $constraint => $value) { - if (!empty($definition['primary'])) { - if (!array_key_exists('primary', $options)) { - $options['primary'] = $definition['fields']; - //remove from the $constraint array, it's already handled by createTable() - unset($constraints[$constraint]); - } - } else { - $c_definition = $db->reverse->getTableConstraintDefinition($name, $constraint); - if (PEAR::isError($c_definition)) { - return $c_definition; - } - if (!empty($c_definition['foreign'])) { - if (!array_key_exists($constraint, $options['foreign_keys'])) { - $options['foreign_keys'][$constraint] = $c_definition; - } - //remove from the $constraint array, it's already handled by createTable() - unset($constraints[$constraint]); - } else { - $constraints[$constraint] = $c_definition; - } - } - } - - $name_new = $name; - $create_order = $select_fields = array_keys($fields); - foreach ($changes as $change_name => $change) { - switch ($change_name) { - case 'add': - foreach ($change as $field_name => $field) { - $fields[$field_name] = $field; - $create_order[] = $field_name; - } - break; - case 'remove': - foreach ($change as $field_name => $field) { - unset($fields[$field_name]); - $select_fields = array_diff($select_fields, array($field_name)); - $create_order = array_diff($create_order, array($field_name)); - } - break; - case 'change': - foreach ($change as $field_name => $field) { - $fields[$field_name] = $field['definition']; - } - break; - case 'name': - $name_new = $change; - break; - case 'rename': - foreach ($change as $field_name => $field) { - unset($fields[$field_name]); - $fields[$field['name']] = $field['definition']; - $create_order[array_search($field_name, $create_order)] = $field['name']; - } - break; - default: - return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, - 'change type "'.$change_name.'" not yet supported', __FUNCTION__); - } - } - - $data = null; - if (!empty($select_fields)) { - $query = 'SELECT '.implode(', ', $select_fields).' FROM '.$db->quoteIdentifier($name, true); - $data = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); - } - - $result = $this->dropTable($name); - if (PEAR::isError($result)) { - return $result; - } - - $result = $this->createTable($name_new, $fields, $options); - if (PEAR::isError($result)) { - return $result; - } - - foreach ($indexes as $index => $definition) { - $this->createIndex($name_new, $index, $definition); - } - - foreach ($constraints as $constraint => $definition) { - if(empty($definition['primary']) and empty($definition['foreign'])){ - $this->createConstraint($name_new, $constraint, $definition); - } - } - - if (!empty($select_fields) && !empty($data)) { - $query = 'INSERT INTO '.$db->quoteIdentifier($name_new, true); - $query.= '('.implode(', ', array_slice(array_keys($fields), 0, count($select_fields))).')'; - $query.=' VALUES (?'.str_repeat(', ?', (count($select_fields) - 1)).')'; - $stmt =$db->prepare($query, null, MDB2_PREPARE_MANIP); - if (PEAR::isError($stmt)) { - return $stmt; - } - foreach ($data as $row) { - $result = $stmt->execute($row); - if (PEAR::isError($result)) { - return $result; - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ listDatabases() - - /** - * list all databases - * - * @return mixed array of database names on success, a MDB2 error on failure - * @access public - */ - function listDatabases() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'list databases is not supported', __FUNCTION__); - } - - // }}} - // {{{ listUsers() - - /** - * list all users - * - * @return mixed array of user names on success, a MDB2 error on failure - * @access public - */ - function listUsers() - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'list databases is not supported', __FUNCTION__); - } - - // }}} - // {{{ listViews() - - /** - * list all views in the current database - * - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listViews($dummy=null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name FROM sqlite_master WHERE type='view' AND sql NOT NULL"; - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableViews() - - /** - * list the views in the database that reference a given table - * - * @param string table for which all referenced views should be found - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listTableViews($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; - $views = $db->queryAll($query, array('text', 'text'), MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($views)) { - return $views; - } - $result = array(); - foreach ($views as $row) { - if (preg_match("/^create view .* \bfrom\b\s+\b{$table}\b /i", $row['sql'])) { - if (!empty($row['name'])) { - $result[$row['name']] = true; - } - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ listTables() - - /** - * list all tables in the current database - * - * @return mixed array of table names on success, a MDB2 error on failure - * @access public - */ - function listTables($dummy=null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; - $table_names = $db->queryCol($query); - if (PEAR::isError($table_names)) { - return $table_names; - } - $result = array(); - foreach ($table_names as $table_name) { - if (!$this->_fixSequenceName($table_name, true)) { - $result[] = $table_name; - } - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableFields() - - /** - * list all fields in a table in the current database - * - * @param string $table name of table that should be used in method - * @return mixed array of field names on success, a MDB2 error on failure - * @access public - */ - function listTableFields($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->loadModule('Reverse', null, true); - if (PEAR::isError($result)) { - return $result; - } - $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); - } else { - $query.= 'name='.$db->quote($table, 'text'); - } - $sql = $db->queryOne($query); - if (PEAR::isError($sql)) { - return $sql; - } - $columns = $db->reverse->_getTableColumns($sql); - $fields = array(); - foreach ($columns as $column) { - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $column['name'] = strtolower($column['name']); - } else { - $column['name'] = strtoupper($column['name']); - } - } else { - $column = array_change_key_case($column, $db->options['field_case']); - } - $fields[] = $column['name']; - } - return $fields; - } - - // }}} - // {{{ listTableTriggers() - - /** - * list all triggers in the database that reference a given table - * - * @param string table for which all referenced triggers should be found - * @return mixed array of trigger names on success, a MDB2 error on failure - * @access public - */ - function listTableTriggers($table = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name FROM sqlite_master WHERE type='trigger' AND sql NOT NULL"; - if (!is_null($table)) { - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= ' AND LOWER(tbl_name)='.$db->quote(strtolower($table), 'text'); - } else { - $query.= ' AND tbl_name='.$db->quote($table, 'text'); - } - } - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ createIndex() - - /** - * Get the stucture of a field into an array - * - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the index fields as array - * indexes. Each entry of this array is set to another type of associative - * array that specifies properties of the index that are specific to - * each field. - * - * Currently, only the sorting property is supported. It should be used - * to define the sorting direction of the index. It may be set to either - * ascending or descending. - * - * Not all DBMS support index sorting direction configuration. The DBMS - * drivers of those that do not support it ignore this property. Use the - * function support() to determine whether the DBMS driver can manage indexes. - - * Example - * array( - * 'fields' => array( - * 'user_name' => array( - * 'sorting' => 'ascending' - * ), - * 'last_login' => array() - * ) - * ) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createIndex($table, $name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->getIndexName($name); - $query = "CREATE INDEX $name ON $table"; - $fields = array(); - foreach ($definition['fields'] as $field_name => $field) { - $field_string = $field_name; - if (!empty($field['sorting'])) { - switch ($field['sorting']) { - case 'ascending': - $field_string.= ' ASC'; - break; - case 'descending': - $field_string.= ' DESC'; - break; - } - } - $fields[] = $field_string; - } - $query .= ' ('.implode(', ', $fields) . ')'; - return $db->exec($query); - } - - // }}} - // {{{ dropIndex() - - /** - * drop existing index - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropIndex($table, $name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->getIndexName($name); - return $db->exec("DROP INDEX $name"); - } - - // }}} - // {{{ listTableIndexes() - - /** - * list all indexes in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of index names on success, a MDB2 error on failure - * @access public - */ - function listTableIndexes($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quote($table, 'text'); - $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(tbl_name)='.strtolower($table); - } else { - $query.= "tbl_name=$table"; - } - $query.= " AND sql NOT NULL ORDER BY name"; - $indexes = $db->queryCol($query, 'text'); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $sql) { - if (preg_match("/^create index ([^ ]+) on /i", $sql, $tmp)) { - $index = $this->_fixIndexName($tmp[1]); - if (!empty($index)) { - $result[$index] = true; - } - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createConstraint() - - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the constraint fields as array - * constraints. Each entry of this array is set to another type of associative - * array that specifies properties of the constraint that are specific to - * each field. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array(), - * 'last_login' => array() - * ) - * ) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createConstraint($table, $name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!empty($definition['primary'])) { - return $db->manager->alterTable($table, array(), false, array('primary' => $definition['fields'])); - } - - if (!empty($definition['foreign'])) { - return $db->manager->alterTable($table, array(), false, array('foreign_keys' => array($name => $definition))); - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->getIndexName($name); - $query = "CREATE UNIQUE INDEX $name ON $table"; - $fields = array(); - foreach ($definition['fields'] as $field_name => $field) { - $field_string = $field_name; - if (!empty($field['sorting'])) { - switch ($field['sorting']) { - case 'ascending': - $field_string.= ' ASC'; - break; - case 'descending': - $field_string.= ' DESC'; - break; - } - } - $fields[] = $field_string; - } - $query .= ' ('.implode(', ', $fields) . ')'; - return $db->exec($query); - } - - // }}} - // {{{ dropConstraint() - - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropConstraint($table, $name, $primary = false) - { - if ($primary || $name == 'PRIMARY') { - return $this->alterTable($table, array(), false, array('primary' => null)); - } - - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - //is it a FK constraint? If so, also delete the associated triggers - $db->loadModule('Reverse', null, true); - $definition = $db->reverse->getTableConstraintDefinition($table, $name); - if (!PEAR::isError($definition) && !empty($definition['foreign'])) { - //first drop the FK enforcing triggers - $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); - if (PEAR::isError($result)) { - return $result; - } - //then drop the constraint itself - return $this->alterTable($table, array(), false, array('foreign_keys' => array($name => null))); - } - - $name = $db->getIndexName($name); - return $db->exec("DROP INDEX $name"); - } - - // }}} - // {{{ _dropFKTriggers() - - /** - * Drop the triggers created to enforce the FOREIGN KEY constraint on the table - * - * @param string $table table name - * @param string $fkname FOREIGN KEY constraint name - * @param string $referenced_table referenced table name - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access private - */ - function _dropFKTriggers($table, $fkname, $referenced_table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $triggers = $this->listTableTriggers($table); - $triggers2 = $this->listTableTriggers($referenced_table); - if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { - $triggers = array_merge($triggers, $triggers2); - $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; - foreach ($triggers as $trigger) { - if (preg_match($pattern, $trigger)) { - $result = $db->exec('DROP TRIGGER '.$trigger); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ listTableConstraints() - - /** - * list all constraints in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of constraint names on success, a MDB2 error on failure - * @access public - */ - function listTableConstraints($table) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quote($table, 'text'); - $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(tbl_name)='.strtolower($table); - } else { - $query.= "tbl_name=$table"; - } - $query.= " AND sql NOT NULL ORDER BY name"; - $indexes = $db->queryCol($query, 'text'); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $sql) { - if (preg_match("/^create unique index ([^ ]+) on /i", $sql, $tmp)) { - $index = $this->_fixIndexName($tmp[1]); - if (!empty($index)) { - $result[$index] = true; - } - } - } - - // also search in table definition for PRIMARY KEYs... - $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(name)='.strtolower($table); - } else { - $query.= "name=$table"; - } - $query.= " AND sql NOT NULL ORDER BY name"; - $table_def = $db->queryOne($query, 'text'); - if (PEAR::isError($table_def)) { - return $table_def; - } - if (preg_match("/\bPRIMARY\s+KEY\b/i", $table_def, $tmp)) { - $result['primary'] = true; - } - - // ...and for FOREIGN KEYs - if (preg_match_all("/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN\s+KEY/imsx", $table_def, $tmp)) { - foreach ($tmp[1] as $fk) { - $result[$fk] = true; - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createSequence() - - /** - * create sequence - * - * @param string $seq_name name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createSequence($seq_name, $start = 1) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); - $query = "CREATE TABLE $sequence_name ($seqcol_name INTEGER PRIMARY KEY DEFAULT 0 NOT NULL)"; - $res = $db->exec($query); - if (PEAR::isError($res)) { - return $res; - } - if ($start == 1) { - return MDB2_OK; - } - $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); - if (!PEAR::isError($res)) { - return MDB2_OK; - } - // Handle error - $result = $db->exec("DROP TABLE $sequence_name"); - if (PEAR::isError($result)) { - return $db->raiseError($result, null, null, - 'could not drop inconsistent sequence table', __FUNCTION__); - } - return $db->raiseError($res, null, null, - 'could not create sequence table', __FUNCTION__); - } - - // }}} - // {{{ dropSequence() - - /** - * drop existing sequence - * - * @param string $seq_name name of the sequence to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropSequence($seq_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - return $db->exec("DROP TABLE $sequence_name"); - } - - // }}} - // {{{ listSequences() - - /** - * list all sequences in the current database - * - * @return mixed array of sequence names on success, a MDB2 error on failure - * @access public - */ - function listSequences($dummy=null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; - $table_names = $db->queryCol($query); - if (PEAR::isError($table_names)) { - return $table_names; - } - $result = array(); - foreach ($table_names as $table_name) { - if ($sqn = $this->_fixSequenceName($table_name, true)) { - $result[] = $sqn; - } - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} -} -?> + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 SQLite driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_sqlite extends MDB2_Driver_Manager_Common +{ + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $database_file = $db->_getDatabaseFile($name); + if (file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_ALREADY_EXISTS, null, null, + 'database already exists', __FUNCTION__); + } + $php_errormsg = ''; + $handle = @sqlite_open($database_file, $db->dsn['mode'], $php_errormsg); + if (!$handle) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not create the database file'), __FUNCTION__); + } + if (!empty($options['charset'])) { + $query = 'PRAGMA encoding = ' . $db->quote($options['charset'], 'text'); + @sqlite_query($query, $handle); + } + @sqlite_close($handle); + return MDB2_OK; + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $database_file = $db->_getDatabaseFile($name); + if (!@file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + 'database does not exist', __FUNCTION__); + } + $result = @unlink($database_file); + if (!$result) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not remove the database file'), __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + $query = ''; + if (!empty($definition['match'])) { + $query .= ' MATCH '.$definition['match']; + } + if (!empty($definition['onupdate']) && (strtoupper($definition['onupdate']) != 'NO ACTION')) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete']) && (strtoupper($definition['ondelete']) != 'NO ACTION')) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + if (!empty($options['foreign_keys'])) { + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + $query_fields.= ', CONSTRAINT '.$fkname.' FOREIGN KEY ('.implode(', ', array_keys($fkdef['fields'])).')'; + $query_fields.= ' REFERENCES '.$fkdef['references']['table'].' ('.implode(', ', array_keys($fkdef['references']['fields'])).')'; + $query_fields.= $this->_getAdvancedFKOptions($fkdef); + } + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition + * of each field of the new table + * @param array $options An associative array of table options + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $result = parent::createTable($name, $fields, $options); + if (PEAR::isError($result)) { + return $result; + } + // create triggers to enforce FOREIGN KEY constraints + if (!empty($options['foreign_keys'])) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + //set actions to default if not set + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); + $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); + + $trigger_names = array( + 'insert' => $fkname.'_insert_trg', + 'update' => $fkname.'_update_trg', + 'pk_update' => $fkname.'_pk_update_trg', + 'pk_delete' => $fkname.'_pk_delete_trg', + ); + + //create the [insert|update] triggers on the FK table + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + $query = 'CREATE TRIGGER %s BEFORE %s ON '.$name + .' FOR EACH ROW BEGIN' + .' SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($referenced_fields as $field) { + $aliased_fields[] = $fkdef['references']['table'] .'.'.$field .' AS '.$field; + } + $query .= implode(',', $aliased_fields) + .' FROM '.$fkdef['references']['table'] + .' WHERE '; + $conditions = array(); + for ($i=0; $iexec(sprintf($query, $trigger_names['insert'], 'INSERT', 'insert')); + if (PEAR::isError($result)) { + return $result; + } + + $result = $db->exec(sprintf($query, $trigger_names['update'], 'UPDATE', 'update')); + if (PEAR::isError($result)) { + return $result; + } + + //create the ON [UPDATE|DELETE] triggers on the primary table + $restrict_action = 'SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $name .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$name + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $i OLD.'.$referenced_fields[$i]; + } + $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' + .' AND (' .implode(' OR ', $conditions2) .')'; + + $cascade_action_update = 'UPDATE '.$name.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions); + $cascade_action_delete = 'DELETE FROM '.$name.' WHERE '.implode(' AND ', $conditions); + $setnull_action = 'UPDATE '.$name.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions); + + if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { + $db->loadModule('Reverse', null, true); + $default_values = array(); + foreach ($table_fields as $table_field) { + $field_definition = $db->reverse->getTableFieldDefinition($name, $field); + if (PEAR::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$name.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions); + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW BEGIN '; + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . $cascade_action_update. '; END;'; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . '; END;'; + } elseif ('RESTRICT' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . '; END;'; + } + if ('CASCADE' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . $cascade_action_delete. '; END;'; + } elseif ('SET NULL' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . '; END;'; + } elseif ('RESTRICT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . '; END;'; + } + + if (PEAR::isError($result)) { + return $result; + } + $result = $db->exec($sql_delete); + if (PEAR::isError($result)) { + return $result; + } + $result = $db->exec($sql_update); + if (PEAR::isError($result)) { + return $result; + } + } + } + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + //delete the triggers associated to existing FK constraints + $constraints = $this->listTableConstraints($name); + if (!PEAR::isError($constraints) && !empty($constraints)) { + $db->loadModule('Reverse', null, true); + foreach ($constraints as $constraint) { + $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP TABLE $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'VACUUM'; + if (!empty($table)) { + $query .= ' '.$db->quoteIdentifier($table, true); + } + return $db->exec($query); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $db->loadModule('Reverse', null, true); + + // actually sqlite 2.x supports no ALTER TABLE at all .. so we emulate it + $fields = $db->manager->listTableFields($name); + if (PEAR::isError($fields)) { + return $fields; + } + + $fields = array_flip($fields); + foreach ($fields as $field => $value) { + $definition = $db->reverse->getTableFieldDefinition($name, $field); + if (PEAR::isError($definition)) { + return $definition; + } + $fields[$field] = $definition[0]; + } + + $indexes = $db->manager->listTableIndexes($name); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $indexes = array_flip($indexes); + foreach ($indexes as $index => $value) { + $definition = $db->reverse->getTableIndexDefinition($name, $index); + if (PEAR::isError($definition)) { + return $definition; + } + $indexes[$index] = $definition; + } + + $constraints = $db->manager->listTableConstraints($name); + if (PEAR::isError($constraints)) { + return $constraints; + } + + if (!array_key_exists('foreign_keys', $options)) { + $options['foreign_keys'] = array(); + } + $constraints = array_flip($constraints); + foreach ($constraints as $constraint => $value) { + if (!empty($definition['primary'])) { + if (!array_key_exists('primary', $options)) { + $options['primary'] = $definition['fields']; + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } + } else { + $c_definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (PEAR::isError($c_definition)) { + return $c_definition; + } + if (!empty($c_definition['foreign'])) { + if (!array_key_exists($constraint, $options['foreign_keys'])) { + $options['foreign_keys'][$constraint] = $c_definition; + } + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } else { + $constraints[$constraint] = $c_definition; + } + } + } + + $name_new = $name; + $create_order = $select_fields = array_keys($fields); + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field; + $create_order[] = $field_name; + } + break; + case 'remove': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $select_fields = array_diff($select_fields, array($field_name)); + $create_order = array_diff($create_order, array($field_name)); + } + break; + case 'change': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field['definition']; + } + break; + case 'name': + $name_new = $change; + break; + case 'rename': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $fields[$field['name']] = $field['definition']; + $create_order[array_search($field_name, $create_order)] = $field['name']; + } + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + $data = null; + if (!empty($select_fields)) { + $query = 'SELECT '.implode(', ', $select_fields).' FROM '.$db->quoteIdentifier($name, true); + $data = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + } + + $result = $this->dropTable($name); + if (PEAR::isError($result)) { + return $result; + } + + $result = $this->createTable($name_new, $fields, $options); + if (PEAR::isError($result)) { + return $result; + } + + foreach ($indexes as $index => $definition) { + $this->createIndex($name_new, $index, $definition); + } + + foreach ($constraints as $constraint => $definition) { + $this->createConstraint($name_new, $constraint, $definition); + } + + if (!empty($select_fields) && !empty($data)) { + $query = 'INSERT INTO '.$db->quoteIdentifier($name_new, true); + $query.= '('.implode(', ', array_slice(array_keys($fields), 0, count($select_fields))).')'; + $query.=' VALUES (?'.str_repeat(', ?', (count($select_fields) - 1)).')'; + $stmt = $db->prepare($query, null, MDB2_PREPARE_MANIP); + if (PEAR::isError($stmt)) { + return $stmt; + } + foreach ($data as $row) { + $result = $stmt->execute($row); + if (PEAR::isError($result)) { + return $result; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $views = $db->queryAll($query, array('text', 'text'), MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($views)) { + return $views; + } + $result = array(); + foreach ($views as $row) { + if (preg_match("/^create view .* \bfrom\b\s+\b{$table}\b /i", $row['sql'])) { + if (!empty($row['name'])) { + $result[$row['name']] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Reverse', null, true); + if (PEAR::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (PEAR::isError($sql)) { + return $sql; + } + $columns = $db->reverse->_getTableColumns($sql); + $fields = array(); + foreach ($columns as $column) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column['name'] = strtolower($column['name']); + } else { + $column['name'] = strtoupper($column['name']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + $fields[] = $column['name']; + } + return $fields; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='trigger' AND sql NOT NULL"; + if (null !== $table) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(tbl_name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= ' AND tbl_name='.$db->quote($table, 'text'); + } + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function support() to determine whether the DBMS driver can manage indexes. + + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field_name => $field) { + $field_string = $field_name; + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $query .= ' ('.implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->getIndexName($name); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $result[$index] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($definition['primary'])) { + return $db->manager->alterTable($table, array(), false, array('primary' => $definition['fields'])); + } + + if (!empty($definition['foreign'])) { + return $db->manager->alterTable($table, array(), false, array('foreign_keys' => array($name => $definition))); + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE UNIQUE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field_name => $field) { + $field_string = $field_name; + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $query .= ' ('.implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + if ($primary || $name == 'PRIMARY') { + return $this->alterTable($table, array(), false, array('primary' => null)); + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + //is it a FK constraint? If so, also delete the associated triggers + $db->loadModule('Reverse', null, true); + $definition = $db->reverse->getTableConstraintDefinition($table, $name); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + //first drop the FK enforcing triggers + $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + //then drop the constraint itself + return $this->alterTable($table, array(), false, array('foreign_keys' => array($name => null))); + } + + $name = $db->getIndexName($name); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ _dropFKTriggers() + + /** + * Drop the triggers created to enforce the FOREIGN KEY constraint on the table + * + * @param string $table table name + * @param string $fkname FOREIGN KEY constraint name + * @param string $referenced_table referenced table name + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropFKTriggers($table, $fkname, $referenced_table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $triggers = $this->listTableTriggers($table); + $triggers2 = $this->listTableTriggers($referenced_table); + if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { + $triggers = array_merge($triggers, $triggers2); + $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; + foreach ($triggers as $trigger) { + if (preg_match($pattern, $trigger)) { + $result = $db->exec('DROP TRIGGER '.$trigger); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create unique index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $result[$index] = true; + } + } + } + + // also search in table definition for PRIMARY KEYs... + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.strtolower($table); + } else { + $query.= "name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $table_def = $db->queryOne($query, 'text'); + if (PEAR::isError($table_def)) { + return $table_def; + } + if (preg_match("/\bPRIMARY\s+KEY\b/i", $table_def, $tmp)) { + $result['primary'] = true; + } + + // ...and for FOREIGN KEYs + if (preg_match_all("/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN\s+KEY/imsx", $table_def, $tmp)) { + foreach ($tmp[1] as $fk) { + $result[$fk] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + $query = "CREATE TABLE $sequence_name ($seqcol_name INTEGER PRIMARY KEY DEFAULT 0 NOT NULL)"; + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + if ($start == 1) { + return MDB2_OK; + } + $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} +?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Native/Common.php b/3rdparty/MDB2/Driver/Native/Common.php index c01caa35b7..7cd536ee56 100644 --- a/3rdparty/MDB2/Driver/Native/Common.php +++ b/3rdparty/MDB2/Driver/Native/Common.php @@ -1,61 +1,61 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.2 2007/09/09 13:47:36 quipo Exp $ -// - -/** - * Base class for the natuve modules that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Native'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Native_Common extends MDB2_Module_Common -{ -} + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php 242348 2007-09-09 13:47:36Z quipo $ +// + +/** + * Base class for the natuve modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Native'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_Common extends MDB2_Module_Common +{ +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Native/mysql.php b/3rdparty/MDB2/Driver/Native/mysql.php index 90ff068e6f..f32e3ec337 100644 --- a/3rdparty/MDB2/Driver/Native/mysql.php +++ b/3rdparty/MDB2/Driver/Native/mysql.php @@ -1,60 +1,60 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.9 2006/06/18 21:59:05 lsmith Exp $ -// - -require_once 'MDB2/Driver/Native/Common.php'; - -/** - * MDB2 MySQL driver for the native module - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Native_mysql extends MDB2_Driver_Native_Common -{ -} + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php 215004 2006-06-18 21:59:05Z lsmith $ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 MySQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_mysql extends MDB2_Driver_Native_Common +{ +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Native/pgsql.php b/3rdparty/MDB2/Driver/Native/pgsql.php index acab8389c8..7977b38d3b 100644 --- a/3rdparty/MDB2/Driver/Native/pgsql.php +++ b/3rdparty/MDB2/Driver/Native/pgsql.php @@ -1,88 +1,88 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.12 2006/07/15 13:07:15 lsmith Exp $ - -require_once 'MDB2/Driver/Native/Common.php'; - -/** - * MDB2 PostGreSQL driver for the native module - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_Driver_Native_pgsql extends MDB2_Driver_Native_Common -{ - // }}} - // {{{ deleteOID() - - /** - * delete an OID - * - * @param integer $OID - * @return mixed MDB2_OK on success or MDB2 Error Object on failure - * @access public - */ - function deleteOID($OID) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $connection = $db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - if (!@pg_lo_unlink($connection, $OID)) { - return $db->raiseError(null, null, null, - 'Unable to unlink OID: '.$OID, __FUNCTION__); - } - return MDB2_OK; - } - -} + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php 295587 2010-02-28 17:16:38Z quipo $ + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 PostGreSQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_Native_pgsql extends MDB2_Driver_Native_Common +{ + // }}} + // {{{ deleteOID() + + /** + * delete an OID + * + * @param integer $OID + * @return mixed MDB2_OK on success or MDB2 Error Object on failure + * @access public + */ + function deleteOID($OID) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $connection = $db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + if (!@pg_lo_unlink($connection, $OID)) { + return $db->raiseError(null, null, null, + 'Unable to unlink OID: '.$OID, __FUNCTION__); + } + return MDB2_OK; + } + +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Native/sqlite.php b/3rdparty/MDB2/Driver/Native/sqlite.php index 0213293a50..b4da1ce83a 100644 --- a/3rdparty/MDB2/Driver/Native/sqlite.php +++ b/3rdparty/MDB2/Driver/Native/sqlite.php @@ -1,60 +1,60 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: sqlite.php,v 1.9 2006/06/18 21:59:05 lsmith Exp $ -// - -require_once 'MDB2/Driver/Native/Common.php'; - -/** - * MDB2 SQLite driver for the native module - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Native_sqlite extends MDB2_Driver_Native_Common -{ -} + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php 215004 2006-06-18 21:59:05Z lsmith $ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 SQLite driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_sqlite extends MDB2_Driver_Native_Common +{ +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Reverse/Common.php b/3rdparty/MDB2/Driver/Reverse/Common.php index c78d84f760..153516d80d 100644 --- a/3rdparty/MDB2/Driver/Reverse/Common.php +++ b/3rdparty/MDB2/Driver/Reverse/Common.php @@ -1,517 +1,517 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.43 2009/01/14 15:01:21 quipo Exp $ -// - -/** - * @package MDB2 - * @category Database - */ - -/** - * These are constants for the tableInfo-function - * they are bitwised or'ed. so if there are more constants to be defined - * in the future, adjust MDB2_TABLEINFO_FULL accordingly - */ - -define('MDB2_TABLEINFO_ORDER', 1); -define('MDB2_TABLEINFO_ORDERTABLE', 2); -define('MDB2_TABLEINFO_FULL', 3); - -/** - * Base class for the schema reverse engineering module that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Reverse'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Reverse_Common extends MDB2_Module_Common -{ - // {{{ splitTableSchema() - - /** - * Split the "[owner|schema].table" notation into an array - * - * @param string $table [schema and] table name - * - * @return array array(schema, table) - * @access private - */ - function splitTableSchema($table) - { - $ret = array(); - if (strpos($table, '.') !== false) { - return explode('.', $table); - } - return array(null, $table); - } - - // }}} - // {{{ getTableFieldDefinition() - - /** - * Get the structure of a field into an array - * - * @param string $table name of table that should be used in method - * @param string $field name of field that should be used in method - * @return mixed data array on success, a MDB2 error on failure. - * The returned array contains an array for each field definition, - * with all or some of these indices, depending on the field data type: - * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] - * @access public - */ - function getTableFieldDefinition($table, $field) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ getTableIndexDefinition() - - /** - * Get the structure of an index into an array - * - * @param string $table name of table that should be used in method - * @param string $index name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - * - * array ( - * [fields] => array ( - * [field1name] => array() // one entry per each field covered - * [field2name] => array() // by the index - * [field3name] => array( - * [sorting] => ascending - * ) - * ) - * ); - * - * @access public - */ - function getTableIndexDefinition($table, $index) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ getTableConstraintDefinition() - - /** - * Get the structure of an constraints into an array - * - * @param string $table name of table that should be used in method - * @param string $index name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - *
-     *          array (
-     *              [primary] => 0
-     *              [unique]  => 0
-     *              [foreign] => 1
-     *              [check]   => 0
-     *              [fields] => array (
-     *                  [field1name] => array() // one entry per each field covered
-     *                  [field2name] => array() // by the index
-     *                  [field3name] => array(
-     *                      [sorting]  => ascending
-     *                      [position] => 3
-     *                  )
-     *              )
-     *              [references] => array(
-     *                  [table] => name
-     *                  [fields] => array(
-     *                      [field1name] => array(  //one entry per each referenced field
-     *                           [position] => 1
-     *                      )
-     *                  )
-     *              )
-     *              [deferrable] => 0
-     *              [initiallydeferred] => 0
-     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [match] => SIMPLE|PARTIAL|FULL
-     *          );
-     *          
- * @access public - */ - function getTableConstraintDefinition($table, $index) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ getSequenceDefinition() - - /** - * Get the structure of a sequence into an array - * - * @param string $sequence name of sequence that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - *
-     *          array (
-     *              [start] => n
-     *          );
-     *          
- * @access public - */ - function getSequenceDefinition($sequence) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $start = $db->currId($sequence); - if (PEAR::isError($start)) { - return $start; - } - if ($db->supports('current_id')) { - $start++; - } else { - $db->warnings[] = 'database does not support getting current - sequence value, the sequence value was incremented'; - } - $definition = array(); - if ($start != 1) { - $definition = array('start' => $start); - } - return $definition; - } - - // }}} - // {{{ getTriggerDefinition() - - /** - * Get the structure of a trigger into an array - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change the returned value - * at any time until labelled as non-experimental - * - * @param string $trigger name of trigger that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - *
-     *          array (
-     *              [trigger_name]    => 'trigger name',
-     *              [table_name]      => 'table name',
-     *              [trigger_body]    => 'trigger body definition',
-     *              [trigger_type]    => 'BEFORE' | 'AFTER',
-     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
-     *                  //or comma separated list of multiple events, when supported
-     *              [trigger_enabled] => true|false
-     *              [trigger_comment] => 'trigger comment',
-     *          );
-     *          
- * The oci8 driver also returns a [when_clause] index. - * @access public - */ - function getTriggerDefinition($trigger) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * The format of the resulting array depends on which $mode - * you select. The sample output below is based on this query: - *
-     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
-     *    FROM tblFoo
-     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
-     * 
- * - *
    - *
  • - * - * null (default) - *
    -     *   [0] => Array (
    -     *       [table] => tblFoo
    -     *       [name] => fldId
    -     *       [type] => int
    -     *       [len] => 11
    -     *       [flags] => primary_key not_null
    -     *   )
    -     *   [1] => Array (
    -     *       [table] => tblFoo
    -     *       [name] => fldPhone
    -     *       [type] => string
    -     *       [len] => 20
    -     *       [flags] =>
    -     *   )
    -     *   [2] => Array (
    -     *       [table] => tblBar
    -     *       [name] => fldId
    -     *       [type] => int
    -     *       [len] => 11
    -     *       [flags] => primary_key not_null
    -     *   )
    -     *   
    - * - *
  • - * - * MDB2_TABLEINFO_ORDER - * - *

    In addition to the information found in the default output, - * a notation of the number of columns is provided by the - * num_fields element while the order - * element provides an array with the column names as the keys and - * their location index number (corresponding to the keys in the - * the default output) as the values.

    - * - *

    If a result set has identical field names, the last one is - * used.

    - * - *
    -     *   [num_fields] => 3
    -     *   [order] => Array (
    -     *       [fldId] => 2
    -     *       [fldTrans] => 1
    -     *   )
    -     *   
    - * - *
  • - * - * MDB2_TABLEINFO_ORDERTABLE - * - *

    Similar to MDB2_TABLEINFO_ORDER but adds more - * dimensions to the array in which the table names are keys and - * the field names are sub-keys. This is helpful for queries that - * join tables which have identical field names.

    - * - *
    -     *   [num_fields] => 3
    -     *   [ordertable] => Array (
    -     *       [tblFoo] => Array (
    -     *           [fldId] => 0
    -     *           [fldPhone] => 1
    -     *       )
    -     *       [tblBar] => Array (
    -     *           [fldId] => 2
    -     *       )
    -     *   )
    -     *   
    - * - *
  • - *
- * - * The flags element contains a space separated list - * of extra information about the field. This data is inconsistent - * between DBMS's due to the way each DBMS works. - * + primary_key - * + unique_key - * + multiple_key - * + not_null - * - * Most DBMS's only provide the table and flags - * elements if $result is a table name. The following DBMS's - * provide full information from queries: - * + fbsql - * + mysql - * - * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE - * turned on, the names of tables and fields will be lower or upper cased. - * - * @param object|string $result MDB2_result object from a query or a - * string containing the name of a table. - * While this also accepts a query result - * resource identifier, this behavior is - * deprecated. - * @param int $mode either unused or one of the tableInfo modes: - * MDB2_TABLEINFO_ORDERTABLE, - * MDB2_TABLEINFO_ORDER or - * MDB2_TABLEINFO_FULL (which does both). - * These are bitwise, so the first two can be - * combined using |. - * - * @return array an associative array with the information requested. - * A MDB2_Error object on failure. - * - * @see MDB2_Driver_Common::setOption() - */ - function tableInfo($result, $mode = null) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!is_string($result)) { - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - $db->loadModule('Manager', null, true); - $fields = $db->manager->listTableFields($result); - if (PEAR::isError($fields)) { - return $fields; - } - - $flags = array(); - - $idxname_format = $db->getOption('idxname_format'); - $db->setOption('idxname_format', '%s'); - - $indexes = $db->manager->listTableIndexes($result); - if (PEAR::isError($indexes)) { - $db->setOption('idxname_format', $idxname_format); - return $indexes; - } - - foreach ($indexes as $index) { - $definition = $this->getTableIndexDefinition($result, $index); - if (PEAR::isError($definition)) { - $db->setOption('idxname_format', $idxname_format); - return $definition; - } - if (count($definition['fields']) > 1) { - foreach ($definition['fields'] as $field => $sort) { - $flags[$field] = 'multiple_key'; - } - } - } - - $constraints = $db->manager->listTableConstraints($result); - if (PEAR::isError($constraints)) { - return $constraints; - } - - foreach ($constraints as $constraint) { - $definition = $this->getTableConstraintDefinition($result, $constraint); - if (PEAR::isError($definition)) { - $db->setOption('idxname_format', $idxname_format); - return $definition; - } - $flag = !empty($definition['primary']) - ? 'primary_key' : (!empty($definition['unique']) - ? 'unique_key' : false); - if ($flag) { - foreach ($definition['fields'] as $field => $sort) { - if (empty($flags[$field]) || $flags[$field] != 'primary_key') { - $flags[$field] = $flag; - } - } - } - } - - $res = array(); - - if ($mode) { - $res['num_fields'] = count($fields); - } - - foreach ($fields as $i => $field) { - $definition = $this->getTableFieldDefinition($result, $field); - if (PEAR::isError($definition)) { - $db->setOption('idxname_format', $idxname_format); - return $definition; - } - $res[$i] = $definition[0]; - $res[$i]['name'] = $field; - $res[$i]['table'] = $result; - $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); - // 'primary_key', 'unique_key', 'multiple_key' - $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; - // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' - if (!empty($res[$i]['notnull'])) { - $res[$i]['flags'].= ' not_null'; - } - if (!empty($res[$i]['unsigned'])) { - $res[$i]['flags'].= ' unsigned'; - } - if (!empty($res[$i]['auto_increment'])) { - $res[$i]['flags'].= ' autoincrement'; - } - if (!empty($res[$i]['default'])) { - $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); - } - - if ($mode & MDB2_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & MDB2_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - $db->setOption('idxname_format', $idxname_format); - return $res; - } -} + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php 295587 2010-02-28 17:16:38Z quipo $ +// + +/** + * @package MDB2 + * @category Database + */ + +/** + * These are constants for the tableInfo-function + * they are bitwised or'ed. so if there are more constants to be defined + * in the future, adjust MDB2_TABLEINFO_FULL accordingly + */ + +define('MDB2_TABLEINFO_ORDER', 1); +define('MDB2_TABLEINFO_ORDERTABLE', 2); +define('MDB2_TABLEINFO_FULL', 3); + +/** + * Base class for the schema reverse engineering module that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Reverse'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * + * @param string $table [schema and] table name + * + * @return array array(schema, table) + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure. + * The returned array contains an array for each field definition, + * with all or some of these indices, depending on the field data type: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table, $field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + * + * array ( + * [fields] => array ( + * [field1name] => array() // one entry per each field covered + * [field2name] => array() // by the index + * [field3name] => array( + * [sorting] => ascending + * ) + * ) + * ); + * + * @access public + */ + function getTableIndexDefinition($table, $index) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of an constraints into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @access public + */ + function getTableConstraintDefinition($table, $index) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the structure of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [start] => n
+     *          );
+     *          
+ * @access public + */ + function getSequenceDefinition($sequence) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $start = $db->currId($sequence); + if (PEAR::isError($start)) { + return $start; + } + if ($db->supports('current_id')) { + $start++; + } else { + $db->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [trigger_name]    => 'trigger name',
+     *              [table_name]      => 'table name',
+     *              [trigger_body]    => 'trigger body definition',
+     *              [trigger_type]    => 'BEFORE' | 'AFTER',
+     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
+     *                  //or comma separated list of multiple events, when supported
+     *              [trigger_enabled] => true|false
+     *              [trigger_comment] => 'trigger comment',
+     *          );
+     *          
+ * The oci8 driver also returns a [when_clause] index. + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDER + * + *

    In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * num_fields element while the order + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.

    + * + *

    If a result set has identical field names, the last one is + * used.

    + * + *
    +     *   [num_fields] => 3
    +     *   [order] => Array (
    +     *       [fldId] => 2
    +     *       [fldTrans] => 1
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDERTABLE + * + *

    Similar to MDB2_TABLEINFO_ORDER but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE + * turned on, the names of tables and fields will be lower or upper cased. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * MDB2_TABLEINFO_ORDERTABLE, + * MDB2_TABLEINFO_ORDER or + * MDB2_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_string($result)) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $db->loadModule('Manager', null, true); + $fields = $db->manager->listTableFields($result); + if (PEAR::isError($fields)) { + return $fields; + } + + $flags = array(); + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + $indexes = $db->manager->listTableIndexes($result); + if (PEAR::isError($indexes)) { + $db->setOption('idxname_format', $idxname_format); + return $indexes; + } + + foreach ($indexes as $index) { + $definition = $this->getTableIndexDefinition($result, $index); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + if (count($definition['fields']) > 1) { + foreach ($definition['fields'] as $field => $sort) { + $flags[$field] = 'multiple_key'; + } + } + } + + $constraints = $db->manager->listTableConstraints($result); + if (PEAR::isError($constraints)) { + return $constraints; + } + + foreach ($constraints as $constraint) { + $definition = $this->getTableConstraintDefinition($result, $constraint); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $flag = !empty($definition['primary']) + ? 'primary_key' : (!empty($definition['unique']) + ? 'unique_key' : false); + if ($flag) { + foreach ($definition['fields'] as $field => $sort) { + if (empty($flags[$field]) || $flags[$field] != 'primary_key') { + $flags[$field] = $flag; + } + } + } + } + + $res = array(); + + if ($mode) { + $res['num_fields'] = count($fields); + } + + foreach ($fields as $i => $field) { + $definition = $this->getTableFieldDefinition($result, $field); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $res[$i] = $definition[0]; + $res[$i]['name'] = $field; + $res[$i]['table'] = $result; + $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); + // 'primary_key', 'unique_key', 'multiple_key' + $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; + // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' + if (!empty($res[$i]['notnull'])) { + $res[$i]['flags'].= ' not_null'; + } + if (!empty($res[$i]['unsigned'])) { + $res[$i]['flags'].= ' unsigned'; + } + if (!empty($res[$i]['auto_increment'])) { + $res[$i]['flags'].= ' autoincrement'; + } + if (!empty($res[$i]['default'])) { + $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); + } + + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + $db->setOption('idxname_format', $idxname_format); + return $res; + } +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Reverse/mysql.php b/3rdparty/MDB2/Driver/Reverse/mysql.php index 6e366c22a5..7b9b4e0019 100644 --- a/3rdparty/MDB2/Driver/Reverse/mysql.php +++ b/3rdparty/MDB2/Driver/Reverse/mysql.php @@ -1,536 +1,546 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.80 2008/03/26 21:15:37 quipo Exp $ -// - -require_once('MDB2/Driver/Reverse/Common.php'); - -/** - * MDB2 MySQL driver for the schema reverse engineering module - * - * @package MDB2 - * @category Database - * @author Lukas Smith - * @author Lorenzo Alberton - */ -class MDB2_Driver_Reverse_mysql extends MDB2_Driver_Reverse_Common -{ - // {{{ getTableFieldDefinition() - - /** - * Get the structure of a field into an array - * - * @param string $table_name name of table that should be used in method - * @param string $field_name name of field that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableFieldDefinition($table_name, $field_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $table = $db->quoteIdentifier($table, true); - $query = "SHOW FULL COLUMNS FROM $table LIKE ".$db->quote($field_name); - $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($columns)) { - return $columns; - } - foreach ($columns as $column) { - $column = array_change_key_case($column, CASE_LOWER); - $column['name'] = $column['field']; - unset($column['field']); - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $column['name'] = strtolower($column['name']); - } else { - $column['name'] = strtoupper($column['name']); - } - } else { - $column = array_change_key_case($column, $db->options['field_case']); - } - if ($field_name == $column['name']) { - $mapped_datatype = $db->datatype->mapNativeDatatype($column); - if (PEAR::isError($mapped_datatype)) { - return $mapped_datatype; - } - list($types, $length, $unsigned, $fixed) = $mapped_datatype; - $notnull = false; - if (empty($column['null']) || $column['null'] !== 'YES') { - $notnull = true; - } - $default = false; - if (array_key_exists('default', $column)) { - $default = $column['default']; - if (is_null($default) && $notnull) { - $default = ''; - } - } - $autoincrement = false; - if (!empty($column['extra']) && $column['extra'] == 'auto_increment') { - $autoincrement = true; - } - $collate = null; - if (!empty($column['collation'])) { - $collate = $column['collation']; - $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate); - } - - $definition[0] = array( - 'notnull' => $notnull, - 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) - ); - if (!is_null($length)) { - $definition[0]['length'] = $length; - } - if (!is_null($unsigned)) { - $definition[0]['unsigned'] = $unsigned; - } - if (!is_null($fixed)) { - $definition[0]['fixed'] = $fixed; - } - if ($default !== false) { - $definition[0]['default'] = $default; - } - if ($autoincrement !== false) { - $definition[0]['autoincrement'] = $autoincrement; - } - if (!is_null($collate)) { - $definition[0]['collate'] = $collate; - $definition[0]['charset'] = $charset; - } - foreach ($types as $key => $type) { - $definition[$key] = $definition[0]; - if ($type == 'clob' || $type == 'blob') { - unset($definition[$key]['default']); - } elseif ($type == 'timestamp' && $notnull && empty($definition[$key]['default'])) { - $definition[$key]['default'] = '0000-00-00 00:00:00'; - } - $definition[$key]['type'] = $type; - $definition[$key]['mdb2type'] = $type; - } - return $definition; - } - } - - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table column', __FUNCTION__); - } - - // }}} - // {{{ getTableIndexDefinition() - - /** - * Get the structure of an index into an array - * - * @param string $table_name name of table that should be used in method - * @param string $index_name name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableIndexDefinition($table_name, $index_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $table = $db->quoteIdentifier($table, true); - $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */"; - $index_name_mdb2 = $db->getIndexName($index_name); - $result = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2))); - if (!PEAR::isError($result) && !is_null($result)) { - // apply 'idxname_format' only if the query succeeded, otherwise - // fallback to the given $index_name, without transformation - $index_name = $index_name_mdb2; - } - $result = $db->query(sprintf($query, $db->quote($index_name))); - if (PEAR::isError($result)) { - return $result; - } - $colpos = 1; - $definition = array(); - while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { - $row = array_change_key_case($row, CASE_LOWER); - $key_name = $row['key_name']; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $key_name = strtolower($key_name); - } else { - $key_name = strtoupper($key_name); - } - } - if ($index_name == $key_name) { - if (!$row['non_unique']) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $index_name . ' is not an existing table index', __FUNCTION__); - } - $column_name = $row['column_name']; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $column_name = strtolower($column_name); - } else { - $column_name = strtoupper($column_name); - } - } - $definition['fields'][$column_name] = array( - 'position' => $colpos++ - ); - if (!empty($row['collation'])) { - $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' - ? 'ascending' : 'descending'); - } - } - } - $result->free(); - if (empty($definition['fields'])) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $index_name . ' is not an existing table index', __FUNCTION__); - } - return $definition; - } - - // }}} - // {{{ getTableConstraintDefinition() - - /** - * Get the structure of a constraint into an array - * - * @param string $table_name name of table that should be used in method - * @param string $constraint_name name of constraint that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableConstraintDefinition($table_name, $constraint_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - $constraint_name_original = $constraint_name; - - $table = $db->quoteIdentifier($table, true); - $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */"; - if (strtolower($constraint_name) != 'primary') { - $constraint_name_mdb2 = $db->getIndexName($constraint_name); - $result = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2))); - if (!PEAR::isError($result) && !is_null($result)) { - // apply 'idxname_format' only if the query succeeded, otherwise - // fallback to the given $index_name, without transformation - $constraint_name = $constraint_name_mdb2; - } - } - $result = $db->query(sprintf($query, $db->quote($constraint_name))); - if (PEAR::isError($result)) { - return $result; - } - $colpos = 1; - //default values, eventually overridden - $definition = array( - 'primary' => false, - 'unique' => false, - 'foreign' => false, - 'check' => false, - 'fields' => array(), - 'references' => array( - 'table' => '', - 'fields' => array(), - ), - 'onupdate' => '', - 'ondelete' => '', - 'match' => '', - 'deferrable' => false, - 'initiallydeferred' => false, - ); - while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { - $row = array_change_key_case($row, CASE_LOWER); - $key_name = $row['key_name']; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $key_name = strtolower($key_name); - } else { - $key_name = strtoupper($key_name); - } - } - if ($constraint_name == $key_name) { - if ($row['non_unique']) { - //FOREIGN KEY? - return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition); - } - if ($row['key_name'] == 'PRIMARY') { - $definition['primary'] = true; - } elseif (!$row['non_unique']) { - $definition['unique'] = true; - } - $column_name = $row['column_name']; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $column_name = strtolower($column_name); - } else { - $column_name = strtoupper($column_name); - } - } - $definition['fields'][$column_name] = array( - 'position' => $colpos++ - ); - if (!empty($row['collation'])) { - $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' - ? 'ascending' : 'descending'); - } - } - } - $result->free(); - if (empty($definition['fields'])) { - return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition); - } - return $definition; - } - - // }}} - // {{{ _getTableFKConstraintDefinition() - - /** - * Get the FK definition from the CREATE TABLE statement - * - * @param string $table table name - * @param string $constraint_name constraint name - * @param array $definition default values for constraint definition - * - * @return array|PEAR_Error - * @access private - */ - function _getTableFKConstraintDefinition($table, $constraint_name, $definition) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $query = 'SHOW CREATE TABLE '. $db->escape($table); - $constraint = $db->queryOne($query, 'text', 1); - if (!PEAR::isError($constraint) && !empty($constraint)) { - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $constraint = strtolower($constraint); - } else { - $constraint = strtoupper($constraint); - } - } - $constraint_name_original = $constraint_name; - $constraint_name = $db->getIndexName($constraint_name); - $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^ ]+) \(([^\)]+)\)/i'; - if (!preg_match($pattern, str_replace('`', '', $constraint), $matches)) { - //fallback to original constraint name - $pattern = '/\bCONSTRAINT\s+'.$constraint_name_original.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^ ]+) \(([^\)]+)\)/i'; - } - if (preg_match($pattern, str_replace('`', '', $constraint), $matches)) { - $definition['foreign'] = true; - $column_names = explode(',', $matches[1]); - $referenced_cols = explode(',', $matches[3]); - $definition['references'] = array( - 'table' => $matches[2], - 'fields' => array(), - ); - $colpos = 1; - foreach ($column_names as $column_name) { - $definition['fields'][trim($column_name)] = array( - 'position' => $colpos++ - ); - } - $colpos = 1; - foreach ($referenced_cols as $column_name) { - $definition['references']['fields'][trim($column_name)] = array( - 'position' => $colpos++ - ); - } - $definition['onupdate'] = 'NO ACTION'; - $definition['ondelete'] = 'NO ACTION'; - $definition['match'] = 'SIMPLE'; - return $definition; - } - } - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $constraint_name . ' is not an existing table constraint', __FUNCTION__); - } - - // }}} - // {{{ getTriggerDefinition() - - /** - * Get the structure of a trigger into an array - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change the returned value - * at any time until labelled as non-experimental - * - * @param string $trigger name of trigger that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTriggerDefinition($trigger) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT trigger_name, - event_object_table AS table_name, - action_statement AS trigger_body, - action_timing AS trigger_type, - event_manipulation AS trigger_event - FROM information_schema.triggers - WHERE trigger_name = '. $db->quote($trigger, 'text'); - $types = array( - 'trigger_name' => 'text', - 'table_name' => 'text', - 'trigger_body' => 'text', - 'trigger_type' => 'text', - 'trigger_event' => 'text', - ); - $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($def)) { - return $def; - } - $def['trigger_comment'] = ''; - $def['trigger_enabled'] = true; - return $def; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * @param object|string $result MDB2_result object from a query or a - * string containing the name of a table. - * While this also accepts a query result - * resource identifier, this behavior is - * deprecated. - * @param int $mode a valid tableInfo mode - * - * @return array an associative array with the information requested. - * A MDB2_Error object on failure. - * - * @see MDB2_Driver_Common::setOption() - */ - function tableInfo($result, $mode = null) - { - if (is_string($result)) { - return parent::tableInfo($result, $mode); - } - - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; - if (!is_resource($resource)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'Could not generate result resource', __FUNCTION__); - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $case_func = 'strtolower'; - } else { - $case_func = 'strtoupper'; - } - } else { - $case_func = 'strval'; - } - - $count = @mysql_num_fields($resource); - $res = array(); - if ($mode) { - $res['num_fields'] = $count; - } - - $db->loadModule('Datatype', null, true); - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => $case_func(@mysql_field_table($resource, $i)), - 'name' => $case_func(@mysql_field_name($resource, $i)), - 'type' => @mysql_field_type($resource, $i), - 'length' => @mysql_field_len($resource, $i), - 'flags' => @mysql_field_flags($resource, $i), - ); - if ($res[$i]['type'] == 'string') { - $res[$i]['type'] = 'char'; - } elseif ($res[$i]['type'] == 'unknown') { - $res[$i]['type'] = 'decimal'; - } - $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); - if (PEAR::isError($mdb2type_info)) { - return $mdb2type_info; - } - $res[$i]['mdb2type'] = $mdb2type_info[0][0]; - if ($mode & MDB2_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & MDB2_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - return $res; - } -} + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MySQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_mysql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW FULL COLUMNS FROM $table LIKE ".$db->quote($field_name); + $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($columns)) { + return $columns; + } + foreach ($columns as $column) { + $column = array_change_key_case($column, CASE_LOWER); + $column['name'] = $column['field']; + unset($column['field']); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column['name'] = strtolower($column['name']); + } else { + $column['name'] = strtoupper($column['name']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + if ($field_name == $column['name']) { + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (PEAR::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (empty($column['null']) || $column['null'] !== 'YES') { + $notnull = true; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if ((null === $default) && $notnull) { + $default = ''; + } + } + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + $autoincrement = false; + if (!empty($column['extra'])) { + if ($column['extra'] == 'auto_increment') { + $autoincrement = true; + } else { + $definition[0]['extra'] = $column['extra']; + } + } + $collate = null; + if (!empty($column['collation'])) { + $collate = $column['collation']; + $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate); + } + + if (null !== $length) { + $definition[0]['length'] = $length; + } + if (null !== $unsigned) { + $definition[0]['unsigned'] = $unsigned; + } + if (null !== $fixed) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + if (null !== $collate) { + $definition[0]['collate'] = $collate; + $definition[0]['charset'] = $charset; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } elseif ($type == 'timestamp' && $notnull && empty($definition[$key]['default'])) { + $definition[$key]['default'] = '0000-00-00 00:00:00'; + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */"; + $index_name_mdb2 = $db->getIndexName($index_name); + $result = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2))); + if (!PEAR::isError($result) && (null !== $result)) { + // apply 'idxname_format' only if the query succeeded, otherwise + // fallback to the given $index_name, without transformation + $index_name = $index_name_mdb2; + } + $result = $db->query(sprintf($query, $db->quote($index_name))); + if (PEAR::isError($result)) { + return $result; + } + $colpos = 1; + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $key_name = $row['key_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + } else { + $key_name = strtoupper($key_name); + } + } + if ($index_name == $key_name) { + if (!$row['non_unique']) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name . ' is not an existing table index', __FUNCTION__); + } + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array( + 'position' => $colpos++ + ); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + } + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name . ' is not an existing table index', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + $constraint_name_original = $constraint_name; + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */"; + if (strtolower($constraint_name) != 'primary') { + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $result = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2))); + if (!PEAR::isError($result) && (null !== $result)) { + // apply 'idxname_format' only if the query succeeded, otherwise + // fallback to the given $index_name, without transformation + $constraint_name = $constraint_name_mdb2; + } + } + $result = $db->query(sprintf($query, $db->quote($constraint_name))); + if (PEAR::isError($result)) { + return $result; + } + $colpos = 1; + //default values, eventually overridden + $definition = array( + 'primary' => false, + 'unique' => false, + 'foreign' => false, + 'check' => false, + 'fields' => array(), + 'references' => array( + 'table' => '', + 'fields' => array(), + ), + 'onupdate' => '', + 'ondelete' => '', + 'match' => '', + 'deferrable' => false, + 'initiallydeferred' => false, + ); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $key_name = $row['key_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + } else { + $key_name = strtoupper($key_name); + } + } + if ($constraint_name == $key_name) { + if ($row['non_unique']) { + //FOREIGN KEY? + return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition); + } + if ($row['key_name'] == 'PRIMARY') { + $definition['primary'] = true; + } elseif (!$row['non_unique']) { + $definition['unique'] = true; + } + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array( + 'position' => $colpos++ + ); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + } + } + $result->free(); + if (empty($definition['fields'])) { + return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition); + } + return $definition; + } + + // }}} + // {{{ _getTableFKConstraintDefinition() + + /** + * Get the FK definition from the CREATE TABLE statement + * + * @param string $table table name + * @param string $constraint_name constraint name + * @param array $definition default values for constraint definition + * + * @return array|PEAR_Error + * @access private + */ + function _getTableFKConstraintDefinition($table, $constraint_name, $definition) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + //Use INFORMATION_SCHEMA instead? + //SELECT * + // FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS + // WHERE CONSTRAINT_SCHEMA = '$dbname' + // AND TABLE_NAME = '$table' + // AND CONSTRAINT_NAME = '$constraint_name'; + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $constraint = $db->queryOne($query, 'text', 1); + if (!PEAR::isError($constraint) && !empty($constraint)) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $constraint = strtolower($constraint); + } else { + $constraint = strtoupper($constraint); + } + } + $constraint_name_original = $constraint_name; + $constraint_name = $db->getIndexName($constraint_name); + $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i'; + if (!preg_match($pattern, str_replace('`', '', $constraint), $matches)) { + //fallback to original constraint name + $pattern = '/\bCONSTRAINT\s+'.$constraint_name_original.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i'; + } + if (preg_match($pattern, str_replace('`', '', $constraint), $matches)) { + $definition['foreign'] = true; + $column_names = explode(',', $matches[1]); + $referenced_cols = explode(',', $matches[3]); + $definition['references'] = array( + 'table' => $matches[2], + 'fields' => array(), + ); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $colpos = 1; + foreach ($referenced_cols as $column_name) { + $definition['references']['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $definition['ondelete'] = empty($matches[4]) ? 'RESTRICT' : strtoupper($matches[4]); + $definition['onupdate'] = empty($matches[5]) ? 'RESTRICT' : strtoupper($matches[5]); + $definition['match'] = 'SIMPLE'; + return $definition; + } + } + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT trigger_name, + event_object_table AS table_name, + action_statement AS trigger_body, + action_timing AS trigger_type, + event_manipulation AS trigger_event + FROM information_schema.triggers + WHERE trigger_name = '. $db->quote($trigger, 'text'); + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + ); + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($def)) { + return $def; + } + $def['trigger_comment'] = ''; + $def['trigger_enabled'] = true; + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($resource); + $res = array(); + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($resource, $i)), + 'name' => $case_func(@mysql_field_name($resource, $i)), + 'type' => @mysql_field_type($resource, $i), + 'length' => @mysql_field_len($resource, $i), + 'flags' => @mysql_field_flags($resource, $i), + ); + if ($res[$i]['type'] == 'string') { + $res[$i]['type'] = 'char'; + } elseif ($res[$i]['type'] == 'unknown') { + $res[$i]['type'] = 'decimal'; + } + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Reverse/pgsql.php b/3rdparty/MDB2/Driver/Reverse/pgsql.php index 8669c2b919..45aa5a1503 100644 --- a/3rdparty/MDB2/Driver/Reverse/pgsql.php +++ b/3rdparty/MDB2/Driver/Reverse/pgsql.php @@ -1,573 +1,574 @@ - | -// | Lorenzo Alberton | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.75 2008/08/22 16:36:20 quipo Exp $ - -require_once('MDB2/Driver/Reverse/Common.php'); - -/** - * MDB2 PostGreSQL driver for the schema reverse engineering module - * - * @package MDB2 - * @category Database - * @author Paul Cooper - * @author Lorenzo Alberton - */ -class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common -{ - // {{{ getTableFieldDefinition() - - /** - * Get the structure of a field into an array - * - * @param string $table_name name of table that should be used in method - * @param string $field_name name of field that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableFieldDefinition($table_name, $field_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = "SELECT a.attname AS name, - t.typname AS type, - CASE a.attlen - WHEN -1 THEN - CASE t.typname - WHEN 'numeric' THEN (a.atttypmod / 65536) - WHEN 'decimal' THEN (a.atttypmod / 65536) - WHEN 'money' THEN (a.atttypmod / 65536) - ELSE CASE a.atttypmod - WHEN -1 THEN NULL - ELSE a.atttypmod - 4 - END - END - ELSE a.attlen - END AS length, - CASE t.typname - WHEN 'numeric' THEN (a.atttypmod % 65536) - 4 - WHEN 'decimal' THEN (a.atttypmod % 65536) - 4 - WHEN 'money' THEN (a.atttypmod % 65536) - 4 - ELSE 0 - END AS scale, - a.attnotnull, - a.atttypmod, - a.atthasdef, - (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) - FROM pg_attrdef d - WHERE d.adrelid = a.attrelid - AND d.adnum = a.attnum - AND a.atthasdef - ) as default - FROM pg_attribute a, - pg_class c, - pg_type t - WHERE c.relname = ".$db->quote($table, 'text')." - AND a.atttypid = t.oid - AND c.oid = a.attrelid - AND NOT a.attisdropped - AND a.attnum > 0 - AND a.attname = ".$db->quote($field_name, 'text')." - ORDER BY a.attnum"; - $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($column)) { - return $column; - } - - if (empty($column)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table column', __FUNCTION__); - } - - $column = array_change_key_case($column, CASE_LOWER); - $mapped_datatype = $db->datatype->mapNativeDatatype($column); - if (PEAR::isError($mapped_datatype)) { - return $mapped_datatype; - } - list($types, $length, $unsigned, $fixed) = $mapped_datatype; - $notnull = false; - if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') { - $notnull = true; - } - $default = null; - if ($column['atthasdef'] === 't' - && !preg_match("/nextval\('([^']+)'/", $column['default']) - ) { - $pattern = '/^\'(.*)\'::[\w ]+$/i'; - $default = $column['default'];#substr($column['adsrc'], 1, -1); - if (is_null($default) && $notnull) { - $default = ''; - } elseif (!empty($default) && preg_match($pattern, $default)) { - //remove data type cast - $default = preg_replace ($pattern, '\\1', $default); - } - } - $autoincrement = false; - if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) { - $autoincrement = true; - } - $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); - if (!is_null($length)) { - $definition[0]['length'] = $length; - } - if (!is_null($unsigned)) { - $definition[0]['unsigned'] = $unsigned; - } - if (!is_null($fixed)) { - $definition[0]['fixed'] = $fixed; - } - if ($default !== false) { - $definition[0]['default'] = $default; - } - if ($autoincrement !== false) { - $definition[0]['autoincrement'] = $autoincrement; - } - foreach ($types as $key => $type) { - $definition[$key] = $definition[0]; - if ($type == 'clob' || $type == 'blob') { - unset($definition[$key]['default']); - } - $definition[$key]['type'] = $type; - $definition[$key]['mdb2type'] = $type; - } - return $definition; - } - - // }}} - // {{{ getTableIndexDefinition() - - /** - * Get the structure of an index into an array - * - * @param string $table_name name of table that should be used in method - * @param string $index_name name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableIndexDefinition($table_name, $index_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = 'SELECT relname, indkey FROM pg_index, pg_class'; - $query.= ' WHERE pg_class.oid = pg_index.indexrelid'; - $query.= " AND indisunique != 't' AND indisprimary != 't'"; - $query.= ' AND pg_class.relname = %s'; - $index_name_mdb2 = $db->getIndexName($index_name); - $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($row) || empty($row)) { - // fallback to the given $index_name, without transformation - $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC); - } - if (PEAR::isError($row)) { - return $row; - } - - if (empty($row)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table index', __FUNCTION__); - } - - $row = array_change_key_case($row, CASE_LOWER); - - $db->loadModule('Manager', null, true); - $columns = $db->manager->listTableFields($table_name); - - $definition = array(); - - $index_column_numbers = explode(' ', $row['indkey']); - - $colpos = 1; - foreach ($index_column_numbers as $number) { - $definition['fields'][$columns[($number - 1)]] = array( - 'position' => $colpos++, - 'sorting' => 'ascending', - ); - } - return $definition; - } - - // }}} - // {{{ getTableConstraintDefinition() - - /** - * Get the structure of a constraint into an array - * - * @param string $table_name name of table that should be used in method - * @param string $constraint_name name of constraint that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableConstraintDefinition($table_name, $constraint_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = "SELECT c.oid, - c.conname AS constraint_name, - CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\", - CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\", - CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\", - CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\", - CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable, - CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred, - --array_to_string(c.conkey, ' ') AS constraint_key, - t.relname AS table_name, - t2.relname AS references_table, - CASE confupdtype - WHEN 'a' THEN 'NO ACTION' - WHEN 'r' THEN 'RESTRICT' - WHEN 'c' THEN 'CASCADE' - WHEN 'n' THEN 'SET NULL' - WHEN 'd' THEN 'SET DEFAULT' - END AS onupdate, - CASE confdeltype - WHEN 'a' THEN 'NO ACTION' - WHEN 'r' THEN 'RESTRICT' - WHEN 'c' THEN 'CASCADE' - WHEN 'n' THEN 'SET NULL' - WHEN 'd' THEN 'SET DEFAULT' - END AS ondelete, - CASE confmatchtype - WHEN 'u' THEN 'UNSPECIFIED' - WHEN 'f' THEN 'FULL' - WHEN 'p' THEN 'PARTIAL' - END AS match, - --array_to_string(c.confkey, ' ') AS fk_constraint_key, - consrc - FROM pg_constraint c - LEFT JOIN pg_class t ON c.conrelid = t.oid - LEFT JOIN pg_class t2 ON c.confrelid = t2.oid - WHERE c.conname = %s - AND t.relname = " . $db->quote($table, 'text'); - $constraint_name_mdb2 = $db->getIndexName($constraint_name); - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($row) || empty($row)) { - // fallback to the given $index_name, without transformation - $constraint_name_mdb2 = $constraint_name; - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - } - if (PEAR::isError($row)) { - return $row; - } - $uniqueIndex = false; - if (empty($row)) { - // We might be looking for a UNIQUE index that was not created - // as a constraint but should be treated as such. - $query = 'SELECT relname AS constraint_name, - indkey, - 0 AS "check", - 0 AS "foreign", - 0 AS "primary", - 1 AS "unique", - 0 AS deferrable, - 0 AS initiallydeferred, - NULL AS references_table, - NULL AS onupdate, - NULL AS ondelete, - NULL AS match - FROM pg_index, pg_class - WHERE pg_class.oid = pg_index.indexrelid - AND indisunique = \'t\' - AND pg_class.relname = %s'; - $constraint_name_mdb2 = $db->getIndexName($constraint_name); - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($row) || empty($row)) { - // fallback to the given $index_name, without transformation - $constraint_name_mdb2 = $constraint_name; - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - } - if (PEAR::isError($row)) { - return $row; - } - if (empty($row)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $constraint_name . ' is not an existing table constraint', __FUNCTION__); - } - $uniqueIndex = true; - } - - $row = array_change_key_case($row, CASE_LOWER); - - $definition = array( - 'primary' => (boolean)$row['primary'], - 'unique' => (boolean)$row['unique'], - 'foreign' => (boolean)$row['foreign'], - 'check' => (boolean)$row['check'], - 'fields' => array(), - 'references' => array( - 'table' => $row['references_table'], - 'fields' => array(), - ), - 'deferrable' => (boolean)$row['deferrable'], - 'initiallydeferred' => (boolean)$row['initiallydeferred'], - 'onupdate' => $row['onupdate'], - 'ondelete' => $row['ondelete'], - 'match' => $row['match'], - ); - - if ($uniqueIndex) { - $db->loadModule('Manager', null, true); - $columns = $db->manager->listTableFields($table_name); - $index_column_numbers = explode(' ', $row['indkey']); - $colpos = 1; - foreach ($index_column_numbers as $number) { - $definition['fields'][$columns[($number - 1)]] = array( - 'position' => $colpos++, - 'sorting' => 'ascending', - ); - } - return $definition; - } - - $query = 'SELECT a.attname - FROM pg_constraint c - LEFT JOIN pg_class t ON c.conrelid = t.oid - LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey) - WHERE c.conname = %s - AND t.relname = ' . $db->quote($table, 'text'); - $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); - if (PEAR::isError($fields)) { - return $fields; - } - $colpos = 1; - foreach ($fields as $field) { - $definition['fields'][$field] = array( - 'position' => $colpos++, - 'sorting' => 'ascending', - ); - } - - if ($definition['foreign']) { - $query = 'SELECT a.attname - FROM pg_constraint c - LEFT JOIN pg_class t ON c.confrelid = t.oid - LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey) - WHERE c.conname = %s - AND t.relname = ' . $db->quote($definition['references']['table'], 'text'); - $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); - if (PEAR::isError($foreign_fields)) { - return $foreign_fields; - } - $colpos = 1; - foreach ($foreign_fields as $foreign_field) { - $definition['references']['fields'][$foreign_field] = array( - 'position' => $colpos++, - ); - } - } - - if ($definition['check']) { - $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')"); - // ... - } - return $definition; - } - - // }}} - // {{{ getTriggerDefinition() - - /** - * Get the structure of a trigger into an array - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change the returned value - * at any time until labelled as non-experimental - * - * @param string $trigger name of trigger that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - * - * @TODO: add support for plsql functions and functions with args - */ - function getTriggerDefinition($trigger) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT trg.tgname AS trigger_name, - tbl.relname AS table_name, - CASE - WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();' - ELSE '' - END AS trigger_body, - CASE trg.tgtype & cast(2 as int2) - WHEN 0 THEN 'AFTER' - ELSE 'BEFORE' - END AS trigger_type, - CASE trg.tgtype & cast(28 as int2) - WHEN 16 THEN 'UPDATE' - WHEN 8 THEN 'DELETE' - WHEN 4 THEN 'INSERT' - WHEN 20 THEN 'INSERT, UPDATE' - WHEN 28 THEN 'INSERT, UPDATE, DELETE' - WHEN 24 THEN 'UPDATE, DELETE' - WHEN 12 THEN 'INSERT, DELETE' - END AS trigger_event, - CASE trg.tgenabled - WHEN 'O' THEN 't' - ELSE trg.tgenabled - END AS trigger_enabled, - obj_description(trg.oid, 'pg_trigger') AS trigger_comment - FROM pg_trigger trg, - pg_class tbl, - pg_proc p - WHERE trg.tgrelid = tbl.oid - AND trg.tgfoid = p.oid - AND trg.tgname = ". $db->quote($trigger, 'text'); - $types = array( - 'trigger_name' => 'text', - 'table_name' => 'text', - 'trigger_body' => 'text', - 'trigger_type' => 'text', - 'trigger_event' => 'text', - 'trigger_comment' => 'text', - 'trigger_enabled' => 'boolean', - ); - return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * @param object|string $result MDB2_result object from a query or a - * string containing the name of a table. - * While this also accepts a query result - * resource identifier, this behavior is - * deprecated. - * @param int $mode a valid tableInfo mode - * - * @return array an associative array with the information requested. - * A MDB2_Error object on failure. - * - * @see MDB2_Driver_Common::tableInfo() - */ - function tableInfo($result, $mode = null) - { - if (is_string($result)) { - return parent::tableInfo($result, $mode); - } - - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; - if (!is_resource($resource)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'Could not generate result resource', __FUNCTION__); - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $case_func = 'strtolower'; - } else { - $case_func = 'strtoupper'; - } - } else { - $case_func = 'strval'; - } - - $count = @pg_num_fields($resource); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - $db->loadModule('Datatype', null, true); - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '', - 'name' => $case_func(@pg_field_name($resource, $i)), - 'type' => @pg_field_type($resource, $i), - 'length' => @pg_field_size($resource, $i), - 'flags' => '', - ); - $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); - if (PEAR::isError($mdb2type_info)) { - return $mdb2type_info; - } - $res[$i]['mdb2type'] = $mdb2type_info[0][0]; - if ($mode & MDB2_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & MDB2_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - return $res; - } -} + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php 295587 2010-02-28 17:16:38Z quipo $ + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 PostGreSQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Paul Cooper + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT a.attname AS name, + t.typname AS type, + CASE a.attlen + WHEN -1 THEN + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod / 65536) + WHEN 'decimal' THEN (a.atttypmod / 65536) + WHEN 'money' THEN (a.atttypmod / 65536) + ELSE CASE a.atttypmod + WHEN -1 THEN NULL + ELSE a.atttypmod - 4 + END + END + ELSE a.attlen + END AS length, + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod % 65536) - 4 + WHEN 'decimal' THEN (a.atttypmod % 65536) - 4 + WHEN 'money' THEN (a.atttypmod % 65536) - 4 + ELSE 0 + END AS scale, + a.attnotnull, + a.atttypmod, + a.atthasdef, + (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) + FROM pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) as default + FROM pg_attribute a, + pg_class c, + pg_type t + WHERE c.relname = ".$db->quote($table, 'text')." + AND a.atttypid = t.oid + AND c.oid = a.attrelid + AND NOT a.attisdropped + AND a.attnum > 0 + AND a.attname = ".$db->quote($field_name, 'text')." + ORDER BY a.attnum"; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($column)) { + return $column; + } + + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + $column = array_change_key_case($column, CASE_LOWER); + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (PEAR::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') { + $notnull = true; + } + $default = null; + if ($column['atthasdef'] === 't' + && strpos($column['default'], 'NULL') !== 0 + && !preg_match("/nextval\('([^']+)'/", $column['default']) + ) { + $pattern = '/^\'(.*)\'::[\w ]+$/i'; + $default = $column['default'];#substr($column['adsrc'], 1, -1); + if ((null === $default) && $notnull) { + $default = ''; + } elseif (!empty($default) && preg_match($pattern, $default)) { + //remove data type cast + $default = preg_replace ($pattern, '\\1', $default); + } + } + $autoincrement = false; + if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) { + $autoincrement = true; + } + $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); + if (null !== $length) { + $definition[0]['length'] = $length; + } + if (null !== $unsigned) { + $definition[0]['unsigned'] = $unsigned; + } + if (null !== $fixed) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = 'SELECT relname, indkey FROM pg_index, pg_class'; + $query.= ' WHERE pg_class.oid = pg_index.indexrelid'; + $query.= " AND indisunique != 't' AND indisprimary != 't'"; + $query.= ' AND pg_class.relname = %s'; + $index_name_mdb2 = $db->getIndexName($index_name); + $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $row = array_change_key_case($row, CASE_LOWER); + + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + + $definition = array(); + + $index_column_numbers = explode(' ', $row['indkey']); + + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT c.oid, + c.conname AS constraint_name, + CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\", + CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\", + CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\", + CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\", + CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable, + CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred, + --array_to_string(c.conkey, ' ') AS constraint_key, + t.relname AS table_name, + t2.relname AS references_table, + CASE confupdtype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS onupdate, + CASE confdeltype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS ondelete, + CASE confmatchtype + WHEN 'u' THEN 'UNSPECIFIED' + WHEN 'f' THEN 'FULL' + WHEN 'p' THEN 'PARTIAL' + END AS match, + --array_to_string(c.confkey, ' ') AS fk_constraint_key, + consrc + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_class t2 ON c.confrelid = t2.oid + WHERE c.conname = %s + AND t.relname = " . $db->quote($table, 'text'); + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + $uniqueIndex = false; + if (empty($row)) { + // We might be looking for a UNIQUE index that was not created + // as a constraint but should be treated as such. + $query = 'SELECT relname AS constraint_name, + indkey, + 0 AS "check", + 0 AS "foreign", + 0 AS "primary", + 1 AS "unique", + 0 AS deferrable, + 0 AS initiallydeferred, + NULL AS references_table, + NULL AS onupdate, + NULL AS ondelete, + NULL AS match + FROM pg_index, pg_class + WHERE pg_class.oid = pg_index.indexrelid + AND indisunique = \'t\' + AND pg_class.relname = %s'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + $uniqueIndex = true; + } + + $row = array_change_key_case($row, CASE_LOWER); + + $definition = array( + 'primary' => (boolean)$row['primary'], + 'unique' => (boolean)$row['unique'], + 'foreign' => (boolean)$row['foreign'], + 'check' => (boolean)$row['check'], + 'fields' => array(), + 'references' => array( + 'table' => $row['references_table'], + 'fields' => array(), + ), + 'deferrable' => (boolean)$row['deferrable'], + 'initiallydeferred' => (boolean)$row['initiallydeferred'], + 'onupdate' => $row['onupdate'], + 'ondelete' => $row['ondelete'], + 'match' => $row['match'], + ); + + if ($uniqueIndex) { + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + $index_column_numbers = explode(' ', $row['indkey']); + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($table, 'text'); + $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (PEAR::isError($fields)) { + return $fields; + } + $colpos = 1; + foreach ($fields as $field) { + $definition['fields'][$field] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + + if ($definition['foreign']) { + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.confrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($definition['references']['table'], 'text'); + $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (PEAR::isError($foreign_fields)) { + return $foreign_fields; + } + $colpos = 1; + foreach ($foreign_fields as $foreign_field) { + $definition['references']['fields'][$foreign_field] = array( + 'position' => $colpos++, + ); + } + } + + if ($definition['check']) { + $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')"); + // ... + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + * + * @TODO: add support for plsql functions and functions with args + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT trg.tgname AS trigger_name, + tbl.relname AS table_name, + CASE + WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();' + ELSE '' + END AS trigger_body, + CASE trg.tgtype & cast(2 as int2) + WHEN 0 THEN 'AFTER' + ELSE 'BEFORE' + END AS trigger_type, + CASE trg.tgtype & cast(28 as int2) + WHEN 16 THEN 'UPDATE' + WHEN 8 THEN 'DELETE' + WHEN 4 THEN 'INSERT' + WHEN 20 THEN 'INSERT, UPDATE' + WHEN 28 THEN 'INSERT, UPDATE, DELETE' + WHEN 24 THEN 'UPDATE, DELETE' + WHEN 12 THEN 'INSERT, DELETE' + END AS trigger_event, + CASE trg.tgenabled + WHEN 'O' THEN 't' + ELSE trg.tgenabled + END AS trigger_enabled, + obj_description(trg.oid, 'pg_trigger') AS trigger_comment + FROM pg_trigger trg, + pg_class tbl, + pg_proc p + WHERE trg.tgrelid = tbl.oid + AND trg.tgfoid = p.oid + AND trg.tgname = ". $db->quote($trigger, 'text'); + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @pg_num_fields($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '', + 'name' => $case_func(@pg_field_name($resource, $i)), + 'type' => @pg_field_type($resource, $i), + 'length' => @pg_field_size($resource, $i), + 'flags' => '', + ); + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Reverse/sqlite.php b/3rdparty/MDB2/Driver/Reverse/sqlite.php index 60c686c418..b98ccdb62c 100644 --- a/3rdparty/MDB2/Driver/Reverse/sqlite.php +++ b/3rdparty/MDB2/Driver/Reverse/sqlite.php @@ -1,609 +1,609 @@ - | -// | Lorenzo Alberton | -// +----------------------------------------------------------------------+ -// -// $Id: sqlite.php,v 1.80 2008/05/03 10:30:14 quipo Exp $ -// - -require_once('MDB2/Driver/Reverse/Common.php'); - -/** - * MDB2 SQlite driver for the schema reverse engineering module - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Reverse_sqlite extends MDB2_Driver_Reverse_Common -{ - /** - * Remove SQL comments from the field definition - * - * @access private - */ - function _removeComments($sql) { - $lines = explode("\n", $sql); - foreach ($lines as $k => $line) { - $pieces = explode('--', $line); - if (count($pieces) > 1 && (substr_count($pieces[0], '\'') % 2) == 0) { - $lines[$k] = substr($line, 0, strpos($line, '--')); - } - } - return implode("\n", $lines); - } - - /** - * - */ - function _getTableColumns($sql) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $start_pos = strpos($sql, '('); - $end_pos = strrpos($sql, ')'); - $column_def = substr($sql, $start_pos+1, $end_pos-$start_pos-1); - // replace the decimal length-places-separator with a colon - $column_def = preg_replace('/(\d),(\d)/', '\1:\2', $column_def); - $column_def = $this->_removeComments($column_def); - $column_sql = explode(',', $column_def); - $columns = array(); - $count = count($column_sql); - if ($count == 0) { - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'unexpected empty table column definition list', __FUNCTION__); - } - $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; - $regexp2 = '/^\s*([^ ]+) +(PRIMARY|UNIQUE|CHECK)$/i'; - for ($i=0, $j=0; $i<$count; ++$i) { - if (!preg_match($regexp, trim($column_sql[$i]), $matches)) { - if (!preg_match($regexp2, trim($column_sql[$i]))) { - continue; - } - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'unexpected table column SQL definition: "'.$column_sql[$i].'"', __FUNCTION__); - } - $columns[$j]['name'] = trim($matches[1], implode('', $db->identifier_quoting)); - $columns[$j]['type'] = strtolower($matches[2]); - if (isset($matches[4]) && strlen($matches[4])) { - $columns[$j]['length'] = $matches[4]; - } - if (isset($matches[6]) && strlen($matches[6])) { - $columns[$j]['decimal'] = $matches[6]; - } - if (isset($matches[8]) && strlen($matches[8])) { - $columns[$j]['unsigned'] = true; - } - if (isset($matches[9]) && strlen($matches[9])) { - $columns[$j]['autoincrement'] = true; - } - if (isset($matches[12]) && strlen($matches[12])) { - $default = $matches[12]; - if (strlen($default) && $default[0]=="'") { - $default = str_replace("''", "'", substr($default, 1, strlen($default)-2)); - } - if ($default === 'NULL') { - $default = null; - } - $columns[$j]['default'] = $default; - } - if (isset($matches[7]) && strlen($matches[7])) { - $columns[$j]['notnull'] = ($matches[7] === ' NOT NULL'); - } else if (isset($matches[9]) && strlen($matches[9])) { - $columns[$j]['notnull'] = ($matches[9] === ' NOT NULL'); - } else if (isset($matches[13]) && strlen($matches[13])) { - $columns[$j]['notnull'] = ($matches[13] === ' NOT NULL'); - } - ++$j; - } - return $columns; - } - - // {{{ getTableFieldDefinition() - - /** - * Get the stucture of a field into an array - * - * @param string $table_name name of table that should be used in method - * @param string $field_name name of field that should be used in method - * @return mixed data array on success, a MDB2 error on failure. - * The returned array contains an array for each field definition, - * with (some of) these indices: - * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] - * @access public - */ - function getTableFieldDefinition($table_name, $field_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $result = $db->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); - } else { - $query.= 'name='.$db->quote($table, 'text'); - } - $sql = $db->queryOne($query); - if (PEAR::isError($sql)) { - return $sql; - } - $columns = $this->_getTableColumns($sql); - foreach ($columns as $column) { - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $column['name'] = strtolower($column['name']); - } else { - $column['name'] = strtoupper($column['name']); - } - } else { - $column = array_change_key_case($column, $db->options['field_case']); - } - if ($field_name == $column['name']) { - $mapped_datatype = $db->datatype->mapNativeDatatype($column); - if (PEAR::isError($mapped_datatype)) { - return $mapped_datatype; - } - list($types, $length, $unsigned, $fixed) = $mapped_datatype; - $notnull = false; - if (!empty($column['notnull'])) { - $notnull = $column['notnull']; - } - $default = false; - if (array_key_exists('default', $column)) { - $default = $column['default']; - if (is_null($default) && $notnull) { - $default = ''; - } - } - $autoincrement = false; - if (!empty($column['autoincrement'])) { - $autoincrement = true; - } - - $definition[0] = array( - 'notnull' => $notnull, - 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) - ); - if (!is_null($length)) { - $definition[0]['length'] = $length; - } - if (!is_null($unsigned)) { - $definition[0]['unsigned'] = $unsigned; - } - if (!is_null($fixed)) { - $definition[0]['fixed'] = $fixed; - } - if ($default !== false) { - $definition[0]['default'] = $default; - } - if ($autoincrement !== false) { - $definition[0]['autoincrement'] = $autoincrement; - } - foreach ($types as $key => $type) { - $definition[$key] = $definition[0]; - if ($type == 'clob' || $type == 'blob') { - unset($definition[$key]['default']); - } - $definition[$key]['type'] = $type; - $definition[$key]['mdb2type'] = $type; - } - return $definition; - } - } - - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table column', __FUNCTION__); - } - - // }}} - // {{{ getTableIndexDefinition() - - /** - * Get the stucture of an index into an array - * - * @param string $table_name name of table that should be used in method - * @param string $index_name name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableIndexDefinition($table_name, $index_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); - } else { - $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); - } - $query.= ' AND sql NOT NULL ORDER BY name'; - $index_name_mdb2 = $db->getIndexName($index_name); - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $qry = sprintf($query, $db->quote(strtolower($index_name_mdb2), 'text')); - } else { - $qry = sprintf($query, $db->quote($index_name_mdb2, 'text')); - } - $sql = $db->queryOne($qry, 'text'); - if (PEAR::isError($sql) || empty($sql)) { - // fallback to the given $index_name, without transformation - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $qry = sprintf($query, $db->quote(strtolower($index_name), 'text')); - } else { - $qry = sprintf($query, $db->quote($index_name, 'text')); - } - $sql = $db->queryOne($qry, 'text'); - } - if (PEAR::isError($sql)) { - return $sql; - } - if (!$sql) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table index', __FUNCTION__); - } - - $sql = strtolower($sql); - $start_pos = strpos($sql, '('); - $end_pos = strrpos($sql, ')'); - $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); - $column_names = explode(',', $column_names); - - if (preg_match("/^create unique/", $sql)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table index', __FUNCTION__); - } - - $definition = array(); - $count = count($column_names); - for ($i=0; $i<$count; ++$i) { - $column_name = strtok($column_names[$i], ' '); - $collation = strtok(' '); - $definition['fields'][$column_name] = array( - 'position' => $i+1 - ); - if (!empty($collation)) { - $definition['fields'][$column_name]['sorting'] = - ($collation=='ASC' ? 'ascending' : 'descending'); - } - } - - if (empty($definition['fields'])) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table index', __FUNCTION__); - } - return $definition; - } - - // }}} - // {{{ getTableConstraintDefinition() - - /** - * Get the stucture of a constraint into an array - * - * @param string $table_name name of table that should be used in method - * @param string $constraint_name name of constraint that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableConstraintDefinition($table_name, $constraint_name) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); - } else { - $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); - } - $query.= ' AND sql NOT NULL ORDER BY name'; - $constraint_name_mdb2 = $db->getIndexName($constraint_name); - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $qry = sprintf($query, $db->quote(strtolower($constraint_name_mdb2), 'text')); - } else { - $qry = sprintf($query, $db->quote($constraint_name_mdb2, 'text')); - } - $sql = $db->queryOne($qry, 'text'); - if (PEAR::isError($sql) || empty($sql)) { - // fallback to the given $index_name, without transformation - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $qry = sprintf($query, $db->quote(strtolower($constraint_name), 'text')); - } else { - $qry = sprintf($query, $db->quote($constraint_name, 'text')); - } - $sql = $db->queryOne($qry, 'text'); - } - if (PEAR::isError($sql)) { - return $sql; - } - //default values, eventually overridden - $definition = array( - 'primary' => false, - 'unique' => false, - 'foreign' => false, - 'check' => false, - 'fields' => array(), - 'references' => array( - 'table' => '', - 'fields' => array(), - ), - 'onupdate' => '', - 'ondelete' => '', - 'match' => '', - 'deferrable' => false, - 'initiallydeferred' => false, - ); - if (!$sql) { - $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); - } else { - $query.= 'name='.$db->quote($table, 'text'); - } - $query.= " AND sql NOT NULL ORDER BY name"; - $sql = $db->queryOne($query, 'text'); - if (PEAR::isError($sql)) { - return $sql; - } - if ($constraint_name == 'primary') { - // search in table definition for PRIMARY KEYs - if (preg_match("/\bPRIMARY\s+KEY\b\s*\(([^)]+)/i", $sql, $tmp)) { - $definition['primary'] = true; - $definition['fields'] = array(); - $column_names = explode(',', $tmp[1]); - $colpos = 1; - foreach ($column_names as $column_name) { - $definition['fields'][trim($column_name)] = array( - 'position' => $colpos++ - ); - } - return $definition; - } - if (preg_match("/\"([^\"]+)\"[^\,\"]+\bPRIMARY\s+KEY\b[^\,\)]*/i", $sql, $tmp)) { - $definition['primary'] = true; - $definition['fields'] = array(); - $column_names = explode(',', $tmp[1]); - $colpos = 1; - foreach ($column_names as $column_name) { - $definition['fields'][trim($column_name)] = array( - 'position' => $colpos++ - ); - } - return $definition; - } - } else { - // search in table definition for FOREIGN KEYs - $pattern = "/\bCONSTRAINT\b\s+%s\s+ - \bFOREIGN\s+KEY\b\s*\(([^\)]+)\)\s* - \bREFERENCES\b\s+([^\s]+)\s*\(([^\)]+)\)\s* - (?:\bMATCH\s*([^\s]+))?\s* - (?:\bON\s+UPDATE\s+([^\s,\)]+))?\s* - (?:\bON\s+DELETE\s+([^\s,\)]+))?\s* - /imsx"; - $found_fk = false; - if (preg_match(sprintf($pattern, $constraint_name_mdb2), $sql, $tmp)) { - $found_fk = true; - } elseif (preg_match(sprintf($pattern, $constraint_name), $sql, $tmp)) { - $found_fk = true; - } - if ($found_fk) { - $definition['foreign'] = true; - $definition['match'] = 'SIMPLE'; - $definition['onupdate'] = 'NO ACTION'; - $definition['ondelete'] = 'NO ACTION'; - $definition['references']['table'] = $tmp[2]; - $column_names = explode(',', $tmp[1]); - $colpos = 1; - foreach ($column_names as $column_name) { - $definition['fields'][trim($column_name)] = array( - 'position' => $colpos++ - ); - } - $referenced_cols = explode(',', $tmp[3]); - $colpos = 1; - foreach ($referenced_cols as $column_name) { - $definition['references']['fields'][trim($column_name)] = array( - 'position' => $colpos++ - ); - } - if (isset($tmp[4])) { - $definition['match'] = $tmp[4]; - } - if (isset($tmp[5])) { - $definition['onupdate'] = $tmp[5]; - } - if (isset($tmp[6])) { - $definition['ondelete'] = $tmp[6]; - } - return $definition; - } - } - $sql = false; - } - if (!$sql) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $constraint_name . ' is not an existing table constraint', __FUNCTION__); - } - - $sql = strtolower($sql); - $start_pos = strpos($sql, '('); - $end_pos = strrpos($sql, ')'); - $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); - $column_names = explode(',', $column_names); - - if (!preg_match("/^create unique/", $sql)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $constraint_name . ' is not an existing table constraint', __FUNCTION__); - } - - $definition['unique'] = true; - $count = count($column_names); - for ($i=0; $i<$count; ++$i) { - $column_name = strtok($column_names[$i]," "); - $collation = strtok(" "); - $definition['fields'][$column_name] = array( - 'position' => $i+1 - ); - if (!empty($collation)) { - $definition['fields'][$column_name]['sorting'] = - ($collation=='ASC' ? 'ascending' : 'descending'); - } - } - - if (empty($definition['fields'])) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $constraint_name . ' is not an existing table constraint', __FUNCTION__); - } - return $definition; - } - - // }}} - // {{{ getTriggerDefinition() - - /** - * Get the structure of a trigger into an array - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change the returned value - * at any time until labelled as non-experimental - * - * @param string $trigger name of trigger that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTriggerDefinition($trigger) - { - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name as trigger_name, - tbl_name AS table_name, - sql AS trigger_body, - NULL AS trigger_type, - NULL AS trigger_event, - NULL AS trigger_comment, - 1 AS trigger_enabled - FROM sqlite_master - WHERE type='trigger'"; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $query.= ' AND LOWER(name)='.$db->quote(strtolower($trigger), 'text'); - } else { - $query.= ' AND name='.$db->quote($trigger, 'text'); - } - $types = array( - 'trigger_name' => 'text', - 'table_name' => 'text', - 'trigger_body' => 'text', - 'trigger_type' => 'text', - 'trigger_event' => 'text', - 'trigger_comment' => 'text', - 'trigger_enabled' => 'boolean', - ); - $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($def)) { - return $def; - } - if (empty($def)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing trigger', __FUNCTION__); - } - if (preg_match("/^create\s+(?:temp|temporary)?trigger\s+(?:if\s+not\s+exists\s+)?.*(before|after)?\s+(insert|update|delete)/Uims", $def['trigger_body'], $tmp)) { - $def['trigger_type'] = strtoupper($tmp[1]); - $def['trigger_event'] = strtoupper($tmp[2]); - } - return $def; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table - * - * @param string $result a string containing the name of a table - * @param int $mode a valid tableInfo mode - * - * @return array an associative array with the information requested. - * A MDB2_Error object on failure. - * - * @see MDB2_Driver_Common::tableInfo() - * @since Method available since Release 1.7.0 - */ - function tableInfo($result, $mode = null) - { - if (is_string($result)) { - return parent::tableInfo($result, $mode); - } - - $db =$this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, - 'This DBMS can not obtain tableInfo from result sets', __FUNCTION__); - } -} - + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 SQlite driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_sqlite extends MDB2_Driver_Reverse_Common +{ + /** + * Remove SQL comments from the field definition + * + * @access private + */ + function _removeComments($sql) { + $lines = explode("\n", $sql); + foreach ($lines as $k => $line) { + $pieces = explode('--', $line); + if (count($pieces) > 1 && (substr_count($pieces[0], '\'') % 2) == 0) { + $lines[$k] = substr($line, 0, strpos($line, '--')); + } + } + return implode("\n", $lines); + } + + /** + * + */ + function _getTableColumns($sql) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_def = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + // replace the decimal length-places-separator with a colon + $column_def = preg_replace('/(\d),(\d)/', '\1:\2', $column_def); + $column_def = $this->_removeComments($column_def); + $column_sql = explode(',', $column_def); + $columns = array(); + $count = count($column_sql); + if ($count == 0) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected empty table column definition list', __FUNCTION__); + } + $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; + $regexp2 = '/^\s*([^ ]+) +(PRIMARY|UNIQUE|CHECK)$/i'; + for ($i=0, $j=0; $i<$count; ++$i) { + if (!preg_match($regexp, trim($column_sql[$i]), $matches)) { + if (!preg_match($regexp2, trim($column_sql[$i]))) { + continue; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected table column SQL definition: "'.$column_sql[$i].'"', __FUNCTION__); + } + $columns[$j]['name'] = trim($matches[1], implode('', $db->identifier_quoting)); + $columns[$j]['type'] = strtolower($matches[2]); + if (isset($matches[4]) && strlen($matches[4])) { + $columns[$j]['length'] = $matches[4]; + } + if (isset($matches[6]) && strlen($matches[6])) { + $columns[$j]['decimal'] = $matches[6]; + } + if (isset($matches[8]) && strlen($matches[8])) { + $columns[$j]['unsigned'] = true; + } + if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['autoincrement'] = true; + } + if (isset($matches[12]) && strlen($matches[12])) { + $default = $matches[12]; + if (strlen($default) && $default[0]=="'") { + $default = str_replace("''", "'", substr($default, 1, strlen($default)-2)); + } + if ($default === 'NULL') { + $default = null; + } + $columns[$j]['default'] = $default; + } + if (isset($matches[7]) && strlen($matches[7])) { + $columns[$j]['notnull'] = ($matches[7] === ' NOT NULL'); + } else if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['notnull'] = ($matches[9] === ' NOT NULL'); + } else if (isset($matches[13]) && strlen($matches[13])) { + $columns[$j]['notnull'] = ($matches[13] === ' NOT NULL'); + } + ++$j; + } + return $columns; + } + + // {{{ getTableFieldDefinition() + + /** + * Get the stucture of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure. + * The returned array contains an array for each field definition, + * with (some of) these indices: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (PEAR::isError($sql)) { + return $sql; + } + $columns = $this->_getTableColumns($sql); + foreach ($columns as $column) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column['name'] = strtolower($column['name']); + } else { + $column['name'] = strtoupper($column['name']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + if ($field_name == $column['name']) { + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (PEAR::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (!empty($column['notnull'])) { + $notnull = $column['notnull']; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if ((null === $default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['autoincrement'])) { + $autoincrement = true; + } + + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + if (null !== $length) { + $definition[0]['length'] = $length; + } + if (null !== $unsigned) { + $definition[0]['unsigned'] = $unsigned; + } + if (null !== $fixed) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the stucture of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $index_name_mdb2 = $db->getIndexName($index_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (PEAR::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (PEAR::isError($sql)) { + return $sql; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $definition = array(); + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i], ' '); + $collation = strtok(' '); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the stucture of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (PEAR::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (PEAR::isError($sql)) { + return $sql; + } + //default values, eventually overridden + $definition = array( + 'primary' => false, + 'unique' => false, + 'foreign' => false, + 'check' => false, + 'fields' => array(), + 'references' => array( + 'table' => '', + 'fields' => array(), + ), + 'onupdate' => '', + 'ondelete' => '', + 'match' => '', + 'deferrable' => false, + 'initiallydeferred' => false, + ); + if (!$sql) { + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $query.= " AND sql NOT NULL ORDER BY name"; + $sql = $db->queryOne($query, 'text'); + if (PEAR::isError($sql)) { + return $sql; + } + if ($constraint_name == 'primary') { + // search in table definition for PRIMARY KEYs + if (preg_match("/\bPRIMARY\s+KEY\b\s*\(([^)]+)/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + if (preg_match("/\"([^\"]+)\"[^\,\"]+\bPRIMARY\s+KEY\b[^\,\)]*/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + } else { + // search in table definition for FOREIGN KEYs + $pattern = "/\bCONSTRAINT\b\s+%s\s+ + \bFOREIGN\s+KEY\b\s*\(([^\)]+)\)\s* + \bREFERENCES\b\s+([^\s]+)\s*\(([^\)]+)\)\s* + (?:\bMATCH\s*([^\s]+))?\s* + (?:\bON\s+UPDATE\s+([^\s,\)]+))?\s* + (?:\bON\s+DELETE\s+([^\s,\)]+))?\s* + /imsx"; + $found_fk = false; + if (preg_match(sprintf($pattern, $constraint_name_mdb2), $sql, $tmp)) { + $found_fk = true; + } elseif (preg_match(sprintf($pattern, $constraint_name), $sql, $tmp)) { + $found_fk = true; + } + if ($found_fk) { + $definition['foreign'] = true; + $definition['match'] = 'SIMPLE'; + $definition['onupdate'] = 'NO ACTION'; + $definition['ondelete'] = 'NO ACTION'; + $definition['references']['table'] = $tmp[2]; + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $referenced_cols = explode(',', $tmp[3]); + $colpos = 1; + foreach ($referenced_cols as $column_name) { + $definition['references']['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + if (isset($tmp[4])) { + $definition['match'] = $tmp[4]; + } + if (isset($tmp[5])) { + $definition['onupdate'] = $tmp[5]; + } + if (isset($tmp[6])) { + $definition['ondelete'] = $tmp[6]; + } + return $definition; + } + } + $sql = false; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (!preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['unique'] = true; + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i]," "); + $collation = strtok(" "); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name as trigger_name, + tbl_name AS table_name, + sql AS trigger_body, + NULL AS trigger_type, + NULL AS trigger_event, + NULL AS trigger_comment, + 1 AS trigger_enabled + FROM sqlite_master + WHERE type='trigger'"; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(name)='.$db->quote(strtolower($trigger), 'text'); + } else { + $query.= ' AND name='.$db->quote($trigger, 'text'); + } + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($def)) { + return $def; + } + if (empty($def)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing trigger', __FUNCTION__); + } + if (preg_match("/^create\s+(?:temp|temporary)?trigger\s+(?:if\s+not\s+exists\s+)?.*(before|after)?\s+(insert|update|delete)/Uims", $def['trigger_body'], $tmp)) { + $def['trigger_type'] = strtoupper($tmp[1]); + $def['trigger_event'] = strtoupper($tmp[2]); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table + * + * @param string $result a string containing the name of a table + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, + 'This DBMS can not obtain tableInfo from result sets', __FUNCTION__); + } +} + ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/mysql.php b/3rdparty/MDB2/Driver/mysql.php index 2cec1e9c03..eda3310dee 100644 --- a/3rdparty/MDB2/Driver/mysql.php +++ b/3rdparty/MDB2/Driver/mysql.php @@ -1,1700 +1,1710 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.214 2008/11/16 21:45:08 quipo Exp $ -// - -/** - * MDB2 MySQL driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_mysql extends MDB2_Driver_Common -{ - // {{{ properties - - var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\'); - - var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`'); - - var $sql_comments = array( - array('start' => '-- ', 'end' => "\n", 'escape' => false), - array('start' => '#', 'end' => "\n", 'escape' => false), - array('start' => '/*', 'end' => '*/', 'escape' => false), - ); - - var $server_capabilities_checked = false; - - var $start_transaction = false; - - var $varchar_max_length = 255; - - // }}} - // {{{ constructor - - /** - * Constructor - */ - function __construct() - { - parent::__construct(); - - $this->phptype = 'mysql'; - $this->dbsyntax = 'mysql'; - - $this->supported['sequences'] = 'emulated'; - $this->supported['indexes'] = true; - $this->supported['affected_rows'] = true; - $this->supported['transactions'] = false; - $this->supported['savepoints'] = false; - $this->supported['summary_functions'] = true; - $this->supported['order_by_text'] = true; - $this->supported['current_id'] = 'emulated'; - $this->supported['limit_queries'] = true; - $this->supported['LOBs'] = true; - $this->supported['replace'] = true; - $this->supported['sub_selects'] = 'emulated'; - $this->supported['triggers'] = false; - $this->supported['auto_increment'] = true; - $this->supported['primary_key'] = true; - $this->supported['result_introspection'] = true; - $this->supported['prepared_statements'] = 'emulated'; - $this->supported['identifier_quoting'] = true; - $this->supported['pattern_escaping'] = true; - $this->supported['new_link'] = true; - - $this->options['DBA_username'] = false; - $this->options['DBA_password'] = false; - $this->options['default_table_type'] = ''; - $this->options['max_identifiers_length'] = 64; - - $this->_reCheckSupportedOptions(); - } - - // }}} - // {{{ _reCheckSupportedOptions() - - /** - * If the user changes certain options, other capabilities may depend - * on the new settings, so we need to check them (again). - * - * @access private - */ - function _reCheckSupportedOptions() - { - $this->supported['transactions'] = $this->options['use_transactions']; - $this->supported['savepoints'] = $this->options['use_transactions']; - if ($this->options['default_table_type']) { - switch (strtoupper($this->options['default_table_type'])) { - case 'BLACKHOLE': - case 'MEMORY': - case 'ARCHIVE': - case 'CSV': - case 'HEAP': - case 'ISAM': - case 'MERGE': - case 'MRG_ISAM': - case 'ISAM': - case 'MRG_MYISAM': - case 'MYISAM': - $this->supported['savepoints'] = false; - $this->supported['transactions'] = false; - $this->warnings[] = $this->options['default_table_type'] . - ' is not a supported default table type'; - break; - } - } - } - - // }}} - // {{{ function setOption($option, $value) - - /** - * set the option for the db class - * - * @param string option name - * @param mixed value for the option - * - * @return mixed MDB2_OK or MDB2 Error Object - * - * @access public - */ - function setOption($option, $value) - { - $res = parent::setOption($option, $value); - $this->_reCheckSupportedOptions(); - } - - // }}} - // {{{ errorInfo() - - /** - * This method is used to collect information about an error - * - * @param integer $error - * @return array - * @access public - */ - function errorInfo($error = null) - { - if ($this->connection) { - $native_code = @mysql_errno($this->connection); - $native_msg = @mysql_error($this->connection); - } else { - $native_code = @mysql_errno(); - $native_msg = @mysql_error(); - } - if (is_null($error)) { - static $ecode_map; - if (empty($ecode_map)) { - $ecode_map = array( - 1000 => MDB2_ERROR_INVALID, //hashchk - 1001 => MDB2_ERROR_INVALID, //isamchk - 1004 => MDB2_ERROR_CANNOT_CREATE, - 1005 => MDB2_ERROR_CANNOT_CREATE, - 1006 => MDB2_ERROR_CANNOT_CREATE, - 1007 => MDB2_ERROR_ALREADY_EXISTS, - 1008 => MDB2_ERROR_CANNOT_DROP, - 1009 => MDB2_ERROR_CANNOT_DROP, - 1010 => MDB2_ERROR_CANNOT_DROP, - 1011 => MDB2_ERROR_CANNOT_DELETE, - 1022 => MDB2_ERROR_ALREADY_EXISTS, - 1029 => MDB2_ERROR_NOT_FOUND, - 1032 => MDB2_ERROR_NOT_FOUND, - 1044 => MDB2_ERROR_ACCESS_VIOLATION, - 1045 => MDB2_ERROR_ACCESS_VIOLATION, - 1046 => MDB2_ERROR_NODBSELECTED, - 1048 => MDB2_ERROR_CONSTRAINT, - 1049 => MDB2_ERROR_NOSUCHDB, - 1050 => MDB2_ERROR_ALREADY_EXISTS, - 1051 => MDB2_ERROR_NOSUCHTABLE, - 1054 => MDB2_ERROR_NOSUCHFIELD, - 1060 => MDB2_ERROR_ALREADY_EXISTS, - 1061 => MDB2_ERROR_ALREADY_EXISTS, - 1062 => MDB2_ERROR_ALREADY_EXISTS, - 1064 => MDB2_ERROR_SYNTAX, - 1067 => MDB2_ERROR_INVALID, - 1072 => MDB2_ERROR_NOT_FOUND, - 1086 => MDB2_ERROR_ALREADY_EXISTS, - 1091 => MDB2_ERROR_NOT_FOUND, - 1100 => MDB2_ERROR_NOT_LOCKED, - 1109 => MDB2_ERROR_NOT_FOUND, - 1125 => MDB2_ERROR_ALREADY_EXISTS, - 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, - 1138 => MDB2_ERROR_INVALID, - 1142 => MDB2_ERROR_ACCESS_VIOLATION, - 1143 => MDB2_ERROR_ACCESS_VIOLATION, - 1146 => MDB2_ERROR_NOSUCHTABLE, - 1149 => MDB2_ERROR_SYNTAX, - 1169 => MDB2_ERROR_CONSTRAINT, - 1176 => MDB2_ERROR_NOT_FOUND, - 1177 => MDB2_ERROR_NOSUCHTABLE, - 1213 => MDB2_ERROR_DEADLOCK, - 1216 => MDB2_ERROR_CONSTRAINT, - 1217 => MDB2_ERROR_CONSTRAINT, - 1227 => MDB2_ERROR_ACCESS_VIOLATION, - 1235 => MDB2_ERROR_CANNOT_CREATE, - 1299 => MDB2_ERROR_INVALID_DATE, - 1300 => MDB2_ERROR_INVALID, - 1304 => MDB2_ERROR_ALREADY_EXISTS, - 1305 => MDB2_ERROR_NOT_FOUND, - 1306 => MDB2_ERROR_CANNOT_DROP, - 1307 => MDB2_ERROR_CANNOT_CREATE, - 1334 => MDB2_ERROR_CANNOT_ALTER, - 1339 => MDB2_ERROR_NOT_FOUND, - 1356 => MDB2_ERROR_INVALID, - 1359 => MDB2_ERROR_ALREADY_EXISTS, - 1360 => MDB2_ERROR_NOT_FOUND, - 1363 => MDB2_ERROR_NOT_FOUND, - 1365 => MDB2_ERROR_DIVZERO, - 1451 => MDB2_ERROR_CONSTRAINT, - 1452 => MDB2_ERROR_CONSTRAINT, - 1542 => MDB2_ERROR_CANNOT_DROP, - 1546 => MDB2_ERROR_CONSTRAINT, - 1582 => MDB2_ERROR_CONSTRAINT, - 2003 => MDB2_ERROR_CONNECT_FAILED, - 2019 => MDB2_ERROR_INVALID, - ); - } - if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { - $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; - $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; - $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; - } else { - // Doing this in case mode changes during runtime. - $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; - $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; - $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; - } - if (isset($ecode_map[$native_code])) { - $error = $ecode_map[$native_code]; - } - } - return array($error, $native_code, $native_msg); - } - - // }}} - // {{{ escape() - - /** - * Quotes a string so it can be safely used in a query. It will quote - * the text so it can safely be used within a query. - * - * @param string the input string to quote - * @param bool escape wildcards - * - * @return string quoted string - * - * @access public - */ - function escape($text, $escape_wildcards = false) - { - if ($escape_wildcards) { - $text = $this->escapePattern($text); - } - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $text = @mysql_real_escape_string($text, $connection); - return $text; - } - - // }}} - // {{{ beginTransaction() - - /** - * Start a transaction or set a savepoint. - * - * @param string name of a savepoint to set - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function beginTransaction($savepoint = null) - { - $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - $this->_getServerCapabilities(); - if (!is_null($savepoint)) { - if (!$this->supports('savepoints')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'savepoint cannot be released when changes are auto committed', __FUNCTION__); - } - $query = 'SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } elseif ($this->in_transaction) { - return MDB2_OK; //nothing to do - } - if (!$this->destructor_registered && $this->opened_persistent) { - $this->destructor_registered = true; - register_shutdown_function('MDB2_closeOpenTransactions'); - } - $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 0'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = true; - return MDB2_OK; - } - - // }}} - // {{{ commit() - - /** - * Commit the database changes done during a transaction that is in - * progress or release a savepoint. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after committing the pending changes. - * - * @param string name of a savepoint to release - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function commit($savepoint = null) - { - $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - if (!$this->supports('savepoints')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - $server_info = $this->getServerVersion(); - if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { - return MDB2_OK; - } - $query = 'RELEASE SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } - - if (!$this->supports('transactions')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'transactions are not supported', __FUNCTION__); - } - - $result =& $this->_doQuery('COMMIT', true); - if (PEAR::isError($result)) { - return $result; - } - if (!$this->start_transaction) { - $query = 'SET AUTOCOMMIT = 1'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ rollback() - - /** - * Cancel any database changes done during a transaction or since a specific - * savepoint that is in progress. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after canceling the pending changes. - * - * @param string name of a savepoint to rollback to - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function rollback($savepoint = null) - { - $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'rollback cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - if (!$this->supports('savepoints')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } - - $query = 'ROLLBACK'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - if (!$this->start_transaction) { - $query = 'SET AUTOCOMMIT = 1'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ function setTransactionIsolation() - - /** - * Set the transacton isolation level. - * - * @param string standard isolation level - * READ UNCOMMITTED (allows dirty reads) - * READ COMMITTED (prevents dirty reads) - * REPEATABLE READ (prevents nonrepeatable reads) - * SERIALIZABLE (prevents phantom reads) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - static function setTransactionIsolation($isolation, $options = array()) - { - $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); - if (!$this->supports('transactions')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'transactions are not supported', __FUNCTION__); - } - switch ($isolation) { - case 'READ UNCOMMITTED': - case 'READ COMMITTED': - case 'REPEATABLE READ': - case 'SERIALIZABLE': - break; - default: - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'isolation level is not supported: '.$isolation, __FUNCTION__); - } - - $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; - return $this->_doQuery($query, true); - } - - // }}} - // {{{ _doConnect() - - /** - * do the grunt work of the connect - * - * @return connection on success or MDB2 Error Object on failure - * @access protected - */ - function _doConnect($username, $password, $persistent = false) - { - if (!PEAR::loadExtension($this->phptype)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); - } - - $params = array(); - if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') { - $params[0] = ':' . $this->dsn['socket']; - } else { - $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec'] - : 'localhost'; - if ($this->dsn['port']) { - $params[0].= ':' . $this->dsn['port']; - } - } - $params[] = $username ? $username : null; - $params[] = $password ? $password : null; - if (!$persistent) { - if ($this->_isNewLinkSet()) { - $params[] = true; - } else { - $params[] = false; - } - } - if (version_compare(phpversion(), '4.3.0', '>=')) { - $params[] = isset($this->dsn['client_flags']) - ? $this->dsn['client_flags'] : null; - } - $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; - - $connection = @call_user_func_array($connect_function, $params); - if (!$connection) { - if (($err = @mysql_error()) != '') { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - $err, __FUNCTION__); - } else { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - 'unable to establish a connection', __FUNCTION__); - } - } - - if (!empty($this->dsn['charset'])) { - $result = $this->setCharset($this->dsn['charset'], $connection); - if (PEAR::isError($result)) { - $this->disconnect(false); - return $result; - } - } - - return $connection; - } - - // }}} - // {{{ connect() - - /** - * Connect to the database - * - * @return MDB2_OK on success, MDB2 Error Object on failure - * @access public - */ - function connect() - { - if (is_resource($this->connection)) { - //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 - if (MDB2::areEquals($this->connected_dsn, $this->dsn) - && $this->opened_persistent == $this->options['persistent'] - ) { - return MDB2_OK; - } - $this->disconnect(false); - } - - $connection = $this->_doConnect( - $this->dsn['username'], - $this->dsn['password'], - $this->options['persistent'] - ); - if (PEAR::isError($connection)) { - return $connection; - } - - $this->connection = $connection; - $this->connected_dsn = $this->dsn; - $this->connected_database_name = ''; - $this->opened_persistent = $this->options['persistent']; - $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; - - if ($this->database_name) { - if ($this->database_name != $this->connected_database_name) { - if (!@mysql_select_db($this->database_name, $connection)) { - $err = $this->raiseError(null, null, null, - 'Could not select the database: '.$this->database_name, __FUNCTION__); - return $err; - } - $this->connected_database_name = $this->database_name; - } - } - - $this->_getServerCapabilities(); - - return MDB2_OK; - } - - // }}} - // {{{ setCharset() - - /** - * Set the charset on the current connection - * - * @param string charset (or array(charset, collation)) - * @param resource connection handle - * - * @return true on success, MDB2 Error Object on failure - */ - function setCharset($charset, $connection = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - $collation = null; - if (is_array($charset) && 2 == count($charset)) { - $collation = array_pop($charset); - $charset = array_pop($charset); - } - $client_info = mysql_get_client_info(); - if (function_exists('mysql_set_charset') && version_compare($client_info, '5.0.6')) { - if (!$result = mysql_set_charset($charset, $connection)) { - $err =& $this->raiseError(null, null, null, - 'Could not set client character set', __FUNCTION__); - return $err; - } - return $result; - } - $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'"; - if (!is_null($collation)) { - $query .= " COLLATE '".mysqli_real_escape_string($connection, $collation)."'"; - } - return $this->_doQuery($query, true, $connection); - } - - // }}} - // {{{ databaseExists() - - /** - * check if given database name is exists? - * - * @param string $name name of the database that should be checked - * - * @return mixed true/false on success, a MDB2 error on failure - * @access public - */ - function databaseExists($name) - { - $connection = $this->_doConnect($this->dsn['username'], - $this->dsn['password'], - $this->options['persistent']); - if (PEAR::isError($connection)) { - return $connection; - } - - $result = @mysql_select_db($name, $connection); - @mysql_close($connection); - - return $result; - } - - // }}} - // {{{ disconnect() - - /** - * Log out and disconnect from the database. - * - * @param boolean $force if the disconnect should be forced even if the - * connection is opened persistently - * @return mixed true on success, false if not connected and error - * object on error - * @access public - */ - function disconnect($force = true) - { - if (is_resource($this->connection)) { - if ($this->in_transaction) { - $dsn = $this->dsn; - $database_name = $this->database_name; - $persistent = $this->options['persistent']; - $this->dsn = $this->connected_dsn; - $this->database_name = $this->connected_database_name; - $this->options['persistent'] = $this->opened_persistent; - $this->rollback(); - $this->dsn = $dsn; - $this->database_name = $database_name; - $this->options['persistent'] = $persistent; - } - - if (!$this->opened_persistent || $force) { - $ok = @mysql_close($this->connection); - if (!$ok) { - return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, - null, null, null, __FUNCTION__); - } - } - } else { - return false; - } - return parent::disconnect($force); - } - - // }}} - // {{{ standaloneQuery() - - /** - * execute a query as DBA - * - * @param string $query the SQL query - * @param mixed $types array that contains the types of the columns in - * the result set - * @param boolean $is_manip if the query is a manipulation query - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &standaloneQuery($query, $types = null, $is_manip = false) - { - $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; - $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; - $connection = $this->_doConnect($user, $pass, $this->options['persistent']); - if (PEAR::isError($connection)) { - return $connection; - } - - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - - $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name); - if (!PEAR::isError($result)) { - $result = $this->_affectedRows($connection, $result); - } - - @mysql_close($connection); - return $result; - } - - // }}} - // {{{ _doQuery() - - /** - * Execute a query - * @param string $query query - * @param boolean $is_manip if the query is a manipulation query - * @param resource $connection - * @param string $database_name - * @return result or error object - * @access protected - */ - function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) - { - $this->last_query = $query; - $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - if ($this->options['disable_query']) { - $result = $is_manip ? 0 : null; - return $result; - } - - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - if (is_null($database_name)) { - $database_name = $this->database_name; - } - - if ($database_name) { - if ($database_name != $this->connected_database_name) { - if (!@mysql_select_db($database_name, $connection)) { - $err = $this->raiseError(null, null, null, - 'Could not select the database: '.$database_name, __FUNCTION__); - return $err; - } - $this->connected_database_name = $database_name; - } - } - - $function = $this->options['result_buffering'] - ? 'mysql_query' : 'mysql_unbuffered_query'; - $result = @$function($query, $connection); - if (!$result) { - $err =$this->raiseError(null, null, null, - 'Could not execute statement', __FUNCTION__); - return $err; - } - - $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ _affectedRows() - - /** - * Returns the number of rows affected - * - * @param resource $result - * @param resource $connection - * @return mixed MDB2 Error Object or the number of rows affected - * @access private - */ - function _affectedRows($connection, $result = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - return @mysql_affected_rows($connection); - } - - // }}} - // {{{ _modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * @param string $query query to modify - * @param boolean $is_manip if it is a DML query - * @param integer $limit limit the number of rows - * @param integer $offset start reading from given offset - * @return string modified query - * @access protected - */ - function _modifyQuery($query, $is_manip, $limit, $offset) - { - if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { - // "DELETE FROM table" gives 0 affected rows in MySQL. - // This little hack lets you know how many rows were deleted. - if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { - $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', - 'DELETE FROM \1 WHERE 1=1', $query); - } - } - if ($limit > 0 - && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) - ) { - $query = rtrim($query); - if (substr($query, -1) == ';') { - $query = substr($query, 0, -1); - } - - // LIMIT doesn't always come last in the query - // @see http://dev.mysql.com/doc/refman/5.0/en/select.html - $after = ''; - if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) { - $after = $matches[0]; - $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query); - } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) { - $after = $matches[0]; - $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query); - } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) { - $after = $matches[0]; - $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query); - } - - if ($is_manip) { - return $query . " LIMIT $limit" . $after; - } else { - return $query . " LIMIT $offset, $limit" . $after; - } - } - return $query; - } - - // }}} - // {{{ getServerVersion() - - /** - * return version information about the server - * - * @param bool $native determines if the raw version string should be returned - * @return mixed array/string with version information or MDB2 error object - * @access public - */ - function getServerVersion($native = false) - { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - if ($this->connected_server_info) { - $server_info = $this->connected_server_info; - } else { - $server_info = @mysql_get_server_info($connection); - } - if (!$server_info) { - return $this->raiseError(null, null, null, - 'Could not get server information', __FUNCTION__); - } - // cache server_info - $this->connected_server_info = $server_info; - if (!$native) { - $tmp = explode('.', $server_info, 3); - if (isset($tmp[2]) && strpos($tmp[2], '-')) { - $tmp2 = explode('-', @$tmp[2], 2); - } else { - $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null; - $tmp2[1] = null; - } - $server_info = array( - 'major' => isset($tmp[0]) ? $tmp[0] : null, - 'minor' => isset($tmp[1]) ? $tmp[1] : null, - 'patch' => $tmp2[0], - 'extra' => $tmp2[1], - 'native' => $server_info, - ); - } - return $server_info; - } - - // }}} - // {{{ _getServerCapabilities() - - /** - * Fetch some information about the server capabilities - * (transactions, subselects, prepared statements, etc). - * - * @access private - */ - function _getServerCapabilities() - { - if (!$this->server_capabilities_checked) { - $this->server_capabilities_checked = true; - - //set defaults - $this->supported['sub_selects'] = 'emulated'; - $this->supported['prepared_statements'] = 'emulated'; - $this->supported['triggers'] = false; - $this->start_transaction = false; - $this->varchar_max_length = 255; - - $server_info = $this->getServerVersion(); - if (is_array($server_info)) { - $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch']; - - if (!version_compare($server_version, '4.1.0', '<')) { - $this->supported['sub_selects'] = true; - $this->supported['prepared_statements'] = true; - } - - // SAVEPOINTs were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB) - if (version_compare($server_version, '4.1.0', '>=')) { - if (version_compare($server_version, '4.1.1', '<')) { - $this->supported['savepoints'] = false; - } - } elseif (version_compare($server_version, '4.0.14', '<')) { - $this->supported['savepoints'] = false; - } - - if (!version_compare($server_version, '4.0.11', '<')) { - $this->start_transaction = true; - } - - if (!version_compare($server_version, '5.0.3', '<')) { - $this->varchar_max_length = 65532; - } - - if (!version_compare($server_version, '5.0.2', '<')) { - $this->supported['triggers'] = true; - } - } - } - } - - // }}} - // {{{ function _skipUserDefinedVariable($query, $position) - - /** - * Utility method, used by prepare() to avoid misinterpreting MySQL user - * defined variables (SELECT @x:=5) for placeholders. - * Check if the placeholder is a false positive, i.e. if it is an user defined - * variable instead. If so, skip it and advance the position, otherwise - * return the current position, which is valid - * - * @param string $query - * @param integer $position current string cursor position - * @return integer $new_position - * @access protected - */ - function _skipUserDefinedVariable($query, $position) - { - $found = strpos(strrev(substr($query, 0, $position)), '@'); - if ($found === false) { - return $position; - } - $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1; - $substring = substr($query, $pos, $position - $pos + 2); - if (preg_match('/^@\w+\s*:=$/', $substring)) { - return $position + 1; //found an user defined variable: skip it - } - return $position; - } - - // }}} - // {{{ prepare() - - /** - * Prepares a query for multiple execution with execute(). - * With some database backends, this is emulated. - * prepare() requires a generic query as string like - * 'INSERT INTO numbers VALUES(?,?)' or - * 'INSERT INTO numbers VALUES(:foo,:bar)'. - * The ? and :name and are placeholders which can be set using - * bindParam() and the query can be sent off using the execute() method. - * The allowed format for :name can be set with the 'bindname_format' option. - * - * @param string $query the query to prepare - * @param mixed $types array that contains the types of the placeholders - * @param mixed $result_types array that contains the types of the columns in - * the result set or MDB2_PREPARE_RESULT, if set to - * MDB2_PREPARE_MANIP the query is handled as a manipulation query - * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders - * @return mixed resource handle for the prepared query on success, a MDB2 - * error on failure - * @access public - * @see bindParam, execute - */ - function &prepare($query, $types = null, $result_types = null, $lobs = array()) - { - if ($this->options['emulate_prepared'] - || $this->supported['prepared_statements'] !== true - ) { - $obj =& parent::prepare($query, $types, $result_types, $lobs); - return $obj; - } - $is_manip = ($result_types === MDB2_PREPARE_MANIP); - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - $placeholder_type_guess = $placeholder_type = null; - $question = '?'; - $colon = ':'; - $positions = array(); - $position = 0; - while ($position < strlen($query)) { - $q_position = strpos($query, $question, $position); - $c_position = strpos($query, $colon, $position); - if ($q_position && $c_position) { - $p_position = min($q_position, $c_position); - } elseif ($q_position) { - $p_position = $q_position; - } elseif ($c_position) { - $p_position = $c_position; - } else { - break; - } - if (is_null($placeholder_type)) { - $placeholder_type_guess = $query[$p_position]; - } - - $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); - if (PEAR::isError($new_pos)) { - return $new_pos; - } - if ($new_pos != $position) { - $position = $new_pos; - continue; //evaluate again starting from the new position - } - - //make sure this is not part of an user defined variable - $new_pos = $this->_skipUserDefinedVariable($query, $position); - if ($new_pos != $position) { - $position = $new_pos; - continue; //evaluate again starting from the new position - } - - if ($query[$position] == $placeholder_type_guess) { - if (is_null($placeholder_type)) { - $placeholder_type = $query[$p_position]; - $question = $colon = $placeholder_type; - } - if ($placeholder_type == ':') { - $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; - $parameter = preg_replace($regexp, '\\1', $query); - if ($parameter === '') { - $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'named parameter name must match "bindname_format" option', __FUNCTION__); - return $err; - } - $positions[$p_position] = $parameter; - $query = substr_replace($query, '?', $position, strlen($parameter)+1); - } else { - $positions[$p_position] = count($positions); - } - $position = $p_position + 1; - } else { - $position = $p_position; - } - } - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - static $prep_statement_counter = 1; - $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand())); - $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); - $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); - $statement =& $this->_doQuery($query, true, $connection); - if (PEAR::isError($statement)) { - return $statement; - } - - $class_name = 'MDB2_Statement_'.$this->phptype; - $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); - $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); - return $obj; - } - - // }}} - // {{{ replace() - - /** - * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT - * query, except that if there is already a row in the table with the same - * key field values, the old row is deleted before the new row is inserted. - * - * The REPLACE type of query does not make part of the SQL standards. Since - * practically only MySQL implements it natively, this type of query is - * emulated through this method for other DBMS using standard types of - * queries inside a transaction to assure the atomicity of the operation. - * - * @access public - * - * @param string $table name of the table on which the REPLACE query will - * be executed. - * @param array $fields associative array that describes the fields and the - * values that will be inserted or updated in the specified table. The - * indexes of the array are the names of all the fields of the table. The - * values of the array are also associative arrays that describe the - * values and other properties of the table fields. - * - * Here follows a list of field properties that need to be specified: - * - * value: - * Value to be assigned to the specified field. This value may be - * of specified in database independent type format as this - * function can perform the necessary datatype conversions. - * - * Default: - * this property is required unless the Null property - * is set to 1. - * - * type - * Name of the type of the field. Currently, all types Metabase - * are supported except for clob and blob. - * - * Default: no type conversion - * - * null - * Boolean property that indicates that the value for this field - * should be set to null. - * - * The default value for fields missing in INSERT queries may be - * specified the definition of a table. Often, the default value - * is already null, but since the REPLACE may be emulated using - * an UPDATE query, make sure that all fields of the table are - * listed in this function argument array. - * - * Default: 0 - * - * key - * Boolean property that indicates that this field should be - * handled as a primary key or at least as part of the compound - * unique index of the table that will determine the row that will - * updated if it exists or inserted a new row otherwise. - * - * This function will fail if no key field is specified or if the - * value of a key field is set to null because fields that are - * part of unique index they may not be null. - * - * Default: 0 - * - * @see http://dev.mysql.com/doc/refman/5.0/en/replace.html - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function replace($table, $fields) - { - $count = count($fields); - $query = $values = ''; - $keys = $colnum = 0; - for (reset($fields); $colnum < $count; next($fields), $colnum++) { - $name = key($fields); - if ($colnum > 0) { - $query .= ','; - $values.= ','; - } - $query.= $this->quoteIdentifier($name, true); - if (isset($fields[$name]['null']) && $fields[$name]['null']) { - $value = 'NULL'; - } else { - $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; - $value = $this->quote($fields[$name]['value'], $type); - if (PEAR::isError($value)) { - return $value; - } - } - $values.= $value; - if (isset($fields[$name]['key']) && $fields[$name]['key']) { - if ($value === 'NULL') { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'key value '.$name.' may not be NULL', __FUNCTION__); - } - $keys++; - } - } - if ($keys == 0) { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'not specified which fields are keys', __FUNCTION__); - } - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $table = $this->quoteIdentifier($table, true); - $query = "REPLACE INTO $table ($query) VALUES ($values)"; - $result =& $this->_doQuery($query, true, $connection); - if (PEAR::isError($result)) { - return $result; - } - return $this->_affectedRows($connection, $result); - } - - // }}} - // {{{ nextID() - - /** - * Returns the next free id of a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true the sequence is - * automatic created, if it - * not exists - * - * @return mixed MDB2 Error Object or id - * @access public - */ - function nextID($seq_name, $ondemand = true) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); - $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $this->expectError(MDB2_ERROR_NOSUCHTABLE); - $result =& $this->_doQuery($query, true); - $this->popExpect(); - $this->popErrorHandling(); - if (PEAR::isError($result)) { - if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { - $this->loadModule('Manager', null, true); - $result = $this->manager->createSequence($seq_name); - if (PEAR::isError($result)) { - return $this->raiseError($result, null, null, - 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); - } else { - return $this->nextID($seq_name, false); - } - } - return $result; - } - $value = $this->lastInsertID(); - if (is_numeric($value)) { - $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; - } - } - return $value; - } - - // }}} - // {{{ lastInsertID() - - /** - * Returns the autoincrement ID if supported or $id or fetches the current - * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) - * - * @param string $table name of the table into which a new row was inserted - * @param string $field name of the field into which a new row was inserted - * @return mixed MDB2 Error Object or id - * @access public - */ - function lastInsertID($table = null, $field = null) - { - // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 - return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); - } - - // }}} - // {{{ currID() - - /** - * Returns the current id of a sequence - * - * @param string $seq_name name of the sequence - * @return mixed MDB2 Error Object or id - * @access public - */ - function currID($seq_name) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); - $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; - return $this->queryOne($query, 'integer'); - } -} - -/** - * MDB2 MySQL result driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Result_mysql extends MDB2_Result_Common -{ - // }}} - // {{{ fetchRow() - - /** - * Fetch a row and insert the data into an existing array. - * - * @param int $fetchmode how the array data should be indexed - * @param int $rownum number of the row where the data can be found - * @return int data array on success, a MDB2 error on failure - * @access public - */ - function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) - { - if (!is_null($rownum)) { - $seek = $this->seek($rownum); - if (PEAR::isError($seek)) { - return $seek; - } - } - if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { - $fetchmode = $this->db->fetchmode; - } - if ($fetchmode & MDB2_FETCHMODE_ASSOC) { - $row = @mysql_fetch_assoc($this->result); - if (is_array($row) - && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE - ) { - $row = array_change_key_case($row, $this->db->options['field_case']); - } - } else { - $row = @mysql_fetch_row($this->result); - } - - if (!$row) { - if ($this->result === false) { - $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - return $err; - } - $null = null; - return $null; - } - $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; - $rtrim = false; - if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { - if (empty($this->types)) { - $mode += MDB2_PORTABILITY_RTRIM; - } else { - $rtrim = true; - } - } - if ($mode) { - $this->db->_fixResultArrayValues($row, $mode); - } - if (!empty($this->types)) { - $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); - } - if (!empty($this->values)) { - $this->_assignBindColumns($row); - } - if ($fetchmode === MDB2_FETCHMODE_OBJECT) { - $object_class = $this->db->options['fetch_class']; - if ($object_class == 'stdClass') { - $row = (object) $row; - } else { - $row = new $object_class($row); - } - } - ++$this->rownum; - return $row; - } - - // }}} - // {{{ _getColumnNames() - - /** - * Retrieve the names of columns returned by the DBMS in a query result. - * - * @return mixed Array variable that holds the names of columns as keys - * or an MDB2 error on failure. - * Some DBMS may not return any columns when the result set - * does not contain any rows. - * @access private - */ - function _getColumnNames() - { - $columns = array(); - $numcols = $this->numCols(); - if (PEAR::isError($numcols)) { - return $numcols; - } - for ($column = 0; $column < $numcols; $column++) { - $column_name = @mysql_field_name($this->result, $column); - $columns[$column_name] = $column; - } - if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $columns = array_change_key_case($columns, $this->db->options['field_case']); - } - return $columns; - } - - // }}} - // {{{ numCols() - - /** - * Count the number of columns returned by the DBMS in a query result. - * - * @return mixed integer value with the number of columns, a MDB2 error - * on failure - * @access public - */ - function numCols() - { - $cols = @mysql_num_fields($this->result); - if (is_null($cols)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return count($this->types); - } - return $this->db->raiseError(null, null, null, - 'Could not get column count', __FUNCTION__); - } - return $cols; - } - - // }}} - // {{{ free() - - /** - * Free the internal resources associated with result. - * - * @return boolean true on success, false if result is invalid - * @access public - */ - function free() - { - if (is_resource($this->result) && $this->db->connection) { - $free = @mysql_free_result($this->result); - if ($free === false) { - return $this->db->raiseError(null, null, null, - 'Could not free result', __FUNCTION__); - } - } - $this->result = false; - return MDB2_OK; - } -} - -/** - * MDB2 MySQL buffered result driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_BufferedResult_mysql extends MDB2_Result_mysql -{ - // }}} - // {{{ seek() - - /** - * Seek to a specific row in a result set - * - * @param int $rownum number of the row where the data can be found - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function seek($rownum = 0) - { - if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return MDB2_OK; - } - return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, - 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); - } - $this->rownum = $rownum - 1; - return MDB2_OK; - } - - // }}} - // {{{ valid() - - /** - * Check if the end of the result set has been reached - * - * @return mixed true or false on sucess, a MDB2 error on failure - * @access public - */ - function valid() - { - $numrows = $this->numRows(); - if (PEAR::isError($numrows)) { - return $numrows; - } - return $this->rownum < ($numrows - 1); - } - - // }}} - // {{{ numRows() - - /** - * Returns the number of rows in a result object - * - * @return mixed MDB2 Error Object or the number of rows - * @access public - */ - function numRows() - { - $rows = @mysql_num_rows($this->result); - if (false === $rows) { - if (false === $this->result) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return 0; - } - return $this->db->raiseError(null, null, null, - 'Could not get row count', __FUNCTION__); - } - return $rows; - } -} - -/** - * MDB2 MySQL statement driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Statement_mysql extends MDB2_Statement_Common -{ - // {{{ _execute() - - /** - * Execute a prepared query statement helper method. - * - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * - * @return mixed MDB2_Result or integer (affected rows) on success, - * a MDB2 error on failure - * @access private - */ - function &_execute($result_class = true, $result_wrap_class = false) - { - if (is_null($this->statement)) { - $result =& parent::_execute($result_class, $result_wrap_class); - return $result; - } - $this->db->last_query = $this->query; - $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); - if ($this->db->getOption('disable_query')) { - $result = $this->is_manip ? 0 : null; - return $result; - } - - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $query = 'EXECUTE '.$this->statement; - if (!empty($this->positions)) { - $parameters = array(); - foreach ($this->positions as $parameter) { - if (!array_key_exists($parameter, $this->values)) { - return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); - } - $value = $this->values[$parameter]; - $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; - if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) { - if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { - if ($match[1] == 'file://') { - $value = $match[2]; - } - $value = @fopen($value, 'r'); - $close = true; - } - if (is_resource($value)) { - $data = ''; - while (!@feof($value)) { - $data.= @fread($value, $this->db->options['lob_buffer_length']); - } - if ($close) { - @fclose($value); - } - $value = $data; - } - } - $quoted = $this->db->quote($value, $type); - if (PEAR::isError($quoted)) { - return $quoted; - } - $param_query = 'SET @'.$parameter.' = '.$quoted; - $result = $this->db->_doQuery($param_query, true, $connection); - if (PEAR::isError($result)) { - return $result; - } - } - $query.= ' USING @'.implode(', @', array_values($this->positions)); - } - - $result = $this->db->_doQuery($query, $this->is_manip, $connection); - if (PEAR::isError($result)) { - return $result; - } - - if ($this->is_manip) { - $affected_rows = $this->db->_affectedRows($connection, $result); - return $affected_rows; - } - - $result =& $this->db->_wrapResult($result, $this->result_types, - $result_class, $result_wrap_class, $this->limit, $this->offset); - $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ free() - - /** - * Release resources allocated for the specified prepared query. - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function free() - { - if (is_null($this->positions)) { - return $this->db->raiseError(MDB2_ERROR, null, null, - 'Prepared statement has already been freed', __FUNCTION__); - } - $result = MDB2_OK; - - if (!is_null($this->statement)) { - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $query = 'DEALLOCATE PREPARE '.$this->statement; - $result = $this->db->_doQuery($query, true, $connection); - } - - parent::free(); - return $result; - } -} -?> \ No newline at end of file + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php 302867 2010-08-29 11:22:07Z quipo $ +// + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysql extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`'); + + var $sql_comments = array( + array('start' => '-- ', 'end' => "\n", 'escape' => false), + array('start' => '#', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + var $server_capabilities_checked = false; + + var $start_transaction = false; + + var $varchar_max_length = 255; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'mysql'; + $this->dbsyntax = 'mysql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = false; + $this->supported['savepoints'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['sub_selects'] = 'emulated'; + $this->supported['triggers'] = false; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['default_table_type'] = ''; + $this->options['max_identifiers_length'] = 64; + + $this->_reCheckSupportedOptions(); + } + + // }}} + // {{{ _reCheckSupportedOptions() + + /** + * If the user changes certain options, other capabilities may depend + * on the new settings, so we need to check them (again). + * + * @access private + */ + function _reCheckSupportedOptions() + { + $this->supported['transactions'] = $this->options['use_transactions']; + $this->supported['savepoints'] = $this->options['use_transactions']; + if ($this->options['default_table_type']) { + switch (strtoupper($this->options['default_table_type'])) { + case 'BLACKHOLE': + case 'MEMORY': + case 'ARCHIVE': + case 'CSV': + case 'HEAP': + case 'ISAM': + case 'MERGE': + case 'MRG_ISAM': + case 'ISAM': + case 'MRG_MYISAM': + case 'MYISAM': + $this->supported['savepoints'] = false; + $this->supported['transactions'] = false; + $this->warnings[] = $this->options['default_table_type'] . + ' is not a supported default table type'; + break; + } + } + } + + // }}} + // {{{ function setOption($option, $value) + + /** + * set the option for the db class + * + * @param string option name + * @param mixed value for the option + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + */ + function setOption($option, $value) + { + $res = parent::setOption($option, $value); + $this->_reCheckSupportedOptions(); + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if ($this->connection) { + $native_code = @mysql_errno($this->connection); + $native_msg = @mysql_error($this->connection); + } else { + $native_code = @mysql_errno(); + $native_msg = @mysql_error(); + } + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1000 => MDB2_ERROR_INVALID, //hashchk + 1001 => MDB2_ERROR_INVALID, //isamchk + 1004 => MDB2_ERROR_CANNOT_CREATE, + 1005 => MDB2_ERROR_CANNOT_CREATE, + 1006 => MDB2_ERROR_CANNOT_CREATE, + 1007 => MDB2_ERROR_ALREADY_EXISTS, + 1008 => MDB2_ERROR_CANNOT_DROP, + 1009 => MDB2_ERROR_CANNOT_DROP, + 1010 => MDB2_ERROR_CANNOT_DROP, + 1011 => MDB2_ERROR_CANNOT_DELETE, + 1022 => MDB2_ERROR_ALREADY_EXISTS, + 1029 => MDB2_ERROR_NOT_FOUND, + 1032 => MDB2_ERROR_NOT_FOUND, + 1044 => MDB2_ERROR_ACCESS_VIOLATION, + 1045 => MDB2_ERROR_ACCESS_VIOLATION, + 1046 => MDB2_ERROR_NODBSELECTED, + 1048 => MDB2_ERROR_CONSTRAINT, + 1049 => MDB2_ERROR_NOSUCHDB, + 1050 => MDB2_ERROR_ALREADY_EXISTS, + 1051 => MDB2_ERROR_NOSUCHTABLE, + 1054 => MDB2_ERROR_NOSUCHFIELD, + 1060 => MDB2_ERROR_ALREADY_EXISTS, + 1061 => MDB2_ERROR_ALREADY_EXISTS, + 1062 => MDB2_ERROR_ALREADY_EXISTS, + 1064 => MDB2_ERROR_SYNTAX, + 1067 => MDB2_ERROR_INVALID, + 1072 => MDB2_ERROR_NOT_FOUND, + 1086 => MDB2_ERROR_ALREADY_EXISTS, + 1091 => MDB2_ERROR_NOT_FOUND, + 1100 => MDB2_ERROR_NOT_LOCKED, + 1109 => MDB2_ERROR_NOT_FOUND, + 1125 => MDB2_ERROR_ALREADY_EXISTS, + 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 1138 => MDB2_ERROR_INVALID, + 1142 => MDB2_ERROR_ACCESS_VIOLATION, + 1143 => MDB2_ERROR_ACCESS_VIOLATION, + 1146 => MDB2_ERROR_NOSUCHTABLE, + 1149 => MDB2_ERROR_SYNTAX, + 1169 => MDB2_ERROR_CONSTRAINT, + 1176 => MDB2_ERROR_NOT_FOUND, + 1177 => MDB2_ERROR_NOSUCHTABLE, + 1213 => MDB2_ERROR_DEADLOCK, + 1216 => MDB2_ERROR_CONSTRAINT, + 1217 => MDB2_ERROR_CONSTRAINT, + 1227 => MDB2_ERROR_ACCESS_VIOLATION, + 1235 => MDB2_ERROR_CANNOT_CREATE, + 1299 => MDB2_ERROR_INVALID_DATE, + 1300 => MDB2_ERROR_INVALID, + 1304 => MDB2_ERROR_ALREADY_EXISTS, + 1305 => MDB2_ERROR_NOT_FOUND, + 1306 => MDB2_ERROR_CANNOT_DROP, + 1307 => MDB2_ERROR_CANNOT_CREATE, + 1334 => MDB2_ERROR_CANNOT_ALTER, + 1339 => MDB2_ERROR_NOT_FOUND, + 1356 => MDB2_ERROR_INVALID, + 1359 => MDB2_ERROR_ALREADY_EXISTS, + 1360 => MDB2_ERROR_NOT_FOUND, + 1363 => MDB2_ERROR_NOT_FOUND, + 1365 => MDB2_ERROR_DIVZERO, + 1451 => MDB2_ERROR_CONSTRAINT, + 1452 => MDB2_ERROR_CONSTRAINT, + 1542 => MDB2_ERROR_CANNOT_DROP, + 1546 => MDB2_ERROR_CONSTRAINT, + 1582 => MDB2_ERROR_CONSTRAINT, + 2003 => MDB2_ERROR_CONNECT_FAILED, + 2019 => MDB2_ERROR_INVALID, + ); + } + if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { + $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; + $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $text = @mysql_real_escape_string($text, $connection); + return $text; + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + $this->_getServerCapabilities(); + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 0'; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $server_info = $this->getServerVersion(); + if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + return MDB2_OK; + } + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + $result = $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 1'; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 1'; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @param array some transaction options: + * 'wait' => 'WAIT' | 'NO WAIT' + * 'rw' => 'READ WRITE' | 'READ ONLY' + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation, $options = array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ _doConnect() + + /** + * do the grunt work of the connect + * + * @return connection on success or MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $persistent = false) + { + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $params = array(); + $unix = ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix'); + if (empty($this->dsn['hostspec'])) { + $this->dsn['hostspec'] = $unix ? '' : 'localhost'; + } + if ($this->dsn['hostspec']) { + $params[0] = $this->dsn['hostspec'] . ($this->dsn['port'] ? ':' . $this->dsn['port'] : ''); + } else { + $params[0] = ':' . $this->dsn['socket']; + } + $params[] = $username ? $username : null; + $params[] = $password ? $password : null; + if (!$persistent) { + if ($this->_isNewLinkSet()) { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($this->dsn['client_flags']) + ? $this->dsn['client_flags'] : null; + } + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $connection = @call_user_func_array($connect_function, $params); + if (!$connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + $err, __FUNCTION__); + } else { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + $this->disconnect(false); + return $result; + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return MDB2_OK on success, MDB2 Error Object on failure + * @access public + */ + function connect() + { + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + $connection = $this->_doConnect( + $this->dsn['username'], + $this->dsn['password'], + $this->options['persistent'] + ); + if (PEAR::isError($connection)) { + return $connection; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = ''; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + if ($this->database_name) { + if ($this->database_name != $this->connected_database_name) { + if (!@mysql_select_db($this->database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$this->database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $this->database_name; + } + } + + $this->_getServerCapabilities(); + + return MDB2_OK; + } + + // }}} + // {{{ setCharset() + + /** + * Set the charset on the current connection + * + * @param string charset (or array(charset, collation)) + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + $collation = null; + if (is_array($charset) && 2 == count($charset)) { + $collation = array_pop($charset); + $charset = array_pop($charset); + } + $client_info = mysql_get_client_info(); + if (function_exists('mysql_set_charset') && version_compare($client_info, '5.0.6')) { + if (!$result = mysql_set_charset($charset, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not set client character set', __FUNCTION__); + return $err; + } + return $result; + } + $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'"; + if (!is_null($collation)) { + $query .= " COLLATE '".mysql_real_escape_string($collation, $connection)."'"; + } + return $this->_doQuery($query, true, $connection); + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name is exists? + * + * @param string $name name of the database that should be checked + * + * @return mixed true/false on success, a MDB2 error on failure + * @access public + */ + function databaseExists($name) + { + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $result = @mysql_select_db($name, $connection); + @mysql_close($connection); + + return $result; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @param boolean $force if the disconnect should be forced even if the + * connection is opened persistently + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if ($this->in_transaction) { + $dsn = $this->dsn; + $database_name = $this->database_name; + $persistent = $this->options['persistent']; + $this->dsn = $this->connected_dsn; + $this->database_name = $this->connected_database_name; + $this->options['persistent'] = $this->opened_persistent; + $this->rollback(); + $this->dsn = $dsn; + $this->database_name = $database_name; + $this->options['persistent'] = $persistent; + } + + if (!$this->opened_persistent || $force) { + $ok = @mysql_close($this->connection); + if (!$ok) { + return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, + null, null, null, __FUNCTION__); + } + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ standaloneQuery() + + /** + * execute a query as DBA + * + * @param string $query the SQL query + * @param mixed $types array that contains the types of the columns in + * the result set + * @param boolean $is_manip if the query is a manipulation query + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function standaloneQuery($query, $types = null, $is_manip = false) + { + $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; + $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; + $connection = $this->_doConnect($user, $pass, $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $result = $this->_doQuery($query, $is_manip, $connection, $this->database_name); + if (!PEAR::isError($result)) { + $result = $this->_affectedRows($connection, $result); + } + + @mysql_close($connection); + return $result; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function _doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mysql_select_db($database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $database_name; + } + } + + $function = $this->options['result_buffering'] + ? 'mysql_query' : 'mysql_unbuffered_query'; + $result = @$function($query, $connection); + if (!$result && 0 !== mysql_errno($connection)) { + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @mysql_affected_rows($connection); + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + + // LIMIT doesn't always come last in the query + // @see http://dev.mysql.com/doc/refman/5.0/en/select.html + $after = ''; + if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query); + } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query); + } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query); + } + + if ($is_manip) { + return $query . " LIMIT $limit" . $after; + } else { + return $query . " LIMIT $offset, $limit" . $after; + } + } + return $query; + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param bool $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = @mysql_get_server_info($connection); + } + if (!$server_info) { + return $this->raiseError(null, null, null, + 'Could not get server information', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + if (isset($tmp[2]) && strpos($tmp[2], '-')) { + $tmp2 = explode('-', @$tmp[2], 2); + } else { + $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null; + $tmp2[1] = null; + } + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => $tmp2[0], + 'extra' => $tmp2[1], + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ _getServerCapabilities() + + /** + * Fetch some information about the server capabilities + * (transactions, subselects, prepared statements, etc). + * + * @access private + */ + function _getServerCapabilities() + { + if (!$this->server_capabilities_checked) { + $this->server_capabilities_checked = true; + + //set defaults + $this->supported['sub_selects'] = 'emulated'; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['triggers'] = false; + $this->start_transaction = false; + $this->varchar_max_length = 255; + + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch']; + + if (!version_compare($server_version, '4.1.0', '<')) { + $this->supported['sub_selects'] = true; + $this->supported['prepared_statements'] = true; + } + + // SAVEPOINTs were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB) + if (version_compare($server_version, '4.1.0', '>=')) { + if (version_compare($server_version, '4.1.1', '<')) { + $this->supported['savepoints'] = false; + } + } elseif (version_compare($server_version, '4.0.14', '<')) { + $this->supported['savepoints'] = false; + } + + if (!version_compare($server_version, '4.0.11', '<')) { + $this->start_transaction = true; + } + + if (!version_compare($server_version, '5.0.3', '<')) { + $this->varchar_max_length = 65532; + } + + if (!version_compare($server_version, '5.0.2', '<')) { + $this->supported['triggers'] = true; + } + } + } + } + + // }}} + // {{{ function _skipUserDefinedVariable($query, $position) + + /** + * Utility method, used by prepare() to avoid misinterpreting MySQL user + * defined variables (SELECT @x:=5) for placeholders. + * Check if the placeholder is a false positive, i.e. if it is an user defined + * variable instead. If so, skip it and advance the position, otherwise + * return the current position, which is valid + * + * @param string $query + * @param integer $position current string cursor position + * @return integer $new_position + * @access protected + */ + function _skipUserDefinedVariable($query, $position) + { + $found = strpos(strrev(substr($query, 0, $position)), '@'); + if ($found === false) { + return $position; + } + $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1; + $substring = substr($query, $pos, $position - $pos + 2); + if (preg_match('/^@\w+\s*:=$/', $substring)) { + return $position + 1; //found an user defined variable: skip it + } + return $position; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + // connect to get server capabilities (http://pear.php.net/bugs/16147) + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + if ($this->options['emulate_prepared'] + || $this->supported['prepared_statements'] !== true + ) { + return parent::prepare($query, $types, $result_types, $lobs); + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + //make sure this is not part of an user defined variable + $new_pos = $this->_skipUserDefinedVariable($query, $position); + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $positions[$p_position] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + + static $prep_statement_counter = 1; + $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand())); + $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); + $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); + $statement = $this->_doQuery($query, true, $connection); + if (PEAR::isError($statement)) { + return $statement; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the old row is deleted before the new row is inserted. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL implements it natively, this type of query is + * emulated through this method for other DBMS using standard types of + * queries inside a transaction to assure the atomicity of the operation. + * + * @access public + * + * @param string $table name of the table on which the REPLACE query will + * be executed. + * @param array $fields associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. The + * values of the array are also associative arrays that describe the + * values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value: + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: + * this property is required unless the Null property + * is set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * Boolean property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * Boolean property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @see http://dev.mysql.com/doc/refman/5.0/en/replace.html + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function replace($table, $fields) + { + $count = count($fields); + $query = $values = ''; + $keys = $colnum = 0; + for (reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if ($colnum > 0) { + $query .= ','; + $values.= ','; + } + $query.= $this->quoteIdentifier($name, true); + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $table = $this->quoteIdentifier($table, true); + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result = $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->_doQuery($query, true); + $this->popExpect(); + $this->popErrorHandling(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 + // not casting to integer to handle BIGINT http://pear.php.net/bugs/bug.php?id=17650 + return $this->queryOne('SELECT LAST_INSERT_ID()'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 MySQL result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_mysql extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @mysql_fetch_assoc($this->result); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @mysql_fetch_row($this->result); + } + + if (!$row) { + if ($this->result === false) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + return null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $rowObj = new $object_class($row); + $row = $rowObj; + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @mysql_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * @access public + */ + function numCols() + { + $cols = @mysql_num_fields($this->result); + if (is_null($cols)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + if (is_resource($this->result) && $this->db->connection) { + $free = @mysql_free_result($this->result); + if ($free === false) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 MySQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_mysql extends MDB2_Result_mysql +{ + // }}} + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mysql_num_rows($this->result); + if (false === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } + + // }}} +} + +/** + * MDB2 MySQL statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_mysql extends MDB2_Statement_Common +{ + // {{{ _execute() + + /** + * Execute a prepared query statement helper method. + * + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access private + */ + function _execute($result_class = true, $result_wrap_class = false) + { + if (is_null($this->statement)) { + $result = parent::_execute($result_class, $result_wrap_class); + return $result; + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 'EXECUTE '.$this->statement; + if (!empty($this->positions)) { + $parameters = array(); + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $close = false; + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) { + if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + $close = true; + } + if (is_resource($value)) { + $data = ''; + while (!@feof($value)) { + $data.= @fread($value, $this->db->options['lob_buffer_length']); + } + if ($close) { + @fclose($value); + } + $value = $data; + } + } + $quoted = $this->db->quote($value, $type); + if (PEAR::isError($quoted)) { + return $quoted; + } + $param_query = 'SET @'.$parameter.' = '.$quoted; + $result = $this->db->_doQuery($param_query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + } + $query.= ' USING @'.implode(', @', array_values($this->positions)); + } + + $result = $this->db->_doQuery($query, $this->is_manip, $connection); + if (PEAR::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result = $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (!is_null($this->statement)) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} +?> diff --git a/3rdparty/MDB2/Driver/pgsql.php b/3rdparty/MDB2/Driver/pgsql.php index 13fea69068..15bd280f40 100644 --- a/3rdparty/MDB2/Driver/pgsql.php +++ b/3rdparty/MDB2/Driver/pgsql.php @@ -1,1519 +1,1548 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.203 2008/11/29 14:04:46 afz Exp $ - -/** - * MDB2 PostGreSQL driver - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_Driver_pgsql extends MDB2_Driver_Common -{ - // {{{ properties - var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '\\'); - - var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); - // }}} - // {{{ constructor - - /** - * Constructor - */ - function __construct() - { - parent::__construct(); - - $this->phptype = 'pgsql'; - $this->dbsyntax = 'pgsql'; - - $this->supported['sequences'] = true; - $this->supported['indexes'] = true; - $this->supported['affected_rows'] = true; - $this->supported['summary_functions'] = true; - $this->supported['order_by_text'] = true; - $this->supported['transactions'] = true; - $this->supported['savepoints'] = true; - $this->supported['current_id'] = true; - $this->supported['limit_queries'] = true; - $this->supported['LOBs'] = true; - $this->supported['replace'] = 'emulated'; - $this->supported['sub_selects'] = true; - $this->supported['triggers'] = true; - $this->supported['auto_increment'] = 'emulated'; - $this->supported['primary_key'] = true; - $this->supported['result_introspection'] = true; - $this->supported['prepared_statements'] = true; - $this->supported['identifier_quoting'] = true; - $this->supported['pattern_escaping'] = true; - $this->supported['new_link'] = true; - - $this->options['DBA_username'] = false; - $this->options['DBA_password'] = false; - $this->options['multi_query'] = false; - $this->options['disable_smart_seqname'] = true; - $this->options['max_identifiers_length'] = 63; - } - - // }}} - // {{{ errorInfo() - - /** - * This method is used to collect information about an error - * - * @param integer $error - * @return array - * @access public - */ - function errorInfo($error = null) - { - // Fall back to MDB2_ERROR if there was no mapping. - $error_code = MDB2_ERROR; - - $native_msg = ''; - if (is_resource($error)) { - $native_msg = @pg_result_error($error); - } elseif ($this->connection) { - $native_msg = @pg_last_error($this->connection); - if (!$native_msg && @pg_connection_status($this->connection) === PGSQL_CONNECTION_BAD) { - $native_msg = 'Database connection has been lost.'; - $error_code = MDB2_ERROR_CONNECT_FAILED; - } - } else { - $native_msg = @pg_last_error(); - } - - static $error_regexps; - if (empty($error_regexps)) { - $error_regexps = array( - '/column .* (of relation .*)?does not exist/i' - => MDB2_ERROR_NOSUCHFIELD, - '/(relation|sequence|table).*does not exist|class .* not found/i' - => MDB2_ERROR_NOSUCHTABLE, - '/database .* does not exist/' - => MDB2_ERROR_NOT_FOUND, - '/constraint .* does not exist/' - => MDB2_ERROR_NOT_FOUND, - '/index .* does not exist/' - => MDB2_ERROR_NOT_FOUND, - '/database .* already exists/i' - => MDB2_ERROR_ALREADY_EXISTS, - '/relation .* already exists/i' - => MDB2_ERROR_ALREADY_EXISTS, - '/(divide|division) by zero$/i' - => MDB2_ERROR_DIVZERO, - '/pg_atoi: error in .*: can\'t parse /i' - => MDB2_ERROR_INVALID_NUMBER, - '/invalid input syntax for( type)? (integer|numeric)/i' - => MDB2_ERROR_INVALID_NUMBER, - '/value .* is out of range for type \w*int/i' - => MDB2_ERROR_INVALID_NUMBER, - '/integer out of range/i' - => MDB2_ERROR_INVALID_NUMBER, - '/value too long for type character/i' - => MDB2_ERROR_INVALID, - '/attribute .* not found|relation .* does not have attribute/i' - => MDB2_ERROR_NOSUCHFIELD, - '/column .* specified in USING clause does not exist in (left|right) table/i' - => MDB2_ERROR_NOSUCHFIELD, - '/parser: parse error at or near/i' - => MDB2_ERROR_SYNTAX, - '/syntax error at/' - => MDB2_ERROR_SYNTAX, - '/column reference .* is ambiguous/i' - => MDB2_ERROR_SYNTAX, - '/permission denied/' - => MDB2_ERROR_ACCESS_VIOLATION, - '/violates not-null constraint/' - => MDB2_ERROR_CONSTRAINT_NOT_NULL, - '/violates [\w ]+ constraint/' - => MDB2_ERROR_CONSTRAINT, - '/referential integrity violation/' - => MDB2_ERROR_CONSTRAINT, - '/more expressions than target columns/i' - => MDB2_ERROR_VALUE_COUNT_ON_ROW, - ); - } - if (is_numeric($error) && $error < 0) { - $error_code = $error; - } else { - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $native_msg)) { - $error_code = $code; - break; - } - } - } - return array($error_code, null, $native_msg); - } - - // }}} - // {{{ escape() - - /** - * Quotes a string so it can be safely used in a query. It will quote - * the text so it can safely be used within a query. - * - * @param string the input string to quote - * @param bool escape wildcards - * - * @return string quoted string - * - * @access public - */ - function escape($text, $escape_wildcards = false) - { - if ($escape_wildcards) { - $text = $this->escapePattern($text); - } - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - if (is_resource($connection) && version_compare(PHP_VERSION, '5.2.0RC5', '>=')) { - $text = @pg_escape_string($connection, $text); - } else { - $text = @pg_escape_string($text); - } - return $text; - } - - // }}} - // {{{ beginTransaction() - - /** - * Start a transaction or set a savepoint. - * - * @param string name of a savepoint to set - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function beginTransaction($savepoint = null) - { - $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!is_null($savepoint)) { - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'savepoint cannot be released when changes are auto committed', __FUNCTION__); - } - $query = 'SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } elseif ($this->in_transaction) { - return MDB2_OK; //nothing to do - } - if (!$this->destructor_registered && $this->opened_persistent) { - $this->destructor_registered = true; - register_shutdown_function('MDB2_closeOpenTransactions'); - } - $result =& $this->_doQuery('BEGIN', true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = true; - return MDB2_OK; - } - - // }}} - // {{{ commit() - - /** - * Commit the database changes done during a transaction that is in - * progress or release a savepoint. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after committing the pending changes. - * - * @param string name of a savepoint to release - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function commit($savepoint = null) - { - $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - $query = 'RELEASE SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } - - $result =& $this->_doQuery('COMMIT', true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ rollback() - - /** - * Cancel any database changes done during a transaction or since a specific - * savepoint that is in progress. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after canceling the pending changes. - * - * @param string name of a savepoint to rollback to - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function rollback($savepoint = null) - { - $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'rollback cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } - - $query = 'ROLLBACK'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ function setTransactionIsolation() - - /** - * Set the transacton isolation level. - * - * @param string standard isolation level - * READ UNCOMMITTED (allows dirty reads) - * READ COMMITTED (prevents dirty reads) - * REPEATABLE READ (prevents nonrepeatable reads) - * SERIALIZABLE (prevents phantom reads) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - static function setTransactionIsolation($isolation, $options = array()) - { - $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); - switch ($isolation) { - case 'READ UNCOMMITTED': - case 'READ COMMITTED': - case 'REPEATABLE READ': - case 'SERIALIZABLE': - break; - default: - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'isolation level is not supported: '.$isolation, __FUNCTION__); - } - - $query = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL $isolation"; - return $this->_doQuery($query, true); - } - - // }}} - // {{{ _doConnect() - - /** - * Do the grunt work of connecting to the database - * - * @return mixed connection resource on success, MDB2 Error Object on failure - * @access protected - */ - function _doConnect($username, $password, $database_name, $persistent = false) - { - if (!PEAR::loadExtension($this->phptype)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); - } - - if ($database_name == '') { - $database_name = 'template1'; - } - - $protocol = $this->dsn['protocol'] ? $this->dsn['protocol'] : 'tcp'; - - $params = array(''); - if ($protocol == 'tcp') { - if ($this->dsn['hostspec']) { - $params[0].= 'host=' . $this->dsn['hostspec']; - } - if ($this->dsn['port']) { - $params[0].= ' port=' . $this->dsn['port']; - } - } elseif ($protocol == 'unix') { - // Allow for pg socket in non-standard locations. - if ($this->dsn['socket']) { - $params[0].= 'host=' . $this->dsn['socket']; - } - if ($this->dsn['port']) { - $params[0].= ' port=' . $this->dsn['port']; - } - } - if ($database_name) { - $params[0].= ' dbname=\'' . addslashes($database_name) . '\''; - } - if ($username) { - $params[0].= ' user=\'' . addslashes($username) . '\''; - } - if ($password) { - $params[0].= ' password=\'' . addslashes($password) . '\''; - } - if (!empty($this->dsn['options'])) { - $params[0].= ' options=' . $this->dsn['options']; - } - if (!empty($this->dsn['tty'])) { - $params[0].= ' tty=' . $this->dsn['tty']; - } - if (!empty($this->dsn['connect_timeout'])) { - $params[0].= ' connect_timeout=' . $this->dsn['connect_timeout']; - } - if (!empty($this->dsn['sslmode'])) { - $params[0].= ' sslmode=' . $this->dsn['sslmode']; - } - if (!empty($this->dsn['service'])) { - $params[0].= ' service=' . $this->dsn['service']; - } - - if ($this->_isNewLinkSet()) { - if (version_compare(phpversion(), '4.3.0', '>=')) { - $params[] = PGSQL_CONNECT_FORCE_NEW; - } - } - - $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; - $connection = @call_user_func_array($connect_function, $params); - if (!$connection) { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - 'unable to establish a connection', __FUNCTION__); - } - - if (empty($this->dsn['disable_iso_date'])) { - if (!@pg_query($connection, "SET SESSION DATESTYLE = 'ISO'")) { - return $this->raiseError(null, null, null, - 'Unable to set date style to iso', __FUNCTION__); - } - } - - if (!empty($this->dsn['charset'])) { - $result = $this->setCharset($this->dsn['charset'], $connection); - if (PEAR::isError($result)) { - return $result; - } - } - - return $connection; - } - - // }}} - // {{{ connect() - - /** - * Connect to the database - * - * @return true on success, MDB2 Error Object on failure - * @access public - */ - function connect() - { - if (is_resource($this->connection)) { - //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 - if (MDB2::areEquals($this->connected_dsn, $this->dsn) - && $this->connected_database_name == $this->database_name - && ($this->opened_persistent == $this->options['persistent']) - ) { - return MDB2_OK; - } - $this->disconnect(false); - } - - if ($this->database_name) { - $connection = $this->_doConnect($this->dsn['username'], - $this->dsn['password'], - $this->database_name, - $this->options['persistent']); - if (PEAR::isError($connection)) { - return $connection; - } - - $this->connection = $connection; - $this->connected_dsn = $this->dsn; - $this->connected_database_name = $this->database_name; - $this->opened_persistent = $this->options['persistent']; - $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; - } - - return MDB2_OK; - } - - // }}} - // {{{ setCharset() - - /** - * Set the charset on the current connection - * - * @param string charset - * @param resource connection handle - * - * @return true on success, MDB2 Error Object on failure - */ - function setCharset($charset, $connection = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - if (is_array($charset)) { - $charset = array_shift($charset); - $this->warnings[] = 'postgresql does not support setting client collation'; - } - $result = @pg_set_client_encoding($connection, $charset); - if ($result == -1) { - return $this->raiseError(null, null, null, - 'Unable to set client charset: '.$charset, __FUNCTION__); - } - return MDB2_OK; - } - - // }}} - // {{{ databaseExists() - - /** - * check if given database name is exists? - * - * @param string $name name of the database that should be checked - * - * @return mixed true/false on success, a MDB2 error on failure - * @access public - */ - function databaseExists($name) - { - $res = $this->_doConnect($this->dsn['username'], - $this->dsn['password'], - $this->escape($name), - $this->options['persistent']); - if (!PEAR::isError($res)) { - return true; - } - - return false; - } - - // }}} - // {{{ disconnect() - - /** - * Log out and disconnect from the database. - * - * @param boolean $force if the disconnect should be forced even if the - * connection is opened persistently - * @return mixed true on success, false if not connected and error - * object on error - * @access public - */ - function disconnect($force = true) - { - if (is_resource($this->connection)) { - if ($this->in_transaction) { - $dsn = $this->dsn; - $database_name = $this->database_name; - $persistent = $this->options['persistent']; - $this->dsn = $this->connected_dsn; - $this->database_name = $this->connected_database_name; - $this->options['persistent'] = $this->opened_persistent; - $this->rollback(); - $this->dsn = $dsn; - $this->database_name = $database_name; - $this->options['persistent'] = $persistent; - } - - if (!$this->opened_persistent || $force) { - $ok = @pg_close($this->connection); - if (!$ok) { - return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, - null, null, null, __FUNCTION__); - } - } - } else { - return false; - } - return parent::disconnect($force); - } - - // }}} - // {{{ standaloneQuery() - - /** - * execute a query as DBA - * - * @param string $query the SQL query - * @param mixed $types array that contains the types of the columns in - * the result set - * @param boolean $is_manip if the query is a manipulation query - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &standaloneQuery($query, $types = null, $is_manip = false) - { - $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; - $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; - $connection = $this->_doConnect($user, $pass, $this->database_name, $this->options['persistent']); - if (PEAR::isError($connection)) { - return $connection; - } - - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - - $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name); - if (!PEAR::isError($result)) { - if ($is_manip) { - $result = $this->_affectedRows($connection, $result); - } else { - $result =& $this->_wrapResult($result, $types, true, false, $limit, $offset); - } - } - - @pg_close($connection); - return $result; - } - - // }}} - // {{{ _doQuery() - - /** - * Execute a query - * @param string $query query - * @param boolean $is_manip if the query is a manipulation query - * @param resource $connection - * @param string $database_name - * @return result or error object - * @access protected - */ - function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) - { - $this->last_query = $query; - $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - if ($this->options['disable_query']) { - $result = $is_manip ? 0 : null; - return $result; - } - - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - - $function = $this->options['multi_query'] ? 'pg_send_query' : 'pg_query'; - $result = @$function($connection, $query); - if (!$result) { - $err =& $this->raiseError(null, null, null, - 'Could not execute statement', __FUNCTION__); - return $err; - } elseif ($this->options['multi_query']) { - if (!($result = @pg_get_result($connection))) { - $err =& $this->raiseError(null, null, null, - 'Could not get the first result from a multi query', __FUNCTION__); - return $err; - } - } - - $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ _affectedRows() - - /** - * Returns the number of rows affected - * - * @param resource $result - * @param resource $connection - * @return mixed MDB2 Error Object or the number of rows affected - * @access private - */ - function _affectedRows($connection, $result = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - return @pg_affected_rows($result); - } - - // }}} - // {{{ _modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * @param string $query query to modify - * @param boolean $is_manip if it is a DML query - * @param integer $limit limit the number of rows - * @param integer $offset start reading from given offset - * @return string modified query - * @access protected - */ - function _modifyQuery($query, $is_manip, $limit, $offset) - { - if ($limit > 0 - && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) - ) { - $query = rtrim($query); - if (substr($query, -1) == ';') { - $query = substr($query, 0, -1); - } - if ($is_manip) { - $query = $this->_modifyManipQuery($query, $limit); - } else { - $query.= " LIMIT $limit OFFSET $offset"; - } - } - return $query; - } - - // }}} - // {{{ _modifyManipQuery() - - /** - * Changes a manip query string for various DBMS specific reasons - * - * @param string $query query to modify - * @param integer $limit limit the number of rows - * @return string modified query - * @access protected - */ - function _modifyManipQuery($query, $limit) - { - $pos = strpos(strtolower($query), 'where'); - $where = $pos ? substr($query, $pos) : ''; - - $manip_clause = '(\bDELETE\b\s+(?:\*\s+)?\bFROM\b|\bUPDATE\b)'; - $from_clause = '([\w\.]+)'; - $where_clause = '(?:(.*)\bWHERE\b\s+(.*))|(.*)'; - $pattern = '/^'. $manip_clause . '\s+' . $from_clause .'(?:\s)*(?:'. $where_clause .')?$/i'; - $matches = preg_match($pattern, $query, $match); - if ($matches) { - $manip = $match[1]; - $from = $match[2]; - $what = (count($matches) == 6) ? $match[5] : $match[3]; - return $manip.' '.$from.' '.$what.' WHERE ctid=(SELECT ctid FROM '.$from.' '.$where.' LIMIT '.$limit.')'; - } - //return error? - return $query; - } - - // }}} - // {{{ getServerVersion() - - /** - * return version information about the server - * - * @param bool $native determines if the raw version string should be returned - * @return mixed array/string with version information or MDB2 error object - * @access public - */ - function getServerVersion($native = false) - { - $query = 'SHOW SERVER_VERSION'; - if ($this->connected_server_info) { - $server_info = $this->connected_server_info; - } else { - $server_info = $this->queryOne($query, 'text'); - if (PEAR::isError($server_info)) { - return $server_info; - } - } - // cache server_info - $this->connected_server_info = $server_info; - if (!$native && !PEAR::isError($server_info)) { - $tmp = explode('.', $server_info, 3); - if (empty($tmp[2]) - && isset($tmp[1]) - && preg_match('/(\d+)(.*)/', $tmp[1], $tmp2) - ) { - $server_info = array( - 'major' => $tmp[0], - 'minor' => $tmp2[1], - 'patch' => null, - 'extra' => $tmp2[2], - 'native' => $server_info, - ); - } else { - $server_info = array( - 'major' => isset($tmp[0]) ? $tmp[0] : null, - 'minor' => isset($tmp[1]) ? $tmp[1] : null, - 'patch' => isset($tmp[2]) ? $tmp[2] : null, - 'extra' => null, - 'native' => $server_info, - ); - } - } - return $server_info; - } - - // }}} - // {{{ prepare() - - /** - * Prepares a query for multiple execution with execute(). - * With some database backends, this is emulated. - * prepare() requires a generic query as string like - * 'INSERT INTO numbers VALUES(?,?)' or - * 'INSERT INTO numbers VALUES(:foo,:bar)'. - * The ? and :name and are placeholders which can be set using - * bindParam() and the query can be sent off using the execute() method. - * The allowed format for :name can be set with the 'bindname_format' option. - * - * @param string $query the query to prepare - * @param mixed $types array that contains the types of the placeholders - * @param mixed $result_types array that contains the types of the columns in - * the result set or MDB2_PREPARE_RESULT, if set to - * MDB2_PREPARE_MANIP the query is handled as a manipulation query - * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders - * @return mixed resource handle for the prepared query on success, a MDB2 - * error on failure - * @access public - * @see bindParam, execute - */ - function &prepare($query, $types = null, $result_types = null, $lobs = array()) - { - if ($this->options['emulate_prepared']) { - $obj =& parent::prepare($query, $types, $result_types, $lobs); - return $obj; - } - $is_manip = ($result_types === MDB2_PREPARE_MANIP); - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - $pgtypes = function_exists('pg_prepare') ? false : array(); - if ($pgtypes !== false && !empty($types)) { - $this->loadModule('Datatype', null, true); - } - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - $placeholder_type_guess = $placeholder_type = null; - $question = '?'; - $colon = ':'; - $positions = array(); - $position = $parameter = 0; - while ($position < strlen($query)) { - $q_position = strpos($query, $question, $position); - $c_position = strpos($query, $colon, $position); - //skip "::type" cast ("select id::varchar(20) from sometable where name=?") - $doublecolon_position = strpos($query, '::', $position); - if ($doublecolon_position !== false && $doublecolon_position == $c_position) { - $c_position = strpos($query, $colon, $position+2); - } - if ($q_position && $c_position) { - $p_position = min($q_position, $c_position); - } elseif ($q_position) { - $p_position = $q_position; - } elseif ($c_position) { - $p_position = $c_position; - } else { - break; - } - if (is_null($placeholder_type)) { - $placeholder_type_guess = $query[$p_position]; - } - - $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); - if (PEAR::isError($new_pos)) { - return $new_pos; - } - if ($new_pos != $position) { - $position = $new_pos; - continue; //evaluate again starting from the new position - } - - if ($query[$position] == $placeholder_type_guess) { - if (is_null($placeholder_type)) { - $placeholder_type = $query[$p_position]; - $question = $colon = $placeholder_type; - if (!empty($types) && is_array($types)) { - if ($placeholder_type == ':') { - } else { - $types = array_values($types); - } - } - } - if ($placeholder_type_guess == '?') { - $length = 1; - $name = $parameter; - } else { - $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; - $param = preg_replace($regexp, '\\1', $query); - if ($param === '') { - $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'named parameter name must match "bindname_format" option', __FUNCTION__); - return $err; - } - $length = strlen($param) + 1; - $name = $param; - } - if ($pgtypes !== false) { - if (is_array($types) && array_key_exists($name, $types)) { - $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$name]); - } elseif (is_array($types) && array_key_exists($parameter, $types)) { - $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$parameter]); - } else { - $pgtypes[] = 'text'; - } - } - if (($key_parameter = array_search($name, $positions))) { - $next_parameter = 1; - foreach ($positions as $key => $value) { - if ($key_parameter == $key) { - break; - } - ++$next_parameter; - } - } else { - ++$parameter; - $next_parameter = $parameter; - $positions[] = $name; - } - $query = substr_replace($query, '$'.$parameter, $position, $length); - $position = $p_position + strlen($parameter); - } else { - $position = $p_position; - } - } - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - static $prep_statement_counter = 1; - $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand())); - $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); - if ($pgtypes === false) { - $result = @pg_prepare($connection, $statement_name, $query); - if (!$result) { - $err =& $this->raiseError(null, null, null, - 'Unable to create prepared statement handle', __FUNCTION__); - return $err; - } - } else { - $types_string = ''; - if ($pgtypes) { - $types_string = ' ('.implode(', ', $pgtypes).') '; - } - $query = 'PREPARE '.$statement_name.$types_string.' AS '.$query; - $statement =& $this->_doQuery($query, true, $connection); - if (PEAR::isError($statement)) { - return $statement; - } - } - - $class_name = 'MDB2_Statement_'.$this->phptype; - $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); - $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); - return $obj; - } - - // }}} - // {{{ function getSequenceName($sqn) - - /** - * adds sequence name formatting to a sequence name - * - * @param string name of the sequence - * - * @return string formatted sequence name - * - * @access public - */ - function getSequenceName($sqn) - { - if (false === $this->options['disable_smart_seqname']) { - if (strpos($sqn, '_') !== false) { - list($table, $field) = explode('_', $sqn, 2); - } - $schema_list = $this->queryOne("SELECT array_to_string(current_schemas(false), ',')"); - if (PEAR::isError($schema_list) || empty($schema_list) || count($schema_list) < 2) { - $order_by = ' a.attnum'; - $schema_clause = ' AND n.nspname=current_schema()'; - } else { - $schemas = explode(',', $schema_list); - $schema_clause = ' AND n.nspname IN ('.$schema_list.')'; - $counter = 1; - $order_by = ' CASE '; - foreach ($schemas as $schema) { - $order_by .= ' WHEN n.nspname='.$schema.' THEN '.$counter++; - } - $order_by .= ' ELSE '.$counter.' END, a.attnum'; - } - - $query = "SELECT substring((SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) - FROM pg_attrdef d - WHERE d.adrelid = a.attrelid - AND d.adnum = a.attnum - AND a.atthasdef - ) FROM 'nextval[^'']*''([^'']*)') - FROM pg_attribute a - LEFT JOIN pg_class c ON c.oid = a.attrelid - LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef - LEFT JOIN pg_namespace n ON c.relnamespace = n.oid - WHERE (c.relname = ".$this->quote($sqn, 'text'); - if (!empty($field)) { - $query .= " OR (c.relname = ".$this->quote($table, 'text')." AND a.attname = ".$this->quote($field, 'text').")"; - } - $query .= " )" - .$schema_clause." - AND NOT a.attisdropped - AND a.attnum > 0 - AND pg_get_expr(d.adbin, d.adrelid) LIKE 'nextval%' - ORDER BY ".$order_by; - $seqname = $this->queryOne($query); - if (!PEAR::isError($seqname) && !empty($seqname) && is_string($seqname)) { - return $seqname; - } - } - - return parent::getSequenceName($sqn); - } - - // }}} - // {{{ nextID() - - /** - * Returns the next free id of a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true the sequence is - * automatic created, if it - * not exists - * @return mixed MDB2 Error Object or id - * @access public - */ - function nextID($seq_name, $ondemand = true) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $query = "SELECT NEXTVAL('$sequence_name')"; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $this->expectError(MDB2_ERROR_NOSUCHTABLE); - $result = $this->queryOne($query, 'integer'); - $this->popExpect(); - $this->popErrorHandling(); - if (PEAR::isError($result)) { - if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { - $this->loadModule('Manager', null, true); - $result = $this->manager->createSequence($seq_name); - if (PEAR::isError($result)) { - return $this->raiseError($result, null, null, - 'on demand sequence could not be created', __FUNCTION__); - } - return $this->nextId($seq_name, false); - } - } - return $result; - } - - // }}} - // {{{ lastInsertID() - - /** - * Returns the autoincrement ID if supported or $id or fetches the current - * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) - * - * @param string $table name of the table into which a new row was inserted - * @param string $field name of the field into which a new row was inserted - * @return mixed MDB2 Error Object or id - * @access public - */ - function lastInsertID($table = null, $field = null) - { - if (empty($table) && empty($field)) { - return $this->queryOne('SELECT lastval()', 'integer'); - } - $seq = $table.(empty($field) ? '' : '_'.$field); - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq), true); - return $this->queryOne("SELECT currval('$sequence_name')", 'integer'); - } - - // }}} - // {{{ currID() - - /** - * Returns the current id of a sequence - * - * @param string $seq_name name of the sequence - * @return mixed MDB2 Error Object or id - * @access public - */ - function currID($seq_name) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - return $this->queryOne("SELECT last_value FROM $sequence_name", 'integer'); - } -} - -/** - * MDB2 PostGreSQL result driver - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_Result_pgsql extends MDB2_Result_Common -{ - // }}} - // {{{ fetchRow() - - /** - * Fetch a row and insert the data into an existing array. - * - * @param int $fetchmode how the array data should be indexed - * @param int $rownum number of the row where the data can be found - * @return int data array on success, a MDB2 error on failure - * @access public - */ - function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) - { - if (!is_null($rownum)) { - $seek = $this->seek($rownum); - if (PEAR::isError($seek)) { - return $seek; - } - } - if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { - $fetchmode = $this->db->fetchmode; - } - if ($fetchmode & MDB2_FETCHMODE_ASSOC) { - $row = @pg_fetch_array($this->result, null, PGSQL_ASSOC); - if (is_array($row) - && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE - ) { - $row = array_change_key_case($row, $this->db->options['field_case']); - } - } else { - $row = @pg_fetch_row($this->result); - } - if (!$row) { - if ($this->result === false) { - $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - return $err; - } - $null = null; - return $null; - } - $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; - $rtrim = false; - if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { - if (empty($this->types)) { - $mode += MDB2_PORTABILITY_RTRIM; - } else { - $rtrim = true; - } - } - if ($mode) { - $this->db->_fixResultArrayValues($row, $mode); - } - if (!empty($this->types)) { - $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); - } - if (!empty($this->values)) { - $this->_assignBindColumns($row); - } - if ($fetchmode === MDB2_FETCHMODE_OBJECT) { - $object_class = $this->db->options['fetch_class']; - if ($object_class == 'stdClass') { - $row = (object) $row; - } else { - $row = new $object_class($row); - } - } - ++$this->rownum; - return $row; - } - - // }}} - // {{{ _getColumnNames() - - /** - * Retrieve the names of columns returned by the DBMS in a query result. - * - * @return mixed Array variable that holds the names of columns as keys - * or an MDB2 error on failure. - * Some DBMS may not return any columns when the result set - * does not contain any rows. - * @access private - */ - function _getColumnNames() - { - $columns = array(); - $numcols = $this->numCols(); - if (PEAR::isError($numcols)) { - return $numcols; - } - for ($column = 0; $column < $numcols; $column++) { - $column_name = @pg_field_name($this->result, $column); - $columns[$column_name] = $column; - } - if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $columns = array_change_key_case($columns, $this->db->options['field_case']); - } - return $columns; - } - - // }}} - // {{{ numCols() - - /** - * Count the number of columns returned by the DBMS in a query result. - * - * @access public - * @return mixed integer value with the number of columns, a MDB2 error - * on failure - */ - function numCols() - { - $cols = @pg_num_fields($this->result); - if (is_null($cols)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return count($this->types); - } - return $this->db->raiseError(null, null, null, - 'Could not get column count', __FUNCTION__); - } - return $cols; - } - - // }}} - // {{{ nextResult() - - /** - * Move the internal result pointer to the next available result - * - * @return true on success, false if there is no more result set or an error object on failure - * @access public - */ - function nextResult() - { - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - if (!($this->result = @pg_get_result($connection))) { - return false; - } - return MDB2_OK; - } - - // }}} - // {{{ free() - - /** - * Free the internal resources associated with result. - * - * @return boolean true on success, false if result is invalid - * @access public - */ - function free() - { - if (is_resource($this->result) && $this->db->connection) { - $free = @pg_free_result($this->result); - if ($free === false) { - return $this->db->raiseError(null, null, null, - 'Could not free result', __FUNCTION__); - } - } - $this->result = false; - return MDB2_OK; - } -} - -/** - * MDB2 PostGreSQL buffered result driver - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_BufferedResult_pgsql extends MDB2_Result_pgsql -{ - // {{{ seek() - - /** - * Seek to a specific row in a result set - * - * @param int $rownum number of the row where the data can be found - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function seek($rownum = 0) - { - if ($this->rownum != ($rownum - 1) && !@pg_result_seek($this->result, $rownum)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return MDB2_OK; - } - return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, - 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); - } - $this->rownum = $rownum - 1; - return MDB2_OK; - } - - // }}} - // {{{ valid() - - /** - * Check if the end of the result set has been reached - * - * @return mixed true or false on sucess, a MDB2 error on failure - * @access public - */ - function valid() - { - $numrows = $this->numRows(); - if (PEAR::isError($numrows)) { - return $numrows; - } - return $this->rownum < ($numrows - 1); - } - - // }}} - // {{{ numRows() - - /** - * Returns the number of rows in a result object - * - * @return mixed MDB2 Error Object or the number of rows - * @access public - */ - function numRows() - { - $rows = @pg_num_rows($this->result); - if (is_null($rows)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return 0; - } - return $this->db->raiseError(null, null, null, - 'Could not get row count', __FUNCTION__); - } - return $rows; - } -} - -/** - * MDB2 PostGreSQL statement driver - * - * @package MDB2 - * @category Database - * @author Paul Cooper - */ -class MDB2_Statement_pgsql extends MDB2_Statement_Common -{ - // {{{ _execute() - - /** - * Execute a prepared query statement helper method. - * - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * - * @return mixed MDB2_Result or integer (affected rows) on success, - * a MDB2 error on failure - * @access private - */ - function &_execute($result_class = true, $result_wrap_class = false) - { - if (is_null($this->statement)) { - $result =& parent::_execute($result_class, $result_wrap_class); - return $result; - } - $this->db->last_query = $this->query; - $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); - if ($this->db->getOption('disable_query')) { - $result = $this->is_manip ? 0 : null; - return $result; - } - - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $query = false; - $parameters = array(); - // todo: disabled until pg_execute() bytea issues are cleared up - if (true || !function_exists('pg_execute')) { - $query = 'EXECUTE '.$this->statement; - } - if (!empty($this->positions)) { - foreach ($this->positions as $parameter) { - if (!array_key_exists($parameter, $this->values)) { - return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); - } - $value = $this->values[$parameter]; - $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; - if (is_resource($value) || $type == 'clob' || $type == 'blob' || $this->db->options['lob_allow_url_include']) { - if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { - if ($match[1] == 'file://') { - $value = $match[2]; - } - $value = @fopen($value, 'r'); - $close = true; - } - if (is_resource($value)) { - $data = ''; - while (!@feof($value)) { - $data.= @fread($value, $this->db->options['lob_buffer_length']); - } - if ($close) { - @fclose($value); - } - $value = $data; - } - } - $quoted = $this->db->quote($value, $type, $query); - if (PEAR::isError($quoted)) { - return $quoted; - } - $parameters[] = $quoted; - } - if ($query) { - $query.= ' ('.implode(', ', $parameters).')'; - } - } - - if (!$query) { - $result = @pg_execute($connection, $this->statement, $parameters); - if (!$result) { - $err =& $this->db->raiseError(null, null, null, - 'Unable to execute statement', __FUNCTION__); - return $err; - } - } else { - $result = $this->db->_doQuery($query, $this->is_manip, $connection); - if (PEAR::isError($result)) { - return $result; - } - } - - if ($this->is_manip) { - $affected_rows = $this->db->_affectedRows($connection, $result); - return $affected_rows; - } - - $result =& $this->db->_wrapResult($result, $this->result_types, - $result_class, $result_wrap_class, $this->limit, $this->offset); - $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ free() - - /** - * Release resources allocated for the specified prepared query. - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function free() - { - if (is_null($this->positions)) { - return $this->db->raiseError(MDB2_ERROR, null, null, - 'Prepared statement has already been freed', __FUNCTION__); - } - $result = MDB2_OK; - - if (!is_null($this->statement)) { - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $query = 'DEALLOCATE PREPARE '.$this->statement; - $result = $this->db->_doQuery($query, true, $connection); - } - - parent::free(); - return $result; - } -} + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php 295587 2010-02-28 17:16:38Z quipo $ + +/** + * MDB2 PostGreSQL driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_pgsql extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'pgsql'; + $this->dbsyntax = 'pgsql'; + + $this->supported['sequences'] = true; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = true; + $this->supported['current_id'] = true; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = 'emulated'; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = true; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['multi_query'] = false; + $this->options['disable_smart_seqname'] = true; + $this->options['max_identifiers_length'] = 63; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + // Fall back to MDB2_ERROR if there was no mapping. + $error_code = MDB2_ERROR; + + $native_msg = ''; + if (is_resource($error)) { + $native_msg = @pg_result_error($error); + } elseif ($this->connection) { + $native_msg = @pg_last_error($this->connection); + if (!$native_msg && @pg_connection_status($this->connection) === PGSQL_CONNECTION_BAD) { + $native_msg = 'Database connection has been lost.'; + $error_code = MDB2_ERROR_CONNECT_FAILED; + } + } else { + $native_msg = @pg_last_error(); + } + + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/column .* (of relation .*)?does not exist/i' + => MDB2_ERROR_NOSUCHFIELD, + '/(relation|sequence|table).*does not exist|class .* not found/i' + => MDB2_ERROR_NOSUCHTABLE, + '/database .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/constraint .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/index .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/database .* already exists/i' + => MDB2_ERROR_ALREADY_EXISTS, + '/relation .* already exists/i' + => MDB2_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => MDB2_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => MDB2_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => MDB2_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => MDB2_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => MDB2_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => MDB2_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => MDB2_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => MDB2_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => MDB2_ERROR_SYNTAX, + '/syntax error at/' + => MDB2_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => MDB2_ERROR_SYNTAX, + '/permission denied/' + => MDB2_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => MDB2_ERROR_CONSTRAINT, + '/referential integrity violation/' + => MDB2_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => MDB2_ERROR_VALUE_COUNT_ON_ROW, + ); + } + if (is_numeric($error) && $error < 0) { + $error_code = $error; + } else { + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg)) { + $error_code = $code; + break; + } + } + } + return array($error_code, null, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if (is_resource($connection) && version_compare(PHP_VERSION, '5.2.0RC5', '>=')) { + $text = @pg_escape_string($connection, $text); + } else { + $text = @pg_escape_string($text); + } + return $text; + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (null !== $savepoint) { + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + if ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $result = $this->_doQuery('BEGIN', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (null !== $savepoint) { + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $result = $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (null !== $savepoint) { + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @param array some transaction options: + * 'wait' => 'WAIT' | 'NO WAIT' + * 'rw' => 'READ WRITE' | 'READ ONLY' + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation, $options = array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ _doConnect() + + /** + * Do the grunt work of connecting to the database + * + * @return mixed connection resource on success, MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $database_name, $persistent = false) + { + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + if ($database_name == '') { + $database_name = 'template1'; + } + + $protocol = $this->dsn['protocol'] ? $this->dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($this->dsn['hostspec']) { + $params[0].= 'host=' . $this->dsn['hostspec']; + } + if ($this->dsn['port']) { + $params[0].= ' port=' . $this->dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($this->dsn['socket']) { + $params[0].= 'host=' . $this->dsn['socket']; + } + if ($this->dsn['port']) { + $params[0].= ' port=' . $this->dsn['port']; + } + } + if ($database_name) { + $params[0].= ' dbname=\'' . addslashes($database_name) . '\''; + } + if ($username) { + $params[0].= ' user=\'' . addslashes($username) . '\''; + } + if ($password) { + $params[0].= ' password=\'' . addslashes($password) . '\''; + } + if (!empty($this->dsn['options'])) { + $params[0].= ' options=' . $this->dsn['options']; + } + if (!empty($this->dsn['tty'])) { + $params[0].= ' tty=' . $this->dsn['tty']; + } + if (!empty($this->dsn['connect_timeout'])) { + $params[0].= ' connect_timeout=' . $this->dsn['connect_timeout']; + } + if (!empty($this->dsn['sslmode'])) { + $params[0].= ' sslmode=' . $this->dsn['sslmode']; + } + if (!empty($this->dsn['service'])) { + $params[0].= ' service=' . $this->dsn['service']; + } + + if ($this->_isNewLinkSet()) { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + $connection = @call_user_func_array($connect_function, $params); + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if (empty($this->dsn['disable_iso_date'])) { + if (!@pg_query($connection, "SET SESSION DATESTYLE = 'ISO'")) { + return $this->raiseError(null, null, null, + 'Unable to set date style to iso', __FUNCTION__); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + return $result; + } + } + + // Enable extra compatibility settings on 8.2 and later + if (function_exists('pg_parameter_status')) { + $version = pg_parameter_status($connection, 'server_version'); + if ($version == false) { + return $this->raiseError(null, null, null, + 'Unable to retrieve server version', __FUNCTION__); + } + $version = explode ('.', $version); + if ( $version['0'] > 8 + || ($version['0'] == 8 && $version['1'] >= 2) + ) { + if (!@pg_query($connection, "SET SESSION STANDARD_CONFORMING_STRINGS = OFF")) { + return $this->raiseError(null, null, null, + 'Unable to set standard_conforming_strings to off', __FUNCTION__); + } + + if (!@pg_query($connection, "SET SESSION ESCAPE_STRING_WARNING = OFF")) { + return $this->raiseError(null, null, null, + 'Unable to set escape_string_warning to off', __FUNCTION__); + } + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + * @access public + */ + function connect() + { + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->connected_database_name == $this->database_name + && ($this->opened_persistent == $this->options['persistent']) + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if ($this->database_name) { + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->database_name, + $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $this->database_name; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + } + + return MDB2_OK; + } + + // }}} + // {{{ setCharset() + + /** + * Set the charset on the current connection + * + * @param string charset + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_array($charset)) { + $charset = array_shift($charset); + $this->warnings[] = 'postgresql does not support setting client collation'; + } + $result = @pg_set_client_encoding($connection, $charset); + if ($result == -1) { + return $this->raiseError(null, null, null, + 'Unable to set client charset: '.$charset, __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name is exists? + * + * @param string $name name of the database that should be checked + * + * @return mixed true/false on success, a MDB2 error on failure + * @access public + */ + function databaseExists($name) + { + $res = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->escape($name), + $this->options['persistent']); + if (!PEAR::isError($res)) { + return true; + } + + return false; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @param boolean $force if the disconnect should be forced even if the + * connection is opened persistently + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if ($this->in_transaction) { + $dsn = $this->dsn; + $database_name = $this->database_name; + $persistent = $this->options['persistent']; + $this->dsn = $this->connected_dsn; + $this->database_name = $this->connected_database_name; + $this->options['persistent'] = $this->opened_persistent; + $this->rollback(); + $this->dsn = $dsn; + $this->database_name = $database_name; + $this->options['persistent'] = $persistent; + } + + if (!$this->opened_persistent || $force) { + $ok = @pg_close($this->connection); + if (!$ok) { + return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, + null, null, null, __FUNCTION__); + } + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ standaloneQuery() + + /** + * execute a query as DBA + * + * @param string $query the SQL query + * @param mixed $types array that contains the types of the columns in + * the result set + * @param boolean $is_manip if the query is a manipulation query + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function standaloneQuery($query, $types = null, $is_manip = false) + { + $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; + $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; + $connection = $this->_doConnect($user, $pass, $this->database_name, $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $result = $this->_doQuery($query, $is_manip, $connection, $this->database_name); + if (!PEAR::isError($result)) { + if ($is_manip) { + $result = $this->_affectedRows($connection, $result); + } else { + $result =& $this->_wrapResult($result, $types, true, false, $limit, $offset); + } + } + + @pg_close($connection); + return $result; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function _doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $function = $this->options['multi_query'] ? 'pg_send_query' : 'pg_query'; + $result = @$function($connection, $query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } elseif ($this->options['multi_query']) { + if (!($result = @pg_get_result($connection))) { + $err = $this->raiseError(null, null, null, + 'Could not get the first result from a multi query', __FUNCTION__); + return $err; + } + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @pg_affected_rows($result); + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + if ($is_manip) { + $query = $this->_modifyManipQuery($query, $limit); + } else { + $query.= " LIMIT $limit OFFSET $offset"; + } + } + return $query; + } + + // }}} + // {{{ _modifyManipQuery() + + /** + * Changes a manip query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param integer $limit limit the number of rows + * @return string modified query + * @access protected + */ + function _modifyManipQuery($query, $limit) + { + $pos = strpos(strtolower($query), 'where'); + $where = $pos ? substr($query, $pos) : ''; + + $manip_clause = '(\bDELETE\b\s+(?:\*\s+)?\bFROM\b|\bUPDATE\b)'; + $from_clause = '([\w\.]+)'; + $where_clause = '(?:(.*)\bWHERE\b\s+(.*))|(.*)'; + $pattern = '/^'. $manip_clause . '\s+' . $from_clause .'(?:\s)*(?:'. $where_clause .')?$/i'; + $matches = preg_match($pattern, $query, $match); + if ($matches) { + $manip = $match[1]; + $from = $match[2]; + $what = (count($matches) == 6) ? $match[5] : $match[3]; + return $manip.' '.$from.' '.$what.' WHERE ctid=(SELECT ctid FROM '.$from.' '.$where.' LIMIT '.$limit.')'; + } + //return error? + return $query; + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param bool $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $query = 'SHOW SERVER_VERSION'; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = $this->queryOne($query, 'text'); + if (PEAR::isError($server_info)) { + return $server_info; + } + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native && !PEAR::isError($server_info)) { + $tmp = explode('.', $server_info, 3); + if (empty($tmp[2]) + && isset($tmp[1]) + && preg_match('/(\d+)(.*)/', $tmp[1], $tmp2) + ) { + $server_info = array( + 'major' => $tmp[0], + 'minor' => $tmp2[1], + 'patch' => null, + 'extra' => $tmp2[2], + 'native' => $server_info, + ); + } else { + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => null, + 'native' => $server_info, + ); + } + } + return $server_info; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared']) { + return parent::prepare($query, $types, $result_types, $lobs); + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $pgtypes = function_exists('pg_prepare') ? false : array(); + if ($pgtypes !== false && !empty($types)) { + $this->loadModule('Datatype', null, true); + } + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = $parameter = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + //skip "::type" cast ("select id::varchar(20) from sometable where name=?") + $doublecolon_position = strpos($query, '::', $position); + if ($doublecolon_position !== false && $doublecolon_position == $c_position) { + $c_position = strpos($query, $colon, $position+2); + } + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (null === $placeholder_type) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (null === $placeholder_type) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + } else { + $types = array_values($types); + } + } + } + if ($placeholder_type_guess == '?') { + $length = 1; + $name = $parameter; + } else { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $param = preg_replace($regexp, '\\1', $query); + if ($param === '') { + $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $length = strlen($param) + 1; + $name = $param; + } + if ($pgtypes !== false) { + if (is_array($types) && array_key_exists($name, $types)) { + $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$name]); + } elseif (is_array($types) && array_key_exists($parameter, $types)) { + $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$parameter]); + } else { + $pgtypes[] = 'text'; + } + } + if (($key_parameter = array_search($name, $positions))) { + $next_parameter = 1; + foreach ($positions as $key => $value) { + if ($key_parameter == $key) { + break; + } + ++$next_parameter; + } + } else { + ++$parameter; + $next_parameter = $parameter; + $positions[] = $name; + } + $query = substr_replace($query, '$'.$parameter, $position, $length); + $position = $p_position + strlen($parameter); + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + static $prep_statement_counter = 1; + $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand())); + $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); + if (false === $pgtypes) { + $result = @pg_prepare($connection, $statement_name, $query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Unable to create prepared statement handle', __FUNCTION__); + return $err; + } + } else { + $types_string = ''; + if ($pgtypes) { + $types_string = ' ('.implode(', ', $pgtypes).') '; + } + $query = 'PREPARE '.$statement_name.$types_string.' AS '.$query; + $statement = $this->_doQuery($query, true, $connection); + if (PEAR::isError($statement)) { + return $statement; + } + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ function getSequenceName($sqn) + + /** + * adds sequence name formatting to a sequence name + * + * @param string name of the sequence + * + * @return string formatted sequence name + * + * @access public + */ + function getSequenceName($sqn) + { + if (false === $this->options['disable_smart_seqname']) { + if (strpos($sqn, '_') !== false) { + list($table, $field) = explode('_', $sqn, 2); + } + $schema_list = $this->queryOne("SELECT array_to_string(current_schemas(false), ',')"); + if (PEAR::isError($schema_list) || empty($schema_list) || count($schema_list) < 2) { + $order_by = ' a.attnum'; + $schema_clause = ' AND n.nspname=current_schema()'; + } else { + $schemas = explode(',', $schema_list); + $schema_clause = ' AND n.nspname IN ('.$schema_list.')'; + $counter = 1; + $order_by = ' CASE '; + foreach ($schemas as $schema) { + $order_by .= ' WHEN n.nspname='.$schema.' THEN '.$counter++; + } + $order_by .= ' ELSE '.$counter.' END, a.attnum'; + } + + $query = "SELECT substring((SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) + FROM pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) FROM 'nextval[^'']*''([^'']*)') + FROM pg_attribute a + LEFT JOIN pg_class c ON c.oid = a.attrelid + LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef + LEFT JOIN pg_namespace n ON c.relnamespace = n.oid + WHERE (c.relname = ".$this->quote($sqn, 'text'); + if (!empty($field)) { + $query .= " OR (c.relname = ".$this->quote($table, 'text')." AND a.attname = ".$this->quote($field, 'text').")"; + } + $query .= " )" + .$schema_clause." + AND NOT a.attisdropped + AND a.attnum > 0 + AND pg_get_expr(d.adbin, d.adrelid) LIKE 'nextval%' + ORDER BY ".$order_by; + $seqname = $this->queryOne($query); + if (!PEAR::isError($seqname) && !empty($seqname) && is_string($seqname)) { + return $seqname; + } + } + + return parent::getSequenceName($sqn); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $query = "SELECT NEXTVAL('$sequence_name')"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->queryOne($query, 'integer'); + $this->popExpect(); + $this->popErrorHandling(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence could not be created', __FUNCTION__); + } + return $this->nextId($seq_name, false); + } + } + return $result; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + if (empty($table) && empty($field)) { + return $this->queryOne('SELECT lastval()', 'integer'); + } + $seq = $table.(empty($field) ? '' : '_'.$field); + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq), true); + return $this->queryOne("SELECT currval('$sequence_name')", 'integer'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + return $this->queryOne("SELECT last_value FROM $sequence_name", 'integer'); + } +} + +/** + * MDB2 PostGreSQL result driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Result_pgsql extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @pg_fetch_array($this->result, null, PGSQL_ASSOC); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @pg_fetch_row($this->result); + } + if (!$row) { + if (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + return null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $rowObj = new $object_class($row); + $row = $rowObj; + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @pg_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + $cols = @pg_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + if (!($this->result = @pg_get_result($connection))) { + return false; + } + return MDB2_OK; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + if (is_resource($this->result) && $this->db->connection) { + $free = @pg_free_result($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 PostGreSQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_BufferedResult_pgsql extends MDB2_Result_pgsql +{ + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if ($this->rownum != ($rownum - 1) && !@pg_result_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @pg_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 PostGreSQL statement driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Statement_pgsql extends MDB2_Statement_Common +{ + // {{{ _execute() + + /** + * Execute a prepared query statement helper method. + * + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access private + */ + function _execute($result_class = true, $result_wrap_class = false) + { + if (null === $this->statement) { + return parent::_execute($result_class, $result_wrap_class); + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = false; + $parameters = array(); + // todo: disabled until pg_execute() bytea issues are cleared up + if (true || !function_exists('pg_execute')) { + $query = 'EXECUTE '.$this->statement; + } + if (!empty($this->positions)) { + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (is_resource($value) || $type == 'clob' || $type == 'blob' || $this->db->options['lob_allow_url_include']) { + if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + $close = true; + } + if (is_resource($value)) { + $data = ''; + while (!@feof($value)) { + $data.= @fread($value, $this->db->options['lob_buffer_length']); + } + if ($close) { + @fclose($value); + } + $value = $data; + } + } + $quoted = $this->db->quote($value, $type, $query); + if (PEAR::isError($quoted)) { + return $quoted; + } + $parameters[] = $quoted; + } + if ($query) { + $query.= ' ('.implode(', ', $parameters).')'; + } + } + + if (!$query) { + $result = @pg_execute($connection, $this->statement, $parameters); + if (!$result) { + $err = $this->db->raiseError(null, null, null, + 'Unable to execute statement', __FUNCTION__); + return $err; + } + } else { + $result = $this->db->_doQuery($query, $this->is_manip, $connection); + if (PEAR::isError($result)) { + return $result; + } + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result = $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (null !== $this->statement) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Driver/sqlite.php b/3rdparty/MDB2/Driver/sqlite.php index 5d2ad27d01..e8f1bba583 100644 --- a/3rdparty/MDB2/Driver/sqlite.php +++ b/3rdparty/MDB2/Driver/sqlite.php @@ -1,1088 +1,1093 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: sqlite.php,v 1.165 2008/11/30 14:28:01 afz Exp $ -// - -/** - * MDB2 SQLite driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_sqlite extends MDB2_Driver_Common -{ - // {{{ properties - var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); - - var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); - - var $_lasterror = ''; - - var $fix_assoc_fields_names = false; - - // }}} - // {{{ constructor - - /** - * Constructor - */ - function __construct() - { - parent::__construct(); - - $this->phptype = 'sqlite'; - $this->dbsyntax = 'sqlite'; - - $this->supported['sequences'] = 'emulated'; - $this->supported['indexes'] = true; - $this->supported['affected_rows'] = true; - $this->supported['summary_functions'] = true; - $this->supported['order_by_text'] = true; - $this->supported['current_id'] = 'emulated'; - $this->supported['limit_queries'] = true; - $this->supported['LOBs'] = true; - $this->supported['replace'] = true; - $this->supported['transactions'] = true; - $this->supported['savepoints'] = false; - $this->supported['sub_selects'] = true; - $this->supported['triggers'] = true; - $this->supported['auto_increment'] = true; - $this->supported['primary_key'] = false; // requires alter table implementation - $this->supported['result_introspection'] = false; // not implemented - $this->supported['prepared_statements'] = 'emulated'; - $this->supported['identifier_quoting'] = true; - $this->supported['pattern_escaping'] = false; - $this->supported['new_link'] = false; - - $this->options['DBA_username'] = false; - $this->options['DBA_password'] = false; - $this->options['base_transaction_name'] = '___php_MDB2_sqlite_auto_commit_off'; - $this->options['fixed_float'] = 0; - $this->options['database_path'] = ''; - $this->options['database_extension'] = ''; - $this->options['server_version'] = ''; - $this->options['max_identifiers_length'] = 128; //no real limit - } - - // }}} - // {{{ errorInfo() - - /** - * This method is used to collect information about an error - * - * @param integer $error - * @return array - * @access public - */ - function errorInfo($error = null) - { - $native_code = null; - if ($this->connection) { - $native_code = @sqlite_last_error($this->connection); - } - $native_msg = $this->_lasterror - ? html_entity_decode($this->_lasterror) : @sqlite_error_string($native_code); - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg); - - if (is_null($error)) { - static $error_regexps; - if (empty($error_regexps)) { - $error_regexps = array( - '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE, - '/^no such index:/' => MDB2_ERROR_NOT_FOUND, - '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS, - '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT, - '/is not unique/' => MDB2_ERROR_CONSTRAINT, - '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT, - '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT, - '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL, - '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD, - '/no column named/' => MDB2_ERROR_NOSUCHFIELD, - '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD, - '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX, - '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $native_msg)) { - $error = $code; - break; - } - } - } - return array($error, $native_code, $native_msg); - } - - // }}} - // {{{ escape() - - /** - * Quotes a string so it can be safely used in a query. It will quote - * the text so it can safely be used within a query. - * - * @param string the input string to quote - * @param bool escape wildcards - * - * @return string quoted string - * - * @access public - */ - function escape($text, $escape_wildcards = false) - { - $text = @sqlite_escape_string($text); - return $text; - } - - // }}} - // {{{ beginTransaction() - - /** - * Start a transaction or set a savepoint. - * - * @param string name of a savepoint to set - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function beginTransaction($savepoint = null) - { - $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!is_null($savepoint)) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } elseif ($this->in_transaction) { - return MDB2_OK; //nothing to do - } - if (!$this->destructor_registered && $this->opened_persistent) { - $this->destructor_registered = true; - register_shutdown_function('MDB2_closeOpenTransactions'); - } - $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name']; - $result =$this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = true; - return MDB2_OK; - } - - // }}} - // {{{ commit() - - /** - * Commit the database changes done during a transaction that is in - * progress or release a savepoint. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after committing the pending changes. - * - * @param string name of a savepoint to release - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function commit($savepoint = null) - { - $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - - $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name']; - $result =$this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ - - /** - * Cancel any database changes done during a transaction or since a specific - * savepoint that is in progress. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after canceling the pending changes. - * - * @param string name of a savepoint to rollback to - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function rollback($savepoint = null) - { - $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'rollback cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - - $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name']; - $result =$this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ function setTransactionIsolation() - - /** - * Set the transacton isolation level. - * - * @param string standard isolation level - * READ UNCOMMITTED (allows dirty reads) - * READ COMMITTED (prevents dirty reads) - * REPEATABLE READ (prevents nonrepeatable reads) - * SERIALIZABLE (prevents phantom reads) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - static function setTransactionIsolation($isolation,$options=array()) - { - $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); - switch ($isolation) { - case 'READ UNCOMMITTED': - $isolation = 0; - break; - case 'READ COMMITTED': - case 'REPEATABLE READ': - case 'SERIALIZABLE': - $isolation = 1; - break; - default: - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'isolation level is not supported: '.$isolation, __FUNCTION__); - } - - $query = "PRAGMA read_uncommitted=$isolation"; - return $this->_doQuery($query, true); - } - - // }}} - // {{{ getDatabaseFile() - - /** - * Builds the string with path+dbname+extension - * - * @return string full database path+file - * @access protected - */ - function _getDatabaseFile($database_name) - { - if ($database_name === '' || $database_name === ':memory:') { - return $database_name; - } - return $this->options['database_path'].$database_name.$this->options['database_extension']; - } - - // }}} - // {{{ connect() - - /** - * Connect to the database - * - * @return true on success, MDB2 Error Object on failure - **/ - function connect() - { - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); - $database_file = $this->_getDatabaseFile($this->database_name); - if (is_resource($this->connection)) { - //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 - if (MDB2::areEquals($this->connected_dsn, $this->dsn) - && $this->connected_database_name == $database_file - && $this->opened_persistent == $this->options['persistent'] - ) { - return MDB2_OK; - } - $this->disconnect(false); - } - - if (!PEAR::loadExtension($this->phptype)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); - } - - if (empty($this->database_name)) { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - 'unable to establish a connection', __FUNCTION__); - } - - if ($database_file !== ':memory:') { - if(!strpos($database_file,'.db')){ - $database_file="$datadir/$database_file.db"; - } - if (!file_exists($database_file)) { - if (!touch($database_file)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Could not create database file', __FUNCTION__); - } - if (!isset($this->dsn['mode']) - || !is_numeric($this->dsn['mode']) - ) { - $mode = 0644; - } else { - $mode = octdec($this->dsn['mode']); - } - if (!chmod($database_file, $mode)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Could not be chmodded database file', __FUNCTION__); - } - if (!file_exists($database_file)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Could not be found database file', __FUNCTION__); - } - } - if (!is_file($database_file)) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'Database is a directory name', __FUNCTION__); - } - if (!is_readable($database_file)) { - return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null, - 'Could not read database file', __FUNCTION__); - } - } - - $connect_function = ($this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'); - $php_errormsg = ''; - if (version_compare('5.1.0', PHP_VERSION, '>')) { - @ini_set('track_errors', true); - echo 1; - $connection = @$connect_function($database_file); - echo 2; - @ini_restore('track_errors'); - } else { - $connection = @$connect_function($database_file, 0666, $php_errormsg); - } - $this->_lasterror = $php_errormsg; - if (!$connection) { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - 'unable to establish a connection', __FUNCTION__); - } - - if ($this->fix_assoc_fields_names || - $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES) - { - @sqlite_query("PRAGMA short_column_names = 1", $connection); - $this->fix_assoc_fields_names = true; - } - - $this->connection = $connection; - $this->connected_dsn = $this->dsn; - $this->connected_database_name = $database_file; - $this->opened_persistent = $this->getoption('persistent'); - $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; - - return MDB2_OK; - } - - // }}} - // {{{ databaseExists() - - /** - * check if given database name is exists? - * - * @param string $name name of the database that should be checked - * - * @return mixed true/false on success, a MDB2 error on failure - * @access public - */ - function databaseExists($name) - { - $database_file = $this->_getDatabaseFile($name); - $result = file_exists($database_file); - return $result; - } - - // }}} - // {{{ disconnect() - - /** - * Log out and disconnect from the database. - * - * @param boolean $force if the disconnect should be forced even if the - * connection is opened persistently - * @return mixed true on success, false if not connected and error - * object on error - * @access public - */ - function disconnect($force = true) - { - if (is_resource($this->connection)) { - if ($this->in_transaction) { - $dsn = $this->dsn; - $database_name = $this->database_name; - $persistent = $this->options['persistent']; - $this->dsn = $this->connected_dsn; - $this->database_name = $this->connected_database_name; - $this->options['persistent'] = $this->opened_persistent; - $this->rollback(); - $this->dsn = $dsn; - $this->database_name = $database_name; - $this->options['persistent'] = $persistent; - } - - if (!$this->opened_persistent || $force) { - @sqlite_close($this->connection); - } - } else { - return false; - } - return parent::disconnect($force); - } - - // }}} - // {{{ _doQuery() - - /** - * Execute a query - * @param string $query query - * @param boolean $is_manip if the query is a manipulation query - * @param resource $connection - * @param string $database_name - * @return result or error object - * @access protected - */ - function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) - { - $this->last_query = $query; - $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - if ($this->options['disable_query']) { - $result = $is_manip ? 0 : null; - return $result; - } - - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - - $function = $this->options['result_buffering'] - ? 'sqlite_query' : 'sqlite_unbuffered_query'; - $php_errormsg = ''; - if (version_compare('5.1.0', PHP_VERSION, '>')) { - @ini_set('track_errors', true); - do { - $result = @$function($query.';', $connection); - } while (sqlite_last_error($connection) == SQLITE_SCHEMA); - @ini_restore('track_errors'); - } else { - do { - $result = @$function($query.';', $connection, SQLITE_BOTH, $php_errormsg); - } while (sqlite_last_error($connection) == SQLITE_SCHEMA); - } - $this->_lasterror = $php_errormsg; - - if (!$result) { - $err =$this->raiseError(null, null, null, - 'Could not execute statement', __FUNCTION__); - return $err; - } - - $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ _affectedRows() - - /** - * Returns the number of rows affected - * - * @param resource $result - * @param resource $connection - * @return mixed MDB2 Error Object or the number of rows affected - * @access private - */ - function _affectedRows($connection, $result = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - return @sqlite_changes($connection); - } - - // }}} - // {{{ _modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * @param string $query query to modify - * @param boolean $is_manip if it is a DML query - * @param integer $limit limit the number of rows - * @param integer $offset start reading from given offset - * @return string modified query - * @access protected - */ - function _modifyQuery($query, $is_manip, $limit, $offset) - { - if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { - if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { - $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', - 'DELETE FROM \1 WHERE 1=1', $query); - } - } - if ($limit > 0 - && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) - ) { - $query = rtrim($query); - if (substr($query, -1) == ';') { - $query = substr($query, 0, -1); - } - if ($is_manip) { - $query.= " LIMIT $limit"; - } else { - $query.= " LIMIT $offset,$limit"; - } - } - return $query; - } - - // }}} - // {{{ getServerVersion() - - /** - * return version information about the server - * - * @param bool $native determines if the raw version string should be returned - * @return mixed array/string with version information or MDB2 error object - * @access public - */ - function getServerVersion($native = false) - { - $server_info = false; - if ($this->connected_server_info) { - $server_info = $this->connected_server_info; - } elseif ($this->options['server_version']) { - $server_info = $this->options['server_version']; - } elseif (function_exists('sqlite_libversion')) { - $server_info = @sqlite_libversion(); - } - if (!$server_info) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'Requires either the "server_version" option or the sqlite_libversion() function', __FUNCTION__); - } - // cache server_info - $this->connected_server_info = $server_info; - if (!$native) { - $tmp = explode('.', $server_info, 3); - $server_info = array( - 'major' => isset($tmp[0]) ? $tmp[0] : null, - 'minor' => isset($tmp[1]) ? $tmp[1] : null, - 'patch' => isset($tmp[2]) ? $tmp[2] : null, - 'extra' => null, - 'native' => $server_info, - ); - } - return $server_info; - } - - // }}} - // {{{ replace() - - /** - * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT - * query, except that if there is already a row in the table with the same - * key field values, the old row is deleted before the new row is inserted. - * - * The REPLACE type of query does not make part of the SQL standards. Since - * practically only SQLite implements it natively, this type of query is - * emulated through this method for other DBMS using standard types of - * queries inside a transaction to assure the atomicity of the operation. - * - * @access public - * - * @param string $table name of the table on which the REPLACE query will - * be executed. - * @param array $fields associative array that describes the fields and the - * values that will be inserted or updated in the specified table. The - * indexes of the array are the names of all the fields of the table. The - * values of the array are also associative arrays that describe the - * values and other properties of the table fields. - * - * Here follows a list of field properties that need to be specified: - * - * value: - * Value to be assigned to the specified field. This value may be - * of specified in database independent type format as this - * function can perform the necessary datatype conversions. - * - * Default: - * this property is required unless the Null property - * is set to 1. - * - * type - * Name of the type of the field. Currently, all types Metabase - * are supported except for clob and blob. - * - * Default: no type conversion - * - * null - * Boolean property that indicates that the value for this field - * should be set to null. - * - * The default value for fields missing in INSERT queries may be - * specified the definition of a table. Often, the default value - * is already null, but since the REPLACE may be emulated using - * an UPDATE query, make sure that all fields of the table are - * listed in this function argument array. - * - * Default: 0 - * - * key - * Boolean property that indicates that this field should be - * handled as a primary key or at least as part of the compound - * unique index of the table that will determine the row that will - * updated if it exists or inserted a new row otherwise. - * - * This function will fail if no key field is specified or if the - * value of a key field is set to null because fields that are - * part of unique index they may not be null. - * - * Default: 0 - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function replace($table, $fields) - { - $count = count($fields); - $query = $values = ''; - $keys = $colnum = 0; - for (reset($fields); $colnum < $count; next($fields), $colnum++) { - $name = key($fields); - if ($colnum > 0) { - $query .= ','; - $values.= ','; - } - $query.= $this->quoteIdentifier($name, true); - if (isset($fields[$name]['null']) && $fields[$name]['null']) { - $value = 'NULL'; - } else { - $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; - $value = $this->quote($fields[$name]['value'], $type); - if (PEAR::isError($value)) { - return $value; - } - } - $values.= $value; - if (isset($fields[$name]['key']) && $fields[$name]['key']) { - if ($value === 'NULL') { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'key value '.$name.' may not be NULL', __FUNCTION__); - } - $keys++; - } - } - if ($keys == 0) { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'not specified which fields are keys', __FUNCTION__); - } - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $table = $this->quoteIdentifier($table, true); - $query = "REPLACE INTO $table ($query) VALUES ($values)"; - $result =$this->_doQuery($query, true, $connection); - if (PEAR::isError($result)) { - return $result; - } - return $this->_affectedRows($connection, $result); - } - - // }}} - // {{{ nextID() - - /** - * Returns the next free id of a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true the sequence is - * automatic created, if it - * not exists - * - * @return mixed MDB2 Error Object or id - * @access public - */ - function nextID($seq_name, $ondemand = true) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $seqcol_name = $this->options['seqcol_name']; - $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $this->expectError(MDB2_ERROR_NOSUCHTABLE); - $result =$this->_doQuery($query, true); - $this->popExpect(); - $this->popErrorHandling(); - if (PEAR::isError($result)) { - if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { - $this->loadModule('Manager', null, true); - $result = $this->manager->createSequence($seq_name); - if (PEAR::isError($result)) { - return $this->raiseError($result, null, null, - 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); - } else { - return $this->nextID($seq_name, false); - } - } - return $result; - } - $value = $this->lastInsertID(); - if (is_numeric($value)) { - $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; - $result =$this->_doQuery($query, true); - if (PEAR::isError($result)) { - $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; - } - } - return $value; - } - - // }}} - // {{{ lastInsertID() - - /** - * Returns the autoincrement ID if supported or $id or fetches the current - * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) - * - * @param string $table name of the table into which a new row was inserted - * @param string $field name of the field into which a new row was inserted - * @return mixed MDB2 Error Object or id - * @access public - */ - function lastInsertID($table = null, $field = null) - { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $value = @sqlite_last_insert_rowid($connection); - if (!$value) { - return $this->raiseError(null, null, null, - 'Could not get last insert ID', __FUNCTION__); - } - return $value; - } - - // }}} - // {{{ currID() - - /** - * Returns the current id of a sequence - * - * @param string $seq_name name of the sequence - * @return mixed MDB2 Error Object or id - * @access public - */ - function currID($seq_name) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); - $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; - return $this->queryOne($query, 'integer'); - } -} - -/** - * MDB2 SQLite result driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Result_sqlite extends MDB2_Result_Common -{ - // }}} - // {{{ fetchRow() - - /** - * Fetch a row and insert the data into an existing array. - * - * @param int $fetchmode how the array data should be indexed - * @param int $rownum number of the row where the data can be found - * @return int data array on success, a MDB2 error on failure - * @access public - */ - function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) - { - if (!is_null($rownum)) { - $seek = $this->seek($rownum); - if (PEAR::isError($seek)) { - return $seek; - } - } - if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { - $fetchmode = $this->db->fetchmode; - } - if ($fetchmode & MDB2_FETCHMODE_ASSOC) { - $row = @sqlite_fetch_array($this->result, SQLITE_ASSOC); - if (is_array($row) - && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE - ) { - $row = array_change_key_case($row, $this->db->options['field_case']); - } - } else { - $row = @sqlite_fetch_array($this->result, SQLITE_NUM); - } - if (!$row) { - if ($this->result === false) { - $err =$this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - return $err; - } - $null = null; - return $null; - } - $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; - $rtrim = false; - if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { - if (empty($this->types)) { - $mode += MDB2_PORTABILITY_RTRIM; - } else { - $rtrim = true; - } - } - if ($mode) { - $this->db->_fixResultArrayValues($row, $mode); - } - if (!empty($this->types)) { - $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); - } - if (!empty($this->values)) { - $this->_assignBindColumns($row); - } - if ($fetchmode === MDB2_FETCHMODE_OBJECT) { - $object_class = $this->db->options['fetch_class']; - if ($object_class == 'stdClass') { - $row = (object) $row; - } else { - $row = new $object_class($row); - } - } - ++$this->rownum; - return $row; - } - - // }}} - // {{{ _getColumnNames() - - /** - * Retrieve the names of columns returned by the DBMS in a query result. - * - * @return mixed Array variable that holds the names of columns as keys - * or an MDB2 error on failure. - * Some DBMS may not return any columns when the result set - * does not contain any rows. - * @access private - */ - function _getColumnNames() - { - $columns = array(); - $numcols = $this->numCols(); - if (PEAR::isError($numcols)) { - return $numcols; - } - for ($column = 0; $column < $numcols; $column++) { - $column_name = @sqlite_field_name($this->result, $column); - $columns[$column_name] = $column; - } - if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $columns = array_change_key_case($columns, $this->db->options['field_case']); - } - return $columns; - } - - // }}} - // {{{ numCols() - - /** - * Count the number of columns returned by the DBMS in a query result. - * - * @access public - * @return mixed integer value with the number of columns, a MDB2 error - * on failure - */ - function numCols() - { - $cols = @sqlite_num_fields($this->result); - if (is_null($cols)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return count($this->types); - } - return $this->db->raiseError(null, null, null, - 'Could not get column count', __FUNCTION__); - } - return $cols; - } -} - -/** - * MDB2 SQLite buffered result driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_BufferedResult_sqlite extends MDB2_Result_sqlite -{ - // {{{ seek() - - /** - * Seek to a specific row in a result set - * - * @param int $rownum number of the row where the data can be found - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function seek($rownum = 0) - { - if (!@sqlite_seek($this->result, $rownum)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return MDB2_OK; - } - return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, - 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); - } - $this->rownum = $rownum - 1; - return MDB2_OK; - } - - // }}} - // {{{ valid() - - /** - * Check if the end of the result set has been reached - * - * @return mixed true or false on sucess, a MDB2 error on failure - * @access public - */ - function valid() - { - $numrows = $this->numRows(); - if (PEAR::isError($numrows)) { - return $numrows; - } - return $this->rownum < ($numrows - 1); - } - - // }}} - // {{{ numRows() - - /** - * Returns the number of rows in a result object - * - * @return mixed MDB2 Error Object or the number of rows - * @access public - */ - function numRows() - { - $rows = @sqlite_num_rows($this->result); - if (is_null($rows)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return 0; - } - return $this->db->raiseError(null, null, null, - 'Could not get row count', __FUNCTION__); - } - return $rows; - } -} - -/** - * MDB2 SQLite statement driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Statement_sqlite extends MDB2_Statement_Common -{ - -} - -?> + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php 295587 2010-02-28 17:16:38Z quipo $ +// + +/** + * MDB2 SQLite driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_sqlite extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + var $_lasterror = ''; + + var $fix_assoc_fields_names = false; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'sqlite'; + $this->dbsyntax = 'sqlite'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = false; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = false; // requires alter table implementation + $this->supported['result_introspection'] = false; // not implemented + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = false; + $this->supported['new_link'] = false; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['base_transaction_name'] = '___php_MDB2_sqlite_auto_commit_off'; + $this->options['fixed_float'] = 0; + $this->options['database_path'] = ''; + $this->options['database_extension'] = ''; + $this->options['server_version'] = ''; + $this->options['max_identifiers_length'] = 128; //no real limit + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + $native_code = null; + if ($this->connection) { + $native_code = @sqlite_last_error($this->connection); + } + $native_msg = $this->_lasterror + ? html_entity_decode($this->_lasterror) : @sqlite_error_string($native_code); + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg); + + if (null === $error) { + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE, + '/^no such index:/' => MDB2_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT, + '/is not unique/' => MDB2_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT, + '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD, + '/no column named/' => MDB2_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg)) { + $error = $code; + break; + } + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + $text = @sqlite_escape_string($text); + return $text; + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name']; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name']; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name']; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @param array some transaction options: + * 'wait' => 'WAIT' | 'NO WAIT' + * 'rw' => 'READ WRITE' | 'READ ONLY' + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation, $options = array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $isolation = 0; + break; + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + $isolation = 1; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "PRAGMA read_uncommitted=$isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ getDatabaseFile() + + /** + * Builds the string with path+dbname+extension + * + * @return string full database path+file + * @access protected + */ + function _getDatabaseFile($database_name) + { + if ($database_name === '' || $database_name === ':memory:') { + return $database_name; + } + return $this->options['database_path'].$database_name.$this->options['database_extension']; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + **/ + function connect() + { + $database_file = $this->_getDatabaseFile($this->database_name); + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->connected_database_name == $database_file + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + if (empty($this->database_name)) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($database_file !== ':memory:') { + if (!file_exists($database_file)) { + if (!touch($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not create database file', __FUNCTION__); + } + if (!isset($this->dsn['mode']) + || !is_numeric($this->dsn['mode']) + ) { + $mode = 0644; + } else { + $mode = octdec($this->dsn['mode']); + } + if (!chmod($database_file, $mode)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be chmodded database file', __FUNCTION__); + } + if (!file_exists($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be found database file', __FUNCTION__); + } + } + if (!is_file($database_file)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Database is a directory name', __FUNCTION__); + } + if (!is_readable($database_file)) { + return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null, + 'Could not read database file', __FUNCTION__); + } + } + + $connect_function = ($this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'); + $php_errormsg = ''; + if (version_compare('5.1.0', PHP_VERSION, '>')) { + @ini_set('track_errors', true); + $connection = @$connect_function($database_file); + @ini_restore('track_errors'); + } else { + $connection = @$connect_function($database_file, 0666, $php_errormsg); + } + $this->_lasterror = $php_errormsg; + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($this->fix_assoc_fields_names || + $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES) + { + @sqlite_query("PRAGMA short_column_names = 1", $connection); + $this->fix_assoc_fields_names = true; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $database_file; + $this->opened_persistent = $this->getoption('persistent'); + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + return MDB2_OK; + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name is exists? + * + * @param string $name name of the database that should be checked + * + * @return mixed true/false on success, a MDB2 error on failure + * @access public + */ + function databaseExists($name) + { + $database_file = $this->_getDatabaseFile($name); + $result = file_exists($database_file); + return $result; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @param boolean $force if the disconnect should be forced even if the + * connection is opened persistently + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if ($this->in_transaction) { + $dsn = $this->dsn; + $database_name = $this->database_name; + $persistent = $this->options['persistent']; + $this->dsn = $this->connected_dsn; + $this->database_name = $this->connected_database_name; + $this->options['persistent'] = $this->opened_persistent; + $this->rollback(); + $this->dsn = $dsn; + $this->database_name = $database_name; + $this->options['persistent'] = $persistent; + } + + if (!$this->opened_persistent || $force) { + @sqlite_close($this->connection); + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function _doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $function = $this->options['result_buffering'] + ? 'sqlite_query' : 'sqlite_unbuffered_query'; + $php_errormsg = ''; + if (version_compare('5.1.0', PHP_VERSION, '>')) { + @ini_set('track_errors', true); + do { + $result = @$function($query.';', $connection); + } while (sqlite_last_error($connection) == SQLITE_SCHEMA); + @ini_restore('track_errors'); + } else { + do { + $result = @$function($query.';', $connection, SQLITE_BOTH, $php_errormsg); + } while (sqlite_last_error($connection) == SQLITE_SCHEMA); + } + $this->_lasterror = $php_errormsg; + + if (!$result) { + $code = null; + if (0 === strpos($this->_lasterror, 'no such table')) { + $code = MDB2_ERROR_NOSUCHTABLE; + } + $err = $this->raiseError($code, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @sqlite_changes($connection); + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + if ($is_manip) { + $query.= " LIMIT $limit"; + } else { + $query.= " LIMIT $offset,$limit"; + } + } + return $query; + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param bool $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $server_info = false; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } elseif ($this->options['server_version']) { + $server_info = $this->options['server_version']; + } elseif (function_exists('sqlite_libversion')) { + $server_info = @sqlite_libversion(); + } + if (!$server_info) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Requires either the "server_version" option or the sqlite_libversion() function', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => null, + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the old row is deleted before the new row is inserted. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only SQLite implements it natively, this type of query is + * emulated through this method for other DBMS using standard types of + * queries inside a transaction to assure the atomicity of the operation. + * + * @access public + * + * @param string $table name of the table on which the REPLACE query will + * be executed. + * @param array $fields associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. The + * values of the array are also associative arrays that describe the + * values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value: + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: + * this property is required unless the Null property + * is set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * Boolean property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * Boolean property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function replace($table, $fields) + { + $count = count($fields); + $query = $values = ''; + $keys = $colnum = 0; + for (reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if ($colnum > 0) { + $query .= ','; + $values.= ','; + } + $query.= $this->quoteIdentifier($name, true); + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $table = $this->quoteIdentifier($table, true); + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result = $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->options['seqcol_name']; + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->_doQuery($query, true); + $this->popExpect(); + $this->popErrorHandling(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $value = @sqlite_last_insert_rowid($connection); + if (!$value) { + return $this->raiseError(null, null, null, + 'Could not get last insert ID', __FUNCTION__); + } + return $value; + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 SQLite result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_sqlite extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @sqlite_fetch_array($this->result, SQLITE_ASSOC); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @sqlite_fetch_array($this->result, SQLITE_NUM); + } + if (!$row) { + if (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + return null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $rowObj = new $object_class($row); + $row = $rowObj; + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @sqlite_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + $cols = @sqlite_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } +} + +/** + * MDB2 SQLite buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_sqlite extends MDB2_Result_sqlite +{ + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if (!@sqlite_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @sqlite_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 SQLite statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_sqlite extends MDB2_Statement_Common +{ + +} +?> \ No newline at end of file diff --git a/3rdparty/MDB2/Extended.php b/3rdparty/MDB2/Extended.php index 864ab92354..fed82f9659 100644 --- a/3rdparty/MDB2/Extended.php +++ b/3rdparty/MDB2/Extended.php @@ -1,721 +1,720 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Extended.php,v 1.60 2007/11/28 19:49:34 quipo Exp $ - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - */ - -/** - * Used by autoPrepare() - */ -define('MDB2_AUTOQUERY_INSERT', 1); -define('MDB2_AUTOQUERY_UPDATE', 2); -define('MDB2_AUTOQUERY_DELETE', 3); -define('MDB2_AUTOQUERY_SELECT', 4); - -/** - * MDB2_Extended: class which adds several high level methods to MDB2 - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Extended extends MDB2_Module_Common -{ - // {{{ autoPrepare() - - /** - * Generate an insert, update or delete query and call prepare() on it - * - * @param string table - * @param array the fields names - * @param int type of query to build - * MDB2_AUTOQUERY_INSERT - * MDB2_AUTOQUERY_UPDATE - * MDB2_AUTOQUERY_DELETE - * MDB2_AUTOQUERY_SELECT - * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) - * @param array that contains the types of the placeholders - * @param mixed array that contains the types of the columns in - * the result set or MDB2_PREPARE_RESULT, if set to - * MDB2_PREPARE_MANIP the query is handled as a manipulation query - * - * @return resource handle for the query - * @see buildManipSQL - * @access public - */ - function autoPrepare($table, $table_fields, $mode = MDB2_AUTOQUERY_INSERT, - $where = false, $types = null, $result_types = MDB2_PREPARE_MANIP) - { - $query = $this->buildManipSQL($table, $table_fields, $mode, $where); - if (PEAR::isError($query)) { - return $query; - } - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $lobs = array(); - foreach ((array)$types as $param => $type) { - if (($type == 'clob') || ($type == 'blob')) { - $lobs[$param] = $table_fields[$param]; - } - } - return $db->prepare($query, $types, $result_types, $lobs); - } - - // }}} - // {{{ autoExecute() - - /** - * Generate an insert, update or delete query and call prepare() and execute() on it - * - * @param string name of the table - * @param array assoc ($key=>$value) where $key is a field name and $value its value - * @param int type of query to build - * MDB2_AUTOQUERY_INSERT - * MDB2_AUTOQUERY_UPDATE - * MDB2_AUTOQUERY_DELETE - * MDB2_AUTOQUERY_SELECT - * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) - * @param array that contains the types of the placeholders - * @param string which specifies which result class to use - * @param mixed array that contains the types of the columns in - * the result set or MDB2_PREPARE_RESULT, if set to - * MDB2_PREPARE_MANIP the query is handled as a manipulation query - * - * @return bool|MDB2_Error true on success, a MDB2 error on failure - * @see buildManipSQL - * @see autoPrepare - * @access public - */ - function &autoExecute($table, $fields_values, $mode = MDB2_AUTOQUERY_INSERT, - $where = false, $types = null, $result_class = true, $result_types = MDB2_PREPARE_MANIP) - { - $fields_values = (array)$fields_values; - if ($mode == MDB2_AUTOQUERY_SELECT) { - if (is_array($result_types)) { - $keys = array_keys($result_types); - } elseif (!empty($fields_values)) { - $keys = $fields_values; - } else { - $keys = array(); - } - } else { - $keys = array_keys($fields_values); - } - $params = array_values($fields_values); - if (empty($params)) { - $query = $this->buildManipSQL($table, $keys, $mode, $where); - - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - if ($mode == MDB2_AUTOQUERY_SELECT) { - $result =& $db->query($query, $result_types, $result_class); - } else { - $result = $db->exec($query); - } - } else { - $stmt = $this->autoPrepare($table, $keys, $mode, $where, $types, $result_types); - if (PEAR::isError($stmt)) { - return $stmt; - } - $result =& $stmt->execute($params, $result_class); - $stmt->free(); - } - return $result; - } - - // }}} - // {{{ buildManipSQL() - - /** - * Make automaticaly an sql query for prepare() - * - * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), MDB2_AUTOQUERY_INSERT) - * will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) - * NB : - This belongs more to a SQL Builder class, but this is a simple facility - * - Be carefull ! If you don't give a $where param with an UPDATE/DELETE query, all - * the records of the table will be updated/deleted ! - * - * @param string name of the table - * @param ordered array containing the fields names - * @param int type of query to build - * MDB2_AUTOQUERY_INSERT - * MDB2_AUTOQUERY_UPDATE - * MDB2_AUTOQUERY_DELETE - * MDB2_AUTOQUERY_SELECT - * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) - * - * @return string sql query for prepare() - * @access public - */ - function buildManipSQL($table, $table_fields, $mode, $where = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if ($db->options['quote_identifier']) { - $table = $db->quoteIdentifier($table); - } - - if (!empty($table_fields) && $db->options['quote_identifier']) { - foreach ($table_fields as $key => $field) { - $table_fields[$key] = $db->quoteIdentifier($field); - } - } - - if ($where !== false && !is_null($where)) { - if (is_array($where)) { - $where = implode(' AND ', $where); - } - $where = ' WHERE '.$where; - } - - switch ($mode) { - case MDB2_AUTOQUERY_INSERT: - if (empty($table_fields)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'Insert requires table fields', __FUNCTION__); - } - $cols = implode(', ', $table_fields); - $values = '?'.str_repeat(', ?', (count($table_fields) - 1)); - return 'INSERT INTO '.$table.' ('.$cols.') VALUES ('.$values.')'; - break; - case MDB2_AUTOQUERY_UPDATE: - if (empty($table_fields)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'Update requires table fields', __FUNCTION__); - } - $set = implode(' = ?, ', $table_fields).' = ?'; - $sql = 'UPDATE '.$table.' SET '.$set.$where; - return $sql; - break; - case MDB2_AUTOQUERY_DELETE: - $sql = 'DELETE FROM '.$table.$where; - return $sql; - break; - case MDB2_AUTOQUERY_SELECT: - $cols = !empty($table_fields) ? implode(', ', $table_fields) : '*'; - $sql = 'SELECT '.$cols.' FROM '.$table.$where; - return $sql; - break; - } - return $db->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'Non existant mode', __FUNCTION__); - } - - // }}} - // {{{ limitQuery() - - /** - * Generates a limited query - * - * @param string query - * @param array that contains the types of the columns in the result set - * @param integer the numbers of rows to fetch - * @param integer the row to start to fetching - * @param string which specifies which result class to use - * @param mixed string which specifies which class to wrap results in - * - * @return MDB2_Result|MDB2_Error result set on success, a MDB2 error on failure - * @access public - */ - function &limitQuery($query, $types, $limit, $offset = 0, $result_class = true, - $result_wrap_class = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->setLimit($limit, $offset); - if (PEAR::isError($result)) { - return $result; - } - $result =& $db->query($query, $types, $result_class, $result_wrap_class); - return $result; - } - - // }}} - // {{{ execParam() - - /** - * Execute a parameterized DML statement. - * - * @param string the SQL query - * @param array if supplied, prepare/execute will be used - * with this array as execute parameters - * @param array that contains the types of the values defined in $params - * - * @return int|MDB2_Error affected rows on success, a MDB2 error on failure - * @access public - */ - function execParam($query, $params = array(), $param_types = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - settype($params, 'array'); - if (empty($params)) { - return $db->exec($query); - } - - $stmt = $db->prepare($query, $param_types, MDB2_PREPARE_MANIP); - if (PEAR::isError($stmt)) { - return $stmt; - } - - $result = $stmt->execute($params); - if (PEAR::isError($result)) { - return $result; - } - - $stmt->free(); - return $result; - } - - // }}} - // {{{ getOne() - - /** - * Fetch the first column of the first row of data returned from a query. - * Takes care of doing the query and freeing the results when finished. - * - * @param string the SQL query - * @param string that contains the type of the column in the result set - * @param array if supplied, prepare/execute will be used - * with this array as execute parameters - * @param array that contains the types of the values defined in $params - * @param int|string which column to return - * - * @return scalar|MDB2_Error data on success, a MDB2 error on failure - * @access public - */ - function getOne($query, $type = null, $params = array(), - $param_types = null, $colnum = 0) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - settype($params, 'array'); - settype($type, 'array'); - if (empty($params)) { - return $db->queryOne($query, $type, $colnum); - } - - $stmt = $db->prepare($query, $param_types, $type); - if (PEAR::isError($stmt)) { - return $stmt; - } - - $result = $stmt->execute($params); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $one = $result->fetchOne($colnum); - $stmt->free(); - $result->free(); - return $one; - } - - // }}} - // {{{ getRow() - - /** - * Fetch the first row of data returned from a query. Takes care - * of doing the query and freeing the results when finished. - * - * @param string the SQL query - * @param array that contains the types of the columns in the result set - * @param array if supplied, prepare/execute will be used - * with this array as execute parameters - * @param array that contains the types of the values defined in $params - * @param int the fetch mode to use - * - * @return array|MDB2_Error data on success, a MDB2 error on failure - * @access public - */ - function getRow($query, $types = null, $params = array(), - $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - settype($params, 'array'); - if (empty($params)) { - return $db->queryRow($query, $types, $fetchmode); - } - - $stmt = $db->prepare($query, $param_types, $types); - if (PEAR::isError($stmt)) { - return $stmt; - } - - $result = $stmt->execute($params); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $row = $result->fetchRow($fetchmode); - $stmt->free(); - $result->free(); - return $row; - } - - // }}} - // {{{ getCol() - - /** - * Fetch a single column from a result set and return it as an - * indexed array. - * - * @param string the SQL query - * @param string that contains the type of the column in the result set - * @param array if supplied, prepare/execute will be used - * with this array as execute parameters - * @param array that contains the types of the values defined in $params - * @param int|string which column to return - * - * @return array|MDB2_Error data on success, a MDB2 error on failure - * @access public - */ - function getCol($query, $type = null, $params = array(), - $param_types = null, $colnum = 0) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - settype($params, 'array'); - settype($type, 'array'); - if (empty($params)) { - return $db->queryCol($query, $type, $colnum); - } - - $stmt = $db->prepare($query, $param_types, $type); - if (PEAR::isError($stmt)) { - return $stmt; - } - - $result = $stmt->execute($params); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $col = $result->fetchCol($colnum); - $stmt->free(); - $result->free(); - return $col; - } - - // }}} - // {{{ getAll() - - /** - * Fetch all the rows returned from a query. - * - * @param string the SQL query - * @param array that contains the types of the columns in the result set - * @param array if supplied, prepare/execute will be used - * with this array as execute parameters - * @param array that contains the types of the values defined in $params - * @param int the fetch mode to use - * @param bool if set to true, the $all will have the first - * column as its first dimension - * @param bool $force_array used only when the query returns exactly - * two columns. If true, the values of the returned array will be - * one-element arrays instead of scalars. - * @param bool $group if true, the values of the returned array is - * wrapped in another array. If the same key value (in the first - * column) repeats itself, the values will be appended to this array - * instead of overwriting the existing values. - * - * @return array|MDB2_Error data on success, a MDB2 error on failure - * @access public - */ - function getAll($query, $types = null, $params = array(), - $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, - $rekey = false, $force_array = false, $group = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - settype($params, 'array'); - if (empty($params)) { - return $db->queryAll($query, $types, $fetchmode, $rekey, $force_array, $group); - } - - $stmt = $db->prepare($query, $param_types, $types); - if (PEAR::isError($stmt)) { - return $stmt; - } - - $result = $stmt->execute($params); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); - $stmt->free(); - $result->free(); - return $all; - } - - // }}} - // {{{ getAssoc() - - /** - * Fetch the entire result set of a query and return it as an - * associative array using the first column as the key. - * - * If the result set contains more than two columns, the value - * will be an array of the values from column 2-n. If the result - * set contains only two columns, the returned value will be a - * scalar with the value of the second column (unless forced to an - * array with the $force_array parameter). A MDB2 error code is - * returned on errors. If the result set contains fewer than two - * columns, a MDB2_ERROR_TRUNCATED error is returned. - * - * For example, if the table 'mytable' contains: - *
-     *   ID      TEXT       DATE
-     * --------------------------------
-     *   1       'one'      944679408
-     *   2       'two'      944679408
-     *   3       'three'    944679408
-     * 
- * Then the call getAssoc('SELECT id,text FROM mytable') returns: - *
-     *    array(
-     *      '1' => 'one',
-     *      '2' => 'two',
-     *      '3' => 'three',
-     *    )
-     * 
- * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: - *
-     *    array(
-     *      '1' => array('one', '944679408'),
-     *      '2' => array('two', '944679408'),
-     *      '3' => array('three', '944679408')
-     *    )
-     * 
- * - * If the more than one row occurs with the same value in the - * first column, the last row overwrites all previous ones by - * default. Use the $group parameter if you don't want to - * overwrite like this. Example: - *
-     * getAssoc('SELECT category,id,name FROM mytable', null, null
-     *           MDB2_FETCHMODE_ASSOC, false, true) returns:
-     *    array(
-     *      '1' => array(array('id' => '4', 'name' => 'number four'),
-     *                   array('id' => '6', 'name' => 'number six')
-     *             ),
-     *      '9' => array(array('id' => '4', 'name' => 'number four'),
-     *                   array('id' => '6', 'name' => 'number six')
-     *             )
-     *    )
-     * 
- * - * Keep in mind that database functions in PHP usually return string - * values for results regardless of the database's internal type. - * - * @param string the SQL query - * @param array that contains the types of the columns in the result set - * @param array if supplied, prepare/execute will be used - * with this array as execute parameters - * @param array that contains the types of the values defined in $params - * @param bool $force_array used only when the query returns - * exactly two columns. If TRUE, the values of the returned array - * will be one-element arrays instead of scalars. - * @param bool $group if TRUE, the values of the returned array - * is wrapped in another array. If the same key value (in the first - * column) repeats itself, the values will be appended to this array - * instead of overwriting the existing values. - * - * @return array|MDB2_Error data on success, a MDB2 error on failure - * @access public - */ - function getAssoc($query, $types = null, $params = array(), $param_types = null, - $fetchmode = MDB2_FETCHMODE_DEFAULT, $force_array = false, $group = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - settype($params, 'array'); - if (empty($params)) { - return $db->queryAll($query, $types, $fetchmode, true, $force_array, $group); - } - - $stmt = $db->prepare($query, $param_types, $types); - if (PEAR::isError($stmt)) { - return $stmt; - } - - $result = $stmt->execute($params); - if (!MDB2::isResultCommon($result)) { - return $result; - } - - $all = $result->fetchAll($fetchmode, true, $force_array, $group); - $stmt->free(); - $result->free(); - return $all; - } - - // }}} - // {{{ executeMultiple() - - /** - * This function does several execute() calls on the same statement handle. - * $params must be an array indexed numerically from 0, one execute call is - * done for every 'row' in the array. - * - * If an error occurs during execute(), executeMultiple() does not execute - * the unfinished rows, but rather returns that error. - * - * @param resource query handle from prepare() - * @param array numeric array containing the data to insert into the query - * - * @return bool|MDB2_Error true on success, a MDB2 error on failure - * @access public - * @see prepare(), execute() - */ - function executeMultiple(&$stmt, $params = null) - { - for ($i = 0, $j = count($params); $i < $j; $i++) { - $result = $stmt->execute($params[$i]); - if (PEAR::isError($result)) { - return $result; - } - } - return MDB2_OK; - } - - // }}} - // {{{ getBeforeID() - - /** - * Returns the next free id of a sequence if the RDBMS - * does not support auto increment - * - * @param string name of the table into which a new row was inserted - * @param string name of the field into which a new row was inserted - * @param bool when true the sequence is automatic created, if it not exists - * @param bool if the returned value should be quoted - * - * @return int|MDB2_Error id on success, a MDB2 error on failure - * @access public - */ - function getBeforeID($table, $field = null, $ondemand = true, $quote = true) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if ($db->supports('auto_increment') !== true) { - $seq = $table.(empty($field) ? '' : '_'.$field); - $id = $db->nextID($seq, $ondemand); - if (!$quote || PEAR::isError($id)) { - return $id; - } - return $db->quote($id, 'integer'); - } elseif (!$quote) { - return null; - } - return 'NULL'; - } - - // }}} - // {{{ getAfterID() - - /** - * Returns the autoincrement ID if supported or $id - * - * @param mixed value as returned by getBeforeId() - * @param string name of the table into which a new row was inserted - * @param string name of the field into which a new row was inserted - * - * @return int|MDB2_Error id on success, a MDB2 error on failure - * @access public - */ - function getAfterID($id, $table, $field = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if ($db->supports('auto_increment') !== true) { - return $id; - } - return $db->lastInsertID($table, $field); - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: Extended.php 302784 2010-08-25 23:29:16Z quipo $ + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Used by autoPrepare() + */ +define('MDB2_AUTOQUERY_INSERT', 1); +define('MDB2_AUTOQUERY_UPDATE', 2); +define('MDB2_AUTOQUERY_DELETE', 3); +define('MDB2_AUTOQUERY_SELECT', 4); + +/** + * MDB2_Extended: class which adds several high level methods to MDB2 + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Extended extends MDB2_Module_Common +{ + // {{{ autoPrepare() + + /** + * Generate an insert, update or delete query and call prepare() on it + * + * @param string table + * @param array the fields names + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * @param array that contains the types of the placeholders + * @param mixed array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * + * @return resource handle for the query + * @see buildManipSQL + * @access public + */ + function autoPrepare($table, $table_fields, $mode = MDB2_AUTOQUERY_INSERT, + $where = false, $types = null, $result_types = MDB2_PREPARE_MANIP) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (PEAR::isError($query)) { + return $query; + } + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $lobs = array(); + foreach ((array)$types as $param => $type) { + if (($type == 'clob') || ($type == 'blob')) { + $lobs[$param] = $table_fields[$param]; + } + } + return $db->prepare($query, $types, $result_types, $lobs); + } + + // }}} + // {{{ autoExecute() + + /** + * Generate an insert, update or delete query and call prepare() and execute() on it + * + * @param string name of the table + * @param array assoc ($key=>$value) where $key is a field name and $value its value + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * @param array that contains the types of the placeholders + * @param string which specifies which result class to use + * @param mixed array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * + * @return bool|MDB2_Error true on success, a MDB2 error on failure + * @see buildManipSQL + * @see autoPrepare + * @access public + */ + function autoExecute($table, $fields_values, $mode = MDB2_AUTOQUERY_INSERT, + $where = false, $types = null, $result_class = true, $result_types = MDB2_PREPARE_MANIP) + { + $fields_values = (array)$fields_values; + if ($mode == MDB2_AUTOQUERY_SELECT) { + if (is_array($result_types)) { + $keys = array_keys($result_types); + } elseif (!empty($fields_values)) { + $keys = $fields_values; + } else { + $keys = array(); + } + } else { + $keys = array_keys($fields_values); + } + $params = array_values($fields_values); + if (empty($params)) { + $query = $this->buildManipSQL($table, $keys, $mode, $where); + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if ($mode == MDB2_AUTOQUERY_SELECT) { + $result = $db->query($query, $result_types, $result_class); + } else { + $result = $db->exec($query); + } + } else { + $stmt = $this->autoPrepare($table, $keys, $mode, $where, $types, $result_types); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute($params, $result_class); + $stmt->free(); + } + return $result; + } + + // }}} + // {{{ buildManipSQL() + + /** + * Make automaticaly an sql query for prepare() + * + * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), MDB2_AUTOQUERY_INSERT) + * will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * NB : - This belongs more to a SQL Builder class, but this is a simple facility + * - Be carefull ! If you don't give a $where param with an UPDATE/DELETE query, all + * the records of the table will be updated/deleted ! + * + * @param string name of the table + * @param ordered array containing the fields names + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * + * @return string sql query for prepare() + * @access public + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($db->options['quote_identifier']) { + $table = $db->quoteIdentifier($table); + } + + if (!empty($table_fields) && $db->options['quote_identifier']) { + foreach ($table_fields as $key => $field) { + $table_fields[$key] = $db->quoteIdentifier($field); + } + } + + if ((false !== $where) && (null !== $where)) { + if (is_array($where)) { + $where = implode(' AND ', $where); + } + $where = ' WHERE '.$where; + } + + switch ($mode) { + case MDB2_AUTOQUERY_INSERT: + if (empty($table_fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Insert requires table fields', __FUNCTION__); + } + $cols = implode(', ', $table_fields); + $values = '?'.str_repeat(', ?', (count($table_fields) - 1)); + return 'INSERT INTO '.$table.' ('.$cols.') VALUES ('.$values.')'; + break; + case MDB2_AUTOQUERY_UPDATE: + if (empty($table_fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Update requires table fields', __FUNCTION__); + } + $set = implode(' = ?, ', $table_fields).' = ?'; + $sql = 'UPDATE '.$table.' SET '.$set.$where; + return $sql; + break; + case MDB2_AUTOQUERY_DELETE: + $sql = 'DELETE FROM '.$table.$where; + return $sql; + break; + case MDB2_AUTOQUERY_SELECT: + $cols = !empty($table_fields) ? implode(', ', $table_fields) : '*'; + $sql = 'SELECT '.$cols.' FROM '.$table.$where; + return $sql; + break; + } + return $db->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'Non existant mode', __FUNCTION__); + } + + // }}} + // {{{ limitQuery() + + /** + * Generates a limited query + * + * @param string query + * @param array that contains the types of the columns in the result set + * @param integer the numbers of rows to fetch + * @param integer the row to start to fetching + * @param string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * + * @return MDB2_Result|MDB2_Error result set on success, a MDB2 error on failure + * @access public + */ + function limitQuery($query, $types, $limit, $offset = 0, $result_class = true, + $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->setLimit($limit, $offset); + if (PEAR::isError($result)) { + return $result; + } + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ execParam() + + /** + * Execute a parameterized DML statement. + * + * @param string the SQL query + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * + * @return int|MDB2_Error affected rows on success, a MDB2 error on failure + * @access public + */ + function execParam($query, $params = array(), $param_types = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->exec($query); + } + + $stmt = $db->prepare($query, $param_types, MDB2_PREPARE_MANIP); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (PEAR::isError($result)) { + return $result; + } + + $stmt->free(); + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetch the first column of the first row of data returned from a query. + * Takes care of doing the query and freeing the results when finished. + * + * @param string the SQL query + * @param string that contains the type of the column in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int|string which column to return + * + * @return scalar|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getOne($query, $type = null, $params = array(), + $param_types = null, $colnum = 0) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + settype($type, 'array'); + if (empty($params)) { + return $db->queryOne($query, $type, $colnum); + } + + $stmt = $db->prepare($query, $param_types, $type); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $one = $result->fetchOne($colnum); + $stmt->free(); + $result->free(); + return $one; + } + + // }}} + // {{{ getRow() + + /** + * Fetch the first row of data returned from a query. Takes care + * of doing the query and freeing the results when finished. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int the fetch mode to use + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getRow($query, $types = null, $params = array(), + $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryRow($query, $types, $fetchmode); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $row = $result->fetchRow($fetchmode); + $stmt->free(); + $result->free(); + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetch a single column from a result set and return it as an + * indexed array. + * + * @param string the SQL query + * @param string that contains the type of the column in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int|string which column to return + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getCol($query, $type = null, $params = array(), + $param_types = null, $colnum = 0) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + settype($type, 'array'); + if (empty($params)) { + return $db->queryCol($query, $type, $colnum); + } + + $stmt = $db->prepare($query, $param_types, $type); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $col = $result->fetchCol($colnum); + $stmt->free(); + $result->free(); + return $col; + } + + // }}} + // {{{ getAll() + + /** + * Fetch all the rows returned from a query. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int the fetch mode to use + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool $force_array used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool $group if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getAll($query, $types = null, $params = array(), + $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, + $rekey = false, $force_array = false, $group = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryAll($query, $types, $fetchmode, $rekey, $force_array, $group); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); + $stmt->free(); + $result->free(); + return $all; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetch the entire result set of a query and return it as an + * associative array using the first column as the key. + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A MDB2 error code is + * returned on errors. If the result set contains fewer than two + * columns, a MDB2_ERROR_TRUNCATED error is returned. + * + * For example, if the table 'mytable' contains: + *
+     *   ID      TEXT       DATE
+     * --------------------------------
+     *   1       'one'      944679408
+     *   2       'two'      944679408
+     *   3       'three'    944679408
+     * 
+ * Then the call getAssoc('SELECT id,text FROM mytable') returns: + *
+     *    array(
+     *      '1' => 'one',
+     *      '2' => 'two',
+     *      '3' => 'three',
+     *    )
+     * 
+ * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + *
+     *    array(
+     *      '1' => array('one', '944679408'),
+     *      '2' => array('two', '944679408'),
+     *      '3' => array('three', '944679408')
+     *    )
+     * 
+ * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + *
+     * getAssoc('SELECT category,id,name FROM mytable', null, null
+     *           MDB2_FETCHMODE_ASSOC, false, true) returns:
+     *    array(
+     *      '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                   array('id' => '6', 'name' => 'number six')
+     *             ),
+     *      '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                   array('id' => '6', 'name' => 'number six')
+     *             )
+     *    )
+     * 
+ * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param bool $force_array used only when the query returns + * exactly two columns. If TRUE, the values of the returned array + * will be one-element arrays instead of scalars. + * @param bool $group if TRUE, the values of the returned array + * is wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getAssoc($query, $types = null, $params = array(), $param_types = null, + $fetchmode = MDB2_FETCHMODE_DEFAULT, $force_array = false, $group = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryAll($query, $types, $fetchmode, true, $force_array, $group); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, true, $force_array, $group); + $stmt->free(); + $result->free(); + return $all; + } + + // }}} + // {{{ executeMultiple() + + /** + * This function does several execute() calls on the same statement handle. + * $params must be an array indexed numerically from 0, one execute call is + * done for every 'row' in the array. + * + * If an error occurs during execute(), executeMultiple() does not execute + * the unfinished rows, but rather returns that error. + * + * @param resource query handle from prepare() + * @param array numeric array containing the data to insert into the query + * + * @return bool|MDB2_Error true on success, a MDB2 error on failure + * @access public + * @see prepare(), execute() + */ + function executeMultiple($stmt, $params = null) + { + for ($i = 0, $j = count($params); $i < $j; $i++) { + $result = $stmt->execute($params[$i]); + if (PEAR::isError($result)) { + return $result; + } + } + return MDB2_OK; + } + + // }}} + // {{{ getBeforeID() + + /** + * Returns the next free id of a sequence if the RDBMS + * does not support auto increment + * + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * @param bool when true the sequence is automatic created, if it not exists + * @param bool if the returned value should be quoted + * + * @return int|MDB2_Error id on success, a MDB2 error on failure + * @access public + */ + function getBeforeID($table, $field = null, $ondemand = true, $quote = true) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + $seq = $table.(empty($field) ? '' : '_'.$field); + $id = $db->nextID($seq, $ondemand); + if (!$quote || PEAR::isError($id)) { + return $id; + } + return $db->quote($id, 'integer'); + } elseif (!$quote) { + return null; + } + return 'NULL'; + } + + // }}} + // {{{ getAfterID() + + /** + * Returns the autoincrement ID if supported or $id + * + * @param mixed value as returned by getBeforeId() + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * + * @return int|MDB2_Error id on success, a MDB2 error on failure + * @access public + */ + function getAfterID($id, $table, $field = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + return $id; + } + return $db->lastInsertID($table, $field); + } + + // }}} +} ?> \ No newline at end of file diff --git a/3rdparty/MDB2/Iterator.php b/3rdparty/MDB2/Iterator.php index ca5e7b69c2..8d31919b6d 100644 --- a/3rdparty/MDB2/Iterator.php +++ b/3rdparty/MDB2/Iterator.php @@ -1,259 +1,259 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Iterator.php,v 1.22 2006/05/06 14:03:41 lsmith Exp $ - -/** - * PHP5 Iterator - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Iterator implements Iterator -{ - protected $fetchmode; - protected $result; - protected $row; - - // {{{ constructor - - /** - * Constructor - */ - public function __construct($result, $fetchmode = MDB2_FETCHMODE_DEFAULT) - { - $this->result = $result; - $this->fetchmode = $fetchmode; - } - // }}} - - // {{{ seek() - - /** - * Seek forward to a specific row in a result set - * - * @param int number of the row where the data can be found - * - * @return void - * @access public - */ - public function seek($rownum) - { - $this->row = null; - if ($this->result) { - $this->result->seek($rownum); - } - } - // }}} - - // {{{ next() - - /** - * Fetch next row of data - * - * @return void - * @access public - */ - public function next() - { - $this->row = null; - } - // }}} - - // {{{ current() - - /** - * return a row of data - * - * @return void - * @access public - */ - public function current() - { - if (is_null($this->row)) { - $row = $this->result->fetchRow($this->fetchmode); - if (PEAR::isError($row)) { - $row = false; - } - $this->row = $row; - } - return $this->row; - } - // }}} - - // {{{ valid() - - /** - * Check if the end of the result set has been reached - * - * @return bool true/false, false is also returned on failure - * @access public - */ - public function valid() - { - return (bool)$this->current(); - } - // }}} - - // {{{ free() - - /** - * Free the internal resources associated with result. - * - * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid - * @access public - */ - public function free() - { - if ($this->result) { - return $this->result->free(); - } - $this->result = false; - $this->row = null; - return false; - } - // }}} - - // {{{ key() - - /** - * Returns the row number - * - * @return int|bool|MDB2_Error true on success, false|MDB2_Error if result is invalid - * @access public - */ - public function key() - { - if ($this->result) { - return $this->result->rowCount(); - } - return false; - } - // }}} - - // {{{ rewind() - - /** - * Seek to the first row in a result set - * - * @return void - * @access public - */ - public function rewind() - { - } - // }}} - - // {{{ destructor - - /** - * Destructor - */ - public function __destruct() - { - $this->free(); - } - // }}} -} - -/** - * PHP5 buffered Iterator - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_BufferedIterator extends MDB2_Iterator implements SeekableIterator -{ - // {{{ valid() - - /** - * Check if the end of the result set has been reached - * - * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid - * @access public - */ - public function valid() - { - if ($this->result) { - return $this->result->valid(); - } - return false; - } - // }}} - - // {{{count() - - /** - * Returns the number of rows in a result object - * - * @return int|MDB2_Error number of rows, false|MDB2_Error if result is invalid - * @access public - */ - public function count() - { - if ($this->result) { - return $this->result->numRows(); - } - return false; - } - // }}} - - // {{{ rewind() - - /** - * Seek to the first row in a result set - * - * @return void - * @access public - */ - public function rewind() - { - $this->seek(0); - } - // }}} -} - + | +// +----------------------------------------------------------------------+ +// +// $Id: Iterator.php 295586 2010-02-28 17:04:17Z quipo $ + +/** + * PHP5 Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Iterator implements Iterator +{ + protected $fetchmode; + protected $result; + protected $row; + + // {{{ constructor + + /** + * Constructor + */ + public function __construct($result, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $this->result = $result; + $this->fetchmode = $fetchmode; + } + // }}} + + // {{{ seek() + + /** + * Seek forward to a specific row in a result set + * + * @param int number of the row where the data can be found + * + * @return void + * @access public + */ + public function seek($rownum) + { + $this->row = null; + if ($this->result) { + $this->result->seek($rownum); + } + } + // }}} + + // {{{ next() + + /** + * Fetch next row of data + * + * @return void + * @access public + */ + public function next() + { + $this->row = null; + } + // }}} + + // {{{ current() + + /** + * return a row of data + * + * @return void + * @access public + */ + public function current() + { + if (null === $this->row) { + $row = $this->result->fetchRow($this->fetchmode); + if (PEAR::isError($row)) { + $row = false; + } + $this->row = $row; + } + return $this->row; + } + // }}} + + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return bool true/false, false is also returned on failure + * @access public + */ + public function valid() + { + return (bool)$this->current(); + } + // }}} + + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function free() + { + if ($this->result) { + return $this->result->free(); + } + $this->result = false; + $this->row = null; + return false; + } + // }}} + + // {{{ key() + + /** + * Returns the row number + * + * @return int|bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function key() + { + if ($this->result) { + return $this->result->rowCount(); + } + return false; + } + // }}} + + // {{{ rewind() + + /** + * Seek to the first row in a result set + * + * @return void + * @access public + */ + public function rewind() + { + } + // }}} + + // {{{ destructor + + /** + * Destructor + */ + public function __destruct() + { + $this->free(); + } + // }}} +} + +/** + * PHP5 buffered Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedIterator extends MDB2_Iterator implements SeekableIterator +{ + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function valid() + { + if ($this->result) { + return $this->result->valid(); + } + return false; + } + // }}} + + // {{{count() + + /** + * Returns the number of rows in a result object + * + * @return int|MDB2_Error number of rows, false|MDB2_Error if result is invalid + * @access public + */ + public function count() + { + if ($this->result) { + return $this->result->numRows(); + } + return false; + } + // }}} + + // {{{ rewind() + + /** + * Seek to the first row in a result set + * + * @return void + * @access public + */ + public function rewind() + { + $this->seek(0); + } + // }}} +} + ?> \ No newline at end of file diff --git a/3rdparty/MDB2/LOB.php b/3rdparty/MDB2/LOB.php index ae67224020..f7df13dc47 100644 --- a/3rdparty/MDB2/LOB.php +++ b/3rdparty/MDB2/LOB.php @@ -1,264 +1,264 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: LOB.php,v 1.34 2006/10/25 11:52:21 lsmith Exp $ - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - */ - -require_once('MDB2.php'); - -/** - * MDB2_LOB: user land stream wrapper implementation for LOB support - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_LOB -{ - /** - * contains the key to the global MDB2 instance array of the associated - * MDB2 instance - * - * @var integer - * @access protected - */ - var $db_index; - - /** - * contains the key to the global MDB2_LOB instance array of the associated - * MDB2_LOB instance - * - * @var integer - * @access protected - */ - var $lob_index; - - // {{{ stream_open() - - /** - * open stream - * - * @param string specifies the URL that was passed to fopen() - * @param string the mode used to open the file - * @param int holds additional flags set by the streams API - * @param string not used - * - * @return bool - * @access public - */ - function stream_open($path, $mode, $options, &$opened_path) - { - if (!preg_match('/^rb?\+?$/', $mode)) { - return false; - } - $url = parse_url($path); - if (empty($url['host'])) { - return false; - } - $this->db_index = (int)$url['host']; - if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - return false; - } - $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; - $this->lob_index = (int)$url['user']; - if (!isset($db->datatype->lobs[$this->lob_index])) { - return false; - } - return true; - } - // }}} - - // {{{ stream_read() - - /** - * read stream - * - * @param int number of bytes to read - * - * @return string - * @access public - */ - function stream_read($count) - { - if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; - $db->datatype->_retrieveLOB($db->datatype->lobs[$this->lob_index]); - - $data = $db->datatype->_readLOB($db->datatype->lobs[$this->lob_index], $count); - $length = strlen($data); - if ($length == 0) { - $db->datatype->lobs[$this->lob_index]['endOfLOB'] = true; - } - $db->datatype->lobs[$this->lob_index]['position'] += $length; - return $data; - } - } - // }}} - - // {{{ stream_write() - - /** - * write stream, note implemented - * - * @param string data - * - * @return int - * @access public - */ - function stream_write($data) - { - return 0; - } - // }}} - - // {{{ stream_tell() - - /** - * return the current position - * - * @return int current position - * @access public - */ - function stream_tell() - { - if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; - return $db->datatype->lobs[$this->lob_index]['position']; - } - } - // }}} - - // {{{ stream_eof() - - /** - * Check if stream reaches EOF - * - * @return bool - * @access public - */ - function stream_eof() - { - if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - return true; - } - - $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; - $result = $db->datatype->_endOfLOB($db->datatype->lobs[$this->lob_index]); - if (version_compare(phpversion(), "5.0", ">=") - && version_compare(phpversion(), "5.1", "<") - ) { - return !$result; - } - return $result; - } - // }}} - - // {{{ stream_seek() - - /** - * Seek stream, not implemented - * - * @param int offset - * @param int whence - * - * @return bool - * @access public - */ - function stream_seek($offset, $whence) - { - return false; - } - // }}} - - // {{{ stream_stat() - - /** - * return information about stream - * - * @access public - */ - function stream_stat() - { - if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; - return array( - 'db_index' => $this->db_index, - 'lob_index' => $this->lob_index, - ); - } - } - // }}} - - // {{{ stream_close() - - /** - * close stream - * - * @access public - */ - function stream_close() - { - if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { - $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; - if (isset($db->datatype->lobs[$this->lob_index])) { - $db->datatype->_destroyLOB($db->datatype->lobs[$this->lob_index]); - unset($db->datatype->lobs[$this->lob_index]); - } - } - } - // }}} -} - -// register streams wrapper -if (!stream_wrapper_register("MDB2LOB", "MDB2_LOB")) { - MDB2::raiseError(); - return false; -} - -?> + | +// +----------------------------------------------------------------------+ +// +// $Id: LOB.php 222350 2006-10-25 11:52:21Z lsmith $ + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +require_once 'MDB2.php'; + +/** + * MDB2_LOB: user land stream wrapper implementation for LOB support + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_LOB +{ + /** + * contains the key to the global MDB2 instance array of the associated + * MDB2 instance + * + * @var integer + * @access protected + */ + var $db_index; + + /** + * contains the key to the global MDB2_LOB instance array of the associated + * MDB2_LOB instance + * + * @var integer + * @access protected + */ + var $lob_index; + + // {{{ stream_open() + + /** + * open stream + * + * @param string specifies the URL that was passed to fopen() + * @param string the mode used to open the file + * @param int holds additional flags set by the streams API + * @param string not used + * + * @return bool + * @access public + */ + function stream_open($path, $mode, $options, &$opened_path) + { + if (!preg_match('/^rb?\+?$/', $mode)) { + return false; + } + $url = parse_url($path); + if (empty($url['host'])) { + return false; + } + $this->db_index = (int)$url['host']; + if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + return false; + } + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $this->lob_index = (int)$url['user']; + if (!isset($db->datatype->lobs[$this->lob_index])) { + return false; + } + return true; + } + // }}} + + // {{{ stream_read() + + /** + * read stream + * + * @param int number of bytes to read + * + * @return string + * @access public + */ + function stream_read($count) + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $db->datatype->_retrieveLOB($db->datatype->lobs[$this->lob_index]); + + $data = $db->datatype->_readLOB($db->datatype->lobs[$this->lob_index], $count); + $length = strlen($data); + if ($length == 0) { + $db->datatype->lobs[$this->lob_index]['endOfLOB'] = true; + } + $db->datatype->lobs[$this->lob_index]['position'] += $length; + return $data; + } + } + // }}} + + // {{{ stream_write() + + /** + * write stream, note implemented + * + * @param string data + * + * @return int + * @access public + */ + function stream_write($data) + { + return 0; + } + // }}} + + // {{{ stream_tell() + + /** + * return the current position + * + * @return int current position + * @access public + */ + function stream_tell() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + return $db->datatype->lobs[$this->lob_index]['position']; + } + } + // }}} + + // {{{ stream_eof() + + /** + * Check if stream reaches EOF + * + * @return bool + * @access public + */ + function stream_eof() + { + if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + return true; + } + + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $result = $db->datatype->_endOfLOB($db->datatype->lobs[$this->lob_index]); + if (version_compare(phpversion(), "5.0", ">=") + && version_compare(phpversion(), "5.1", "<") + ) { + return !$result; + } + return $result; + } + // }}} + + // {{{ stream_seek() + + /** + * Seek stream, not implemented + * + * @param int offset + * @param int whence + * + * @return bool + * @access public + */ + function stream_seek($offset, $whence) + { + return false; + } + // }}} + + // {{{ stream_stat() + + /** + * return information about stream + * + * @access public + */ + function stream_stat() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + return array( + 'db_index' => $this->db_index, + 'lob_index' => $this->lob_index, + ); + } + } + // }}} + + // {{{ stream_close() + + /** + * close stream + * + * @access public + */ + function stream_close() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + if (isset($db->datatype->lobs[$this->lob_index])) { + $db->datatype->_destroyLOB($db->datatype->lobs[$this->lob_index]); + unset($db->datatype->lobs[$this->lob_index]); + } + } + } + // }}} +} + +// register streams wrapper +if (!stream_wrapper_register("MDB2LOB", "MDB2_LOB")) { + MDB2::raiseError(); + return false; +} + +?> diff --git a/3rdparty/MDB2/Schema.php b/3rdparty/MDB2/Schema.php index 25818460a6..0373970181 100644 --- a/3rdparty/MDB2/Schema.php +++ b/3rdparty/MDB2/Schema.php @@ -51,7 +51,7 @@ * @link http://pear.php.net/packages/MDB2_Schema */ -// require_once('MDB2.php'); +require_once 'MDB2.php'; define('MDB2_SCHEMA_DUMP_ALL', 0); define('MDB2_SCHEMA_DUMP_STRUCTURE', 1); @@ -237,9 +237,10 @@ class MDB2_Schema extends PEAR * @access public * @see MDB2::parseDSN */ - static function factory(&$db, $options = array()) + function &factory(&$db, $options = array()) { - $obj =new MDB2_Schema(); + $obj =& new MDB2_Schema(); + $result = $obj->connect($db, $options); if (PEAR::isError($result)) { return $result; @@ -280,14 +281,16 @@ class MDB2_Schema extends PEAR } } } + $this->disconnect(); if (!MDB2::isConnection($db)) { - $db =MDB2::factory($db, $db_options); + $db =& MDB2::factory($db, $db_options); } if (PEAR::isError($db)) { return $db; } + $this->db =& $db; $this->db->loadModule('Datatype'); $this->db->loadModule('Manager'); @@ -377,7 +380,7 @@ class MDB2_Schema extends PEAR $dtd_file = $this->options['dtd_file']; if ($dtd_file) { include_once 'XML/DTD/XmlValidator.php'; - $dtd =new XML_DTD_XmlValidator; + $dtd =& new XML_DTD_XmlValidator; if (!$dtd->isValid($dtd_file, $input_file)) { return $this->raiseError(MDB2_SCHEMA_ERROR_PARSE, null, null, $dtd->getMessage()); } @@ -390,7 +393,7 @@ class MDB2_Schema extends PEAR return $result; } - $parser =new $class_name($variables, $fail_on_invalid_names, $structure, $this->options['valid_types'], $this->options['force_defaults']); + $parser =& new $class_name($variables, $fail_on_invalid_names, $structure, $this->options['valid_types'], $this->options['force_defaults']); $result = $parser->setInputFile($input_file); if (PEAR::isError($result)) { return $result; @@ -425,6 +428,7 @@ class MDB2_Schema extends PEAR return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, 'it was not specified a valid database name'); } + $class_name = $this->options['validate']; $result = MDB2::loadClass($class_name, $this->db->getOption('debug')); @@ -432,7 +436,7 @@ class MDB2_Schema extends PEAR return $result; } - $val =new $class_name($this->options['fail_on_invalid_names'], $this->options['valid_types'], $this->options['force_defaults']); + $val =& new $class_name($this->options['fail_on_invalid_names'], $this->options['valid_types'], $this->options['force_defaults']); $database_definition = array( 'name' => $database, @@ -1338,15 +1342,15 @@ class MDB2_Schema extends PEAR if ($dbExists) { $this->db->debug('Database already exists: ' . $db_name, __FUNCTION__); -// if (!empty($dbOptions)) { -// $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NO_PERMISSION); -// $this->db->expectError($errorcodes); -// $result = $this->db->manager->alterDatabase($db_name, $dbOptions); -// $this->db->popExpect(); -// if (PEAR::isError($result) && !MDB2::isError($result, $errorcodes)) { -// return $result; -// } -// } + if (!empty($dbOptions)) { + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NO_PERMISSION); + $this->db->expectError($errorcodes); + $result = $this->db->manager->alterDatabase($db_name, $dbOptions); + $this->db->popExpect(); + if (PEAR::isError($result) && !MDB2::isError($result, $errorcodes)) { + return $result; + } + } $create = false; } else { $this->db->expectError(MDB2_ERROR_UNSUPPORTED); @@ -2444,7 +2448,7 @@ class MDB2_Schema extends PEAR } } - $writer = new $class_name($this->options['valid_types']); + $writer =& new $class_name($this->options['valid_types']); return $writer->dumpDatabase($database_definition, $arguments, $dump); } @@ -2692,9 +2696,9 @@ class MDB2_Schema extends PEAR * @access public * @see PEAR_Error */ - function raiseError($code = null, $mode = null, $options = null, $userinfo = null,$a=null,$b=null,$c=null) + function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) { - $err =PEAR::raiseError(null, $code, $mode, $options, + $err =& PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Schema_Error', true); return $err; } @@ -2713,7 +2717,7 @@ class MDB2_Schema extends PEAR * @return bool true if parameter is an error * @access public */ - static function isError($data, $code = null) + function isError($data, $code = null) { if (is_a($data, 'MDB2_Schema_Error')) { if (is_null($code)) { diff --git a/3rdparty/MDB2/Schema/Parser.php b/3rdparty/MDB2/Schema/Parser.php index b740ef55fa..9e8e74b631 100644 --- a/3rdparty/MDB2/Schema/Parser.php +++ b/3rdparty/MDB2/Schema/Parser.php @@ -54,8 +54,8 @@ */ -require_once('XML/Parser.php'); -require_once('MDB2/Schema/Validate.php'); +require_once 'XML/Parser.php'; +require_once 'MDB2/Schema/Validate.php'; /** * Parses an XML schema file @@ -120,11 +120,18 @@ class MDB2_Schema_Parser extends XML_Parser { // force ISO-8859-1 due to different defaults for PHP4 and PHP5 // todo: this probably needs to be investigated some more andcleaned up - parent::__construct('ISO-8859-1'); + parent::XML_Parser('ISO-8859-1'); $this->variables = $variables; $this->structure = $structure; - $this->val =new MDB2_Schema_Validate($fail_on_invalid_names, $valid_types, $force_defaults); + $this->val =& new MDB2_Schema_Validate($fail_on_invalid_names, $valid_types, $force_defaults); + } + + function MDB2_Schema_Parser($variables, $fail_on_invalid_names = true, + $structure = false, $valid_types = array(), + $force_defaults = true) + { + $this->__construct($variables, $fail_on_invalid_names, $structure, $valid_types, $force_defaults); } function startHandler($xp, $element, $attribs) @@ -496,7 +503,7 @@ class MDB2_Schema_Parser extends XML_Parser $this->element = implode('-', $this->elements); } - function raiseError($msg = null, $xmlecode = 0, $xp = null, $ecode = MDB2_SCHEMA_ERROR_PARSE,$a=null,$b=null,$c=null) + function &raiseError($msg = null, $xmlecode = 0, $xp = null, $ecode = MDB2_SCHEMA_ERROR_PARSE) { if (is_null($this->error)) { $error = ''; diff --git a/3rdparty/MDB2/Schema/Validate.php b/3rdparty/MDB2/Schema/Validate.php index 217cf51b95..21be024ce9 100644 --- a/3rdparty/MDB2/Schema/Validate.php +++ b/3rdparty/MDB2/Schema/Validate.php @@ -91,6 +91,11 @@ class MDB2_Schema_Validate $this->force_defaults = $force_defaults; } + function MDB2_Schema_Validate($fail_on_invalid_names = true, $valid_types = array(), $force_defaults = true) + { + $this->__construct($fail_on_invalid_names, $valid_types, $force_defaults); + } + // }}} // {{{ raiseError() diff --git a/3rdparty/OS/Guess.php b/3rdparty/OS/Guess.php new file mode 100644 index 0000000000..d3f2cc764e --- /dev/null +++ b/3rdparty/OS/Guess.php @@ -0,0 +1,338 @@ + + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Guess.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 + */ + +// {{{ uname examples + +// php_uname() without args returns the same as 'uname -a', or a PHP-custom +// string for Windows. +// PHP versions prior to 4.3 return the uname of the host where PHP was built, +// as of 4.3 it returns the uname of the host running the PHP code. +// +// PC RedHat Linux 7.1: +// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown +// +// PC Debian Potato: +// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown +// +// PC FreeBSD 3.3: +// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.3: +// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5 w/uname from GNU shellutils: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown +// +// HP 9000/712 HP-UX 10: +// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license +// +// HP 9000/712 HP-UX 10 w/uname from GNU shellutils: +// HP-UX host B.10.10 A 9000/712 unknown +// +// IBM RS6000/550 AIX 4.3: +// AIX host 3 4 000003531C00 +// +// AIX 4.3 w/uname from GNU shellutils: +// AIX host 3 4 000003531C00 unknown +// +// SGI Onyx IRIX 6.5 w/uname from GNU shellutils: +// IRIX64 host 6.5 01091820 IP19 mips +// +// SGI Onyx IRIX 6.5: +// IRIX64 host 6.5 01091820 IP19 +// +// SparcStation 20 Solaris 8 w/uname from GNU shellutils: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc +// +// SparcStation 20 Solaris 8: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20 +// +// Mac OS X (Darwin) +// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh +// +// Mac OS X early versions +// + +// }}} + +/* TODO: + * - define endianness, to allow matchSignature("bigend") etc. + */ + +/** + * Retrieves information about the current operating system + * + * This class uses php_uname() to grok information about the current OS + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class OS_Guess +{ + var $sysname; + var $nodename; + var $cpu; + var $release; + var $extra; + + function OS_Guess($uname = null) + { + list($this->sysname, + $this->release, + $this->cpu, + $this->extra, + $this->nodename) = $this->parseSignature($uname); + } + + function parseSignature($uname = null) + { + static $sysmap = array( + 'HP-UX' => 'hpux', + 'IRIX64' => 'irix', + ); + static $cpumap = array( + 'i586' => 'i386', + 'i686' => 'i386', + 'ppc' => 'powerpc', + ); + if ($uname === null) { + $uname = php_uname(); + } + $parts = preg_split('/\s+/', trim($uname)); + $n = count($parts); + + $release = $machine = $cpu = ''; + $sysname = $parts[0]; + $nodename = $parts[1]; + $cpu = $parts[$n-1]; + $extra = ''; + if ($cpu == 'unknown') { + $cpu = $parts[$n - 2]; + } + + switch ($sysname) { + case 'AIX' : + $release = "$parts[3].$parts[2]"; + break; + case 'Windows' : + switch ($parts[1]) { + case '95/98': + $release = '9x'; + break; + default: + $release = $parts[1]; + break; + } + $cpu = 'i386'; + break; + case 'Linux' : + $extra = $this->_detectGlibcVersion(); + // use only the first two digits from the kernel version + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + case 'Mac' : + $sysname = 'darwin'; + $nodename = $parts[2]; + $release = $parts[3]; + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + break; + case 'Darwin' : + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + default: + $release = preg_replace('/-.*/', '', $parts[2]); + break; + } + + if (isset($sysmap[$sysname])) { + $sysname = $sysmap[$sysname]; + } else { + $sysname = strtolower($sysname); + } + if (isset($cpumap[$cpu])) { + $cpu = $cpumap[$cpu]; + } + return array($sysname, $release, $cpu, $extra, $nodename); + } + + function _detectGlibcVersion() + { + static $glibc = false; + if ($glibc !== false) { + return $glibc; // no need to run this multiple times + } + $major = $minor = 0; + include_once "System.php"; + // Use glibc's header file to + // get major and minor version number: + if (@file_exists('/usr/include/features.h') && + @is_readable('/usr/include/features.h')) { + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + $features_file = fopen('/usr/include/features.h', 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$line || (strpos($line, '#define') === false)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; + } + continue; + } + + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { + break; + } + continue; + } + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return $glibc = ''; + } + return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; + } // no cpp + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line{0} == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + } // features.h + + if (!($major && $minor) && @is_link('/lib/libc.so.6')) { + // Let's try reading the libc.so.6 symlink + if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { + list($major, $minor) = explode('.', $matches[1]); + } + } + + if (!($major && $minor)) { + return $glibc = ''; + } + + return $glibc = "glibc{$major}.{$minor}"; + } + + function getSignature() + { + if (empty($this->extra)) { + return "{$this->sysname}-{$this->release}-{$this->cpu}"; + } + return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}"; + } + + function getSysname() + { + return $this->sysname; + } + + function getNodename() + { + return $this->nodename; + } + + function getCpu() + { + return $this->cpu; + } + + function getRelease() + { + return $this->release; + } + + function getExtra() + { + return $this->extra; + } + + function matchSignature($match) + { + $fragments = is_array($match) ? $match : explode('-', $match); + $n = count($fragments); + $matches = 0; + if ($n > 0) { + $matches += $this->_matchFragment($fragments[0], $this->sysname); + } + if ($n > 1) { + $matches += $this->_matchFragment($fragments[1], $this->release); + } + if ($n > 2) { + $matches += $this->_matchFragment($fragments[2], $this->cpu); + } + if ($n > 3) { + $matches += $this->_matchFragment($fragments[3], $this->extra); + } + return ($matches == $n); + } + + function _matchFragment($fragment, $value) + { + if (strcspn($fragment, '*?') < strlen($fragment)) { + $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + return preg_match($reg, $value); + } + return ($fragment == '*' || !strcasecmp($fragment, $value)); + } + +} +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ \ No newline at end of file diff --git a/3rdparty/PEAR-LICENSE b/3rdparty/PEAR-LICENSE new file mode 100644 index 0000000000..a00a2421fd --- /dev/null +++ b/3rdparty/PEAR-LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 1997-2009, + Stig Bakken , + Gregory Beaver , + Helgi Þormar Þorbjörnsson , + Tomas V.V.Cox , + Martin Jansen . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/PEAR.php b/3rdparty/PEAR.php index f832c0d491..2aa85259d6 100644 --- a/3rdparty/PEAR.php +++ b/3rdparty/PEAR.php @@ -1,26 +1,27 @@ | -// | Stig Bakken | -// | Tomas V.V.Cox | -// +--------------------------------------------------------------------+ -// -// $Id: PEAR.php,v 1.82.2.6 2005/01/01 05:24:51 cellog Exp $ -// +/** + * PEAR, the PHP Extension and Application Repository + * + * PEAR class and PEAR_Error class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Sterling Hughes + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2010 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PEAR.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/**#@+ + * ERROR constants + */ define('PEAR_ERROR_RETURN', 1); define('PEAR_ERROR_PRINT', 2); define('PEAR_ERROR_TRIGGER', 4); @@ -31,6 +32,7 @@ define('PEAR_ERROR_CALLBACK', 16); * @deprecated */ define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ define('PEAR_ZE2', (function_exists('version_compare') && version_compare(zend_version(), "2-dev", "ge"))); @@ -44,15 +46,6 @@ if (substr(PHP_OS, 0, 3) == 'WIN') { define('PEAR_OS', 'Unix'); // blatant assumption } -// instant backwards compatibility -if (!defined('PATH_SEPARATOR')) { - if (OS_WINDOWS) { - define('PATH_SEPARATOR', ';'); - } else { - define('PATH_SEPARATOR', ':'); - } -} - $GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; $GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; $GLOBALS['_PEAR_destructor_object_list'] = array(); @@ -78,14 +71,21 @@ $GLOBALS['_PEAR_error_handler_stack'] = array(); * IMPORTANT! To use the emulated destructors you need to create the * objects by reference: $obj =& new PEAR_child; * - * @since PHP 4.0.2 - * @author Stig Bakken - * @see http://pear.php.net/manual/ + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear */ class PEAR { - // {{{ properties - /** * Whether to enable internal debug messages. * @@ -136,10 +136,6 @@ class PEAR */ var $_expected_errors = array(); - // }}} - - // {{{ constructor - /** * Constructor. Registers this object in * $_PEAR_destructor_object_list for destructor emulation if a @@ -156,9 +152,11 @@ class PEAR if ($this->_debug) { print "PEAR constructor called, class=$classname\n"; } + if ($error_class !== null) { $this->_error_class = $error_class; } + while ($classname && strcasecmp($classname, "pear")) { $destructor = "_$classname"; if (method_exists($this, $destructor)) { @@ -175,9 +173,6 @@ class PEAR } } - // }}} - // {{{ destructor - /** * Destructor (the emulated type of...). Does nothing right now, * but is included for forward compatibility, so subclass @@ -195,9 +190,6 @@ class PEAR } } - // }}} - // {{{ getStaticProperty() - /** * If you have a class that's mostly/entirely static, and you need static * properties, you can use this method to simulate them. Eg. in your method(s) @@ -213,12 +205,17 @@ class PEAR function &getStaticProperty($class, $var) { static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + return $properties[$class][$var]; } - // }}} - // {{{ registerShutdownFunc() - /** * Use this function to register a shutdown method for static * classes. @@ -230,12 +227,15 @@ class PEAR */ function registerShutdownFunc($func, $args = array()) { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); } - // }}} - // {{{ isError() - /** * Tell whether a value is a PEAR error. * @@ -247,22 +247,20 @@ class PEAR * @access public * @return bool true if parameter is an error */ - static function isError($data, $code = null) + function isError($data, $code = null) { - if ($data instanceof PEAR_Error) { - if (is_null($code)) { - return true; - } elseif (is_string($code)) { - return $data->getMessage() == $code; - } else { - return $data->getCode() == $code; - } + if (!is_a($data, 'PEAR_Error')) { + return false; } - return false; - } - // }}} - // {{{ setErrorHandling() + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } /** * Sets how errors generated by this object should be handled. @@ -302,10 +300,9 @@ class PEAR * * @since PHP 4.0.5 */ - function setErrorHandling($mode = null, $options = null) { - if (isset($this) && $this instanceof PEAR) { + if (isset($this) && is_a($this, 'PEAR')) { $setmode = &$this->_default_error_mode; $setoptions = &$this->_default_error_options; } else { @@ -340,9 +337,6 @@ class PEAR } } - // }}} - // {{{ expectError() - /** * This method is used to tell which errors you expect to get. * Expected errors are always returned with error mode @@ -365,12 +359,9 @@ class PEAR } else { array_push($this->_expected_errors, array($code)); } - return sizeof($this->_expected_errors); + return count($this->_expected_errors); } - // }}} - // {{{ popExpect() - /** * This method pops one element off the expected error codes * stack. @@ -382,9 +373,6 @@ class PEAR return array_pop($this->_expected_errors); } - // }}} - // {{{ _checkDelExpect() - /** * This method checks unsets an error code if available * @@ -396,8 +384,7 @@ class PEAR function _checkDelExpect($error_code) { $deleted = false; - - foreach ($this->_expected_errors AS $key => $error_array) { + foreach ($this->_expected_errors as $key => $error_array) { if (in_array($error_code, $error_array)) { unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); $deleted = true; @@ -408,12 +395,10 @@ class PEAR unset($this->_expected_errors[$key]); } } + return $deleted; } - // }}} - // {{{ delExpect() - /** * This method deletes all occurences of the specified element from * the expected error codes stack. @@ -426,34 +411,26 @@ class PEAR function delExpect($error_code) { $deleted = false; - if ((is_array($error_code) && (0 != count($error_code)))) { - // $error_code is a non-empty array here; - // we walk through it trying to unset all - // values - foreach($error_code as $key => $error) { - if ($this->_checkDelExpect($error)) { - $deleted = true; - } else { - $deleted = false; - } + // $error_code is a non-empty array here; we walk through it trying + // to unset all values + foreach ($error_code as $key => $error) { + $deleted = $this->_checkDelExpect($error) ? true : false; } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME } elseif (!empty($error_code)) { // $error_code comes alone, trying to unset it if ($this->_checkDelExpect($error_code)) { return true; - } else { - return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME } - } else { - // $error_code is empty - return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME - } - } - // }}} - // {{{ raiseError() + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } /** * This method is a wrapper that returns an instance of the @@ -492,7 +469,7 @@ class PEAR * @see PEAR::setErrorHandling * @since PHP 4.0.5 */ - function raiseError($message = null, + function &raiseError($message = null, $code = null, $mode = null, $options = null, @@ -509,13 +486,20 @@ class PEAR $message = $message->getMessage(); } - if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ( + isset($this) && + isset($this->_expected_errors) && + count($this->_expected_errors) > 0 && + count($exp = end($this->_expected_errors)) + ) { if ($exp[0] == "*" || (is_int(reset($exp)) && in_array($code, $exp)) || - (is_string(reset($exp)) && in_array($message, $exp))) { + (is_string(reset($exp)) && in_array($message, $exp)) + ) { $mode = PEAR_ERROR_RETURN; } } + // No mode given, try global ones if ($mode === null) { // Class error handler @@ -536,38 +520,52 @@ class PEAR } else { $ec = 'PEAR_Error'; } - if ($skipmsg) { - return new $ec($code, $mode, $options, $userinfo); - } else { - return new $ec($message, $code, $mode, $options, $userinfo); - } - } - // }}} - // {{{ throwError() + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } /** * Simpler form of raiseError with fewer options. In most cases * message, code and userinfo are enough. * - * @param string $message + * @param mixed $message a text error message or a PEAR error object * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @access public + * @return object a PEAR error object + * @see PEAR::raiseError */ - function throwError($message = null, - $code = null, - $userinfo = null) + function &throwError($message = null, $code = null, $userinfo = null) { - if (isset($this) && $this instanceof PEAR) { - return $this->raiseError($message, $code, null, null, $userinfo); - } else { - return PEAR::raiseError($message, $code, null, null, $userinfo); + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; } + + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; } - // }}} function staticPushErrorHandling($mode, $options = null) { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $stack = &$GLOBALS['_PEAR_error_handler_stack']; $def_mode = &$GLOBALS['_PEAR_default_error_mode']; $def_options = &$GLOBALS['_PEAR_default_error_options']; $stack[] = array($def_mode, $def_options); @@ -636,8 +634,6 @@ class PEAR return true; } - // {{{ pushErrorHandling() - /** * Push a new error handler on top of the error handler options stack. With this * you can easily override the actual error handler for some code and restore @@ -653,7 +649,7 @@ class PEAR function pushErrorHandling($mode, $options = null) { $stack = &$GLOBALS['_PEAR_error_handler_stack']; - if (isset($this) && $this instanceof PEAR) { + if (isset($this) && is_a($this, 'PEAR')) { $def_mode = &$this->_default_error_mode; $def_options = &$this->_default_error_options; } else { @@ -662,7 +658,7 @@ class PEAR } $stack[] = array($def_mode, $def_options); - if (isset($this) && $this instanceof PEAR) { + if (isset($this) && is_a($this, 'PEAR')) { $this->setErrorHandling($mode, $options); } else { PEAR::setErrorHandling($mode, $options); @@ -671,9 +667,6 @@ class PEAR return true; } - // }}} - // {{{ popErrorHandling() - /** * Pop the last error handler used * @@ -687,7 +680,7 @@ class PEAR array_pop($stack); list($mode, $options) = $stack[sizeof($stack) - 1]; array_pop($stack); - if (isset($this) && $this instanceof PEAR) { + if (isset($this) && is_a($this, 'PEAR')) { $this->setErrorHandling($mode, $options); } else { PEAR::setErrorHandling($mode, $options); @@ -695,9 +688,6 @@ class PEAR return true; } - // }}} - // {{{ loadExtension() - /** * OS independant PHP extension load. Remember to take care * on the correct extension name for case sensitive OSes. @@ -707,31 +697,38 @@ class PEAR */ function loadExtension($ext) { - if (!extension_loaded($ext)) { - // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { - return false; - } - if (OS_WINDOWS) { - $suffix = '.dll'; - } elseif (PHP_OS == 'HP-UX') { - $suffix = '.sl'; - } elseif (PHP_OS == 'AIX') { - $suffix = '.a'; - } elseif (PHP_OS == 'OSX') { - $suffix = '.bundle'; - } else { - $suffix = '.so'; - } - return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + if (extension_loaded($ext)) { + return true; } - return true; - } - // }}} + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 || + ini_get('safe_mode') == 1 + ) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } } -// {{{ _PEAR_call_destructors() +if (PEAR_ZE2) { + include_once 'PEAR5.php'; +} function _PEAR_call_destructors() { @@ -740,9 +737,16 @@ function _PEAR_call_destructors() sizeof($_PEAR_destructor_object_list)) { reset($_PEAR_destructor_object_list); - if (@PEAR::getStaticProperty('PEAR', 'destructlifo')) { + if (PEAR_ZE2) { + $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); + } else { + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + } + + if ($destructLifoExists) { $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); } + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { $classname = get_class($objref); while ($classname) { @@ -761,19 +765,36 @@ function _PEAR_call_destructors() } // Now call the shutdown functions - if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + if ( + isset($GLOBALS['_PEAR_shutdown_funcs']) && + is_array($GLOBALS['_PEAR_shutdown_funcs']) && + !empty($GLOBALS['_PEAR_shutdown_funcs']) + ) { foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { call_user_func_array($value[0], $value[1]); } } } -// }}} - +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ class PEAR_Error { - // {{{ properties - var $error_message_prefix = ''; var $mode = PEAR_ERROR_RETURN; var $level = E_USER_NOTICE; @@ -782,9 +803,6 @@ class PEAR_Error var $userinfo = ''; var $backtrace = null; - // }}} - // {{{ constructor - /** * PEAR_Error constructor * @@ -815,11 +833,20 @@ class PEAR_Error $this->code = $code; $this->mode = $mode; $this->userinfo = $userinfo; - if (function_exists("debug_backtrace")) { - if (@!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { - $this->backtrace = debug_backtrace(); + + if (PEAR_ZE2) { + $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); + } else { + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + } + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); } } + if ($mode & PEAR_ERROR_CALLBACK) { $this->level = E_USER_NOTICE; $this->callback = $options; @@ -827,20 +854,25 @@ class PEAR_Error if ($options === null) { $options = E_USER_NOTICE; } + $this->level = $options; $this->callback = null; } + if ($this->mode & PEAR_ERROR_PRINT) { if (is_null($options) || is_int($options)) { $format = "%s"; } else { $format = $options; } + printf($format, $this->getMessage()); } + if ($this->mode & PEAR_ERROR_TRIGGER) { trigger_error($this->getMessage(), $this->level); } + if ($this->mode & PEAR_ERROR_DIE) { $msg = $this->getMessage(); if (is_null($options) || is_int($options)) { @@ -853,47 +885,39 @@ class PEAR_Error } die(sprintf($format, $msg)); } - if ($this->mode & PEAR_ERROR_CALLBACK) { - if (is_callable($this->callback)) { - call_user_func($this->callback, $this); - } + + if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) { + call_user_func($this->callback, $this); } + if ($this->mode & PEAR_ERROR_EXCEPTION) { - trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_ErrorStack for exceptions", E_USER_WARNING); - eval('$e = new Exception($this->message, $this->code);$e->PEAR_Error = $this;throw($e);'); + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); } } - // }}} - // {{{ getMode() - /** * Get the error mode from an error object. * * @return int error mode * @access public */ - function getMode() { + function getMode() + { return $this->mode; } - // }}} - // {{{ getCallback() - /** * Get the callback function/method from an error object. * * @return mixed callback function or object/method array * @access public */ - function getCallback() { + function getCallback() + { return $this->callback; } - // }}} - // {{{ getMessage() - - /** * Get the error message from an error object. * @@ -905,10 +929,6 @@ class PEAR_Error return ($this->error_message_prefix . $this->message); } - - // }}} - // {{{ getCode() - /** * Get error code from an error object * @@ -920,9 +940,6 @@ class PEAR_Error return $this->code; } - // }}} - // {{{ getType() - /** * Get the name of this error/exception. * @@ -934,9 +951,6 @@ class PEAR_Error return get_class($this); } - // }}} - // {{{ getUserInfo() - /** * Get additional user-supplied information. * @@ -948,9 +962,6 @@ class PEAR_Error return $this->userinfo; } - // }}} - // {{{ getDebugInfo() - /** * Get additional debug information supplied by the application. * @@ -962,9 +973,6 @@ class PEAR_Error return $this->getUserInfo(); } - // }}} - // {{{ getBacktrace() - /** * Get the call backtrace from where the error was generated. * Supported with PHP 4.3.0 or newer. @@ -975,15 +983,15 @@ class PEAR_Error */ function getBacktrace($frame = null) { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } if ($frame === null) { return $this->backtrace; } return $this->backtrace[$frame]; } - // }}} - // {{{ addUserInfo() - function addUserInfo($info) { if (empty($this->userinfo)) { @@ -993,8 +1001,10 @@ class PEAR_Error } } - // }}} - // {{{ toString() + function __toString() + { + return $this->getMessage(); + } /** * Make a string representation of this object. @@ -1002,7 +1012,8 @@ class PEAR_Error * @return string a string with an object summary * @access public */ - function toString() { + function toString() + { $modes = array(); $levels = array(E_USER_NOTICE => 'notice', E_USER_WARNING => 'warning', @@ -1041,8 +1052,6 @@ class PEAR_Error $this->error_message_prefix, $this->userinfo); } - - // }}} } /* @@ -1052,4 +1061,3 @@ class PEAR_Error * c-basic-offset: 4 * End: */ -?> diff --git a/3rdparty/PEAR/Autoloader.php b/3rdparty/PEAR/Autoloader.php index de0278d619..0ed707ec84 100644 --- a/3rdparty/PEAR/Autoloader.php +++ b/3rdparty/PEAR/Autoloader.php @@ -1,29 +1,31 @@ + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Autoloader.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ + // /* vim: set expandtab tabstop=4 shiftwidth=4: */ -// +----------------------------------------------------------------------+ -// | PHP Version 5 | -// +----------------------------------------------------------------------+ -// | Copyright (c) 1997-2004 The PHP Group | -// +----------------------------------------------------------------------+ -// | This source file is subject to version 3.0 of the PHP license, | -// | that is bundled with this package in the file LICENSE, and is | -// | available through the world-wide-web at the following url: | -// | http://www.php.net/license/3_0.txt. | -// | If you did not receive a copy of the PHP license and are unable to | -// | obtain it through the world-wide-web, please send a note to | -// | license@php.net so we can mail you a copy immediately. | -// +----------------------------------------------------------------------+ -// | Author: Stig Bakken | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Autoloader.php,v 1.11 2004/02/27 02:21:29 cellog Exp $ if (!extension_loaded("overload")) { // die hard without ext/overload die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader"); } +/** + * Include for PEAR_Error and PEAR classes + */ require_once "PEAR.php"; /** @@ -38,7 +40,15 @@ require_once "PEAR.php"; * methods, an instance of each class providing separated methods is * stored and called every time the aggregated method is called. * - * @author Stig Sther Bakken + * @category pear + * @package PEAR + * @author Stig Bakken + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 */ class PEAR_Autoloader extends PEAR { diff --git a/3rdparty/PEAR/Builder.php b/3rdparty/PEAR/Builder.php index 4f6cc135d1..90f3a14555 100644 --- a/3rdparty/PEAR/Builder.php +++ b/3rdparty/PEAR/Builder.php @@ -1,47 +1,60 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Builder.php,v 1.16.2.3 2005/02/17 17:55:01 cellog Exp $ +/** + * PEAR_Builder for building PHP extensions (PECL packages) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Builder.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * + * TODO: log output parameters in PECL command line + * TODO: msdev path in configuration + */ +/** + * Needed for extending PEAR_Builder + */ require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; /** * Class to handle building (compiling) extensions. * - * @author Stig Sther Bakken + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since PHP 4.0.2 + * @see http://pear.php.net/manual/en/core.ppm.pear-builder.php */ class PEAR_Builder extends PEAR_Common { - // {{{ properties - var $php_api_version = 0; var $zend_module_api_no = 0; var $zend_extension_api_no = 0; var $extensions_built = array(); + /** + * @var string Used for reporting when it is not possible to pass function + * via extra parameter, e.g. log, msdevCallback + */ var $current_callback = null; // used for msdev builds var $_lastline = null; var $_firstline = null; - // }}} - // {{{ constructor /** * PEAR_Builder constructor. @@ -56,35 +69,48 @@ class PEAR_Builder extends PEAR_Common $this->setFrontendObject($ui); } - // }}} - - // {{{ _build_win32() - /** * Build an extension from source on windows. * requires msdev */ function _build_win32($descfile, $callback = null) { - if (PEAR::isError($info = $this->infoFromDescriptionFile($descfile))) { - return $info; + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + } else { + $pf = &new PEAR_PackageFile($this->config, $this->debug); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } } $dir = dirname($descfile); $old_cwd = getcwd(); - if (!@chdir($dir)) { + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { return $this->raiseError("could not chdir to $dir"); } + + // packages that were in a .tar have the packagefile in this directory + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (file_exists($dir) && is_dir($vdir)) { + if (!chdir($vdir)) { + return $this->raiseError("could not chdir to " . realpath($vdir)); + } + + $dir = getcwd(); + } + $this->log(2, "building in $dir"); - $dsp = $info['package'].'.dsp'; - if (!@is_file("$dir/$dsp")) { + $dsp = $pkg->getPackage().'.dsp'; + if (!file_exists("$dir/$dsp")) { return $this->raiseError("The DSP $dsp does not exist."); } // XXX TODO: make release build type configurable - $command = 'msdev '.$dsp.' /MAKE "'.$info['package']. ' - Release"'; + $command = 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage(). ' - Release"'; - $this->current_callback = $callback; $err = $this->_runCommand($command, array(&$this, 'msdevCallback')); if (PEAR::isError($err)) { return $err; @@ -93,12 +119,12 @@ class PEAR_Builder extends PEAR_Common // figure out the build platform and type $platform = 'Win32'; $buildtype = 'Release'; - if (preg_match('/.*?'.$info['package'].'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) { + if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) { $platform = $matches[1]; $buildtype = $matches[2]; } - if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/',$this->_lastline,$matches)) { + if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/', $this->_lastline, $matches)) { if ($matches[2]) { // there were errors in the build return $this->raiseError("There were errors during compilation."); @@ -115,18 +141,19 @@ class PEAR_Builder extends PEAR_Common // this regex depends on the build platform and type having been // correctly identified above. $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'. - $info['package'].'\s-\s'. + $pkg->getPackage().'\s-\s'. $platform.'\s'. $buildtype.'").*?'. '\/out:"(.*?)"/is'; - if ($dsptext && preg_match($regex,$dsptext,$matches)) { + if ($dsptext && preg_match($regex, $dsptext, $matches)) { // what we get back is a relative path to the output file itself. $outfile = realpath($matches[2]); } else { return $this->raiseError("Could not retrieve output information from $dsp."); } - if (@copy($outfile, "$dir/$out")) { + // realpath returns false if the file doesn't exist + if ($outfile && copy($outfile, "$dir/$out")) { $outfile = "$dir/$out"; } @@ -147,10 +174,9 @@ class PEAR_Builder extends PEAR_Common if (!$this->_firstline) $this->_firstline = $data; $this->_lastline = $data; + call_user_func($this->current_callback, $what, $data); } - // }}} - // {{{ _harventInstDir /** * @param string * @param string @@ -191,16 +217,13 @@ class PEAR_Builder extends PEAR_Common return $ret; } - // }}} - - // {{{ build() - /** * Build an extension from source. Runs "phpize" in the source * directory, but compiles in a temporary directory - * (/var/tmp/pear-build-USER/PACKAGE-VERSION). + * (TMPDIR/pear-build-USER/PACKAGE-VERSION). * - * @param string $descfile path to XML package description file + * @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or + * a PEAR_PackageFile object * * @param mixed $callback callback function used to report output, * see PEAR_Builder::_runCommand for details @@ -216,48 +239,97 @@ class PEAR_Builder extends PEAR_Common * @access public * * @see PEAR_Builder::_runCommand - * @see PEAR_Common::infoFromDescriptionFile */ function build($descfile, $callback = null) { - if (PEAR_OS == "Windows") { - return $this->_build_win32($descfile,$callback); + if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/', + $this->config->get('php_bin'), $matches)) { + if (isset($matches[2]) && strlen($matches[2]) && + trim($matches[2]) != trim($this->config->get('php_prefix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a prefix ' . $matches[2] . ', but' . + ' config variable php_prefix does not match'); + } + + if (isset($matches[3]) && strlen($matches[3]) && + trim($matches[3]) != trim($this->config->get('php_suffix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a suffix ' . $matches[3] . ', but' . + ' config variable php_suffix does not match'); + } } + + $this->current_callback = $callback; + if (PEAR_OS == "Windows") { + return $this->_build_win32($descfile, $callback); + } + if (PEAR_OS != 'Unix') { return $this->raiseError("building extensions not supported on this platform"); } - if (PEAR::isError($info = $this->infoFromDescriptionFile($descfile))) { - return $info; + + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + if (is_a($pkg, 'PEAR_PackageFile_v1')) { + $dir = dirname($descfile); + } else { + $dir = $pkg->_config->get('temp_dir') . '/' . $pkg->getName(); + // automatically delete at session end + $this->addTempFile($dir); + } + } else { + $pf = &new PEAR_PackageFile($this->config); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + $dir = dirname($descfile); } - $dir = dirname($descfile); + + // Find config. outside of normal path - e.g. config.m4 + foreach (array_keys($pkg->getInstallationFileList()) as $item) { + if (stristr(basename($item), 'config.m4') && dirname($item) != '.') { + $dir .= DIRECTORY_SEPARATOR . dirname($item); + break; + } + } + $old_cwd = getcwd(); - if (!@chdir($dir)) { + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { return $this->raiseError("could not chdir to $dir"); } - $vdir = "$info[package]-$info[version]"; + + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); if (is_dir($vdir)) { chdir($vdir); } + $dir = getcwd(); $this->log(2, "building in $dir"); - $this->current_callback = $callback; putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH')); - $err = $this->_runCommand("phpize", array(&$this, 'phpizeCallback')); + $err = $this->_runCommand($this->config->get('php_prefix') + . "phpize" . + $this->config->get('php_suffix'), + array(&$this, 'phpizeCallback')); if (PEAR::isError($err)) { return $err; } + if (!$err) { return $this->raiseError("`phpize' failed"); } // {{{ start of interactive part $configure_command = "$dir/configure"; - if (isset($info['configure_options'])) { - foreach ($info['configure_options'] as $o) { + $configure_options = $pkg->getConfigureOptions(); + if ($configure_options) { + foreach ($configure_options as $o) { + $default = array_key_exists('default', $o) ? $o['default'] : null; list($r) = $this->ui->userDialog('build', array($o['prompt']), array('text'), - array(@$o['default'])); + array($default)); if (substr($o['name'], 0, 5) == 'with-' && ($r == 'yes' || $r == 'autodetect')) { $configure_command .= " --$o[name]"; @@ -269,40 +341,41 @@ class PEAR_Builder extends PEAR_Common // }}} end of interactive part // FIXME make configurable - if(!$user=getenv('USER')){ + if (!$user=getenv('USER')) { $user='defaultuser'; } - $build_basedir = "/var/tmp/pear-build-$user"; - $build_dir = "$build_basedir/$info[package]-$info[version]"; - $inst_dir = "$build_basedir/install-$info[package]-$info[version]"; + + $tmpdir = $this->config->get('temp_dir'); + $build_basedir = System::mktemp(' -t "' . $tmpdir . '" -d "pear-build-' . $user . '"'); + $build_dir = "$build_basedir/$vdir"; + $inst_dir = "$build_basedir/install-$vdir"; $this->log(1, "building in $build_dir"); if (is_dir($build_dir)) { - System::rm('-rf', $build_dir); + System::rm(array('-rf', $build_dir)); } + if (!System::mkDir(array('-p', $build_dir))) { return $this->raiseError("could not create build dir: $build_dir"); } + $this->addTempFile($build_dir); if (!System::mkDir(array('-p', $inst_dir))) { return $this->raiseError("could not create temporary install dir: $inst_dir"); } $this->addTempFile($inst_dir); - if (getenv('MAKE')) { - $make_command = getenv('MAKE'); - } else { - $make_command = 'make'; - } + $make_command = getenv('MAKE') ? getenv('MAKE') : 'make'; + $to_run = array( $configure_command, $make_command, "$make_command INSTALL_ROOT=\"$inst_dir\" install", - "find \"$inst_dir\" -ls" + "find \"$inst_dir\" | xargs ls -dils" ); - if (!@chdir($build_dir)) { + if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) { return $this->raiseError("could not chdir to $build_dir"); } - putenv('PHP_PEAR_VERSION=@PEAR-VER@'); + putenv('PHP_PEAR_VERSION=1.9.4'); foreach ($to_run as $cmd) { $err = $this->_runCommand($cmd, $callback); if (PEAR::isError($err)) { @@ -319,15 +392,14 @@ class PEAR_Builder extends PEAR_Common return $this->raiseError("no `modules' directory found"); } $built_files = array(); - $prefix = exec("php-config --prefix"); + $prefix = exec($this->config->get('php_prefix') + . "php-config" . + $this->config->get('php_suffix') . " --prefix"); $this->_harvestInstDir($prefix, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files); chdir($old_cwd); return $built_files; } - // }}} - // {{{ phpizeCallback() - /** * Message callback function used when running the "phpize" * program. Extracts the API numbers used. Ignores other message @@ -361,9 +433,6 @@ class PEAR_Builder extends PEAR_Common } } - // }}} - // {{{ _runCommand() - /** * Run an external command, using a message callback to report * output. The command will be run through popen and output is @@ -383,7 +452,7 @@ class PEAR_Builder extends PEAR_Common function _runCommand($command, $callback = null) { $this->log(1, "running: $command"); - $pp = @popen("$command 2>&1", "r"); + $pp = popen("$command 2>&1", "r"); if (!$pp) { return $this->raiseError("failed to run `$command'"); } @@ -402,13 +471,11 @@ class PEAR_Builder extends PEAR_Common if ($callback && isset($olddbg)) { $callback[0]->debug = $olddbg; } - $exitcode = @pclose($pp); + + $exitcode = is_resource($pp) ? pclose($pp) : -1; return ($exitcode == 0); } - // }}} - // {{{ log() - function log($level, $msg) { if ($this->current_callback) { @@ -419,8 +486,4 @@ class PEAR_Builder extends PEAR_Common } return PEAR_Common::log($level, $msg); } - - // }}} -} - -?> +} \ No newline at end of file diff --git a/3rdparty/PEAR/ChannelFile.php b/3rdparty/PEAR/ChannelFile.php new file mode 100644 index 0000000000..f2c02ab42b --- /dev/null +++ b/3rdparty/PEAR/ChannelFile.php @@ -0,0 +1,1559 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ChannelFile.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR/ErrorStack.php'; +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/Common.php'; + +/** + * Error code if the channel.xml tag does not contain a valid version + */ +define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1); +/** + * Error code if the channel.xml tag version is not supported (version 1.0 is the only supported version, + * currently + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2); + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5); + +/**#@+ + * Validation errors + */ +/** + * Error code when channel name is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6); +/** + * Error code when channel name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7); +/** + * Error code when channel summary is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8); +/** + * Error code when channel summary is multi-line + */ +define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9); +/** + * Error code when channel server is missing for protocol + */ +define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10); +/** + * Error code when channel server is invalid for protocol + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11); +/** + * Error code when a mirror name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21); +/** + * Error code when a mirror type is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22); +/** + * Error code when an attempt is made to generate xml, but the parsed content is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID', 23); +/** + * Error code when an empty package name validate regex is passed in + */ +define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24); +/** + * Error code when a tag has no version + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27); +/** + * Error code when a tag has no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28); +/** + * Error code when a mirror does not exist but is called for in one of the set* + * methods. + */ +define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32); +/** + * Error code when a server port is not numeric + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33); +/** + * Error code when contains no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34); +/** + * Error code when contains no type attribute in a protocol definition + */ +define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35); +/** + * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel + */ +define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36); +/** + * Error code when ssl attribute is present and is not "yes" + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37); +/**#@-*/ + +/** + * Mirror types allowed. Currently only internet servers are recognized. + */ +$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] = array('server'); + + +/** + * The Channel handling class + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * Supported channel.xml versions, for parsing + * @var array + * @access private + */ + var $_supportedVersions = array('1.0'); + + /** + * Parsed channel information + * @var array + * @access private + */ + var $_channelInfo; + + /** + * index into the subchannels array, used for parsing xml + * @var int + * @access private + */ + var $_subchannelIndex; + + /** + * index into the mirrors array, used for parsing xml + * @var int + * @access private + */ + var $_mirrorIndex; + + /** + * Flag used to determine the validity of parsed content + * @var boolean + * @access private + */ + var $_isValid = false; + + function PEAR_ChannelFile() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = false; + } + + /** + * @return array + * @access protected + */ + function _getErrorMessage() + { + return + array( + PEAR_CHANNELFILE_ERROR_INVALID_VERSION => + 'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%', + PEAR_CHANNELFILE_ERROR_NO_VERSION => + 'No version number found in tag', + PEAR_CHANNELFILE_ERROR_NO_XML_EXT => + '%error%', + PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER => + 'Unable to create XML parser', + PEAR_CHANNELFILE_ERROR_PARSER_ERROR => + '%error%', + PEAR_CHANNELFILE_ERROR_NO_NAME => + 'Missing channel name', + PEAR_CHANNELFILE_ERROR_INVALID_NAME => + 'Invalid channel %tag% "%name%"', + PEAR_CHANNELFILE_ERROR_NO_SUMMARY => + 'Missing channel summary', + PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY => + 'Channel summary should be on one line, but is multi-line', + PEAR_CHANNELFILE_ERROR_NO_HOST => + 'Missing channel server for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_HOST => + 'Server name "%server%" is invalid for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_MIRROR => + 'Invalid mirror name "%name%", mirror type %type%', + PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE => + 'Invalid mirror type "%type%"', + PEAR_CHANNELFILE_ERROR_INVALID => + 'Cannot generate xml, contents are invalid', + PEAR_CHANNELFILE_ERROR_EMPTY_REGEX => + 'packagenameregex cannot be empty', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION => + '%parent% %protocol% function has no version', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME => + '%parent% %protocol% function has no name', + PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE => + '%parent% rest baseurl has no type', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME => + 'Validation package has no name in tag', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION => + 'Validation package "%package%" has no version', + PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND => + 'Mirror "%mirror%" does not exist', + PEAR_CHANNELFILE_ERROR_INVALID_PORT => + 'Port "%port%" must be numeric', + PEAR_CHANNELFILE_ERROR_NO_STATICVERSION => + ' tag must contain version attribute', + PEAR_CHANNELFILE_URI_CANT_MIRROR => + 'The __uri pseudo-channel cannot have mirrors', + PEAR_CHANNELFILE_ERROR_INVALID_SSL => + '%server% has invalid ssl attribute "%ssl%" can only be yes or not present', + ); + } + + /** + * @param string contents of package.xml file + * @return bool success of parsing + */ + function fromXmlString($data) + { + if (preg_match('/_supportedVersions)) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error', + array('version' => $channelversion[1])); + return false; + } + $parser = new PEAR_XMLParser; + $result = $parser->parse($data); + if ($result !== true) { + if ($result->getCode() == 1) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error', + array('error' => $result->getMessage())); + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error'); + } + return false; + } + $this->_channelInfo = $parser->getData(); + return true; + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data)); + return false; + } + } + + /** + * @return array + */ + function toArray() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + return $this->_channelInfo; + } + + /** + * @param array + * @static + * @return PEAR_ChannelFile|false false if invalid + */ + function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + if (!$a->validate()) { + $a = false; + return $a; + } + return $a; + } + + /** + * Unlike {@link fromArray()} this does not do any validation + * @param array + * @static + * @return PEAR_ChannelFile + */ + function &fromArrayWithErrors($data, $compatibility = false, + $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + return $a; + } + + /** + * @param array + * @access private + */ + function _fromArray($data) + { + $this->_channelInfo = $data; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getErrors($purge = false) + { + return $this->_stack->getErrors($purge); + } + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * Parse a channel.xml file. Expects the name of + * a channel xml file as input. + * + * @param string $descfile name of channel xml file + * @return bool success of parsing + */ + function fromXmlFile($descfile) + { + if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) || + (!$fp = fopen($descfile, 'r'))) { + require_once 'PEAR.php'; + return PEAR::raiseError("Unable to open $descfile"); + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + return $this->fromXmlString($data); + } + + /** + * Parse channel information from different sources + * + * This method is able to extract information about a channel + * from an .xml file or a string + * + * @access public + * @param string Filename of the source or the source itself + * @return bool + */ + function fromAny($info) + { + if (is_string($info) && file_exists($info) && strlen($info) < 255) { + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = $this->fromXmlFile($info); + } else { + $fp = fopen($info, "r"); + $test = fread($fp, 5); + fclose($fp); + if ($test == "fromXmlFile($info); + } + } + if (PEAR::isError($info)) { + require_once 'PEAR.php'; + return PEAR::raiseError($info); + } + } + if (is_string($info)) { + $info = $this->fromXmlString($info); + } + return $info; + } + + /** + * Return an XML document based on previous parsing and modifications + * + * @return string XML data + * + * @access public + */ + function toXml() + { + if (!$this->_isValid && !$this->validate()) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID); + return false; + } + if (!isset($this->_channelInfo['attribs']['version'])) { + $this->_channelInfo['attribs']['version'] = '1.0'; + } + $channelInfo = $this->_channelInfo; + $ret = "\n"; + $ret .= " + $channelInfo[name] + " . htmlspecialchars($channelInfo['summary'])." +"; + if (isset($channelInfo['suggestedalias'])) { + $ret .= ' ' . $channelInfo['suggestedalias'] . "\n"; + } + if (isset($channelInfo['validatepackage'])) { + $ret .= ' ' . + htmlspecialchars($channelInfo['validatepackage']['_content']) . + "\n"; + } + $ret .= " \n"; + $ret .= ' _makeRestXml($channelInfo['servers']['primary']['rest'], ' '); + } + $ret .= " \n"; + if (isset($channelInfo['servers']['mirror'])) { + $ret .= $this->_makeMirrorsXml($channelInfo); + } + $ret .= " \n"; + $ret .= ""; + return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret)); + } + + /** + * Generate the tag + * @access private + */ + function _makeRestXml($info, $indent) + { + $ret = $indent . "\n"; + if (isset($info['baseurl']) && !isset($info['baseurl'][0])) { + $info['baseurl'] = array($info['baseurl']); + } + + if (isset($info['baseurl'])) { + foreach ($info['baseurl'] as $url) { + $ret .= "$indent \n"; + } + } + $ret .= $indent . "\n"; + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeMirrorsXml($channelInfo) + { + $ret = ""; + if (!isset($channelInfo['servers']['mirror'][0])) { + $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']); + } + foreach ($channelInfo['servers']['mirror'] as $mirror) { + $ret .= ' _makeRestXml($mirror['rest'], ' '); + } + $ret .= " \n"; + } else { + $ret .= "/>\n"; + } + } + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeFunctionsXml($functions, $indent, $rest = false) + { + $ret = ''; + if (!isset($functions[0])) { + $functions = array($functions); + } + foreach ($functions as $function) { + $ret .= "$indent\n"; + } + return $ret; + } + + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params); + } + + /** + * Validate parsed file. + * + * @access public + * @return boolean + */ + function validate() + { + $this->_isValid = true; + $info = $this->_channelInfo; + if (empty($info['name'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME); + } elseif (!$this->validChannelServer($info['name'])) { + if ($info['name'] != '__uri') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name', + 'name' => $info['name'])); + } + } + if (empty($info['summary'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (isset($info['suggestedalias'])) { + if (!$this->validChannelServer($info['suggestedalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias'])); + } + } + if (isset($info['localalias'])) { + if (!$this->validChannelServer($info['localalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'localalias', 'name' =>$info['localalias'])); + } + } + if (isset($info['validatepackage'])) { + if (!isset($info['validatepackage']['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME); + } + if (!isset($info['validatepackage']['attribs']['version'])) { + $content = isset($info['validatepackage']['_content']) ? + $info['validatepackage']['_content'] : + null; + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION, + array('package' => $content)); + } + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) && + !is_numeric($info['servers']['primary']['attribs']['port'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT, + array('port' => $info['servers']['primary']['attribs']['port'])); + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) && + $info['servers']['primary']['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['servers']['primary']['attribs']['ssl'], + 'server' => $info['name'])); + } + + if (isset($info['servers']['primary']['rest']) && + isset($info['servers']['primary']['rest']['baseurl'])) { + $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']); + } + if (isset($info['servers']['mirror'])) { + if ($this->_channelInfo['name'] == '__uri') { + $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR); + } + if (!isset($info['servers']['mirror'][0])) { + $info['servers']['mirror'] = array($info['servers']['mirror']); + } + foreach ($info['servers']['mirror'] as $mirror) { + if (!isset($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST, + array('type' => 'mirror')); + } elseif (!$this->validChannelServer($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST, + array('server' => $mirror['attribs']['host'], 'type' => 'mirror')); + } + if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host'])); + } + if (isset($mirror['rest'])) { + $this->_validateFunctions('rest', $mirror['rest']['baseurl'], + $mirror['attribs']['host']); + } + } + } + return $this->_isValid; + } + + /** + * @param string rest - protocol name this function applies to + * @param array the functions + * @param string the name of the parent element (mirror name, for instance) + */ + function _validateFunctions($protocol, $functions, $parent = '') + { + if (!isset($functions[0])) { + $functions = array($functions); + } + + foreach ($functions as $function) { + if (!isset($function['_content']) || empty($function['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME, + array('parent' => $parent, 'protocol' => $protocol)); + } + + if ($protocol == 'rest') { + if (!isset($function['attribs']['type']) || + empty($function['attribs']['type'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE, + array('parent' => $parent, 'protocol' => $protocol)); + } + } else { + if (!isset($function['attribs']['version']) || + empty($function['attribs']['version'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION, + array('parent' => $parent, 'protocol' => $protocol)); + } + } + } + } + + /** + * Test whether a string contains a valid channel server. + * @param string $ver the package version to test + * @return bool + */ + function validChannelServer($server) + { + if ($server == '__uri') { + return true; + } + return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server); + } + + /** + * @return string|false + */ + function getName() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return string|false + */ + function getServer() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return int|80 port number to connect to + */ + function getPort($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['port'])) { + return $mir['attribs']['port']; + } + + if ($this->getSSL($mirror)) { + return 443; + } + + return 80; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) { + return $this->_channelInfo['servers']['primary']['attribs']['port']; + } + + if ($this->getSSL()) { + return 443; + } + + return 80; + } + + /** + * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel + */ + function getSSL($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['ssl'])) { + return true; + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + return true; + } + + return false; + } + + /** + * @return string|false + */ + function getSummary() + { + if (isset($this->_channelInfo['summary'])) { + return $this->_channelInfo['summary']; + } + + return false; + } + + /** + * @param string protocol type + * @param string Mirror name + * @return array|false + */ + function getFunctions($protocol, $mirror = false) + { + if ($this->getName() == '__uri') { + return false; + } + + $function = $protocol == 'rest' ? 'baseurl' : 'function'; + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir[$protocol][$function])) { + return $mir[$protocol][$function]; + } + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) { + return $this->_channelInfo['servers']['primary'][$protocol][$function]; + } + + return false; + } + + /** + * @param string Protocol type + * @param string Function name (null to return the + * first protocol of the type requested) + * @param string Mirror name, if any + * @return array + */ + function getFunction($type, $name = null, $mirror = false) + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($name === null) { + return $protocol; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return $protocol; + } + + return false; + } + + /** + * @param string protocol type + * @param string protocol name + * @param string version + * @param string mirror name + * @return boolean + */ + function supports($type, $name = null, $mirror = false, $version = '1.0') + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($protocol['attribs']['version'] != $version) { + continue; + } + + if ($name === null) { + return true; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return true; + } + + return false; + } + + /** + * Determines whether a channel supports Representational State Transfer (REST) protocols + * for retrieving channel information + * @param string + * @return bool + */ + function supportsREST($mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + return isset($mir['rest']); + } + + return false; + } + + return isset($this->_channelInfo['servers']['primary']['rest']); + } + + /** + * Get the URL to access a base resource. + * + * Hyperlinks in the returned xml will be used to retrieve the proper information + * needed. This allows extreme extensibility and flexibility in implementation + * @param string Resource Type to retrieve + */ + function getBaseURL($resourceType, $mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + $mir = $this->getMirror($mirror); + if (!$mir) { + return false; + } + + $rest = $mir['rest']; + } else { + $rest = $this->_channelInfo['servers']['primary']['rest']; + } + + if (!isset($rest['baseurl'][0])) { + $rest['baseurl'] = array($rest['baseurl']); + } + + foreach ($rest['baseurl'] as $baseurl) { + if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) { + return $baseurl['_content']; + } + } + + return false; + } + + /** + * Since REST does not implement RPC, provide this as a logical wrapper around + * resetFunctions for REST + * @param string|false mirror name, if any + */ + function resetREST($mirror = false) + { + return $this->resetFunctions('rest', $mirror); + } + + /** + * Empty all protocol definitions + * @param string protocol type + * @param string|false mirror name, if any + */ + function resetFunctions($type, $mirror = false) + { + if ($mirror) { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + foreach ($mirrors as $i => $mir) { + if ($mir['attribs']['host'] == $mirror) { + if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) { + unset($this->_channelInfo['servers']['mirror'][$i][$type]); + } + + return true; + } + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$type])) { + unset($this->_channelInfo['servers']['primary'][$type]); + } + + return true; + } + + /** + * Set a channel's protocols to the protocols supported by pearweb + */ + function setDefaultPEARProtocols($version = '1.0', $mirror = false) + { + switch ($version) { + case '1.0' : + $this->resetREST($mirror); + + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array('rest' => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array('rest' => array()); + } + + return true; + break; + default : + return false; + break; + } + } + + /** + * @return array + */ + function getMirrors() + { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + return $mirrors; + } + + return array(); + } + + /** + * Get the unserialized XML representing a mirror + * @return array|false + */ + function getMirror($server) + { + foreach ($this->getMirrors() as $mirror) { + if ($mirror['attribs']['host'] == $server) { + return $mirror; + } + } + + return false; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_NAME + * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME + */ + function setName($name) + { + return $this->setServer($name); + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param integer + * @param string|false name of the mirror server, or false for the primary + */ + function setPort($port, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port; + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + } + + $this->_channelInfo['servers']['primary']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param bool Determines whether to turn on SSL support or turn it off + * @param string|false name of the mirror server, or false for the primary + */ + function setSSL($ssl = true, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror'][$i] + ['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes'; + } + + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes'; + } + + $this->_isValid = false; + return true; + } + } + + if ($ssl) { + $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes'; + } else { + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['primary']['attribs']['ssl']); + } + } + + $this->_isValid = false; + return true; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_SERVER + * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER + */ + function setServer($server, $mirror = false) + { + if (empty($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER); + return false; + } elseif (!$this->validChannelServer($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'name', 'name' => $server)); + return false; + } + + if ($mirror) { + $found = false; + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $found = true; + break; + } + } + + if (!$found) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server; + return true; + } + + $this->_channelInfo['name'] = $server; + return true; + } + + /** + * @param string + * @return boolean success + * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY + * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY + */ + function setSummary($summary) + { + if (empty($summary)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + return false; + } elseif (strpos(trim($summary), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $summary)); + } + + $this->_channelInfo['summary'] = $summary; + return true; + } + + /** + * @param string + * @param boolean determines whether the alias is in channel.xml or local + * @return boolean success + */ + function setAlias($alias, $local = false) + { + if (!$this->validChannelServer($alias)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' => $alias)); + return false; + } + + if ($local) { + $this->_channelInfo['localalias'] = $alias; + } else { + $this->_channelInfo['suggestedalias'] = $alias; + } + + return true; + } + + /** + * @return string + */ + function getAlias() + { + if (isset($this->_channelInfo['localalias'])) { + return $this->_channelInfo['localalias']; + } + if (isset($this->_channelInfo['suggestedalias'])) { + return $this->_channelInfo['suggestedalias']; + } + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + return ''; + } + + /** + * Set the package validation object if it differs from PEAR's default + * The class must be includeable via changing _ in the classname to path separator, + * but no checking of this is made. + * @param string|false pass in false to reset to the default packagename regex + * @return boolean success + */ + function setValidationPackage($validateclass, $version) + { + if (empty($validateclass)) { + unset($this->_channelInfo['validatepackage']); + } + $this->_channelInfo['validatepackage'] = array('_content' => $validateclass); + $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version); + } + + /** + * Add a protocol to the provides section + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + * @param string mirror name, if this is a mirror's protocol + * @return bool + */ + function addFunction($type, $version, $name = '', $mirror = false) + { + if ($mirror) { + return $this->addMirrorFunction($mirror, $type, $version, $name); + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) { + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array($type => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array($type => array()); + } + + $this->_channelInfo['servers']['primary'][$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) { + $this->_channelInfo['servers']['primary'][$type]['function'] = array( + $this->_channelInfo['servers']['primary'][$type]['function']); + } + + $this->_channelInfo['servers']['primary'][$type]['function'][] = $set; + return true; + } + /** + * Add a protocol to a mirror's provides section + * @param string mirror name (server) + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + */ + function addMirrorFunction($mirror, $type, $version, $name = '') + { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + + if (!$setmirror) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($setmirror[$type]['function'])) { + $setmirror[$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror[$type]['function'][0])) { + $setmirror[$type]['function'] = array($setmirror[$type]['function']); + } + + $setmirror[$type]['function'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string Resource Type this url links to + * @param string URL + * @param string|false mirror name, if this is not a primary server REST base URL + */ + function setBaseURL($resourceType, $url, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + } else { + $setmirror = &$this->_channelInfo['servers']['primary']; + } + + $set = array('attribs' => array('type' => $resourceType), '_content' => $url); + if (!isset($setmirror['rest'])) { + $setmirror['rest'] = array(); + } + + if (!isset($setmirror['rest']['baseurl'])) { + $setmirror['rest']['baseurl'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror['rest']['baseurl'][0])) { + $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']); + } + + foreach ($setmirror['rest']['baseurl'] as $i => $url) { + if ($url['attribs']['type'] == $resourceType) { + $this->_isValid = false; + $setmirror['rest']['baseurl'][$i] = $set; + return true; + } + } + + $setmirror['rest']['baseurl'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string mirror server + * @param int mirror http port + * @return boolean + */ + function addMirror($server, $port = null) + { + if ($this->_channelInfo['name'] == '__uri') { + return false; // the __uri channel cannot have mirrors by definition + } + + $set = array('attribs' => array('host' => $server)); + if (is_numeric($port)) { + $set['attribs']['port'] = $port; + } + + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_channelInfo['servers']['mirror'] = $set; + return true; + } + + if (!isset($this->_channelInfo['servers']['mirror'][0])) { + $this->_channelInfo['servers']['mirror'] = + array($this->_channelInfo['servers']['mirror']); + } + + $this->_channelInfo['servers']['mirror'][] = $set; + return true; + } + + /** + * Retrieve the name of the validation package for this channel + * @return string|false + */ + function getValidationPackage() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + + if (!isset($this->_channelInfo['validatepackage'])) { + return array('attribs' => array('version' => 'default'), + '_content' => 'PEAR_Validate'); + } + + return $this->_channelInfo['validatepackage']; + } + + /** + * Retrieve the object that can be used for custom validation + * @param string|false the name of the package to validate. If the package is + * the channel validation package, PEAR_Validate is returned + * @return PEAR_Validate|false false is returned if the validation package + * cannot be located + */ + function &getValidationObject($package = false) + { + if (!class_exists('PEAR_Validate')) { + require_once 'PEAR/Validate.php'; + } + + if (!$this->_isValid) { + if (!$this->validate()) { + $a = false; + return $a; + } + } + + if (isset($this->_channelInfo['validatepackage'])) { + if ($package == $this->_channelInfo['validatepackage']) { + // channel validation packages are always validated by PEAR_Validate + $val = &new PEAR_Validate; + return $val; + } + + if (!class_exists(str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']))) { + if ($this->isIncludeable(str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php')) { + include_once str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php'; + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } else { + $a = false; + return $a; + } + } else { + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } + } else { + $val = &new PEAR_Validate; + } + + return $val; + } + + function isIncludeable($path) + { + $possibilities = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($possibilities as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $path) + && is_readable($dir . DIRECTORY_SEPARATOR . $path)) { + return true; + } + } + + return false; + } + + /** + * This function is used by the channel updater and retrieves a value set by + * the registry, or the current time if it has not been set + * @return string + */ + function lastModified() + { + if (isset($this->_channelInfo['_lastmodified'])) { + return $this->_channelInfo['_lastmodified']; + } + + return time(); + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/ChannelFile/Parser.php b/3rdparty/PEAR/ChannelFile/Parser.php new file mode 100644 index 0000000000..e630ace224 --- /dev/null +++ b/3rdparty/PEAR/ChannelFile/Parser.php @@ -0,0 +1,68 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Parser.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/ChannelFile.php'; +/** + * Parser for channel.xml + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile_Parser extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + function parse($data, $file) + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new PEAR_ChannelFile; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + // make sure the filelist is in the easy to read format needed + $ret->flattenFilelist(); + $ret->setPackagefile($file, $archive); + return $ret; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command.php b/3rdparty/PEAR/Command.php index 2ea68743d2..db39b8f36f 100644 --- a/3rdparty/PEAR/Command.php +++ b/3rdparty/PEAR/Command.php @@ -1,25 +1,26 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Command.php,v 1.24 2004/05/16 15:43:30 pajoye Exp $ +/** + * PEAR_Command, command pattern class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Command.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ - -require_once "PEAR.php"; +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Frontend.php'; +require_once 'PEAR/XMLParser.php'; /** * List of commands and what classes they are implemented in. @@ -27,6 +28,12 @@ require_once "PEAR.php"; */ $GLOBALS['_PEAR_Command_commandlist'] = array(); +/** + * List of commands and their descriptions + * @var array command => description + */ +$GLOBALS['_PEAR_Command_commanddesc'] = array(); + /** * List of shortcuts to common commands. * @var array shortcut => command @@ -39,18 +46,6 @@ $GLOBALS['_PEAR_Command_shortcuts'] = array(); */ $GLOBALS['_PEAR_Command_objects'] = array(); -/** - * Which user interface class is being used. - * @var string class name - */ -$GLOBALS['_PEAR_Command_uiclass'] = 'PEAR_Frontend_CLI'; - -/** - * Instance of $_PEAR_Command_uiclass. - * @var object - */ -$GLOBALS['_PEAR_Command_uiobject'] = null; - /** * PEAR command class, a simple factory class for administrative * commands. @@ -93,6 +88,15 @@ $GLOBALS['_PEAR_Command_uiobject'] = null; * - DON'T USE EXIT OR DIE! Always use pear errors. From static * classes do PEAR::raiseError(), from other classes do * $this->raiseError(). + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Command { @@ -109,7 +113,7 @@ class PEAR_Command * @access public * @static */ - function factory($command, &$config) + function &factory($command, &$config) { if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); @@ -118,13 +122,35 @@ class PEAR_Command $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; } if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { - return PEAR::raiseError("unknown command `$command'"); + $a = PEAR::raiseError("unknown command `$command'"); + return $a; } $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $ui =& PEAR_Command::getFrontendObject(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getObject() + function &getObject($command) + { + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } if (!class_exists($class)) { return PEAR::raiseError("unknown command `$command'"); } $ui =& PEAR_Command::getFrontendObject(); + $config = &PEAR_Config::singleton(); $obj = &new $class($ui, $config); return $obj; } @@ -135,15 +161,13 @@ class PEAR_Command /** * Get instance of frontend object. * - * @return object + * @return object|PEAR_Error * @static */ function &getFrontendObject() { - if (empty($GLOBALS['_PEAR_Command_uiobject'])) { - $GLOBALS['_PEAR_Command_uiobject'] = &new $GLOBALS['_PEAR_Command_uiclass']; - } - return $GLOBALS['_PEAR_Command_uiobject']; + $a = &PEAR_Frontend::singleton(); + return $a; } // }}} @@ -159,59 +183,13 @@ class PEAR_Command */ function &setFrontendClass($uiclass) { - if (is_object($GLOBALS['_PEAR_Command_uiobject']) && - is_a($GLOBALS['_PEAR_Command_uiobject'], $uiclass)) { - return $GLOBALS['_PEAR_Command_uiobject']; - } - if (!class_exists($uiclass)) { - $file = str_replace('_', '/', $uiclass) . '.php'; - if (PEAR_Command::isIncludeable($file)) { - include_once $file; - } - } - if (class_exists($uiclass)) { - $obj = &new $uiclass; - // quick test to see if this class implements a few of the most - // important frontend methods - if (method_exists($obj, 'userConfirm')) { - $GLOBALS['_PEAR_Command_uiobject'] = &$obj; - $GLOBALS['_PEAR_Command_uiclass'] = $uiclass; - return $obj; - } else { - $err = PEAR::raiseError("not a frontend class: $uiclass"); - return $err; - } - } - $err = PEAR::raiseError("no such class: $uiclass"); - return $err; + $a = &PEAR_Frontend::setFrontendClass($uiclass); + return $a; } // }}} // {{{ setFrontendType() - // }}} - // {{{ isIncludeable() - - /** - * @param string $path relative or absolute include path - * @return boolean - * @static - */ - function isIncludeable($path) - { - if (file_exists($path) && is_readable($path)) { - return true; - } - $ipath = explode(PATH_SEPARATOR, ini_get('include_path')); - foreach ($ipath as $include) { - $test = realpath($include . DIRECTORY_SEPARATOR . $path); - if (file_exists($test) && is_readable($test)) { - return true; - } - } - return false; - } - /** * Set current frontend. * @@ -249,9 +227,13 @@ class PEAR_Command */ function registerCommands($merge = false, $dir = null) { + $parser = new PEAR_XMLParser; if ($dir === null) { $dir = dirname(__FILE__) . '/Command'; } + if (!is_dir($dir)) { + return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory"); + } $dp = @opendir($dir); if (empty($dp)) { return PEAR::raiseError("registerCommands: opendir($dir) failed"); @@ -259,27 +241,58 @@ class PEAR_Command if (!$merge) { $GLOBALS['_PEAR_Command_commandlist'] = array(); } - while ($entry = readdir($dp)) { - if ($entry{0} == '.' || substr($entry, -4) != '.php' || $entry == 'Common.php') { + + while ($file = readdir($dp)) { + if ($file{0} == '.' || substr($file, -4) != '.xml') { continue; } - $class = "PEAR_Command_".substr($entry, 0, -4); - $file = "$dir/$entry"; - include_once $file; + + $f = substr($file, 0, -4); + $class = "PEAR_Command_" . $f; // List of commands if (empty($GLOBALS['_PEAR_Command_objects'][$class])) { - $GLOBALS['_PEAR_Command_objects'][$class] = &new $class($ui, $config); + $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php'; } - $implements = $GLOBALS['_PEAR_Command_objects'][$class]->getCommands(); + + $parser->parse(file_get_contents("$dir/$file")); + $implements = $parser->getData(); foreach ($implements as $command => $desc) { + if ($command == 'attribs') { + continue; + } + + if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return PEAR::raiseError('Command "' . $command . '" already registered in ' . + 'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + $GLOBALS['_PEAR_Command_commandlist'][$command] = $class; - $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc; - } - $shortcuts = $GLOBALS['_PEAR_Command_objects'][$class]->getShortcuts(); - foreach ($shortcuts as $shortcut => $command) { - $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command; + $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary']; + if (isset($desc['shortcut'])) { + $shortcut = $desc['shortcut']; + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) { + return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' . + 'registered to command "' . $command . '" in class "' . + $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command; + } + + if (isset($desc['options']) && $desc['options']) { + foreach ($desc['options'] as $oname => $option) { + if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) { + return PEAR::raiseError('Option "' . $oname . '" short option "' . + $option['shortopt'] . '" must be ' . + 'only 1 character in Command "' . $command . '" in class "' . + $class . '"'); + } + } + } } } + + ksort($GLOBALS['_PEAR_Command_shortcuts']); + ksort($GLOBALS['_PEAR_Command_commandlist']); @closedir($dp); return true; } @@ -343,11 +356,13 @@ class PEAR_Command if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { return null; } - $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; - $obj = &$GLOBALS['_PEAR_Command_objects'][$class]; + $obj = &PEAR_Command::getObject($command); return $obj->getGetoptArgs($command, $short_args, $long_args); } @@ -386,13 +401,14 @@ class PEAR_Command function getHelp($command) { $cmds = PEAR_Command::getCommands(); + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } if (isset($cmds[$command])) { - $class = $cmds[$command]; - return $GLOBALS['_PEAR_Command_objects'][$class]->getHelp($command); + $obj = &PEAR_Command::getObject($command); + return $obj->getHelp($command); } return false; } // }}} -} - -?> +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Auth.php b/3rdparty/PEAR/Command/Auth.php index 0b9d3d3965..63cd152b90 100644 --- a/3rdparty/PEAR/Command/Auth.php +++ b/3rdparty/PEAR/Command/Auth.php @@ -1,43 +1,53 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Auth.php,v 1.15 2004/01/08 17:33:13 sniper Exp $ - -require_once "PEAR/Command/Common.php"; -require_once "PEAR/Remote.php"; -require_once "PEAR/Config.php"; +/** + * PEAR_Command_Auth (login, logout commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Auth.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ /** - * PEAR commands for managing configuration data. - * + * base class */ -class PEAR_Command_Auth extends PEAR_Command_Common -{ - // {{{ properties +require_once 'PEAR/Command/Channels.php'; +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ +class PEAR_Command_Auth extends PEAR_Command_Channels +{ var $commands = array( 'login' => array( - 'summary' => 'Connects and authenticates to remote server', + 'summary' => 'Connects and authenticates to remote server [Deprecated in favor of channel-login]', 'shortcut' => 'li', 'function' => 'doLogin', 'options' => array(), - 'doc' => ' -Log in to the remote server. To use remote functions in the installer + 'doc' => ' +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer that require any kind of privileges, you need to log in first. The username and password you enter here will be stored in your per-user PEAR configuration (~/.pearrc on Unix-like systems). After logging @@ -45,11 +55,13 @@ in, your username and password will be sent along in subsequent operations on the remote server.', ), 'logout' => array( - 'summary' => 'Logs out from the remote server', + 'summary' => 'Logs out from the remote server [Deprecated in favor of channel-logout]', 'shortcut' => 'lo', 'function' => 'doLogout', 'options' => array(), 'doc' => ' +WARNING: This function is deprecated in favor of using channel-logout + Logs out from the remote server. This command does not actually connect to the remote server, it only deletes the stored username and password from your user configuration.', @@ -57,10 +69,6 @@ password from your user configuration.', ); - // }}} - - // {{{ constructor - /** * PEAR_Command_Auth constructor. * @@ -68,88 +76,6 @@ password from your user configuration.', */ function PEAR_Command_Auth(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::PEAR_Command_Channels($ui, $config); } - - // }}} - - // {{{ doLogin() - - /** - * Execute the 'login' command. - * - * @param string $command command name - * - * @param array $options option_name => value - * - * @param array $params list of additional parameters - * - * @return bool TRUE on success, FALSE for unknown commands, or - * a PEAR error on failure - * - * @access public - */ - function doLogin($command, $options, $params) - { - $server = $this->config->get('master_server'); - $remote = new PEAR_Remote($this->config); - $username = $this->config->get('username'); - if (empty($username)) { - $username = @$_ENV['USER']; - } - $this->ui->outputData("Logging in to $server.", $command); - - list($username, $password) = $this->ui->userDialog( - $command, - array('Username', 'Password'), - array('text', 'password'), - array($username, '') - ); - $username = trim($username); - $password = trim($password); - - $this->config->set('username', $username); - $this->config->set('password', $password); - - $remote->expectError(401); - $ok = $remote->call('logintest'); - $remote->popExpect(); - if ($ok === true) { - $this->ui->outputData("Logged in.", $command); - $this->config->store(); - } else { - return $this->raiseError("Login failed!"); - } - - } - - // }}} - // {{{ doLogout() - - /** - * Execute the 'logout' command. - * - * @param string $command command name - * - * @param array $options option_name => value - * - * @param array $params list of additional parameters - * - * @return bool TRUE on success, FALSE for unknown commands, or - * a PEAR error on failure - * - * @access public - */ - function doLogout($command, $options, $params) - { - $server = $this->config->get('master_server'); - $this->ui->outputData("Logging out from $server.", $command); - $this->config->remove('username'); - $this->config->remove('password'); - $this->config->store(); - } - - // }}} -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Auth.xml b/3rdparty/PEAR/Command/Auth.xml new file mode 100644 index 0000000000..590193d142 --- /dev/null +++ b/3rdparty/PEAR/Command/Auth.xml @@ -0,0 +1,30 @@ + + + Connects and authenticates to remote server [Deprecated in favor of channel-login] + doLogin + li + + <channel name> +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server. + + + Logs out from the remote server [Deprecated in favor of channel-logout] + doLogout + lo + + +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration. + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Build.php b/3rdparty/PEAR/Command/Build.php index 2ecbbc92f5..1de7320246 100644 --- a/3rdparty/PEAR/Command/Build.php +++ b/3rdparty/PEAR/Command/Build.php @@ -1,36 +1,42 @@ | -// | Tomas V.V.Cox | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Build.php,v 1.9 2004/01/08 17:33:13 sniper Exp $ +/** + * PEAR_Command_Auth (build command) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Build.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -require_once "PEAR/Command/Common.php"; -require_once "PEAR/Builder.php"; +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; /** * PEAR commands for building extensions. * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Command_Build extends PEAR_Command_Common { - // {{{ properties - var $commands = array( 'build' => array( 'summary' => 'Build an Extension From C Source', @@ -42,10 +48,6 @@ Builds one or more extensions contained in a package.' ), ); - // }}} - - // {{{ constructor - /** * PEAR_Command_Build constructor. * @@ -56,27 +58,23 @@ Builds one or more extensions contained in a package.' parent::PEAR_Command_Common($ui, $config); } - // }}} - - // {{{ doBuild() - function doBuild($command, $options, $params) { + require_once 'PEAR/Builder.php'; if (sizeof($params) < 1) { $params[0] = 'package.xml'; } + $builder = &new PEAR_Builder($this->ui); $this->debug = $this->config->get('verbose'); $err = $builder->build($params[0], array(&$this, 'buildCallback')); if (PEAR::isError($err)) { return $err; } + return true; } - // }}} - // {{{ buildCallback() - function buildCallback($what, $data) { if (($what == 'cmdoutput' && $this->debug > 1) || @@ -84,6 +82,4 @@ Builds one or more extensions contained in a package.' $this->ui->outputData(rtrim($data), 'build'); } } - - // }}} -} +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Build.xml b/3rdparty/PEAR/Command/Build.xml new file mode 100644 index 0000000000..ec4e6f554c --- /dev/null +++ b/3rdparty/PEAR/Command/Build.xml @@ -0,0 +1,10 @@ + + + Build an Extension From C Source + doBuild + b + + [package.xml] +Builds one or more extensions contained in a package. + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Channels.php b/3rdparty/PEAR/Command/Channels.php new file mode 100644 index 0000000000..fcf01b5039 --- /dev/null +++ b/3rdparty/PEAR/Command/Channels.php @@ -0,0 +1,883 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Channels.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500); + +/** + * PEAR commands for managing channels. + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Command_Channels extends PEAR_Command_Common +{ + var $commands = array( + 'list-channels' => array( + 'summary' => 'List Available Channels', + 'function' => 'doList', + 'shortcut' => 'lc', + 'options' => array(), + 'doc' => ' +List all available channels for installation. +', + ), + 'update-channels' => array( + 'summary' => 'Update the Channel List', + 'function' => 'doUpdateAll', + 'shortcut' => 'uc', + 'options' => array(), + 'doc' => ' +List all installed packages in all channels. +' + ), + 'channel-delete' => array( + 'summary' => 'Remove a Channel From the List', + 'function' => 'doDelete', + 'shortcut' => 'cde', + 'options' => array(), + 'doc' => ' +Delete a channel from the registry. You may not +remove any channel that has installed packages. +' + ), + 'channel-add' => array( + 'summary' => 'Add a Channel', + 'function' => 'doAdd', + 'shortcut' => 'ca', + 'options' => array(), + 'doc' => ' +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +' + ), + 'channel-update' => array( + 'summary' => 'Update an Existing Channel', + 'function' => 'doUpdate', + 'shortcut' => 'cu', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), + 'channel' => array( + 'shortopt' => 'c', + 'arg' => 'CHANNEL', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), +), + 'doc' => '[|] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. +' + ), + 'channel-info' => array( + 'summary' => 'Retrieve Information on a Channel', + 'function' => 'doInfo', + 'shortcut' => 'ci', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'channel-alias' => array( + 'summary' => 'Specify an alias to a channel name', + 'function' => 'doAlias', + 'shortcut' => 'cha', + 'options' => array(), + 'doc' => ' +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +' + ), + 'channel-discover' => array( + 'summary' => 'Initialize a Channel from its server', + 'function' => 'doDiscover', + 'shortcut' => 'di', + 'options' => array(), + 'doc' => '[|] +Initialize a channel from its server and create a local channel.xml. +If is in the format ":@" then + and will be set as the login username/password for +. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system\'s process list. +' + ), + 'channel-login' => array( + 'summary' => 'Connects and authenticates to remote channel server', + 'shortcut' => 'cli', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'channel-logout' => array( + 'summary' => 'Logs out from the remote channel server', + 'shortcut' => 'clo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +Logs out from a remote channel server. If is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.', + ), + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Channels(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortChannels($a, $b) + { + return strnatcasecmp($a->getName(), $b->getName()); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $registered = $reg->getChannels(); + usort($registered, array(&$this, '_sortchannels')); + $i = $j = 0; + $data = array( + 'caption' => 'Registered Channels:', + 'border' => true, + 'headline' => array('Channel', 'Alias', 'Summary') + ); + foreach ($registered as $channel) { + $data['data'][] = array($channel->getName(), + $channel->getAlias(), + $channel->getSummary()); + } + + if (count($registered) === 0) { + $data = '(no registered channels)'; + } + $this->ui->outputData($data, $command); + return true; + } + + function doUpdateAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channels = $reg->getChannels(); + + $success = true; + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doUpdate('channel-update', + $options, + array($channel->getName())); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + $success = false; + } else { + $success &= $err; + } + } + } + return $success; + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel specified"); + } + + $reg = &$this->config->getRegistry(); + $channel = strtolower($params[0]); + if ($reg->channelExists($channel)) { + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + } else { + if (strpos($channel, '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('Cannot open "' . $channel . + '" (' . $loc->getMessage() . ')'); + } else { + $contents = implode('', file($loc)); + } + } else { + if (!file_exists($params[0])) { + return $this->raiseError('Unknown channel "' . $channel . '"'); + } + + $fp = fopen($params[0], 'r'); + if (!$fp) { + return $this->raiseError('Cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $chan = new PEAR_ChannelFile; + $chan->fromXmlString($contents); + $chan->validate(); + if ($errs = $chan->getErrors(true)) { + foreach ($errs as $err) { + $this->ui->outputData($err['level'] . ': ' . $err['message']); + } + return $this->raiseError('Channel file "' . $params[0] . '" is not valid'); + } + } + + if (!$chan) { + return $this->raiseError('Serious error: Channel "' . $params[0] . + '" has a corrupted registry entry'); + } + + $channel = $chan->getName(); + $caption = 'Channel ' . $channel . ' Information:'; + $data1 = array( + 'caption' => $caption, + 'border' => true); + $data1['data']['server'] = array('Name and Server', $chan->getName()); + if ($chan->getAlias() != $chan->getName()) { + $data1['data']['alias'] = array('Alias', $chan->getAlias()); + } + + $data1['data']['summary'] = array('Summary', $chan->getSummary()); + $validate = $chan->getValidationPackage(); + $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']); + $data1['data']['vpackageversion'] = + array('Validation Package Version', $validate['attribs']['version']); + $d = array(); + $d['main'] = $data1; + + $data['data'] = array(); + $data['caption'] = 'Server Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST()) { + if ($chan->supportsREST()) { + $funcs = $chan->getFunctions('rest'); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + + $d['protocols'] = $data; + $data['data'] = array(); + $mirrors = $chan->getMirrors(); + if ($mirrors) { + $data['caption'] = 'Channel ' . $channel . ' Mirrors:'; + unset($data['headline']); + foreach ($mirrors as $mirror) { + $data['data'][] = array($mirror['attribs']['host']); + $d['mirrors'] = $data; + } + + foreach ($mirrors as $i => $mirror) { + $data['data'] = array(); + $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST($mirror['attribs']['host'])) { + if ($chan->supportsREST($mirror['attribs']['host'])) { + $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + $d['mirrorprotocols' . $i] = $data; + } + } + $this->ui->outputData($d, 'channel-info'); + } + + // }}} + + function doDelete($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-delete: no channel specified'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0])) { + return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist'); + } + + $channel = $reg->channelName($params[0]); + if ($channel == 'pear.php.net') { + return $this->raiseError('Cannot delete the pear.php.net channel'); + } + + if ($channel == 'pecl.php.net') { + return $this->raiseError('Cannot delete the pecl.php.net channel'); + } + + if ($channel == 'doc.php.net') { + return $this->raiseError('Cannot delete the doc.php.net channel'); + } + + if ($channel == '__uri') { + return $this->raiseError('Cannot delete the __uri pseudo-channel'); + } + + if (PEAR::isError($err = $reg->listPackages($channel))) { + return $err; + } + + if (count($err)) { + return $this->raiseError('Channel "' . $channel . + '" has installed packages, cannot delete'); + } + + if (!$reg->deleteChannel($channel)) { + return $this->raiseError('Channel "' . $channel . '" deletion failed'); + } else { + $this->config->deleteChannel($channel); + $this->ui->outputData('Channel "' . $channel . '" deleted', $command); + } + } + + function doAdd($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-add: no channel file specified'); + } + + if (strpos($params[0], '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('channel-add: Cannot open "' . $params[0] . + '" (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $lastmodified = $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError('channel-add: cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $result = $channel->fromXmlString($contents); + PEAR::staticPopErrorHandling(); + if (!$result) { + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('channel-add: invalid channel.xml file'); + } + } + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel->getName())) { + return $this->raiseError('channel-add: Channel "' . $channel->getName() . + '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + $ret = $reg->addChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('channel-add: adding Channel "' . $channel->getName() . + '" to registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command); + } + + function doUpdate($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel file specified"); + } + + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + $reg = &$this->config->getRegistry(); + $lastmodified = false; + if ((!file_exists($params[0]) || is_dir($params[0])) + && $reg->channelExists(strtolower($params[0]))) { + $c = $reg->getChannel(strtolower($params[0])); + if (PEAR::isError($c)) { + return $this->raiseError($c); + } + + $this->ui->outputData("Updating channel \"$params[0]\"", $command); + $dl = &$this->getDownloader(array()); + // if force is specified, use a timestamp of "1" to force retrieval + $lastmodified = isset($options['force']) ? false : $c->lastModified(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + // Attempt to fall back to https + $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage()); + $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead"); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + return $this->raiseError('Cannot retrieve channel.xml for channel "' . + $c->getName() . '" (' . $contents->getMessage() . ')'); + } + } + + list($contents, $lastmodified) = $contents; + if (!$contents) { + $this->ui->outputData("Channel \"$params[0]\" is up to date"); + return; + } + + $contents = implode('', file($contents)); + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + if (!$channel->getErrors()) { + // security check: is the downloaded file for the channel we got it from? + if (strtolower($channel->getName()) != strtolower($c->getName())) { + if (!isset($options['force'])) { + return $this->raiseError('ERROR: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + + $this->ui->log(0, 'WARNING: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + } + } else { + if (strpos($params[0], '://')) { + $dl = &$this->getDownloader(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $dl->downloadHttp($params[0], + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError("Cannot open " . $params[0] . + ' (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError("Cannot open " . $params[0]); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + } + + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('Invalid channel.xml file'); + } + } + + if (!$reg->channelExists($channel->getName())) { + return $this->raiseError('Error: Channel "' . $channel->getName() . + '" does not exist, use channel-add to add an entry'); + } + + $ret = $reg->updateChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('Updating Channel "' . $channel->getName() . + '" in registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded'); + } + + function &getDownloader() + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = new PEAR_Downloader($this->ui, array(), $this->config); + return $a; + } + + function doAlias($command, $options, $params) + { + if (count($params) === 1) { + return $this->raiseError('No channel alias specified'); + } + + if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) { + return $this->raiseError( + 'Invalid format, correct is: channel-alias channel alias'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0], true)) { + $extra = ''; + if ($reg->isAlias($params[0])) { + $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' . + strtolower($params[1]) . '")'; + } + + return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra); + } + + if ($reg->isAlias($params[1])) { + return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' . + 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias'); + } + + $chan = &$reg->getChannel($params[0]); + if (PEAR::isError($chan)) { + return $this->raiseError('Corrupt registry? Error retrieving channel "' . $params[0] . + '" information (' . $chan->getMessage() . ')'); + } + + // make it a local alias + if (!$chan->setAlias(strtolower($params[1]), true)) { + return $this->raiseError('Alias "' . strtolower($params[1]) . + '" is not a valid channel alias'); + } + + $reg->updateChannel($chan); + $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' . + strtolower($params[1]) . '"'); + } + + /** + * The channel-discover command + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters. + * $params[0] should contain a string with either: + * - or + * - :@ + * @return null|PEAR_Error + */ + function doDiscover($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel server specified"); + } + + // Look for the possible input format ":@" + if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) { + $username = $matches[1]; + $password = $matches[2]; + $channel = $matches[3]; + } else { + $channel = $params[0]; + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel)) { + if (!$reg->isAlias($channel)) { + return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + return $this->raiseError("A channel alias named \"$channel\" " . + 'already exists, aliasing channel "' . $reg->channelName($channel) + . '"'); + } + + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + // Attempt fetch via https + $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage()); + $this->ui->outputData("Trying to discover channel $channel over https:// instead"); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + } + + // Store username/password if they were given + // Arguably we should do a logintest on the channel here, but since + // that's awkward on a REST-based channel (even "pear login" doesn't + // do it for those), and XML-RPC is deprecated, it's fairly pointless. + if (isset($username)) { + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + $this->config->store(); + $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command); + } + + $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command); + } + + /** + * Execute the 'login' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogin($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $username = $this->config->get('username', null, $channel); + if (empty($username)) { + $username = isset($_ENV['USER']) ? $_ENV['USER'] : null; + } + $this->ui->outputData("Logging in to $server.", $command); + + list($username, $password) = $this->ui->userDialog( + $command, + array('Username', 'Password'), + array('text', 'password'), + array($username, '') + ); + $username = trim($username); + $password = trim($password); + + $ourfile = $this->config->getConfFile('user'); + if (!$ourfile) { + $ourfile = $this->config->getConfFile('system'); + } + + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + + if ($chan->supportsREST()) { + $ok = true; + } + + if ($ok !== true) { + return $this->raiseError('Login failed!'); + } + + $this->ui->outputData("Logged in.", $command); + // avoid changing any temporary settings changed with -d + $ourconfig = new PEAR_Config($ourfile, $ourfile); + $ourconfig->set('username', $username, 'user', $channel); + $ourconfig->set('password', $password, 'user', $channel); + $ourconfig->store(); + + return true; + } + + /** + * Execute the 'logout' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogout($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $this->ui->outputData("Logging out from $server.", $command); + $this->config->remove('username', 'user', $channel); + $this->config->remove('password', 'user', $channel); + $this->config->store(); + return true; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Channels.xml b/3rdparty/PEAR/Command/Channels.xml new file mode 100644 index 0000000000..47b72066ab --- /dev/null +++ b/3rdparty/PEAR/Command/Channels.xml @@ -0,0 +1,123 @@ + + + List Available Channels + doList + lc + + +List all available channels for installation. + + + + Update the Channel List + doUpdateAll + uc + + +List all installed packages in all channels. + + + + Remove a Channel From the List + doDelete + cde + + <channel name> +Delete a channel from the registry. You may not +remove any channel that has installed packages. + + + + Add a Channel + doAdd + ca + + <channel.xml> +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. + + + + Update an Existing Channel + doUpdate + cu + + + f + will force download of new channel.xml if an existing channel name is used + + + c + will force download of new channel.xml if an existing channel name is used + CHANNEL + + + [<channel.xml>|<channel name>] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. + + + + Retrieve Information on a Channel + doInfo + ci + + <package> +List the files in an installed package. + + + + Specify an alias to a channel name + doAlias + cha + + <channel> <alias> +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. + + + + Initialize a Channel from its server + doDiscover + di + + [<channel.xml>|<channel name>] +Initialize a channel from its server and create a local channel.xml. +If <channel name> is in the format "<username>:<password>@<channel>" then +<username> and <password> will be set as the login username/password for +<channel>. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system's process list. + + + + Connects and authenticates to remote channel server + doLogin + cli + + <channel name> +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server. + + + Logs out from the remote channel server + doLogout + clo + + <channel name> +Logs out from a remote channel server. If <channel name> is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration. + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Common.php b/3rdparty/PEAR/Command/Common.php index c6ace694ca..279a716623 100644 --- a/3rdparty/PEAR/Command/Common.php +++ b/3rdparty/PEAR/Command/Common.php @@ -1,36 +1,52 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.24 2004/01/08 17:33:13 sniper Exp $ +/** + * PEAR_Command_Common base class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -require_once "PEAR.php"; +/** + * base class + */ +require_once 'PEAR.php'; +/** + * PEAR commands base class + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ class PEAR_Command_Common extends PEAR { - // {{{ properties - /** * PEAR_Config object used to pass user system and configuration * on when executing commands * - * @var object + * @var PEAR_Config */ var $config; + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; /** * User Interface object, for all interaction with the user. @@ -50,7 +66,7 @@ class PEAR_Command_Common extends PEAR var $_deps_type_trans = array( 'pkg' => 'package', - 'extension' => 'extension', + 'ext' => 'extension', 'php' => 'PHP', 'prog' => 'external program', 'ldlib' => 'external library for linking', @@ -60,9 +76,6 @@ class PEAR_Command_Common extends PEAR 'sapi' => 'SAPI backend' ); - // }}} - // {{{ constructor - /** * PEAR_Command_Common constructor. * @@ -75,10 +88,6 @@ class PEAR_Command_Common extends PEAR $this->ui = &$ui; } - // }}} - - // {{{ getCommands() - /** * Return a list of all the commands defined by this class. * @return array list of commands @@ -90,12 +99,10 @@ class PEAR_Command_Common extends PEAR foreach (array_keys($this->commands) as $command) { $ret[$command] = $this->commands[$command]['summary']; } + return $ret; } - // }}} - // {{{ getShortcuts() - /** * Return a list of all the command shortcuts defined by this class. * @return array shortcut => command @@ -109,28 +116,34 @@ class PEAR_Command_Common extends PEAR $ret[$this->commands[$command]['shortcut']] = $command; } } + return $ret; } - // }}} - // {{{ getOptions() - function getOptions($command) { - return @$this->commands[$command]['options']; - } + $shortcuts = $this->getShortcuts(); + if (isset($shortcuts[$command])) { + $command = $shortcuts[$command]; + } - // }}} - // {{{ getGetoptArgs() + if (isset($this->commands[$command]) && + isset($this->commands[$command]['options'])) { + return $this->commands[$command]['options']; + } + + return null; + } function getGetoptArgs($command, &$short_args, &$long_args) { - $short_args = ""; + $short_args = ''; $long_args = array(); - if (empty($this->commands[$command])) { + if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) { return; } - reset($this->commands[$command]); + + reset($this->commands[$command]['options']); while (list($option, $info) = each($this->commands[$command]['options'])) { $larg = $sarg = ''; if (isset($info['arg'])) { @@ -144,15 +157,15 @@ class PEAR_Command_Common extends PEAR $arg = $info['arg']; } } + if (isset($info['shortopt'])) { $short_args .= $info['shortopt'] . $sarg; } + $long_args[] = $option . $larg; } } - // }}} - // {{{ getHelp() /** * Returns the help message for the given command * @@ -164,29 +177,38 @@ class PEAR_Command_Common extends PEAR function getHelp($command) { $config = &PEAR_Config::singleton(); - $help = @$this->commands[$command]['doc']; + if (!isset($this->commands[$command])) { + return "No such command \"$command\""; + } + + $help = null; + if (isset($this->commands[$command]['doc'])) { + $help = $this->commands[$command]['doc']; + } + if (empty($help)) { // XXX (cox) Fallback to summary if there is no doc (show both?) - if (!$help = @$this->commands[$command]['summary']) { + if (!isset($this->commands[$command]['summary'])) { return "No help for command \"$command\""; } + $help = $this->commands[$command]['summary']; } + if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) { foreach($matches[0] as $k => $v) { $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help); } } + return array($help, $this->getHelpArgs($command)); } - // }}} - // {{{ getHelpArgs() /** - * Returns the help for the accepted arguments of a command - * - * @param string $command - * @return string The help string - */ + * Returns the help for the accepted arguments of a command + * + * @param string $command + * @return string The help string + */ function getHelpArgs($command) { if (isset($this->commands[$command]['options']) && @@ -195,7 +217,7 @@ class PEAR_Command_Common extends PEAR $help = "Options:\n"; foreach ($this->commands[$command]['options'] as $k => $v) { if (isset($v['arg'])) { - if ($v['arg']{0} == '(') { + if ($v['arg'][0] == '(') { $arg = substr($v['arg'], 1, -1); $sapp = " [$arg]"; $lapp = "[=$arg]"; @@ -206,44 +228,46 @@ class PEAR_Command_Common extends PEAR } else { $sapp = $lapp = ""; } + if (isset($v['shortopt'])) { $s = $v['shortopt']; - @$help .= " -$s$sapp, --$k$lapp\n"; + $help .= " -$s$sapp, --$k$lapp\n"; } else { - @$help .= " --$k$lapp\n"; + $help .= " --$k$lapp\n"; } + $p = " "; $doc = rtrim(str_replace("\n", "\n$p", $v['doc'])); $help .= " $doc\n"; } + return $help; } + return null; } - // }}} - // {{{ run() - function run($command, $options, $params) { - $func = @$this->commands[$command]['function']; - if (empty($func)) { + if (empty($this->commands[$command]['function'])) { // look for shortcuts foreach (array_keys($this->commands) as $cmd) { - if (@$this->commands[$cmd]['shortcut'] == $command) { - $command = $cmd; - $func = @$this->commands[$command]['function']; - if (empty($func)) { + if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) { + if (empty($this->commands[$cmd]['function'])) { return $this->raiseError("unknown command `$command'"); + } else { + $func = $this->commands[$cmd]['function']; } + $command = $cmd; + + //$command = $this->commands[$cmd]['function']; break; } } + } else { + $func = $this->commands[$command]['function']; } + return $this->$func($command, $options, $params); } - - // }}} -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Config.php b/3rdparty/PEAR/Command/Config.php index 474a234517..a761b277f5 100644 --- a/3rdparty/PEAR/Command/Config.php +++ b/3rdparty/PEAR/Command/Config.php @@ -1,74 +1,101 @@ | -// | Tomas V.V.Cox | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Config.php,v 1.27 2004/06/15 16:48:49 pajoye Exp $ +/** + * PEAR_Command_Config (config-show, config-get, config-set, config-help, config-create commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -require_once "PEAR/Command/Common.php"; -require_once "PEAR/Config.php"; +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; /** * PEAR commands for managing configuration data. * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Command_Config extends PEAR_Command_Common { - // {{{ properties - var $commands = array( 'config-show' => array( 'summary' => 'Show All Settings', 'function' => 'doConfigShow', 'shortcut' => 'csh', - 'options' => array(), - 'doc' => ' + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => '[layer] Displays all configuration values. An optional argument may be used to tell which configuration layer to display. Valid -configuration layers are "user", "system" and "default". +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. ', ), 'config-get' => array( 'summary' => 'Show One Setting', 'function' => 'doConfigGet', 'shortcut' => 'cg', - 'options' => array(), + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), 'doc' => ' [layer] Displays the value of one configuration parameter. The first argument is the name of the parameter, an optional second argument may be used to tell which configuration layer to look in. Valid configuration layers are "user", "system" and "default". If no layer is specified, a value will be picked from the first layer that defines the parameter, in the order -just specified. +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. ', ), 'config-set' => array( 'summary' => 'Change Setting', 'function' => 'doConfigSet', 'shortcut' => 'cs', - 'options' => array(), + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), 'doc' => ' [layer] Sets the value of one configuration parameter. The first argument is the name of the parameter, the second argument is the new value. Some parameters are subject to validation, and the command will fail with an error message if the new value does not make sense. An optional third argument may be used to specify in which layer to set the -configuration parameter. The default layer is "user". +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. ', ), 'config-help' => array( @@ -81,11 +108,26 @@ Displays help for a configuration parameter. Without arguments it displays help for all configuration parameters. ', ), + 'config-create' => array( + 'summary' => 'Create a Default configuration file', + 'function' => 'doConfigCreate', + 'shortcut' => 'coc', + 'options' => array( + 'windows' => array( + 'shortopt' => 'w', + 'doc' => 'create a config file for a windows install', + ), + ), + 'doc' => ' +Create a default configuration file with all directory configuration +variables set to subdirectories of , and save it as . +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). +', + ), ); - // }}} - // {{{ constructor - /** * PEAR_Command_Config constructor. * @@ -96,94 +138,154 @@ displays help for all configuration parameters. parent::PEAR_Command_Common($ui, $config); } - // }}} - - // {{{ doConfigShow() - function doConfigShow($command, $options, $params) { - // $params[0] -> the layer - if ($error = $this->_checkLayer(@$params[0])) { - return $this->raiseError($error); + $layer = null; + if (is_array($params)) { + $layer = isset($params[0]) ? $params[0] : null; } + + // $params[0] -> the layer + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-show:$error"); + } + $keys = $this->config->getKeys(); sort($keys); - $data = array('caption' => 'Configuration:'); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); foreach ($keys as $key) { $type = $this->config->getType($key); - $value = $this->config->get($key, @$params[0]); + $value = $this->config->get($key, $layer, $channel); if ($type == 'password' && $value) { $value = '********'; } + if ($value === false) { $value = 'false'; } elseif ($value === true) { $value = 'true'; } + $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value); } + + foreach ($this->config->getLayers() as $layer) { + $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer)); + } + $this->ui->outputData($data, $command); return true; } - // }}} - // {{{ doConfigGet() - function doConfigGet($command, $options, $params) { - // $params[0] -> the parameter - // $params[1] -> the layer - if ($error = $this->_checkLayer(@$params[1])) { - return $this->raiseError($error); + $args_cnt = is_array($params) ? count($params) : 0; + switch ($args_cnt) { + case 1: + $config_key = $params[0]; + $layer = null; + break; + case 2: + $config_key = $params[0]; + $layer = $params[1]; + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-get:$error"); + } + break; + case 0: + default: + return $this->raiseError("config-get expects 1 or 2 parameters"); } - if (sizeof($params) < 1 || sizeof($params) > 2) { - return $this->raiseError("config-get expects 1 or 2 parameters"); - } elseif (sizeof($params) == 1) { - $this->ui->outputData($this->config->get($params[0]), $command); - } else { - $data = $this->config->get($params[0], $params[1]); - $this->ui->outputData($data, $command); + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); } + + $channel = $reg->channelName($channel); + $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command); return true; } - // }}} - // {{{ doConfigSet() - function doConfigSet($command, $options, $params) { // $param[0] -> a parameter to set // $param[1] -> the value for the parameter // $param[2] -> the layer $failmsg = ''; - if (sizeof($params) < 2 || sizeof($params) > 3) { + if (count($params) < 2 || count($params) > 3) { $failmsg .= "config-set expects 2 or 3 parameters"; return PEAR::raiseError($failmsg); } - if ($error = $this->_checkLayer(@$params[2])) { + + if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) { $failmsg .= $error; - return PEAR::raiseError($failmsg); + return PEAR::raiseError("config-set:$failmsg"); } - if (!call_user_func_array(array(&$this->config, 'set'), $params)) - { - $failmsg = "config-set (" . implode(", ", $params) . ") failed"; + + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) { + return $this->raiseError('Channel "' . $params[1] . '" does not exist'); + } + + if ($params[0] == 'preferred_mirror' + && ( + !$reg->mirrorExists($channel, $params[1]) && + (!$reg->channelExists($params[1]) || $channel != $params[1]) + ) + ) { + $msg = 'Channel Mirror "' . $params[1] . '" does not exist'; + $msg .= ' in your registry for channel "' . $channel . '".'; + $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"'; + $msg .= ' if you believe this mirror should exist as you may'; + $msg .= ' have outdated channel information.'; + return $this->raiseError($msg); + } + + if (count($params) == 2) { + array_push($params, 'user'); + $layer = 'user'; } else { - $this->config->store(); + $layer = $params[2]; } + + array_push($params, $channel); + if (!call_user_func_array(array(&$this->config, 'set'), $params)) { + array_pop($params); + $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel"; + } else { + $this->config->store($layer); + } + if ($failmsg) { return $this->raiseError($failmsg); } + + $this->ui->outputData('config-set succeeded', $command); return true; } - // }}} - // {{{ doConfigHelp() - function doConfigHelp($command, $options, $params) { if (empty($params)) { $params = $this->config->getKeys(); } + $data['caption'] = "Config help" . ((count($params) == 1) ? " for $params[0]" : ''); $data['headline'] = array('Name', 'Type', 'Description'); $data['border'] = true; @@ -194,13 +296,103 @@ displays help for all configuration parameters. $docs = rtrim($docs) . "\nValid set: " . implode(' ', $this->config->getSetValues($name)); } + $data['data'][] = array($name, $type, $docs); } + $this->ui->outputData($data, $command); } - // }}} - // {{{ _checkLayer() + function doConfigCreate($command, $options, $params) + { + if (count($params) != 2) { + return PEAR::raiseError('config-create: must have 2 parameters, root path and ' . + 'filename to save as'); + } + + $root = $params[0]; + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"), + array('/', '/', '/'), + $root); + if ($root{0} != '/') { + if (!isset($options['windows'])) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "/", was: "' . $root . '"'); + } + + if (!preg_match('/^[A-Za-z]:/', $root)) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "\\" or "C:\\", was: "' . $root . '"'); + } + } + + $windows = isset($options['windows']); + if ($windows) { + $root = str_replace('/', '\\', $root); + } + + if (!file_exists($params[1]) && !@touch($params[1])) { + return PEAR::raiseError('Could not create "' . $params[1] . '"'); + } + + $params[1] = realpath($params[1]); + $config = &new PEAR_Config($params[1], '#no#system#config#', false, false); + if ($root{strlen($root) - 1} == '/') { + $root = substr($root, 0, strlen($root) - 1); + } + + $config->noRegistry(); + $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user'); + $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data"); + $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www"); + $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg"); + $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext"); + $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs"); + $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests"); + $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache"); + $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download"); + $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp"); + $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear"); + $config->writeConfigFile(); + $this->_showConfig($config); + $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"', + $command); + } + + function _showConfig(&$config) + { + $params = array('user'); + $keys = $config->getKeys(); + sort($keys); + $channel = 'pear.php.net'; + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $config->getType($key); + $value = $config->get($key, 'user', $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + $data['data'][$config->getGroup($key)][] = + array($config->getPrompt($key) , $key, $value); + } + + foreach ($config->getLayers() as $layer) { + $data['data']['Config Files'][] = + array(ucfirst($layer) . ' Configuration File', 'Filename' , + $config->getConfFile($layer)); + } + + $this->ui->outputData($data, 'config-show'); + return true; + } /** * Checks if a layer is defined or not @@ -216,10 +408,7 @@ displays help for all configuration parameters. return " only the layers: \"" . implode('" or "', $layers) . "\" are supported"; } } + return false; } - - // }}} -} - -?> +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Config.xml b/3rdparty/PEAR/Command/Config.xml new file mode 100644 index 0000000000..f64a925f52 --- /dev/null +++ b/3rdparty/PEAR/Command/Config.xml @@ -0,0 +1,92 @@ + + + Show All Settings + doConfigShow + csh + + + c + show configuration variables for another channel + CHAN + + + [layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. + + + + Show One Setting + doConfigGet + cg + + + c + show configuration variables for another channel + CHAN + + + <parameter> [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. + + + + Change Setting + doConfigSet + cs + + + c + show configuration variables for another channel + CHAN + + + <parameter> <value> [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. + + + + Show Information About Setting + doConfigHelp + ch + + [parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. + + + + Create a Default configuration file + doConfigCreate + coc + + + w + create a config file for a windows install + + + <root path> <filename> +Create a default configuration file with all directory configuration +variables set to subdirectories of <root path>, and save it as <filename>. +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Install.php b/3rdparty/PEAR/Command/Install.php index dce52f017e..c035f6d20d 100644 --- a/3rdparty/PEAR/Command/Install.php +++ b/3rdparty/PEAR/Command/Install.php @@ -1,30 +1,38 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Install.php,v 1.53.2.1 2004/10/19 04:08:42 cellog Exp $ +/** + * PEAR_Command_Install (install, upgrade, upgrade-all, uninstall, bundle, run-scripts commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Install.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -require_once "PEAR/Command/Common.php"; -require_once "PEAR/Installer.php"; +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; /** * PEAR commands for installation or deinstallation/upgrading of * packages. * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Command_Install extends PEAR_Command_Common { @@ -40,6 +48,10 @@ class PEAR_Command_Install extends PEAR_Command_Common 'shortopt' => 'f', 'doc' => 'will overwrite newer installed packages', ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), 'nodeps' => array( 'shortopt' => 'n', 'doc' => 'ignore dependencies, install anyway', @@ -63,7 +75,12 @@ class PEAR_Command_Install extends PEAR_Command_Common 'installroot' => array( 'shortopt' => 'R', 'arg' => 'DIR', - 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'packagingroot' => array( + 'shortopt' => 'P', + 'arg' => 'DIR', + 'doc' => 'root directory used when packaging files, like RPM packaging', ), 'ignore-errors' => array( 'doc' => 'force install even if there were errors', @@ -76,8 +93,16 @@ class PEAR_Command_Install extends PEAR_Command_Common 'shortopt' => 'o', 'doc' => 'install all required dependencies', ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), ), - 'doc' => ' ... + 'doc' => '[channel/] ... Installs one or more PEAR packages. You can specify a package to install in four ways: @@ -90,10 +115,17 @@ anywhere on the net. package.xml. Useful for testing, or for wrapping a PEAR package in another package manager such as RPM. -"Package" : queries your configured server +"Package[-version/state][.tar]" : queries your default channel\'s server ({config master_server}) and downloads the newest package with the preferred quality/state ({config preferred_state}). +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + More than one package may be specified at once. It is ok to mix these four ways of specifying packages. '), @@ -102,10 +134,19 @@ four ways of specifying packages. 'function' => 'doInstall', 'shortcut' => 'up', 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), 'force' => array( 'shortopt' => 'f', 'doc' => 'overwrite newer installed packages', ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), 'nodeps' => array( 'shortopt' => 'n', 'doc' => 'ignore dependencies, upgrade anyway', @@ -138,6 +179,14 @@ four ways of specifying packages. 'shortopt' => 'o', 'doc' => 'install all required dependencies', ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), ), 'doc' => ' ... Upgrades one or more PEAR packages. See documentation for the @@ -150,10 +199,15 @@ upgrade anyway). More than one package may be specified at once. '), 'upgrade-all' => array( - 'summary' => 'Upgrade All Packages', - 'function' => 'doInstall', + 'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]', + 'function' => 'doUpgradeAll', 'shortcut' => 'ua', 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), 'nodeps' => array( 'shortopt' => 'n', 'doc' => 'ignore dependencies, upgrade anyway', @@ -173,13 +227,18 @@ More than one package may be specified at once. 'installroot' => array( 'shortopt' => 'R', 'arg' => 'DIR', - 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', ), 'ignore-errors' => array( 'doc' => 'force install even if there were errors', ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), ), 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + Upgrades all packages that have a newer release available. Upgrades are done only if there is a release available of the state specified in "preferred_state" (currently {config preferred_state}), or a state considered @@ -206,10 +265,15 @@ more stable. 'ignore-errors' => array( 'doc' => 'force install even if there were errors', ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), ), - 'doc' => ' ... + 'doc' => '[channel/] ... Uninstalls one or more PEAR packages. More than one package may be -specified at once. +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) '), 'bundle' => array( 'summary' => 'Unpacks a Pecl Package', @@ -229,6 +293,15 @@ specified at once. 'doc' => ' Unpacks a Pecl Package into the selected location. It will download the package if needed. +'), + 'run-scripts' => array( + 'summary' => 'Run Post-Install Scripts bundled with a package', + 'function' => 'doRunScripts', + 'shortcut' => 'rs', + 'options' => array( + ), + 'doc' => ' +Run post-installation scripts in package , if any exist. '), ); @@ -247,123 +320,763 @@ package if needed. // }}} + /** + * For unit testing purposes + */ + function &getDownloader(&$ui, $options, &$config) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($ui, $options, $config); + return $a; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + function enableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + // already enabled - assume if one is, all are + return true; + } + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + } else { + $newini = array(); + } + foreach ($binaries as $binary) { + if ($ini['extension_dir']) { + $binary = basename($binary); + } + $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n"); + } + $newini = array_merge($newini, array_slice($ini['all'], $line)); + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function disableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + $found = false; + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + $found = true; + break; + } + } + if (!$found) { + // not enabled + return true; + } + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + // delete the enable line + $newini = array_merge($newini, array_slice($ini['all'], $line + 1)); + } else { + $newini = array_slice($ini['all'], 1); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function _parseIni($filename) + { + if (!file_exists($filename)) { + return PEAR::raiseError('php.ini "' . $filename . '" does not exist'); + } + + if (filesize($filename) > 300000) { + return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting'); + } + + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $zend_extension_line = 'zend_extension' . $debug . $ts; + $all = @file($filename); + if (!$all) { + return PEAR::raiseError('php.ini "' . $filename .'" could not be read'); + } + $zend_extensions = $extensions = array(); + // assume this is right, but pull from the php.ini if it is found + $extension_dir = ini_get('extension_dir'); + foreach ($all as $linenum => $line) { + $line = trim($line); + if (!$line) { + continue; + } + if ($line[0] == ';') { + continue; + } + if (strtolower(substr($line, 0, 13)) == 'extension_dir') { + $line = trim(substr($line, 13)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extension_dir = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, 9)) == 'extension') { + $line = trim(substr($line, 9)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, strlen($zend_extension_line))) == + $zend_extension_line) { + $line = trim(substr($line, strlen($zend_extension_line))); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $zend_extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + } + return array( + 'extensions' => $extensions, + 'zend_extensions' => $zend_extensions, + 'extension_dir' => $extension_dir, + 'all' => $all, + ); + } + // {{{ doInstall() function doInstall($command, $options, $params) { - require_once 'PEAR/Downloader.php'; + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + if (isset($options['installroot']) && isset($options['packagingroot'])) { + return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot'); + } + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + if (empty($this->installer)) { - $this->installer = &new PEAR_Installer($this->ui); + $this->installer = &$this->getInstaller($this->ui); } - if ($command == 'upgrade') { + + if ($command == 'upgrade' || $command == 'upgrade-all') { + // If people run the upgrade command but pass nothing, emulate a upgrade-all + if ($command == 'upgrade' && empty($params)) { + return $this->doUpgradeAll($command, $options, $params); + } $options['upgrade'] = true; + } else { + $packages = $params; } - if ($command == 'upgrade-all') { - include_once "PEAR/Remote.php"; - $options['upgrade'] = true; - $remote = &new PEAR_Remote($this->config); - $state = $this->config->get('preferred_state'); - if (empty($state) || $state == 'any') { - $latest = $remote->call("package.listLatestReleases"); - } else { - $latest = $remote->call("package.listLatestReleases", $state); - } - if (PEAR::isError($latest)) { - return $latest; - } - $reg = new PEAR_Registry($this->config->get('php_dir')); - $installed = array_flip($reg->listPackages()); - $params = array(); - foreach ($latest as $package => $info) { - $package = strtolower($package); - if (!isset($installed[$package])) { - // skip packages we don't have installed - continue; - } - $inst_version = $reg->packageInfo($package, 'version'); - if (version_compare("$info[version]", "$inst_version", "le")) { - // installed version is up-to-date - continue; - } - $params[] = $package; - $this->ui->outputData(array('data' => "Will upgrade $package"), $command); + + $instreg = &$reg; // instreg used to check if package is installed + if (isset($options['packagingroot']) && !isset($options['upgrade'])) { + $packrootphp_dir = $this->installer->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $options['packagingroot']); + $instreg = new PEAR_Registry($packrootphp_dir); // other instreg! + + if ($this->config->get('verbose') > 2) { + $this->ui->outputData('using package root: ' . $options['packagingroot']); } } - $this->downloader = &new PEAR_Downloader($this->ui, $options, $this->config); - $errors = array(); - $downloaded = array(); - $this->downloader->download($params); - $errors = $this->downloader->getErrorMsgs(); - if (count($errors)) { - $err['data'] = array($errors); - $err['headline'] = 'Install Errors'; - $this->ui->outputData($err); - return $this->raiseError("$command failed"); - } - $downloaded = $this->downloader->getDownloadedPackages(); - $this->installer->sortPkgDeps($downloaded); - foreach ($downloaded as $pkg) { - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - $info = $this->installer->install($pkg['file'], $options, $this->config); - PEAR::popErrorHandling(); - if (PEAR::isError($info)) { - $this->ui->outputData('ERROR: ' .$info->getMessage()); + + $abstractpackages = $otherpackages = array(); + // parse params + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($params as $param) { + if (strpos($param, 'http://') === 0) { + $otherpackages[] = $param; continue; } - if (is_array($info)) { - if ($this->config->get('verbose') > 0) { - $label = "$info[package] $info[version]"; - $out = array('data' => "$command ok: $label"); - if (isset($info['release_warnings'])) { - $out['release_warnings'] = $info['release_warnings']; - } - $this->ui->outputData($out, $command); + + if (strpos($param, 'channel://') === false && @file_exists($param)) { + if (isset($options['force'])) { + $otherpackages[] = $param; + continue; } + + $pkg = new PEAR_PackageFile($this->config); + $pf = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING); + if (PEAR::isError($pf)) { + $otherpackages[] = $param; + continue; + } + + $exists = $reg->packageExists($pf->getPackage(), $pf->getChannel()); + $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel()); + $version_compare = version_compare($pf->getVersion(), $pversion, '<='); + if ($exists && $version_compare) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString( + array('package' => $pf->getPackage(), + 'channel' => $pf->getChannel()), true)); + } + continue; + } + $otherpackages[] = $param; + continue; + } + + $e = $reg->parsePackageName($param, $channel); + if (PEAR::isError($e)) { + $otherpackages[] = $param; } else { + $abstractpackages[] = $e; + } + } + PEAR::staticPopErrorHandling(); + + // if there are any local package .tgz or remote static url, we can't + // filter. The filter only works for abstract packages + if (count($abstractpackages) && !isset($options['force'])) { + // when not being forced, only do necessary upgrades/installs + if (isset($options['upgrade'])) { + $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command); + } else { + $count = count($abstractpackages); + foreach ($abstractpackages as $i => $package) { + if (isset($package['group'])) { + // do not filter out install groups + continue; + } + + if ($instreg->packageExists($package['package'], $package['channel'])) { + if ($count > 1) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString($package, true)); + } + unset($abstractpackages[$i]); + } elseif ($count === 1) { + // Lets try to upgrade it since it's already installed + $options['upgrade'] = true; + } + } + } + } + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } elseif (count($abstractpackages)) { + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } + + $packages = array_merge($abstractpackages, $otherpackages); + if (!count($packages)) { + $c = ''; + if (isset($options['channel'])){ + $c .= ' in channel "' . $options['channel'] . '"'; + } + $this->ui->outputData('Nothing to ' . $command . $c); + return true; + } + + $this->downloader = &$this->getDownloader($this->ui, $options, $this->config); + $errors = $downloaded = $binaries = array(); + $downloaded = &$this->downloader->download($packages); + if (PEAR::isError($downloaded)) { + return $this->raiseError($downloaded); + } + + $errors = $this->downloader->getErrorMsgs(); + if (count($errors)) { + $err = array(); + $err['data'] = array(); + foreach ($errors as $error) { + if ($error !== null) { + $err['data'][] = array($error); + } + } + + if (!empty($err['data'])) { + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + } + + if (!count($downloaded)) { return $this->raiseError("$command failed"); } } + + $data = array( + 'headline' => 'Packages that would be Installed' + ); + + if (isset($options['pretend'])) { + foreach ($downloaded as $package) { + $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage())); + } + $this->ui->outputData($data, 'pretend'); + return true; + } + + $this->installer->setOptions($options); + $this->installer->sortPackagesForInstall($downloaded); + if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) { + $this->raiseError($err->getMessage()); + return true; + } + + $binaries = $extrainfo = array(); + foreach ($downloaded as $param) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->install($param, $options); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $oldinfo = $info; + $pkg = &$param->getPackageFile(); + if ($info->getCode() != PEAR_INSTALLER_NOBINARY) { + if (!($info = $pkg->installBinary($this->installer))) { + $this->ui->outputData('ERROR: ' .$oldinfo->getMessage()); + continue; + } + + // we just installed a different package than requested, + // let's change the param and info so that the rest of this works + $param = $info[0]; + $info = $info[1]; + } + } + + if (!is_array($info)) { + return $this->raiseError("$command failed"); + } + + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin' || + $param->getPackageType() == 'zendextsrc' || + $param->getPackageType() == 'zendextbin' + ) { + $pkg = &$param->getPackageFile(); + if ($instbin = $pkg->getInstalledBinary()) { + $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel()); + } else { + $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel()); + } + + foreach ($instpkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h')) + ) { + continue; // make sure we don't match php_blah.h + } + + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $extrainfo[] = 'You should add "' . $exttype . '=' . + $pinfo[1]['basename'] . '" to php.ini'; + } else { + $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() . + ' enabled in php.ini'; + } + } + } + } + + if ($this->config->get('verbose') > 0) { + $chan = $param->getChannel(); + $label = $reg->parsedPackageNameToString( + array( + 'channel' => $chan, + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + )); + $out = array('data' => "$command ok: $label"); + if (isset($info['release_warnings'])) { + $out['release_warnings'] = $info['release_warnings']; + } + $this->ui->outputData($out, $command); + + if (!isset($options['register-only']) && !isset($options['offline'])) { + if ($this->config->isDefinedLayer('ftp')) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpInstall($param); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote install failed: $label"); + } else { + $this->ui->outputData("remote install ok: $label"); + } + } + } + } + + $deps = $param->getDeps(); + if ($deps) { + if (isset($deps['group'])) { + $groups = $deps['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + + foreach ($groups as $group) { + if ($group['attribs']['name'] == 'default') { + // default group is always installed, unless the user + // explicitly chooses to install another group + continue; + } + $extrainfo[] = $param->getPackage() . ': Optional feature ' . + $group['attribs']['name'] . ' available (' . + $group['attribs']['hint'] . ')'; + } + + $extrainfo[] = $param->getPackage() . + ': To install optional features use "pear install ' . + $reg->parsedPackageNameToString( + array('package' => $param->getPackage(), + 'channel' => $param->getChannel()), true) . + '#featurename"'; + } + } + + $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel()); + // $pkg may be NULL if install is a 'fake' install via --packagingroot + if (is_object($pkg)) { + $pkg->setConfig($this->config); + if ($list = $pkg->listPostinstallScripts()) { + $pn = $reg->parsedPackageNameToString(array('channel' => + $param->getChannel(), 'package' => $param->getPackage()), true); + $extrainfo[] = $pn . ' has post-install scripts:'; + foreach ($list as $file) { + $extrainfo[] = $file; + } + $extrainfo[] = $param->getPackage() . + ': Use "pear run-scripts ' . $pn . '" to finish setup.'; + $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES'; + } + } + } + + if (count($extrainfo)) { + foreach ($extrainfo as $info) { + $this->ui->outputData($info); + } + } + return true; } + // }}} + // {{{ doUpgradeAll() + + function doUpgradeAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $upgrade = array(); + + if (isset($options['channel'])) { + $channels = array($options['channel']); + } else { + $channels = $reg->listChannels(); + } + + foreach ($channels as $channel) { + if ($channel == '__uri') { + continue; + } + + // parse name with channel + foreach ($reg->listPackages($channel) as $name) { + $upgrade[] = $reg->parsedPackageNameToString(array( + 'channel' => $channel, + 'package' => $name + )); + } + } + + $err = $this->doInstall($command, $options, $upgrade); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + } + } + // }}} // {{{ doUninstall() function doUninstall($command, $options, $params) { - if (empty($this->installer)) { - $this->installer = &new PEAR_Installer($this->ui); - } - if (sizeof($params) < 1) { + if (count($params) < 1) { return $this->raiseError("Please supply the package(s) you want to uninstall"); } - include_once 'PEAR/Registry.php'; - $reg = new PEAR_Registry($this->config->get('php_dir')); + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if (isset($options['remoteconfig'])) { + $e = $this->config->readFTPConfigFile($options['remoteconfig']); + if (!PEAR::isError($e)) { + $this->installer->setConfig($this->config); + } + } + + $reg = &$this->config->getRegistry(); $newparams = array(); + $binaries = array(); $badparams = array(); foreach ($params as $pkg) { - $info = $reg->packageInfo($pkg); + $channel = $this->config->get('default_channel'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($pkg, $channel); + PEAR::staticPopErrorHandling(); + if (!$parsed || PEAR::isError($parsed)) { + $badparams[] = $pkg; + continue; + } + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = &$reg->getPackage($package, $channel); + if ($info === null && + ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) { + // make sure this isn't a package that has flipped from pear to pecl but + // used a package.xml 1.0 + $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net'; + $info = &$reg->getPackage($package, $testc); + if ($info !== null) { + $channel = $testc; + } + } if ($info === null) { $badparams[] = $pkg; } else { - $newparams[] = $info; + $newparams[] = &$info; + // check for binary packages (this is an alias for those packages if so) + if ($installedbinary = $info->getInstalledBinary()) { + $this->ui->log('adding binary package ' . + $reg->parsedPackageNameToString(array('channel' => $channel, + 'package' => $installedbinary), true)); + $newparams[] = &$reg->getPackage($installedbinary, $channel); + } + // add the contents of a dependency group to the list of installed packages + if (isset($parsed['group'])) { + $group = $info->getDependencyGroup($parsed['group']); + if ($group) { + $installed = $reg->getInstalledGroup($group); + if ($installed) { + foreach ($installed as $i => $p) { + $newparams[] = &$installed[$i]; + } + } + } + } } } - $this->installer->sortPkgDeps($newparams, true); - $params = array(); - foreach($newparams as $info) { - $params[] = $info['info']['package']; + $err = $this->installer->sortPackagesForUninstall($newparams); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + return true; } + $params = $newparams; + // twist this to use it to check on whether dependent packages are also being uninstalled + // for circular dependencies like subpackages + $this->installer->setUninstallPackages($newparams); $params = array_merge($params, $badparams); + $binaries = array(); foreach ($params as $pkg) { - if ($this->installer->uninstall($pkg, $options)) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + if ($err = $this->installer->uninstall($pkg, $options)) { + $this->installer->popErrorHandling(); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + continue; + } + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin' || + $pkg->getPackageType() == 'zendextsrc' || + $pkg->getPackageType() == 'zendextbin') { + if ($instbin = $pkg->getInstalledBinary()) { + continue; // this will be uninstalled later + } + + foreach ($pkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $this->ui->outputData('Unable to remove "' . $exttype . '=' . + $pinfo[1]['basename'] . '" from php.ini', $command); + } else { + $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() . + ' disabled in php.ini', $command); + } + } + } + } + $savepkg = $pkg; if ($this->config->get('verbose') > 0) { + if (is_object($pkg)) { + $pkg = $reg->parsedPackageNameToString($pkg); + } $this->ui->outputData("uninstall ok: $pkg", $command); } + if (!isset($options['offline']) && is_object($savepkg) && + defined('PEAR_REMOTEINSTALL_OK')) { + if ($this->config->isDefinedLayer('ftp')) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpUninstall($savepkg); + $this->installer->popErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote uninstall failed: $pkg"); + } else { + $this->ui->outputData("remote uninstall ok: $pkg"); + } + } + } } else { - return $this->raiseError("uninstall failed: $pkg"); + $this->installer->popErrorHandling(); + if (!is_object($pkg)) { + return $this->raiseError("uninstall failed: $pkg"); + } + $pkg = $reg->parsedPackageNameToString($pkg); } } + return true; } @@ -379,66 +1092,17 @@ package if needed. function doBundle($command, $options, $params) { - if (empty($this->installer)) { - $this->installer = &new PEAR_Downloader($this->ui); - } - $installer = &$this->installer; - if (sizeof($params) < 1) { + $opts = array( + 'force' => true, + 'nodeps' => true, + 'soft' => true, + 'downloadonly' => true + ); + $downloader = &$this->getDownloader($this->ui, $opts, $this->config); + $reg = &$this->config->getRegistry(); + if (count($params) < 1) { return $this->raiseError("Please supply the package you want to bundle"); } - $pkgfile = $params[0]; - $need_download = false; - if (preg_match('#^(http|ftp)://#', $pkgfile)) { - $need_download = true; - } elseif (!@is_file($pkgfile)) { - if ($installer->validPackageName($pkgfile)) { - $pkgfile = $installer->getPackageDownloadUrl($pkgfile); - $need_download = true; - } else { - if (strlen($pkgfile)) { - return $this->raiseError("Could not open the package file: $pkgfile"); - } else { - return $this->raiseError("No package file given"); - } - } - } - - // Download package ----------------------------------------------- - if ($need_download) { - $downloaddir = $installer->config->get('download_dir'); - if (empty($downloaddir)) { - if (PEAR::isError($downloaddir = System::mktemp('-d'))) { - return $downloaddir; - } - $installer->log(2, '+ tmp dir created at ' . $downloaddir); - } - $callback = $this->ui ? array(&$installer, '_downloadCallback') : null; - $file = $installer->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); - if (PEAR::isError($file)) { - return $this->raiseError($file); - } - $pkgfile = $file; - } - - // Parse xml file ----------------------------------------------- - $pkginfo = $installer->infoFromTgzFile($pkgfile); - if (PEAR::isError($pkginfo)) { - return $this->raiseError($pkginfo); - } - $installer->validatePackageInfo($pkginfo, $errors, $warnings); - // XXX We allow warnings, do we have to do it? - if (count($errors)) { - if (empty($options['force'])) { - return $this->raiseError("The following errors where found:\n". - implode("\n", $errors)); - } else { - $this->log(0, "warning : the following errors were found:\n". - implode("\n", $errors)); - } - } - $pkgname = $pkginfo['package']; - - // Unpacking ------------------------------------------------- if (isset($options['destination'])) { if (!is_dir($options['destination'])) { @@ -446,19 +1110,35 @@ package if needed. } $dest = realpath($options['destination']); } else { - $pwd = getcwd(); - if (is_dir($pwd . DIRECTORY_SEPARATOR . 'ext')) { - $dest = $pwd . DIRECTORY_SEPARATOR . 'ext'; - } else { - $dest = $pwd; - } + $pwd = getcwd(); + $dir = $pwd . DIRECTORY_SEPARATOR . 'ext'; + $dest = is_dir($dir) ? $dir : $pwd; } - $dest .= DIRECTORY_SEPARATOR . $pkgname; - $orig = $pkgname . '-' . $pkginfo['version']; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $downloader->setDownloadDir($dest); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('download directory "' . $dest . + '" is not writeable.'); + } + $result = &$downloader->download(array($params[0])); + if (PEAR::isError($result)) { + return $result; + } + if (!isset($result[0])) { + return $this->raiseError('unable to unpack ' . $params[0]); + } + $pkgfile = &$result[0]->getPackageFile(); + $pkgname = $pkgfile->getName(); + $pkgversion = $pkgfile->getVersion(); - $tar = new Archive_Tar($pkgfile); - if (!@$tar->extractModify($dest, $orig)) { - return $this->raiseError("unable to unpack $pkgfile"); + // Unpacking ------------------------------------------------- + $dest .= DIRECTORY_SEPARATOR . $pkgname; + $orig = $pkgname . '-' . $pkgversion; + + $tar = &new Archive_Tar($pkgfile->getArchiveFile()); + if (!$tar->extractModify($dest, $orig)) { + return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile()); } $this->ui->outputData("Package ready at '$dest'"); // }}} @@ -466,5 +1146,123 @@ package if needed. // }}} -} -?> + function doRunScripts($command, $options, $params) + { + if (!isset($params[0])) { + return $this->raiseError('run-scripts expects 1 parameter: a package name'); + } + + $reg = &$this->config->getRegistry(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = &$reg->getPackage($parsed['package'], $parsed['channel']); + if (!is_object($package)) { + return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry'); + } + + $package->setConfig($this->config); + $package->runPostinstallScripts(); + $this->ui->outputData('Install scripts complete', $command); + return true; + } + + /** + * Given a list of packages, filter out those ones that are already up to date + * + * @param $packages: packages, in parsed array format ! + * @return list of packages that can be upgraded + */ + function _filterUptodatePackages($packages, $command) + { + $reg = &$this->config->getRegistry(); + $latestReleases = array(); + + $ret = array(); + foreach ($packages as $package) { + if (isset($package['group'])) { + $ret[] = $package; + continue; + } + + $channel = $package['channel']; + $name = $package['package']; + if (!$reg->packageExists($name, $channel)) { + $ret[] = $package; + continue; + } + + if (!isset($latestReleases[$channel])) { + // fill in cache for this channel + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror', null, $channel); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + $dorest = true; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!isset($package['state'])) { + $state = $this->config->get('preferred_state', null, $channel); + } else { + $state = $package['state']; + } + + if ($dorest) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + $installed = array_flip($reg->listPackages($channel)); + $latest = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($latest)) { + $this->ui->outputData('Error getting channel info from ' . $channel . + ': ' . $latest->getMessage()); + continue; + } + + $latestReleases[$channel] = array_change_key_case($latest); + } + + // check package for latest release + $name_lower = strtolower($name); + if (isset($latestReleases[$channel][$name_lower])) { + // if not set, up to date + $inst_version = $reg->packageInfo($name, 'version', $channel); + $channel_version = $latestReleases[$channel][$name_lower]['version']; + if (version_compare($channel_version, $inst_version, 'le')) { + // installed version is up-to-date + continue; + } + + // maintain BC + if ($command == 'upgrade-all') { + $this->ui->outputData(array('data' => 'Will upgrade ' . + $reg->parsedPackageNameToString($package)), $command); + } + $ret[] = $package; + } + } + + return $ret; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Install.xml b/3rdparty/PEAR/Command/Install.xml new file mode 100644 index 0000000000..1b1e933c22 --- /dev/null +++ b/3rdparty/PEAR/Command/Install.xml @@ -0,0 +1,276 @@ + + + Install Package + doInstall + i + + + f + will overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, install anyway + + + r + do not install files, only register the package as installed + + + s + soft install, fail silently, or upgrade if already installed + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + P + root directory used when packaging files, like RPM packaging + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + [channel/]<package> ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel's server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. + + + + Upgrade Package + doInstall + up + + + c + upgrade packages from a specific channel + CHAN + + + f + overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + <package> ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. + + + + Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters] + doUpgradeAll + ua + + + c + upgrade packages from a specific channel + CHAN + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + + force install even if there were errors + + + + do not check for recommended dependency version + + + +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. + + + + Un-install Package + doUninstall + un + + + n + ignore dependencies, uninstall anyway + + + r + do not remove files, only register the packages as not installed + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + O + do not attempt to uninstall remotely + + + [channel/]<package> ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) + + + + Unpacks a Pecl Package + doBundle + bun + + + d + Optional destination directory for unpacking (defaults to current path or "ext" if exists) + DIR + + + f + Force the unpacking even if there were errors in the package + + + <package> +Unpacks a Pecl Package into the selected location. It will download the +package if needed. + + + + Run Post-Install Scripts bundled with a package + doRunScripts + rs + + <package> +Run post-installation scripts in package <package>, if any exist. + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Mirror.php b/3rdparty/PEAR/Command/Mirror.php index bf56c3db64..4d157c6b88 100644 --- a/3rdparty/PEAR/Command/Mirror.php +++ b/3rdparty/PEAR/Command/Mirror.php @@ -1,53 +1,58 @@ | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Mirror.php,v 1.5 2004/03/18 12:23:57 mj Exp $ +/** + * PEAR_Command_Mirror (download-all command) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Alexander Merz + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mirror.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.2.0 + */ -require_once "PEAR/Command/Common.php"; -require_once "PEAR/Command.php"; -require_once "PEAR/Remote.php"; -require_once "PEAR.php"; +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; /** * PEAR commands for providing file mirrors * + * @category pear + * @package PEAR + * @author Alexander Merz + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.2.0 */ class PEAR_Command_Mirror extends PEAR_Command_Common { - // {{{ properties - var $commands = array( 'download-all' => array( - 'summary' => 'Downloads each available package from master_server', + 'summary' => 'Downloads each available package from the default channel', 'function' => 'doDownloadAll', 'shortcut' => 'da', - 'options' => array(), + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + ), 'doc' => ' - Requests a list of available packages from the package server - (master_server) and downloads them to current working directory' +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded' ), ); - // }}} - - // {{{ constructor - /** * PEAR_Command_Mirror constructor. * @@ -60,9 +65,15 @@ class PEAR_Command_Mirror extends PEAR_Command_Common parent::PEAR_Command_Common($ui, $config); } - // }}} + /** + * For unit-testing + */ + function &factory($a) + { + $a = &PEAR_Command::factory($a, $this->config); + return $a; + } - // {{{ doDownloadAll() /** * retrieves a list of avaible Packages from master server * and downloads them @@ -72,30 +83,57 @@ class PEAR_Command_Mirror extends PEAR_Command_Common * @param array $options the command options before the command * @param array $params the stuff after the command name * @return bool true if succesful - * @throw PEAR_Error + * @throw PEAR_Error */ function doDownloadAll($command, $options, $params) { - $this->config->set("php_dir", "."); - $remote = &new PEAR_Remote($this->config); - $remoteInfo = $remote->call("package.listAll"); + $savechannel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + $this->config->set('default_channel', $channel); + + $this->ui->outputData('Using Channel ' . $this->config->get('default_channel')); + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $remoteInfo = array_flip($rest->listPackages($base, $channel)); + } + if (PEAR::isError($remoteInfo)) { return $remoteInfo; } - $cmd = &PEAR_Command::factory("download", $this->config); + + $cmd = &$this->factory("download"); if (PEAR::isError($cmd)) { return $cmd; } - foreach ($remoteInfo as $pkgn => $pkg) { - /** - * Error handling not neccesary, because already done by - * the download command - */ - $cmd->run("download", array(), array($pkgn)); + + $this->ui->outputData('Using Preferred State of ' . + $this->config->get('preferred_state')); + $this->ui->outputData('Gathering release information, please wait...'); + + /** + * Error handling not necessary, because already done by + * the download command + */ + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $cmd->run('download', array('downloadonly' => true), array_keys($remoteInfo)); + PEAR::staticPopErrorHandling(); + $this->config->set('default_channel', $savechannel); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage()); } return true; } - - // }}} -} +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Mirror.xml b/3rdparty/PEAR/Command/Mirror.xml new file mode 100644 index 0000000000..fe8be9d03b --- /dev/null +++ b/3rdparty/PEAR/Command/Mirror.xml @@ -0,0 +1,18 @@ + + + Downloads each available package from the default channel + doDownloadAll + da + + + c + specify a channel other than the default channel + CHAN + + + +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Package.php b/3rdparty/PEAR/Command/Package.php index aca8711111..81df7bf694 100644 --- a/3rdparty/PEAR/Command/Package.php +++ b/3rdparty/PEAR/Command/Package.php @@ -1,32 +1,44 @@ | -// | Martin Jansen | -// | Greg Beaver | -// +----------------------------------------------------------------------+ -// -// $Id: Package.php,v 1.61.2.7 2005/02/17 17:47:55 cellog Exp $ +/** + * PEAR_Command_Package (package, package-validate, cvsdiff, cvstag, package-dependencies, + * sign, makerpm, convert commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -require_once 'PEAR/Common.php'; +/** + * base class + */ require_once 'PEAR/Command/Common.php'; +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + class PEAR_Command_Package extends PEAR_Command_Common { - // {{{ properties - var $commands = array( 'package' => array( 'summary' => 'Build Package', @@ -42,9 +54,13 @@ class PEAR_Command_Package extends PEAR_Command_Common 'doc' => 'Print the name of the packaged file.', ), ), - 'doc' => '[descfile] + 'doc' => '[descfile] [descfile2] Creates a PEAR package from its description file (usually called -package.xml). +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" ' ), 'package-validate' => array( @@ -118,6 +134,39 @@ Using the -r or -R option you may compare the current code with that of a specific release. ', ), + 'svntag' => array( + 'summary' => 'Set SVN Release Tag', + 'function' => 'doSvnTag', + 'shortcut' => 'sv', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running svntag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + ', + ), 'cvstag' => array( 'summary' => 'Set CVS Release Tag', 'function' => 'doCvsTag', @@ -144,49 +193,36 @@ of a specific release. 'doc' => 'Don\'t do anything, just pretend', ), ), - 'doc' => ' + 'doc' => ' [files...] Sets a CVS tag on all files in a package. Use this command after you have packaged a distribution tarball with the "package" command to tag what revisions of what files were in that release. If need to fix something after running cvstag once, but before the tarball is released to the public, use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. ', ), - 'run-tests' => array( - 'summary' => 'Run Regression Tests', - 'function' => 'doRunTests', - 'shortcut' => 'rt', - 'options' => array( - 'recur' => array( - 'shortopt' => 'r', - 'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum', - ), - 'ini' => array( - 'shortopt' => 'i', - 'doc' => 'actual string of settings to pass to php in format " -d setting=blah"', - 'arg' => 'SETTINGS' - ), - 'realtimelog' => array( - 'shortopt' => 'l', - 'doc' => 'Log test runs/results as they are run', - ), - ), - 'doc' => '[testfile|dir ...] -Run regression tests with PHP\'s regression testing script (run-tests.php).', - ), 'package-dependencies' => array( 'summary' => 'Show package dependencies', 'function' => 'doPackageDependencies', 'shortcut' => 'pd', 'options' => array(), - 'doc' => ' -List all depencies the package has.' + 'doc' => ' or or +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' ), 'sign' => array( 'summary' => 'Sign a package distribution file', 'function' => 'doSign', 'shortcut' => 'si', - 'options' => array(), + 'options' => array( + 'verbose' => array( + 'shortopt' => 'v', + 'doc' => 'Display GnuPG output', + ), + ), 'doc' => ' Signs a package distribution (.tar or .tgz) file with GnuPG.', ), @@ -220,13 +256,28 @@ $ rpm -bb PEAR::Net_Socket-1.0.spec Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm ', ), + 'convert' => array( + 'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format', + 'function' => 'doConvert', + 'shortcut' => 'c2', + 'options' => array( + 'flat' => array( + 'shortopt' => 'f', + 'doc' => 'do not beautify the filelist.', + ), + ), + 'doc' => '[descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. +' + ), ); var $output; - // }}} - // {{{ constructor - /** * PEAR_Command_Package constructor. * @@ -237,10 +288,6 @@ Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm parent::PEAR_Command_Common($ui, $config); } - // }}} - - // {{{ _displayValidationResults() - function _displayValidationResults($err, $warn, $strict = false) { foreach ($err as $e) { @@ -251,121 +298,262 @@ Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm } $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n", sizeof($err), sizeof($warn)); - if ($strict && sizeof($err) > 0) { + if ($strict && count($err) > 0) { $this->output .= "Fix these errors and try again."; return false; } return true; } - // }}} - // {{{ doPackage() + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + $a = &new PEAR_Packager; + return $a; + } + + function &getPackageFile($config, $debug = false) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($config, $debug); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } function doPackage($command, $options, $params) { $this->output = ''; - include_once 'PEAR/Packager.php'; - if (sizeof($params) < 1) { - $params[0] = "package.xml"; - } $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml'; - $packager =& new PEAR_Packager(); - $err = $warn = array(); - $dir = dirname($pkginfofile); + $pkg2 = isset($params[1]) ? $params[1] : null; + if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) { + $pkg2 = 'package2.xml'; + } + + $packager = &$this->getPackager(); $compress = empty($options['nocompress']) ? true : false; - $result = $packager->package($pkginfofile, $compress); + $result = $packager->package($pkginfofile, $compress, $pkg2); if (PEAR::isError($result)) { - $this->ui->outputData($this->output, $command); return $this->raiseError($result); } + // Don't want output, only the package file name just created if (isset($options['showname'])) { $this->output = $result; } - if (PEAR::isError($result)) { - $this->output .= "Package failed: ".$result->getMessage(); + + if ($this->output) { + $this->ui->outputData($this->output, $command); } - $this->ui->outputData($this->output, $command); + return true; } - // }}} - // {{{ doPackageValidate() - function doPackageValidate($command, $options, $params) { $this->output = ''; - if (sizeof($params) < 1) { - $params[0] = "package.xml"; + if (count($params) < 1) { + $params[0] = 'package.xml'; } - $obj = new PEAR_Common; - $info = null; - if ($fp = @fopen($params[0], "r")) { - $test = fread($fp, 5); - fclose($fp); - if ($test == "infoFromDescriptionFile($params[0]); - } - } - if (empty($info)) { - $info = $obj->infoFromTgzFile($params[0]); + + $obj = &$this->getPackageFile($this->config, $this->_debug); + $obj->rawReturn(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $archive = $info->getArchiveFile(); + $tar = &new Archive_Tar($archive); + $tar->extract(dirname($info->getPackageFile())); + $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR . + $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR . + basename($info->getPackageFile())); } + + PEAR::staticPopErrorHandling(); if (PEAR::isError($info)) { return $this->raiseError($info); } - $obj->validatePackageInfo($info, $err, $warn); + + $valid = false; + if ($info->getPackagexmlVersion() == '2.0') { + if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) { + $info->flattenFileList(); + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + } else { + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + + $err = $warn = array(); + if ($errors = $info->getValidationWarnings()) { + foreach ($errors as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + $this->_displayValidationResults($err, $warn); $this->ui->outputData($this->output, $command); return true; } - // }}} - // {{{ doCvsTag() - - function doCvsTag($command, $options, $params) + function doSvnTag($command, $options, $params) { $this->output = ''; $_cmd = $command; - if (sizeof($params) < 1) { + if (count($params) < 1) { $help = $this->getHelp($command); return $this->raiseError("$command: missing parameter: $help[0]"); } - $obj = new PEAR_Common; - $info = $obj->infoFromDescriptionFile($params[0]); + + $packageFile = realpath($params[0]); + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); if (PEAR::isError($info)) { return $this->raiseError($info); } + $err = $warn = array(); - $obj->validatePackageInfo($info, $err, $warn); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + if (!$this->_displayValidationResults($err, $warn, true)) { $this->ui->outputData($this->output, $command); - break; + return $this->raiseError('SVN tag failed'); } - $version = $info['version']; - $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version); - $cvstag = "RELEASE_$cvsversion"; - $files = array_keys($info['filelist']); - $command = "cvs"; + + $version = $info->getVersion(); + $package = $info->getName(); + $svntag = "$package-$version"; + + if (isset($options['delete'])) { + return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + $path = $this->_svnFindPath($packageFile); + + // Check if there are any modified files + $fp = popen('svn st --xml ' . dirname($packageFile), "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (!isset($options['quiet']) && strpos($out, 'item="modified"')) { + $params = array(array( + 'name' => 'modified', + 'type' => 'yesno', + 'default' => 'no', + 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not commited, do you still want to tag ' . $version . '?', + )); + $answers = $this->ui->confirmDialog($params); + + if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) { + return true; + } + } + + if (isset($options['slide'])) { + $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + // Check if tag already exists + $releaseTag = $path['local']['base'] . 'tags' . DIRECTORY_SEPARATOR . $svntag; + $existsCommand = 'svn ls ' . $path['base'] . 'tags/'; + + $fp = popen($existsCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (in_array($svntag . DIRECTORY_SEPARATOR, explode("\n", $out))) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.'); + } elseif (file_exists($path['local']['base'] . 'tags') === false) { + return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags'); + } elseif (is_writeable($path['local']['base'] . 'tags') === false) { + return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags'); + } else { + $makeCommand = 'svn mkdir ' . $releaseTag; + $this->output .= "+ $makeCommand\n"; + if (empty($options['dry-run'])) { + // We need to create the tag dir. + $fp = popen($makeCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + $this->output .= "$out\n"; + } + } + + $command = 'svn'; if (isset($options['quiet'])) { $command .= ' -q'; } - if (isset($options['reallyquiet'])) { - $command .= ' -Q'; + + $command .= ' copy --parents '; + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1); + $files = array_keys($info->getFilelist()); + if (!in_array(basename($packageFile), $files)) { + $files[] = basename($packageFile); } - $command .= ' tag'; - if (isset($options['slide'])) { - $command .= ' -F'; + + array_shift($params); + if (count($params)) { + // add in additional files to be tagged (package files and such) + $files = array_merge($files, $params); } - if (isset($options['delete'])) { - $command .= ' -d'; - } - $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]); + + $commands = array(); foreach ($files as $file) { - $command .= ' ' . escapeshellarg($file); + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $commands[] = $command . ' ' . escapeshellarg($file) . ' ' . + escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file); } - if ($this->config->get('verbose') > 1) { - $this->output .= "+ $command\n"; + + $this->output .= implode("\n", $commands) . "\n"; + if (empty($options['dry-run'])) { + foreach ($commands as $command) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } } + + $command = 'svn ci -m "Tagging the ' . $version . ' release" ' . $releaseTag . "\n"; $this->output .= "+ $command\n"; if (empty($options['dry-run'])) { $fp = popen($command, "r"); @@ -374,12 +562,155 @@ Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm } pclose($fp); } + $this->ui->outputData($this->output, $_cmd); return true; } - // }}} - // {{{ doCvsDiff() + function _svnFindPath($file) + { + $xml = ''; + $command = "svn info --xml $file"; + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $xml .= rtrim($line)."\n"; + } + pclose($fp); + $url_tag = strpos($xml, ''); + $url = substr($xml, $url_tag + 5, strpos($xml, '', $url_tag + 5) - ($url_tag + 5)); + + $path = array(); + $path['from'] = substr($url, 0, strrpos($url, '/')); + $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1); + + // Figure out the local paths - see http://pear.php.net/bugs/17463 + $pos = strpos($file, DIRECTORY_SEPARATOR . 'trunk' . DIRECTORY_SEPARATOR); + if ($pos === false) { + $pos = strpos($file, DIRECTORY_SEPARATOR . 'branches' . DIRECTORY_SEPARATOR); + } + $path['local']['base'] = substr($file, 0, $pos + 1); + + return $path; + } + + function _svnRemoveTag($version, $package, $tag, $packageFile, $options) + { + $command = 'svn'; + + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' remove'; + $command .= ' -m "Removing tag for the ' . $version . ' release."'; + + $path = $this->_svnFindPath($packageFile); + $command .= ' ' . $path['base'] . 'tags/' . $tag; + + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doCvsTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS tag failed'); + } + + $version = $info->getVersion(); + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version); + $cvstag = "RELEASE_$cvsversion"; + $files = array_keys($info->getFilelist()); + $command = 'cvs'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + if (isset($options['reallyquiet'])) { + $command .= ' -Q'; + } + + $command .= ' tag'; + if (isset($options['slide'])) { + $command .= ' -F'; + } + + if (isset($options['delete'])) { + $command .= ' -d'; + } + + $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]); + array_shift($params); + if (count($params)) { + // add in additional files to be tagged + $files = array_merge($files, $params); + } + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $command .= ' ' . escapeshellarg($file); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } function doCvsDiff($command, $options, $params) { @@ -388,48 +719,78 @@ Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm $help = $this->getHelp($command); return $this->raiseError("$command: missing parameter: $help[0]"); } - $obj = new PEAR_Common; - $info = $obj->infoFromDescriptionFile($params[0]); + + $file = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL); if (PEAR::isError($info)) { return $this->raiseError($info); } - $files = array_keys($info['filelist']); + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS diff failed'); + } + + $info1 = $info->getFilelist(); + $files = $info1; $cmd = "cvs"; if (isset($options['quiet'])) { $cmd .= ' -q'; unset($options['quiet']); } + if (isset($options['reallyquiet'])) { $cmd .= ' -Q'; unset($options['reallyquiet']); } + if (isset($options['release'])) { $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']); $cvstag = "RELEASE_$cvsversion"; $options['revision'] = $cvstag; unset($options['release']); } + $execute = true; if (isset($options['dry-run'])) { $execute = false; unset($options['dry-run']); } + $cmd .= ' diff'; // the rest of the options are passed right on to "cvs diff" foreach ($options as $option => $optarg) { - $arg = @$this->commands[$command]['options'][$option]['arg']; - $short = @$this->commands[$command]['options'][$option]['shortopt']; + $arg = $short = false; + if (isset($this->commands[$command]['options'][$option])) { + $arg = $this->commands[$command]['options'][$option]['arg']; + $short = $this->commands[$command]['options'][$option]['shortopt']; + } $cmd .= $short ? " -$short" : " --$option"; if ($arg && $optarg) { $cmd .= ($short ? '' : '=') . escapeshellarg($optarg); } } + foreach ($files as $file) { - $cmd .= ' ' . escapeshellarg($file); + $cmd .= ' ' . escapeshellarg($file['name']); } + if ($this->config->get('verbose') > 1) { $this->output .= "+ $cmd\n"; } + if ($execute) { $fp = popen($cmd, "r"); while ($line = fgets($fp, 1024)) { @@ -437,179 +798,148 @@ Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm } pclose($fp); } + $this->ui->outputData($this->output, $command); return true; } - // }}} - // {{{ doRunTests() - - function doRunTests($command, $options, $params) - { - include_once 'PEAR/RunTest.php'; - $log = new PEAR_Common; - $log->ui = &$this->ui; // slightly hacky, but it will work - $run = new PEAR_RunTest($log); - $tests = array(); - if (isset($options['recur'])) { - $depth = 4; - } else { - $depth = 1; - } - if (!count($params)) { - $params[] = '.'; - } - foreach ($params as $p) { - if (is_dir($p)) { - $dir = System::find(array($p, '-type', 'f', - '-maxdepth', $depth, - '-name', '*.phpt')); - $tests = array_merge($tests, $dir); - } else { - if (!@file_exists($p)) { - if (!preg_match('/\.phpt$/', $p)) { - $p .= '.phpt'; - } - $dir = System::find(array(dirname($p), '-type', 'f', - '-maxdepth', $depth, - '-name', $p)); - $tests = array_merge($tests, $dir); - } else { - $tests[] = $p; - } - } - } - $ini_settings = ''; - if (isset($options['ini'])) { - $ini_settings .= $options['ini']; - } - if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) { - $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}"; - } - if ($ini_settings) { - $this->ui->outputData('Using INI settings: "' . $ini_settings . '"'); - } - $skipped = $passed = $failed = array(); - $this->ui->outputData('Running ' . count($tests) . ' tests', $command); - $start = time(); - if (isset($options['realtimelog'])) { - @unlink('run-tests.log'); - } - foreach ($tests as $t) { - if (isset($options['realtimelog'])) { - $fp = @fopen('run-tests.log', 'a'); - if ($fp) { - fwrite($fp, "Running test $t..."); - fclose($fp); - } - } - $result = $run->run($t, $ini_settings); - if (OS_WINDOWS) { - for($i=0;$i<2000;$i++) { - $i = $i; // delay - race conditions on windows - } - } - if (isset($options['realtimelog'])) { - $fp = @fopen('run-tests.log', 'a'); - if ($fp) { - fwrite($fp, "$result\n"); - fclose($fp); - } - } - if ($result == 'FAILED') { - $failed[] = $t; - } - if ($result == 'PASSED') { - $passed[] = $t; - } - if ($result == 'SKIPPED') { - $skipped[] = $t; - } - } - $total = date('i:s', time() - $start); - if (count($failed)) { - $output = "TOTAL TIME: $total\n"; - $output .= count($passed) . " PASSED TESTS\n"; - $output .= count($skipped) . " SKIPPED TESTS\n"; - $output .= count($failed) . " FAILED TESTS:\n"; - foreach ($failed as $failure) { - $output .= $failure . "\n"; - } - if (isset($options['realtimelog'])) { - $fp = @fopen('run-tests.log', 'a'); - } else { - $fp = @fopen('run-tests.log', 'w'); - } - if ($fp) { - fwrite($fp, $output, strlen($output)); - fclose($fp); - $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command); - } - } elseif (@file_exists('run-tests.log') && !@is_dir('run-tests.log')) { - @unlink('run-tests.log'); - } - $this->ui->outputData('TOTAL TIME: ' . $total); - $this->ui->outputData(count($passed) . ' PASSED TESTS', $command); - $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command); - if (count($failed)) { - $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command); - foreach ($failed as $failure) { - $this->ui->outputData($failure, $command); - } - } - - return true; - } - - // }}} - // {{{ doPackageDependencies() - function doPackageDependencies($command, $options, $params) { // $params[0] -> the PEAR package to list its information - if (sizeof($params) != 1) { + if (count($params) !== 1) { return $this->raiseError("bad parameter(s), try \"help $command\""); } - $obj = new PEAR_Common(); - if (PEAR::isError($info = $obj->infoFromAny($params[0]))) { + $obj = &$this->getPackageFile($this->config, $this->_debug); + if (is_file($params[0]) || strpos($params[0], '.xml') > 0) { + $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $reg = $this->config->getRegistry(); + $info = $obj->fromArray($reg->packageInfo($params[0])); + } + + if (PEAR::isError($info)) { return $this->raiseError($info); } - if (is_array($info['release_deps'])) { - $data = array( - 'caption' => 'Dependencies for ' . $info['package'], - 'border' => true, - 'headline' => array("Type", "Name", "Relation", "Version"), - ); + $deps = $info->getDeps(); + if (is_array($deps)) { + if ($info->getPackagexmlVersion() == '1.0') { + $data = array( + 'caption' => 'Dependencies for pear/' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", "Relation", "Version"), + ); - foreach ($info['release_deps'] as $d) { + foreach ($deps as $d) { + if (isset($d['optional'])) { + if ($d['optional'] == 'yes') { + $req = 'No'; + } else { + $req = 'Yes'; + } + } else { + $req = 'Yes'; + } - if (isset($this->_deps_rel_trans[$d['rel']])) { - $rel = $this->_deps_rel_trans[$d['rel']]; - } else { - $rel = $d['rel']; + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + + if (isset($d['name'])) { + $name = $d['name']; + } else { + $name = ''; + } + + if (isset($d['version'])) { + $version = $d['version']; + } else { + $version = ''; + } + + $data['data'][] = array($req, $type, $name, $rel, $version); } + } else { // package.xml 2.0 dependencies display + require_once 'PEAR/Dependency2.php'; + $deps = $info->getDependencies(); + $reg = &$this->config->getRegistry(); + if (is_array($deps)) { + $d = new PEAR_Dependency2($this->config, array(), ''); + $data = array( + 'caption' => 'Dependencies for ' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'), + ); + foreach ($deps as $type => $subd) { + $req = ($type == 'required') ? 'Yes' : 'No'; + if ($type == 'group') { + $group = $subd['attribs']['name']; + } else { + $group = ''; + } - if (isset($this->_deps_type_trans[$d['type']])) { - $type = ucfirst($this->_deps_type_trans[$d['type']]); - } else { - $type = $d['type']; + if (!isset($subd[0])) { + $subd = array($subd); + } + + foreach ($subd as $groupa) { + foreach ($groupa as $deptype => $depinfo) { + if ($deptype == 'attribs') { + continue; + } + + if ($deptype == 'pearinstaller') { + $deptype = 'pear Installer'; + } + + if (!isset($depinfo[0])) { + $depinfo = array($depinfo); + } + + foreach ($depinfo as $inf) { + $name = ''; + if (isset($inf['channel'])) { + $alias = $reg->channelAlias($inf['channel']); + if (!$alias) { + $alias = '(channel?) ' .$inf['channel']; + } + $name = $alias . '/'; + + } + if (isset($inf['name'])) { + $name .= $inf['name']; + } elseif (isset($inf['pattern'])) { + $name .= $inf['pattern']; + } else { + $name .= ''; + } + + if (isset($inf['uri'])) { + $name .= ' [' . $inf['uri'] . ']'; + } + + if (isset($inf['conflicts'])) { + $ver = 'conflicts'; + } else { + $ver = $d->_getExtraString($inf); + } + + $data['data'][] = array($req, ucfirst($deptype), $name, + $ver, $group); + } + } + } + } } - - if (isset($d['name'])) { - $name = $d['name']; - } else { - $name = ''; - } - - if (isset($d['version'])) { - $version = $d['version']; - } else { - $version = ''; - } - - $data['data'][] = array($type, $name, $rel, $version); } $this->ui->outputData($data, $command); @@ -620,200 +950,175 @@ Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm $this->ui->outputData("This package does not have any dependencies.", $command); } - // }}} - // {{{ doSign() - function doSign($command, $options, $params) { // should move most of this code into PEAR_Packager // so it'll be easy to implement "pear package --sign" - if (sizeof($params) != 1) { + if (count($params) !== 1) { return $this->raiseError("bad parameter(s), try \"help $command\""); } + + require_once 'System.php'; + require_once 'Archive/Tar.php'; + if (!file_exists($params[0])) { return $this->raiseError("file does not exist: $params[0]"); } - $obj = new PEAR_Common; - $info = $obj->infoFromTgzFile($params[0]); + + $obj = $this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); if (PEAR::isError($info)) { return $this->raiseError($info); } - include_once "Archive/Tar.php"; - include_once "System.php"; + $tar = new Archive_Tar($params[0]); - $tmpdir = System::mktemp('-d pearsign'); - if (!$tar->extractList('package.xml package.sig', $tmpdir)) { + + $tmpdir = $this->config->get('temp_dir'); + $tmpdir = System::mktemp(' -t "' . $tmpdir . '" -d pearsign'); + if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) { return $this->raiseError("failed to extract tar file"); } + if (file_exists("$tmpdir/package.sig")) { return $this->raiseError("package already signed"); } - @unlink("$tmpdir/package.sig"); + + $packagexml = 'package.xml'; + if (file_exists("$tmpdir/package2.xml")) { + $packagexml = 'package2.xml'; + } + + if (file_exists("$tmpdir/package.sig")) { + unlink("$tmpdir/package.sig"); + } + + if (!file_exists("$tmpdir/$packagexml")) { + return $this->raiseError("Extracted file $tmpdir/$packagexml not found."); + } + $input = $this->ui->userDialog($command, array('GnuPG Passphrase'), array('password')); - $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/package.xml 2>/dev/null", "w"); + if (!isset($input[0])) { + //use empty passphrase + $input[0] = ''; + } + + $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null'; + $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w"); if (!$gpg) { return $this->raiseError("gpg command failed"); } - fwrite($gpg, "$input[0]\r"); + + fwrite($gpg, "$input[0]\n"); if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) { return $this->raiseError("gpg sign failed"); } - $tar->addModify("$tmpdir/package.sig", '', $tmpdir); + + if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) { + return $this->raiseError('failed adding signature to file'); + } + + $this->ui->outputData("Package signed.", $command); return true; } - // }}} - // {{{ doMakeRPM() + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } - /* + /** + * For unit testing purposes + */ + function &getCommandPackaging(&$ui, &$config) + { + if (!class_exists('PEAR_Command_Packaging')) { + if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) { + fclose($fp); + include_once 'PEAR/Command/Packaging.php'; + } + } - (cox) + if (class_exists('PEAR_Command_Packaging')) { + $a = &new PEAR_Command_Packaging($ui, $config); + } else { + $a = null; + } - TODO: - - - Fill the rpm dependencies in the template file. - - IDEAS: - - - Instead of mapping the role to rpm vars, perhaps it's better - - to use directly the pear cmd to install the files by itself - - in %postrun so: - - pear -d php_dir=%{_libdir}/php/pear -d test_dir=.. - - */ + return $a; + } function doMakeRPM($command, $options, $params) { - if (sizeof($params) != 1) { - return $this->raiseError("bad parameter(s), try \"help $command\""); - } - if (!file_exists($params[0])) { - return $this->raiseError("file does not exist: $params[0]"); - } - include_once "Archive/Tar.php"; - include_once "PEAR/Installer.php"; - include_once "System.php"; - $tar = new Archive_Tar($params[0]); - $tmpdir = System::mktemp('-d pear2rpm'); - $instroot = System::mktemp('-d pear2rpm'); - $tmp = $this->config->get('verbose'); - $this->config->set('verbose', 0); - $installer = new PEAR_Installer($this->ui); - $info = $installer->install($params[0], - array('installroot' => $instroot, - 'nodeps' => true)); - $pkgdir = "$info[package]-$info[version]"; - $info['rpm_xml_dir'] = '/var/lib/pear'; - $this->config->set('verbose', $tmp); - if (!$tar->extractList("package.xml", $tmpdir, $pkgdir)) { - return $this->raiseError("failed to extract $params[0]"); - } - if (!file_exists("$tmpdir/package.xml")) { - return $this->raiseError("no package.xml found in $params[0]"); - } - if (isset($options['spec-template'])) { - $spec_template = $options['spec-template']; - } else { - $spec_template = $this->config->get('data_dir') . - '/PEAR/template.spec'; - } - if (isset($options['rpm-pkgname'])) { - $rpm_pkgname_format = $options['rpm-pkgname']; - } else { - $rpm_pkgname_format = "PEAR::%s"; + + // Check to see if PEAR_Command_Packaging is installed, and + // transparently switch to use the "make-rpm-spec" command from it + // instead, if it does. Otherwise, continue to use the old version + // of "makerpm" supplied with this package (PEAR). + $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config); + if ($packaging_cmd !== null) { + $this->ui->outputData('PEAR_Command_Packaging is installed; using '. + 'newer "make-rpm-spec" command instead'); + return $packaging_cmd->run('make-rpm-spec', $options, $params); } - $info['extra_headers'] = ''; - $info['doc_files'] = ''; - $info['files'] = ''; - $info['rpm_package'] = sprintf($rpm_pkgname_format, $info['package']); - $srcfiles = 0; - foreach ($info['filelist'] as $name => $attr) { - - if (!isset($attr['role'])) { - continue; - } - $name = preg_replace('![/:\\\\]!', '/', $name); - if ($attr['role'] == 'doc') { - $info['doc_files'] .= " $name"; - - // Map role to the rpm vars - } else { - - $c_prefix = '%{_libdir}/php/pear'; - - switch ($attr['role']) { - - case 'php': - - $prefix = $c_prefix; break; - - case 'ext': - - $prefix = '%{_libdir}/php'; break; // XXX good place? - - case 'src': - - $srcfiles++; - - $prefix = '%{_includedir}/php'; break; // XXX good place? - - case 'test': - - $prefix = "$c_prefix/tests/" . $info['package']; break; - - case 'data': - - $prefix = "$c_prefix/data/" . $info['package']; break; - - case 'script': - - $prefix = '%{_bindir}'; break; - - } - - $name = str_replace('\\', '/', $name); - $info['files'] .= "$prefix/$name\n"; - - } - } - if ($srcfiles > 0) { - include_once "OS/Guess.php"; - $os = new OS_Guess; - $arch = $os->getCpu(); - } else { - $arch = 'noarch'; - } - $cfg = array('master_server', 'php_dir', 'ext_dir', 'doc_dir', - 'bin_dir', 'data_dir', 'test_dir'); - foreach ($cfg as $k) { - $info[$k] = $this->config->get($k); - } - $info['arch'] = $arch; - $fp = @fopen($spec_template, "r"); - if (!$fp) { - return $this->raiseError("could not open RPM spec file template $spec_template: $php_errormsg"); - } - $spec_contents = preg_replace('/@([a-z0-9_-]+)@/e', '$info["\1"]', fread($fp, filesize($spec_template))); - fclose($fp); - $spec_file = "$info[rpm_package]-$info[version].spec"; - $wp = fopen($spec_file, "wb"); - if (!$wp) { - return $this->raiseError("could not write RPM spec file $spec_file: $php_errormsg"); - } - fwrite($wp, $spec_contents); - fclose($wp); - $this->ui->outputData("Wrote RPM spec file $spec_file", $command); - + $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '. + 'improved version is available via "pear make-rpm-spec", which '. + 'is available by installing PEAR_Command_Packaging'); return true; } - // }}} -} + function doConvert($command, $options, $params) + { + $packagexml = isset($params[0]) ? $params[0] : 'package.xml'; + $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) . + DIRECTORY_SEPARATOR . 'package2.xml'; + $pkg = &$this->getPackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + return $this->raiseError($pf); + } -?> + if (is_a($pf, 'PEAR_PackageFile_v2')) { + $this->ui->outputData($packagexml . ' is already a package.xml version 2.0'); + return true; + } + + $gen = &$pf->getDefaultGenerator(); + $newpf = &$gen->toV2(); + $newpf->setPackagefile($newpackagexml); + $gen = &$newpf->getDefaultGenerator(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL); + $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($saved)) { + if (is_array($saved->getUserInfo())) { + foreach ($saved->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + + $this->ui->outputData($saved->getMessage()); + return true; + } + + $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"'); + return true; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Package.xml b/3rdparty/PEAR/Command/Package.xml new file mode 100644 index 0000000000..d1aff9d48d --- /dev/null +++ b/3rdparty/PEAR/Command/Package.xml @@ -0,0 +1,237 @@ + + + Build Package + doPackage + p + + + Z + Do not gzip the package file + + + n + Print the name of the packaged file. + + + [descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" + + + + Validate Package Consistency + doPackageValidate + pv + + + + + + Run a "cvs diff" for all files in a package + doCvsDiff + cd + + + q + Be quiet + + + Q + Be really quiet + + + D + Diff against revision of DATE + DATE + + + R + Diff against tag for package release REL + REL + + + r + Diff against revision REV + REV + + + c + Generate context diff + + + u + Generate unified diff + + + i + Ignore case, consider upper- and lower-case letters equivalent + + + b + Ignore changes in amount of white space + + + B + Ignore changes that insert or delete blank lines + + + + Report only whether the files differ, no details + + + n + Don't do anything, just pretend + + + <package.xml> +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. + + + + Set SVN Release Tag + doSvnTag + sv + + + q + Be quiet + + + F + Move (slide) tag if it exists + + + d + Remove tag + + + n + Don't do anything, just pretend + + + <package.xml> [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running svntag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + + + + Set CVS Release Tag + doCvsTag + ct + + + q + Be quiet + + + Q + Be really quiet + + + F + Move (slide) tag if it exists + + + d + Remove tag + + + n + Don't do anything, just pretend + + + <package.xml> [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. + + + + Show package dependencies + doPackageDependencies + pd + + <package-file> or <package.xml> or <install-package-name> +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package. + + + Sign a package distribution file + doSign + si + + + v + Display GnuPG output + + + <package-file> +Signs a package distribution (.tar or .tgz) file with GnuPG. + + + Builds an RPM spec file from a PEAR package + doMakeRPM + rpm + + + t + Use FILE as RPM spec file template + FILE + + + p + Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s". + FORMAT + + + <package-file> + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm + + + + Convert a package.xml 1.0 to package.xml 2.0 format + doConvert + c2 + + + f + do not beautify the filelist. + + + [descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Pickle.php b/3rdparty/PEAR/Command/Pickle.php new file mode 100644 index 0000000000..87aa25ea30 --- /dev/null +++ b/3rdparty/PEAR/Command/Pickle.php @@ -0,0 +1,421 @@ + + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Pickle.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.1 + */ + +class PEAR_Command_Pickle extends PEAR_Command_Common +{ + var $commands = array( + 'pickle' => array( + 'summary' => 'Build PECL Package', + 'function' => 'doPackage', + 'shortcut' => 'pi', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. +' + ), + ); + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Pickle(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing ease + * + * @return PEAR_Packager + */ + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + + $a = &new PEAR_Packager; + return $a; + } + + /** + * For unit-testing ease + * + * @param PEAR_Config $config + * @param bool $debug + * @param string|null $tmpdir + * @return PEAR_PackageFile + */ + function &getPackageFile($config, $debug = false) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $a = &new PEAR_PackageFile($config, $debug); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package2.xml'; + $packager = &$this->getPackager(); + if (PEAR::isError($err = $this->_convertPackage($pkginfofile))) { + return $err; + } + + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, 'package.xml'); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->ui->outputData($result, $command); + } + + return true; + } + + function _convertPackage($packagexml) + { + $pkg = &$this->getPackageFile($this->config); + $pf2 = &$pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + if (!is_a($pf2, 'PEAR_PackageFile_v2')) { + return $this->raiseError('Cannot process "' . + $packagexml . '", is not a package.xml 2.0'); + } + + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->config); + if ($pf2->getPackageType() != 'extsrc' && $pf2->getPackageType() != 'zendextsrc') { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", is not an extension source package. Using a PEAR_PackageFileManager-based ' . + 'script is an option'); + } + + if (is_array($pf2->getUsesRole())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + if (is_array($pf2->getUsesTask())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom tasks. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + $deps = $pf2->getDependencies(); + if (isset($deps['group'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains dependency groups. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($deps['required']['subpackage']) || + isset($deps['optional']['subpackage'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains subpackage dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['os'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains os dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['arch'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains arch dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + $pf->setPackage($pf2->getPackage()); + $pf->setSummary($pf2->getSummary()); + $pf->setDescription($pf2->getDescription()); + foreach ($pf2->getMaintainers() as $maintainer) { + $pf->addMaintainer($maintainer['role'], $maintainer['handle'], + $maintainer['name'], $maintainer['email']); + } + + $pf->setVersion($pf2->getVersion()); + $pf->setDate($pf2->getDate()); + $pf->setLicense($pf2->getLicense()); + $pf->setState($pf2->getState()); + $pf->setNotes($pf2->getNotes()); + $pf->addPhpDep($deps['required']['php']['min'], 'ge'); + if (isset($deps['required']['php']['max'])) { + $pf->addPhpDep($deps['required']['php']['max'], 'le'); + } + + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['required']['extension'])) { + if (!isset($deps['required']['extension'][0])) { + $deps['required']['extension'] = array($deps['required']['extension']); + } + + foreach ($deps['required']['extension'] as $dep) { + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + if (isset($deps['optional']['extension'])) { + if (!isset($deps['optional']['extension'][0])) { + $deps['optional']['extension'] = array($deps['optional']['extension']); + } + + foreach ($deps['optional']['extension'] as $dep) { + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + $contents = $pf2->getContents(); + $release = $pf2->getReleases(); + if (isset($releases[0])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'multiple extsrcrelease/zendextsrcrelease tags. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if ($configoptions = $pf2->getConfigureOptions()) { + foreach ($configoptions as $option) { + $default = isset($option['default']) ? $option['default'] : false; + $pf->addConfigureOption($option['name'], $option['prompt'], $default); + } + } + + if (isset($release['filelist']['ignore'])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'ignore tags. Using a PEAR_PackageFileManager-based script or the convert' . + ' command is an option'); + } + + if (isset($release['filelist']['install']) && + !isset($release['filelist']['install'][0])) { + $release['filelist']['install'] = array($release['filelist']['install']); + } + + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $baseinstalldir = $contents['dir']['attribs']['baseinstalldir']; + } else { + $baseinstalldir = false; + } + + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + + foreach ($contents['dir']['file'] as $file) { + if ($baseinstalldir && !isset($file['attribs']['baseinstalldir'])) { + $file['attribs']['baseinstalldir'] = $baseinstalldir; + } + + $processFile = $file; + unset($processFile['attribs']); + if (count($processFile)) { + foreach ($processFile as $name => $task) { + if ($name != $pf2->getTasksNs() . ':replace') { + return $this->raiseError('Cannot safely process "' . $packagexml . + '" contains tasks other than replace. Using a ' . + 'PEAR_PackageFileManager-based script is an option.'); + } + $file['attribs']['replace'][] = $task; + } + } + + if (!in_array($file['attribs']['role'], PEAR_Common::getFileRoles())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($release['filelist']['install'])) { + foreach ($release['filelist']['install'] as $installas) { + if ($installas['attribs']['name'] == $file['attribs']['name']) { + $file['attribs']['install-as'] = $installas['attribs']['as']; + } + } + } + + $pf->addFile('/', $file['attribs']['name'], $file['attribs']); + } + + if ($pf2->getChangeLog()) { + $this->ui->outputData('WARNING: changelog is not translated to package.xml ' . + '1.0, use PEAR_PackageFileManager-based script if you need changelog-' . + 'translation for package.xml 1.0'); + } + + $gen = &$pf->getDefaultGenerator(); + $gen->toPackageFile('.'); + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Pickle.xml b/3rdparty/PEAR/Command/Pickle.xml new file mode 100644 index 0000000000..721ecea995 --- /dev/null +++ b/3rdparty/PEAR/Command/Pickle.xml @@ -0,0 +1,36 @@ + + + Build PECL Package + doPackage + pi + + + Z + Do not gzip the package file + + + n + Print the name of the packaged file. + + + [descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Registry.php b/3rdparty/PEAR/Command/Registry.php index 62cb4b00e2..4304db5ddf 100644 --- a/3rdparty/PEAR/Command/Registry.php +++ b/3rdparty/PEAR/Command/Registry.php @@ -1,44 +1,75 @@ | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Registry.php,v 1.36 2004/01/08 17:33:13 sniper Exp $ +/** + * PEAR_Command_Registry (list, list-files, shell-test, info commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ require_once 'PEAR/Command/Common.php'; -require_once 'PEAR/Registry.php'; -require_once 'PEAR/Config.php'; +/** + * PEAR commands for registry manipulation + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ class PEAR_Command_Registry extends PEAR_Command_Common { - // {{{ properties - var $commands = array( 'list' => array( - 'summary' => 'List Installed Packages', + 'summary' => 'List Installed Packages In The Default Channel', 'function' => 'doList', 'shortcut' => 'l', - 'options' => array(), - 'doc' => '[package] + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'list installed packages from this channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'list installed packages from all channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' If invoked without parameters, this command lists the PEAR packages -installed in your php_dir ({config php_dir)). With a parameter, it -lists the files in that package. +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. ', ), + 'list-files' => array( + 'summary' => 'List Files In Installed Package', + 'function' => 'doFileList', + 'shortcut' => 'fl', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), 'shell-test' => array( 'summary' => 'Shell Script Test', 'function' => 'doShellTest', @@ -62,9 +93,6 @@ installed package.' ) ); - // }}} - // {{{ constructor - /** * PEAR_Command_Registry constructor. * @@ -75,64 +103,204 @@ installed package.' parent::PEAR_Command_Common($ui, $config); } - // }}} - - // {{{ doList() - function _sortinfo($a, $b) { - return strcmp($a['package'], $b['package']); + $apackage = isset($a['package']) ? $a['package'] : $a['name']; + $bpackage = isset($b['package']) ? $b['package'] : $b['name']; + return strcmp($apackage, $bpackage); } function doList($command, $options, $params) { - $reg = new PEAR_Registry($this->config->get('php_dir')); - if (sizeof($params) == 0) { - $installed = $reg->packageInfo(); - usort($installed, array(&$this, '_sortinfo')); - $i = $j = 0; - $data = array( - 'caption' => 'Installed packages:', - 'border' => true, - 'headline' => array('Package', 'Version', 'State') - ); - foreach ($installed as $package) { - $data['data'][] = array($package['package'], - $package['version'], - @$package['release_state']); + $reg = &$this->config->getRegistry(); + $channelinfo = isset($options['channelinfo']); + if (isset($options['allchannels']) && !$channelinfo) { + return $this->doListAll($command, array(), $params); + } + + if (isset($options['allchannels']) && $channelinfo) { + // allchannels with $channelinfo + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + $options['channel'] = $channel->getName(); + $ret = $this->doList($command, $options, $params); + + if (PEAR::isError($ret)) { + $errors[] = $ret; + } } - if (count($installed)==0) { - $data = '(no packages installed)'; + + PEAR::staticPopErrorHandling(); + if (count($errors)) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + if (count($params) === 1) { + return $this->doFileList($command, $options, $params); + } + + if (isset($options['channel'])) { + if (!$reg->channelExists($options['channel'])) { + return $this->raiseError('Channel "' . $options['channel'] .'" does not exist'); + } + + $channel = $reg->channelName($options['channel']); + } else { + $channel = $this->config->get('default_channel'); + } + + $installed = $reg->packageInfo(null, null, $channel); + usort($installed, array(&$this, '_sortinfo')); + + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel, + ); + if ($channelinfo) { + $data['headline'] = array('Channel', 'Package', 'Version', 'State'); + } + + if (count($installed) && !isset($data['data'])) { + $data['data'] = array(); + } + + foreach ($installed as $package) { + $pobj = $reg->getPackage(isset($package['package']) ? + $package['package'] : $package['name'], $channel); + if ($channelinfo) { + $packageinfo = array($pobj->getChannel(), $pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } else { + $packageinfo = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + $data['data'][] = $packageinfo; + } + + if (count($installed) === 0) { + if (!$channelinfo) { + $data = '(no packages installed from channel ' . $channel . ')'; + } else { + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'channel' => $channel, + 'data' => array(array('(no packages installed)')), + ); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doListAll($command, $options, $params) + { + // This duplicate code is deprecated over + // list --channelinfo, which gives identical + // output for list and list --allchannels. + $reg = &$this->config->getRegistry(); + $installed = $reg->packageInfo(null, null, null); + foreach ($installed as $channel => $packages) { + usort($packages, array($this, '_sortinfo')); + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel + ); + + foreach ($packages as $package) { + $p = isset($package['package']) ? $package['package'] : $package['name']; + $pobj = $reg->getPackage($p, $channel); + $data['data'][] = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + + // Adds a blank line after each section + $data['data'][] = array(); + + if (count($packages) === 0) { + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'data' => array(array('(no packages installed)'), array()), + 'channel' => $channel + ); } $this->ui->outputData($data, $command); + } + return true; + } + + function doFileList($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('list-files expects 1 parameter'); + } + + $reg = &$this->config->getRegistry(); + $fp = false; + if (!is_dir($params[0]) && (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + $headings = array('Package File', 'Install Path'); + $installed = false; } else { - if (file_exists($params[0]) && !is_dir($params[0])) { - include_once "PEAR/Common.php"; - $obj = &new PEAR_Common; - $info = $obj->infoFromAny($params[0]); - $headings = array('Package File', 'Install Path'); - $installed = false; - } else { - $info = $reg->packageInfo($params[0]); - $headings = array('Type', 'Install Path'); - $installed = true; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); } - if (PEAR::isError($info)) { - return $this->raiseError($info); - } - if ($info === null) { - return $this->raiseError("`$params[0]' not installed"); - } - $list = $info['filelist']; - if ($installed) { - $caption = 'Installed Files For ' . $params[0]; - } else { - $caption = 'Contents of ' . basename($params[0]); - } - $data = array( - 'caption' => $caption, - 'border' => true, - 'headline' => $headings); + + $info = &$reg->getPackage($parsed['package'], $parsed['channel']); + $headings = array('Type', 'Install Path'); + $installed = true; + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + if ($info === null) { + return $this->raiseError("`$params[0]' not installed"); + } + + $list = ($info->getPackagexmlVersion() == '1.0' || $installed) ? + $info->getFilelist() : $info->getContents(); + if ($installed) { + $caption = 'Installed Files For ' . $params[0]; + } else { + $caption = 'Contents of ' . basename($params[0]); + } + + $data = array( + 'caption' => $caption, + 'border' => true, + 'headline' => $headings); + if ($info->getPackagexmlVersion() == '1.0' || $installed) { foreach ($list as $file => $att) { if ($installed) { if (empty($att['installed_as'])) { @@ -140,7 +308,8 @@ installed package.' } $data['data'][] = array($att['role'], $att['installed_as']); } else { - if (isset($att['baseinstalldir'])) { + if (isset($att['baseinstalldir']) && !in_array($att['role'], + array('test', 'data', 'doc'))) { $dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR . $file; } else { @@ -149,89 +318,183 @@ installed package.' switch ($att['role']) { case 'test': case 'data': - if ($installed) { - break 2; - } - $dest = '-- will not be installed --'; - break; case 'doc': - $dest = $this->config->get('doc_dir') . DIRECTORY_SEPARATOR . - $dest; + $role = $att['role']; + if ($role == 'test') { + $role .= 's'; + } + $dest = $this->config->get($role . '_dir') . DIRECTORY_SEPARATOR . + $info->getPackage() . DIRECTORY_SEPARATOR . $dest; break; case 'php': default: $dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR . $dest; } - $dest = preg_replace('!/+!', '/', $dest); + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $dest = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + $dest); $file = preg_replace('!/+!', '/', $file); $data['data'][] = array($file, $dest); } } - $this->ui->outputData($data, $command); - + } else { // package.xml 2.0, not installed + if (!isset($list['dir']['file'][0])) { + $list['dir']['file'] = array($list['dir']['file']); + } + foreach ($list['dir']['file'] as $att) { + $att = $att['attribs']; + $file = $att['name']; + $role = &PEAR_Installer_Role::factory($info, $att['role'], $this->config); + $role->setup($this, $info, $att, $file); + if (!$role->isInstallable()) { + $dest = '(not installable)'; + } else { + $dest = $role->processInstallation($info, $att, $file, ''); + if (PEAR::isError($dest)) { + $dest = '(Unknown role "' . $att['role'] . ')'; + } else { + list(,, $dest) = $dest; + } + } + $data['data'][] = array($file, $dest); + } } + + $this->ui->outputData($data, $command); return true; } - // }}} - // {{{ doShellTest() - function doShellTest($command, $options, $params) { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $reg = &new PEAR_Registry($this->config->get('php_dir')); + if (count($params) < 1) { + return PEAR::raiseError('ERROR, usage: pear shell-test packagename [[relation] version]'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $reg = &$this->config->getRegistry(); + $info = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($info)) { + exit(1); // invalid package name + } + + $package = $info['package']; + $channel = $info['channel']; // "pear shell-test Foo" - if (sizeof($params) == 1) { - if (!$reg->packageExists($params[0])) { + if (!$reg->packageExists($package, $channel)) { + if ($channel == 'pecl.php.net') { + if ($reg->packageExists($package, 'pear.php.net')) { + $channel = 'pear.php.net'; // magically change channels for extensions + } + } + } + + if (count($params) === 1) { + if (!$reg->packageExists($package, $channel)) { exit(1); } // "pear shell-test Foo 1.0" - } elseif (sizeof($params) == 2) { - $v = $reg->packageInfo($params[0], 'version'); + } elseif (count($params) === 2) { + $v = $reg->packageInfo($package, 'version', $channel); if (!$v || !version_compare("$v", "{$params[1]}", "ge")) { exit(1); } // "pear shell-test Foo ge 1.0" - } elseif (sizeof($params) == 3) { - $v = $reg->packageInfo($params[0], 'version'); + } elseif (count($params) === 3) { + $v = $reg->packageInfo($package, 'version', $channel); if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) { exit(1); } } else { - $this->popErrorHandling(); + PEAR::staticPopErrorHandling(); $this->raiseError("$command: expects 1 to 3 parameters"); exit(1); } } - // }}} - // {{{ doInfo - function doInfo($command, $options, $params) { - // $params[0] The package for showing info - if (sizeof($params) != 1) { - return $this->raiseError("This command only accepts one param: ". - "the package you want information"); + if (count($params) !== 1) { + return $this->raiseError('pear info expects 1 parameter'); } - if (@is_file($params[0])) { - $obj = &new PEAR_Common(); - $info = $obj->infoFromAny($params[0]); + + $info = $fp = false; + $reg = &$this->config->getRegistry(); + if (is_file($params[0]) && !is_dir($params[0]) && + (file_exists($params[0]) || $fp = @fopen($params[0], 'r')) + ) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($obj)) { + $uinfo = $obj->getUserInfo(); + if (is_array($uinfo)) { + foreach ($uinfo as $message) { + if (is_array($message)) { + $message = $message['message']; + } + $this->ui->outputData($message); + } + } + + return $this->raiseError($obj); + } + + if ($obj->getPackagexmlVersion() != '1.0') { + return $this->_doInfo2($command, $options, $params, $obj, false); + } + + $info = $obj->toArray(); } else { - $reg = &new PEAR_Registry($this->config->get('php_dir')); - $info = $reg->packageInfo($params[0]); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = $reg->packageInfo($package, null, $channel); + if (isset($info['old'])) { + $obj = $reg->getPackage($package, $channel); + return $this->_doInfo2($command, $options, $params, $obj, true); + } } + if (PEAR::isError($info)) { return $info; } + if (empty($info)) { - $this->raiseError("Nothing found for `$params[0]'"); + $this->raiseError("No information found for `$params[0]'"); return; } + unset($info['filelist']); + unset($info['dirtree']); unset($info['changelog']); + if (isset($info['xsdversion'])) { + $info['package.xml version'] = $info['xsdversion']; + unset($info['xsdversion']); + } + + if (isset($info['packagerversion'])) { + $info['packaged with PEAR version'] = $info['packagerversion']; + unset($info['packagerversion']); + } + $keys = array_keys($info); $longtext = array('description', 'summary'); foreach ($keys as $key) { @@ -279,7 +542,12 @@ installed package.' } else { $version = ''; } - $dstr .= "$type $name$rel $version\n"; + if (isset($d['optional']) && $d['optional'] == 'yes') { + $optional = ' (optional)'; + } else { + $optional = ''; + } + $dstr .= "$type $name$rel $version$optional\n"; } $info[$key] = $dstr; break; @@ -315,16 +583,30 @@ installed package.' $info[$key] = $pstr; break; } + case 'configure_options' : { + foreach ($info[$key] as $i => $p) { + $info[$key][$i] = array_map(null, array_keys($p), array_values($p)); + $info[$key][$i] = array_map(create_function('$a', + 'return join(" = ",$a);'), $info[$key][$i]); + $info[$key][$i] = implode(', ', $info[$key][$i]); + } + $info[$key] = implode("\n", $info[$key]); + break; + } default: { $info[$key] = implode(", ", $info[$key]); break; } } } + if ($key == '_lastmodified') { $hdate = date('Y-m-d', $info[$key]); unset($info[$key]); $info['Last Modified'] = $hdate; + } elseif ($key == '_lastversion') { + $info['Previous Installed Version'] = $info[$key] ? $info[$key] : '- None -'; + unset($info[$key]); } else { $info[$key] = trim($info[$key]); if (in_array($key, $longtext)) { @@ -332,6 +614,7 @@ installed package.' } } } + $caption = 'About ' . $info['package'] . '-' . $info['version']; $data = array( 'caption' => $caption, @@ -345,7 +628,518 @@ installed package.' $this->ui->outputData($data, 'package-info'); } - // }}} -} + /** + * @access private + */ + function _doInfo2($command, $options, $params, &$obj, $installed) + { + $reg = &$this->config->getRegistry(); + $caption = 'About ' . $obj->getChannel() . '/' .$obj->getPackage() . '-' . + $obj->getVersion(); + $data = array( + 'caption' => $caption, + 'border' => true); + switch ($obj->getPackageType()) { + case 'php' : + $release = 'PEAR-style PHP-based Package'; + break; + case 'extsrc' : + $release = 'PECL-style PHP extension (source code)'; + break; + case 'zendextsrc' : + $release = 'PECL-style Zend extension (source code)'; + break; + case 'extbin' : + $release = 'PECL-style PHP extension (binary)'; + break; + case 'zendextbin' : + $release = 'PECL-style Zend extension (binary)'; + break; + case 'bundle' : + $release = 'Package bundle (collection of packages)'; + break; + } + $extends = $obj->getExtends(); + $extends = $extends ? + $obj->getPackage() . ' (extends ' . $extends . ')' : $obj->getPackage(); + if ($src = $obj->getSourcePackage()) { + $extends .= ' (source package ' . $src['channel'] . '/' . $src['package'] . ')'; + } -?> + $info = array( + 'Release Type' => $release, + 'Name' => $extends, + 'Channel' => $obj->getChannel(), + 'Summary' => preg_replace('/ +/', ' ', $obj->getSummary()), + 'Description' => preg_replace('/ +/', ' ', $obj->getDescription()), + ); + $info['Maintainers'] = ''; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + $leads = $obj->{"get{$role}s"}(); + if (!$leads) { + continue; + } + + if (isset($leads['active'])) { + $leads = array($leads); + } + + foreach ($leads as $lead) { + if (!empty($info['Maintainers'])) { + $info['Maintainers'] .= "\n"; + } + + $active = $lead['active'] == 'no' ? ', inactive' : ''; + $info['Maintainers'] .= $lead['name'] . ' <'; + $info['Maintainers'] .= $lead['email'] . "> ($role$active)"; + } + } + + $info['Release Date'] = $obj->getDate(); + if ($time = $obj->getTime()) { + $info['Release Date'] .= ' ' . $time; + } + + $info['Release Version'] = $obj->getVersion() . ' (' . $obj->getState() . ')'; + $info['API Version'] = $obj->getVersion('api') . ' (' . $obj->getState('api') . ')'; + $info['License'] = $obj->getLicense(); + $uri = $obj->getLicenseLocation(); + if ($uri) { + if (isset($uri['uri'])) { + $info['License'] .= ' (' . $uri['uri'] . ')'; + } else { + $extra = $obj->getInstalledLocation($info['filesource']); + if ($extra) { + $info['License'] .= ' (' . $uri['filesource'] . ')'; + } + } + } + + $info['Release Notes'] = $obj->getNotes(); + if ($compat = $obj->getCompatible()) { + if (!isset($compat[0])) { + $compat = array($compat); + } + + $info['Compatible with'] = ''; + foreach ($compat as $package) { + $info['Compatible with'] .= $package['channel'] . '/' . $package['name'] . + "\nVersions >= " . $package['min'] . ', <= ' . $package['max']; + if (isset($package['exclude'])) { + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= $package['channel'] . '/' . + $package['name'] . "\nVersions " . $package['exclude']; + } + } + } + + $usesrole = $obj->getUsesrole(); + if ($usesrole) { + if (!isset($usesrole[0])) { + $usesrole = array($usesrole); + } + + foreach ($usesrole as $roledata) { + if (isset($info['Uses Custom Roles'])) { + $info['Uses Custom Roles'] .= "\n"; + } else { + $info['Uses Custom Roles'] = ''; + } + + if (isset($roledata['package'])) { + $rolepackage = $reg->parsedPackageNameToString($roledata, true); + } else { + $rolepackage = $roledata['uri']; + } + $info['Uses Custom Roles'] .= $roledata['role'] . ' (' . $rolepackage . ')'; + } + } + + $usestask = $obj->getUsestask(); + if ($usestask) { + if (!isset($usestask[0])) { + $usestask = array($usestask); + } + + foreach ($usestask as $taskdata) { + if (isset($info['Uses Custom Tasks'])) { + $info['Uses Custom Tasks'] .= "\n"; + } else { + $info['Uses Custom Tasks'] = ''; + } + + if (isset($taskdata['package'])) { + $taskpackage = $reg->parsedPackageNameToString($taskdata, true); + } else { + $taskpackage = $taskdata['uri']; + } + $info['Uses Custom Tasks'] .= $taskdata['task'] . ' (' . $taskpackage . ')'; + } + } + + $deps = $obj->getDependencies(); + $info['Required Dependencies'] = 'PHP version ' . $deps['required']['php']['min']; + if (isset($deps['required']['php']['max'])) { + $info['Required Dependencies'] .= '-' . $deps['required']['php']['max'] . "\n"; + } else { + $info['Required Dependencies'] .= "\n"; + } + + if (isset($deps['required']['php']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['php']['exclude'])) { + $deps['required']['php']['exclude'] = + implode(', ', $deps['required']['php']['exclude']); + } + $info['Not Compatible with'] .= "PHP versions\n " . + $deps['required']['php']['exclude']; + } + + $info['Required Dependencies'] .= 'PEAR installer version'; + if (isset($deps['required']['pearinstaller']['max'])) { + $info['Required Dependencies'] .= 's ' . + $deps['required']['pearinstaller']['min'] . '-' . + $deps['required']['pearinstaller']['max']; + } else { + $info['Required Dependencies'] .= ' ' . + $deps['required']['pearinstaller']['min'] . ' or newer'; + } + + if (isset($deps['required']['pearinstaller']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['pearinstaller']['exclude'])) { + $deps['required']['pearinstaller']['exclude'] = + implode(', ', $deps['required']['pearinstaller']['exclude']); + } + $info['Not Compatible with'] .= "PEAR installer\n Versions " . + $deps['required']['pearinstaller']['exclude']; + } + + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['required'][$index])) { + if (isset($deps['required'][$index]['name'])) { + $deps['required'][$index] = array($deps['required'][$index]); + } + + foreach ($deps['required'][$index] as $package) { + if (isset($package['conflicts'])) { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Required Dependencies'; + $info[$infoindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['max'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $package['package'] = $package['name']; // for parsedPackageNameToString + if (isset($package['conflicts'])) { + $info['Not Compatible with'] .= '=> except '; + } + $info['Not Compatible with'] .= 'Package ' . + $reg->parsedPackageNameToString($package, true); + $info['Not Compatible with'] .= "\n Versions " . $package['exclude']; + } + } + } + } + + if (isset($deps['required']['os'])) { + if (isset($deps['required']['os']['name'])) { + $dep['required']['os']['name'] = array($dep['required']['os']['name']); + } + + foreach ($dep['required']['os'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "$os[name] Operating System"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "$os[name] Operating System"; + } + } + } + + if (isset($deps['required']['arch'])) { + if (isset($deps['required']['arch']['pattern'])) { + $dep['required']['arch']['pattern'] = array($dep['required']['os']['pattern']); + } + + foreach ($dep['required']['arch'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } + } + } + + if (isset($deps['optional'])) { + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['optional'][$index])) { + if (isset($deps['optional'][$index]['name'])) { + $deps['optional'][$index] = array($deps['optional'][$index]); + } + + foreach ($deps['optional'][$index] as $package) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Optional Dependencies'; + if (!isset($info['Optional Dependencies'])) { + $info['Optional Dependencies'] = ''; + } else { + $info['Optional Dependencies'] .= "\n"; + } + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if ($infoindex == 'Not Compatible with') { + // conflicts is only used to say that all versions conflict + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $info['Not Compatible with'] .= "Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + $info['Dependency Group ' . $group['attribs']['name']] = $group['attribs']['hint']; + $groupindex = $group['attribs']['name'] . ' Contents'; + $info[$groupindex] = ''; + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($group[$index])) { + if (isset($group[$index]['name'])) { + $group[$index] = array($group[$index]); + } + + foreach ($group[$index] as $package) { + if (!empty($info[$groupindex])) { + $info[$groupindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + if (isset($package['uri'])) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + } else { + $info[$groupindex] .= "$type $name"; + } + + $info[$groupindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + continue; + } + + $info[$groupindex] .= "$type $name"; + if (isset($package['max']) && isset($package['min'])) { + $info[$groupindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$groupindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info[$groupindex] .= "Not Compatible with\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + $info[$groupindex] .= " Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + } + + if ($obj->getPackageType() == 'bundle') { + $info['Bundled Packages'] = ''; + foreach ($obj->getBundledPackages() as $package) { + if (!empty($info['Bundled Packages'])) { + $info['Bundled Packages'] .= "\n"; + } + + if (isset($package['uri'])) { + $info['Bundled Packages'] .= '__uri/' . $package['name']; + $info['Bundled Packages'] .= "\n (URI: $package[uri]"; + } else { + $info['Bundled Packages'] .= $package['channel'] . '/' . $package['name']; + } + } + } + + $info['package.xml version'] = '2.0'; + if ($installed) { + if ($obj->getLastModified()) { + $info['Last Modified'] = date('Y-m-d H:i', $obj->getLastModified()); + } + + $v = $obj->getLastInstalledVersion(); + $info['Previous Installed Version'] = $v ? $v : '- None -'; + } + + foreach ($info as $key => $value) { + $data['data'][] = array($key, $value); + } + + $data['raw'] = $obj->getArray(); // no validation needed + $this->ui->outputData($data, 'package-info'); + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Registry.xml b/3rdparty/PEAR/Command/Registry.xml new file mode 100644 index 0000000000..9f4e214967 --- /dev/null +++ b/3rdparty/PEAR/Command/Registry.xml @@ -0,0 +1,58 @@ + + + List Installed Packages In The Default Channel + doList + l + + + c + list installed packages from this channel + CHAN + + + a + list installed packages from all channels + + + i + output fully channel-aware data, even on failure + + + <package> +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. + + + + List Files In Installed Package + doFileList + fl + + <package> +List the files in an installed package. + + + + Shell Script Test + doShellTest + st + + <package> [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + <relation> The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + <version> The version to compare with + + + + Display information about a package + doInfo + in + + <package> +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package. + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Remote.php b/3rdparty/PEAR/Command/Remote.php index bbd16093f5..74478d83c7 100644 --- a/3rdparty/PEAR/Command/Remote.php +++ b/3rdparty/PEAR/Command/Remote.php @@ -1,33 +1,42 @@ | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Remote.php,v 1.39 2004/04/03 15:56:00 cellog Exp $ +/** + * PEAR_Command_Remote (remote-info, list-upgrades, remote-list, search, list-all, download, + * clear-cache commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Remote.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ require_once 'PEAR/Command/Common.php'; -require_once 'PEAR/Common.php'; -require_once 'PEAR/Remote.php'; -require_once 'PEAR/Registry.php'; +require_once 'PEAR/REST.php'; +/** + * PEAR commands for remote server querying + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ class PEAR_Command_Remote extends PEAR_Command_Common { - // {{{ command definitions - var $commands = array( 'remote-info' => array( 'summary' => 'Information About Remote Packages', @@ -41,16 +50,29 @@ Get details on a package from the server.', 'summary' => 'List Available Upgrades', 'function' => 'doListUpgrades', 'shortcut' => 'lu', - 'options' => array(), - 'doc' => ' + 'options' => array( + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[preferred_state] List releases on the server of packages you have installed where -a newer version is available with the same release state (stable etc.).' +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.' ), 'remote-list' => array( 'summary' => 'List Remote Packages', 'function' => 'doRemoteList', 'shortcut' => 'rl', - 'options' => array(), + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ) + ), 'doc' => ' Lists the packages available on the configured server along with the latest stable release of each package.', @@ -59,16 +81,44 @@ latest stable release of each package.', 'summary' => 'Search remote package database', 'function' => 'doSearch', 'shortcut' => 'sp', - 'options' => array(), - 'doc' => ' -Lists all packages which match the search parameters (first param -is package name, second package info)', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'search packages from all known channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description', ), 'list-all' => array( 'summary' => 'List All Packages', 'function' => 'doListAll', 'shortcut' => 'la', - 'options' => array(), + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), 'doc' => ' Lists the packages available on the configured server along with the latest stable release of each package.', @@ -83,26 +133,23 @@ latest stable release of each package.', 'doc' => 'download an uncompressed (.tar) file', ), ), - 'doc' => '{package|package-version} -Download a package tarball. The file will be named as suggested by the + 'doc' => '... +Download package tarballs. The files will be named as suggested by the server, for example if you download the DB package and the latest stable -version of DB is 1.2, the downloaded file will be DB-1.2.tgz.', +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.', ), 'clear-cache' => array( - 'summary' => 'Clear XML-RPC Cache', + 'summary' => 'Clear Web Services Cache', 'function' => 'doClearCache', 'shortcut' => 'cc', 'options' => array(), 'doc' => ' -Clear the XML-RPC cache. See also the cache_ttl configuration +Clear the REST cache. See also the cache_ttl configuration parameter. ', ), ); - // }}} - // {{{ constructor - /** * PEAR_Command_Remote constructor. * @@ -113,98 +160,216 @@ parameter. parent::PEAR_Command_Common($ui, $config); } - // }}} - - // {{{ doRemoteInfo() + function _checkChannelForStatus($channel, $chan) + { + if (PEAR::isError($chan)) { + $this->raiseError($chan); + } + if (!is_a($chan, 'PEAR_ChannelFile')) { + return $this->raiseError('Internal corruption error: invalid channel "' . + $channel . '"'); + } + $rest = new PEAR_REST($this->config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $mirror = $this->config->get('preferred_mirror', null, + $channel); + $a = $rest->downloadHttp('http://' . $channel . + '/channel.xml', $chan->lastModified()); + PEAR::staticPopErrorHandling(); + if (!PEAR::isError($a) && $a) { + $this->ui->outputData('WARNING: channel "' . $channel . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel . + '" to update'); + } + } function doRemoteInfo($command, $options, $params) { if (sizeof($params) != 1) { return $this->raiseError("$command expects one param: the remote package name"); } - $r = new PEAR_Remote($this->config); - $info = $r->call('package.info', $params[0]); + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $package = $params[0]; + $parsed = $reg->parsePackageName($package, $channel); + if (PEAR::isError($parsed)) { + return $this->raiseError('Invalid package name "' . $package . '"'); + } + + $channel = $parsed['channel']; + $this->config->set('default_channel', $channel); + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) { + $rest = &$this->config->getREST('1.0', array()); + $info = $rest->packageInfo($base, $parsed['package'], $channel); + } + + if (!isset($info)) { + return $this->raiseError('No supported protocol was found'); + } + if (PEAR::isError($info)) { + $this->config->set('default_channel', $savechannel); return $this->raiseError($info); } - $reg = new PEAR_Registry($this->config->get('php_dir')); - $installed = $reg->packageInfo($info['name']); + if (!isset($info['name'])) { + return $this->raiseError('No remote package "' . $package . '" was found'); + } + + $installed = $reg->packageInfo($info['name'], null, $channel); $info['installed'] = $installed['version'] ? $installed['version'] : '- no -'; + if (is_array($info['installed'])) { + $info['installed'] = $info['installed']['release']; + } $this->ui->outputData($info, $command); + $this->config->set('default_channel', $savechannel); return true; } - // }}} - // {{{ doRemoteList() - function doRemoteList($command, $options, $params) { - $r = new PEAR_Remote($this->config); + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + $list_options = false; - if ($this->config->get('preferred_state') == 'stable') + if ($this->config->get('preferred_state') == 'stable') { $list_options = true; - $available = $r->call('package.listAll', $list_options); + } + + $available = array(); + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror')) + ) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); return $this->raiseError($available); } + $i = $j = 0; $data = array( - 'caption' => 'Available packages:', + 'caption' => 'Channel ' . $channel . ' Available packages:', 'border' => true, 'headline' => array('Package', 'Version'), + 'channel' => $channel ); - foreach ($available as $name => $info) { - $data['data'][] = array($name, isset($info['stable']) ? $info['stable'] : '-n/a-'); - } - if (count($available)==0) { - $data = '(no packages installed yet)'; + + if (count($available) == 0) { + $data = '(no packages available yet)'; + } else { + foreach ($available as $name => $info) { + $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-'; + $data['data'][] = array($name, $version); + } } $this->ui->outputData($data, $command); + $this->config->set('default_channel', $savechannel); return true; } - // }}} - // {{{ doListAll() - function doListAll($command, $options, $params) { - $r = new PEAR_Remote($this->config); - $reg = new PEAR_Registry($this->config->get('php_dir')); + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError("Channel \"$channel\" does not exist"); + } + + $this->config->set('default_channel', $channel); + } + $list_options = false; - if ($this->config->get('preferred_state') == 'stable') + if ($this->config->get('preferred_state') == 'stable') { $list_options = true; - $available = $r->call('package.listAll', $list_options); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } + if (PEAR::isError($available)) { - return $this->raiseError($available); - } - if (!is_array($available)) { - return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "'.$available.'")'); + $this->config->set('default_channel', $savechannel); + return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")'); } + $data = array( - 'caption' => 'All packages:', + 'caption' => 'All packages [Channel ' . $channel . ']:', 'border' => true, 'headline' => array('Package', 'Latest', 'Local'), + 'channel' => $channel, ); - $local_pkgs = $reg->listPackages(); - - foreach ($available as $name => $info) { - $installed = $reg->packageInfo($name); - $desc = $info['summary']; - if (isset($params[$name])) - $desc .= "\n\n".$info['description']; + if (isset($options['channelinfo'])) { + // add full channelinfo + $data['caption'] = 'Channel ' . $channel . ' All packages:'; + $data['headline'] = array('Channel', 'Package', 'Latest', 'Local', + 'Description', 'Dependencies'); + } + $local_pkgs = $reg->listPackages($channel); + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + if (is_array($installed['version'])) { + $installed['version'] = $installed['version']['release']; + } + $desc = $info['summary']; + if (isset($params[$name])) { + $desc .= "\n\n".$info['description']; + } if (isset($options['mode'])) { - if ($options['mode'] == 'installed' && !isset($installed['version'])) + if ($options['mode'] == 'installed' && !isset($installed['version'])) { continue; - if ($options['mode'] == 'notinstalled' && isset($installed['version'])) + } + if ($options['mode'] == 'notinstalled' && isset($installed['version'])) { continue; + } if ($options['mode'] == 'upgrades' - && (!isset($installed['version']) || $installed['version'] == $info['stable'])) - { + && (!isset($installed['version']) || version_compare($installed['version'], + $info['stable'], '>='))) { continue; } } @@ -213,112 +378,253 @@ parameter. unset($local_pkgs[$pos]); } - $data['data'][$info['category']][] = array( - $name, - @$info['stable'], - @$installed['version'], - @$desc, - @$info['deps'], + if (isset($info['stable']) && !$info['stable']) { + $info['stable'] = null; + } + + if (isset($options['channelinfo'])) { + // add full channelinfo + if ($info['stable'] === $info['unstable']) { + $state = $info['state']; + } else { + $state = 'stable'; + } + $latest = $info['stable'].' ('.$state.')'; + $local = ''; + if (isset($installed['version'])) { + $inst_state = $reg->packageInfo($name, 'release_state', $channel); + $local = $installed['version'].' ('.$inst_state.')'; + } + + $packageinfo = array( + $channel, + $name, + $latest, + $local, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, ); + } else { + $packageinfo = array( + $reg->channelAlias($channel) . '/' . $name, + isset($info['stable']) ? $info['stable'] : null, + isset($installed['version']) ? $installed['version'] : null, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } + $data['data'][$info['category']][] = $packageinfo; } - + + if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) { + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + foreach ($local_pkgs as $name) { - $info = $reg->packageInfo($name); + $info = &$reg->getPackage($name, $channel); $data['data']['Local'][] = array( - $info['package'], + $reg->channelAlias($channel) . '/' . $info->getPackage(), '', - $info['version'], - $info['summary'], - @$info['release_deps'] + $info->getVersion(), + $info->getSummary(), + $info->getDeps() ); } + $this->config->set('default_channel', $savechannel); $this->ui->outputData($data, $command); return true; } - // }}} - // {{{ doSearch() - function doSearch($command, $options, $params) { if ((!isset($params[0]) || empty($params[0])) && (!isset($params[1]) || empty($params[1]))) { return $this->raiseError('no valid search string supplied'); - }; + } + + $channelinfo = isset($options['channelinfo']); + $reg = &$this->config->getRegistry(); + if (isset($options['allchannels'])) { + // search all channels + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + $options['channel'] = $channel->getName(); + $ret = $this->doSearch($command, $options, $params); + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors) !== 0) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + $savechannel = $channel = $this->config->get('default_channel'); + $package = strtolower($params[0]); + $summary = isset($params[1]) ? $params[1] : false; + if (isset($options['channel'])) { + $reg = &$this->config->getRegistry(); + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName()); + } - $r = new PEAR_Remote($this->config); - $reg = new PEAR_Registry($this->config->get('php_dir')); - $available = $r->call('package.listAll', true, false); if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); return $this->raiseError($available); } - $data = array( - 'caption' => 'Matched packages:', - 'border' => true, - 'headline' => array('Package', 'Stable/(Latest)', 'Local'), - ); + + if (!$available && !$channelinfo) { + // clean exit when not found, no error ! + $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.'; + $this->ui->outputData($data); + $this->config->set('default_channel', $channel); + return true; + } + + if ($channelinfo) { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } else { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } + + if (!$available && $channelinfo) { + unset($data['headline']); + $data['data'] = 'No packages found that match pattern "' . $package . '".'; + $available = array(); + } foreach ($available as $name => $info) { - $found = (!empty($params[0]) && stristr($name, $params[0]) !== false); - if (!$found && !(isset($params[1]) && !empty($params[1]) - && (stristr($info['summary'], $params[1]) !== false - || stristr($info['description'], $params[1]) !== false))) - { - continue; - }; - - $installed = $reg->packageInfo($name); + $installed = $reg->packageInfo($name, null, $channel); $desc = $info['summary']; if (isset($params[$name])) $desc .= "\n\n".$info['description']; - $unstable = ''; - if ($info['unstable']) { - $unstable = '/(' . $info['unstable'] . $info['state'] . ')'; - } if (!isset($info['stable']) || !$info['stable']) { - $info['stable'] = 'none'; + $version_remote = 'none'; + } else { + if ($info['unstable']) { + $version_remote = $info['unstable']; + } else { + $version_remote = $info['stable']; + } + $version_remote .= ' ('.$info['state'].')'; } - $data['data'][$info['category']][] = array( - $name, - $info['stable'] . $unstable, - $installed['version'], - $desc, + $version = is_array($installed['version']) ? $installed['version']['release'] : + $installed['version']; + if ($channelinfo) { + $packageinfo = array( + $channel, + $name, + $version_remote, + $version, + $desc, ); + } else { + $packageinfo = array( + $name, + $version_remote, + $version, + $desc, + ); + } + $data['data'][$info['category']][] = $packageinfo; } - if (!isset($data['data'])) { - return $this->raiseError('no packages found'); - } + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $channel); return true; } - // }}} - // {{{ doDownload() + function &getDownloader($options) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($this->ui, $options, $this->config); + return $a; + } function doDownload($command, $options, $params) { - //$params[0] -> The package to download - if (count($params) != 1) { - return PEAR::raiseError("download expects one argument: the package to download"); + // make certain that dependencies are ignored + $options['downloadonly'] = 1; + + // eliminate error messages for preferred_state-related errors + /* TODO: Should be an option, but until now download does respect + prefered state */ + /* $options['ignorepreferred_state'] = 1; */ + // eliminate error messages for preferred_state-related errors + + $downloader = &$this->getDownloader($options); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $e = $downloader->setDownloadDir(getcwd()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + return $this->raiseError('Current directory is not writeable, cannot download'); } - $server = $this->config->get('master_server'); - if (!ereg('^http://', $params[0])) { - $getoption = isset($options['nocompress'])&&$options['nocompress']==1?'?uncompress=on':''; - $pkgfile = "http://$server/get/$params[0]".$getoption; - } else { - $pkgfile = $params[0]; + + $errors = array(); + $downloaded = array(); + $err = $downloader->download($params); + if (PEAR::isError($err)) { + return $err; } - $this->bytes_downloaded = 0; - $saved = PEAR_Common::downloadHttp($pkgfile, $this->ui, '.', - array(&$this, 'downloadCallback')); - if (PEAR::isError($saved)) { - return $this->raiseError($saved); + + $errors = $downloader->getErrorMsgs(); + if (count($errors)) { + foreach ($errors as $error) { + if ($error !== null) { + $this->ui->outputData($error); + } + } + + return $this->raiseError("$command failed"); } - $fname = basename($saved); - $this->ui->outputData("File $fname downloaded ($this->bytes_downloaded bytes)", $command); + + $downloaded = $downloader->getDownloadedPackages(); + foreach ($downloaded as $pkg) { + $this->ui->outputData("File $pkg[file] downloaded", $command); + } + return true; } @@ -329,107 +635,176 @@ parameter. } } - // }}} - // {{{ doListUpgrades() - function doListUpgrades($command, $options, $params) { - include_once "PEAR/Registry.php"; - $remote = new PEAR_Remote($this->config); - if (empty($params[0])) { - $state = $this->config->get('preferred_state'); - } else { - $state = $params[0]; + require_once 'PEAR/Common.php'; + if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) { + return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"'); } - $caption = 'Available Upgrades'; - if (empty($state) || $state == 'any') { - $latest = $remote->call("package.listLatestReleases"); - } else { - $latest = $remote->call("package.listLatestReleases", $state); - $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')'; - } - $caption .= ':'; - if (PEAR::isError($latest)) { - return $latest; - } - $reg = new PEAR_Registry($this->config->get('php_dir')); - $inst = array_flip($reg->listPackages()); - $data = array( - 'caption' => $caption, - 'border' => 1, - 'headline' => array('Package', 'Local', 'Remote', 'Size'), - ); - foreach ((array)$latest as $pkg => $info) { - $package = strtolower($pkg); - if (!isset($inst[$package])) { - // skip packages we don't have installed + + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + foreach ($reg->listChannels() as $channel) { + $inst = array_flip($reg->listPackages($channel)); + if (!count($inst)) { continue; } - extract($info); - $pkginfo = $reg->packageInfo($package); - $inst_version = $pkginfo['version']; - $inst_state = $pkginfo['release_state']; - if (version_compare("$version", "$inst_version", "le")) { - // installed version is up-to-date + + if ($channel == '__uri') { continue; } - if ($filesize >= 20480) { - $filesize += 1024 - ($filesize % 1024); - $fs = sprintf("%dkB", $filesize / 1024); - } elseif ($filesize > 0) { - $filesize += 103 - ($filesize % 103); - $fs = sprintf("%.1fkB", $filesize / 1024.0); + + $this->config->set('default_channel', $channel); + $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0]; + + $caption = $channel . ' Available Upgrades'; + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $latest = array(); + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + + ) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + if (empty($state) || $state == 'any') { + $state = false; + } else { + $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg); + PEAR::staticPopErrorHandling(); + } + + if (PEAR::isError($latest)) { + $this->ui->outputData($latest->getMessage()); + continue; + } + + $caption .= ':'; + if (PEAR::isError($latest)) { + $this->config->set('default_channel', $savechannel); + return $latest; + } + + $data = array( + 'caption' => $caption, + 'border' => 1, + 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'), + 'channel' => $channel + ); + + foreach ((array)$latest as $pkg => $info) { + $package = strtolower($pkg); + if (!isset($inst[$package])) { + // skip packages we don't have installed + continue; + } + + extract($info); + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + if (version_compare("$version", "$inst_version", "le")) { + // installed version is up-to-date + continue; + } + + if ($filesize >= 20480) { + $filesize += 1024 - ($filesize % 1024); + $fs = sprintf("%dkB", $filesize / 1024); + } elseif ($filesize > 0) { + $filesize += 103 - ($filesize % 103); + $fs = sprintf("%.1fkB", $filesize / 1024.0); + } else { + $fs = " -"; // XXX center instead + } + + $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs); + } + + if (isset($options['channelinfo'])) { + if (empty($data['data'])) { + unset($data['headline']); + if (count($inst) == 0) { + $data['data'] = '(no packages installed)'; + } else { + $data['data'] = '(no upgrades available)'; + } + } + $this->ui->outputData($data, $command); } else { - $fs = " -"; // XXX center instead + if (empty($data['data'])) { + $this->ui->outputData('Channel ' . $channel . ': No upgrades available'); + } else { + $this->ui->outputData($data, $command); + } } - $data['data'][] = array($pkg, "$inst_version ($inst_state)", "$version ($state)", $fs); - } - if (empty($data['data'])) { - $this->ui->outputData('No upgrades available'); - } else { - $this->ui->outputData($data, $command); } + + $this->config->set('default_channel', $savechannel); return true; } - // }}} - // {{{ doClearCache() - function doClearCache($command, $options, $params) { $cache_dir = $this->config->get('cache_dir'); - $verbose = $this->config->get('verbose'); + $verbose = $this->config->get('verbose'); $output = ''; + if (!file_exists($cache_dir) || !is_dir($cache_dir)) { + return $this->raiseError("$cache_dir does not exist or is not a directory"); + } + if (!($dp = @opendir($cache_dir))) { return $this->raiseError("opendir($cache_dir) failed: $php_errormsg"); } + if ($verbose >= 1) { $output .= "reading directory $cache_dir\n"; } + $num = 0; while ($ent = readdir($dp)) { - if (preg_match('/^xmlrpc_cache_[a-z0-9]{32}$/', $ent)) { + if (preg_match('/rest.cache(file|id)\\z/', $ent)) { $path = $cache_dir . DIRECTORY_SEPARATOR . $ent; - $ok = @unlink($path); + if (file_exists($path)) { + $ok = @unlink($path); + } else { + $ok = false; + $php_errormsg = ''; + } + if ($ok) { if ($verbose >= 2) { $output .= "deleted $path\n"; } $num++; } elseif ($verbose >= 1) { - $output .= "failed to delete $path\n"; + $output .= "failed to delete $path $php_errormsg\n"; } } } + closedir($dp); if ($verbose >= 1) { $output .= "$num cache entries cleared\n"; } + $this->ui->outputData(rtrim($output), $command); return $num; } - - // }}} -} - -?> +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Remote.xml b/3rdparty/PEAR/Command/Remote.xml new file mode 100644 index 0000000000..b4f6100c79 --- /dev/null +++ b/3rdparty/PEAR/Command/Remote.xml @@ -0,0 +1,109 @@ + + + Information About Remote Packages + doRemoteInfo + ri + + <package> +Get details on a package from the server. + + + List Available Upgrades + doListUpgrades + lu + + + i + output fully channel-aware data, even on failure + + + [preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter. + + + List Remote Packages + doRemoteList + rl + + + c + specify a channel other than the default channel + CHAN + + + +Lists the packages available on the configured server along with the +latest stable release of each package. + + + Search remote package database + doSearch + sp + + + c + specify a channel other than the default channel + CHAN + + + a + search packages from all known channels + + + i + output fully channel-aware data, even on failure + + + [packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description + + + List All Packages + doListAll + la + + + c + specify a channel other than the default channel + CHAN + + + i + output fully channel-aware data, even on failure + + + +Lists the packages available on the configured server along with the +latest stable release of each package. + + + Download Package + doDownload + d + + + Z + download an uncompressed (.tar) file + + + <package>... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz. + + + Clear Web Services Cache + doClearCache + cc + + +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Test.php b/3rdparty/PEAR/Command/Test.php new file mode 100644 index 0000000000..a757d9e579 --- /dev/null +++ b/3rdparty/PEAR/Command/Test.php @@ -0,0 +1,337 @@ + + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Test extends PEAR_Command_Common +{ + var $commands = array( + 'run-tests' => array( + 'summary' => 'Run Regression Tests', + 'function' => 'doRunTests', + 'shortcut' => 'rt', + 'options' => array( + 'recur' => array( + 'shortopt' => 'r', + 'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum', + ), + 'ini' => array( + 'shortopt' => 'i', + 'doc' => 'actual string of settings to pass to php in format " -d setting=blah"', + 'arg' => 'SETTINGS' + ), + 'realtimelog' => array( + 'shortopt' => 'l', + 'doc' => 'Log test runs/results as they are run', + ), + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Only display detail for failed tests', + ), + 'simple' => array( + 'shortopt' => 's', + 'doc' => 'Display simple output for all tests', + ), + 'package' => array( + 'shortopt' => 'p', + 'doc' => 'Treat parameters as installed packages from which to run tests', + ), + 'phpunit' => array( + 'shortopt' => 'u', + 'doc' => 'Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead.', + ), + 'tapoutput' => array( + 'shortopt' => 't', + 'doc' => 'Output run-tests.log in TAP-compliant format', + ), + 'cgi' => array( + 'shortopt' => 'c', + 'doc' => 'CGI php executable (needed for tests with POST/GET section)', + 'arg' => 'PHPCGI', + ), + 'coverage' => array( + 'shortopt' => 'x', + 'doc' => 'Generate a code coverage report (requires Xdebug 2.0.0+)', + ), + ), + 'doc' => '[testfile|dir ...] +Run regression tests with PHP\'s regression testing script (run-tests.php).', + ), + ); + + var $output; + + /** + * PEAR_Command_Test constructor. + * + * @access public + */ + function PEAR_Command_Test(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doRunTests($command, $options, $params) + { + if (isset($options['phpunit']) && isset($options['tapoutput'])) { + return $this->raiseError('ERROR: cannot use both --phpunit and --tapoutput at the same time'); + } + + require_once 'PEAR/Common.php'; + require_once 'System.php'; + $log = new PEAR_Common; + $log->ui = &$this->ui; // slightly hacky, but it will work + $tests = array(); + $depth = isset($options['recur']) ? 14 : 1; + + if (!count($params)) { + $params[] = '.'; + } + + if (isset($options['package'])) { + $oldparams = $params; + $params = array(); + $reg = &$this->config->getRegistry(); + foreach ($oldparams as $param) { + $pname = $reg->parsePackageName($param, $this->config->get('default_channel')); + if (PEAR::isError($pname)) { + return $this->raiseError($pname); + } + + $package = &$reg->getPackage($pname['package'], $pname['channel']); + if (!$package) { + return PEAR::raiseError('Unknown package "' . + $reg->parsedPackageNameToString($pname) . '"'); + } + + $filelist = $package->getFilelist(); + foreach ($filelist as $name => $atts) { + if (isset($atts['role']) && $atts['role'] != 'test') { + continue; + } + + if (isset($options['phpunit']) && preg_match('/AllTests\.php\\z/i', $name)) { + $params[] = $atts['installed_as']; + continue; + } elseif (!preg_match('/\.phpt\\z/', $name)) { + continue; + } + $params[] = $atts['installed_as']; + } + } + } + + foreach ($params as $p) { + if (is_dir($p)) { + if (isset($options['phpunit'])) { + $dir = System::find(array($p, '-type', 'f', + '-maxdepth', $depth, + '-name', 'AllTests.php')); + if (count($dir)) { + foreach ($dir as $p) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + } + continue; + } + + $args = array($p, '-type', 'f', '-name', '*.phpt'); + } else { + if (isset($options['phpunit'])) { + if (preg_match('/AllTests\.php\\z/i', $p)) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + continue; + } + + if (file_exists($p) && preg_match('/\.phpt$/', $p)) { + $tests[] = $p; + continue; + } + + if (!preg_match('/\.phpt\\z/', $p)) { + $p .= '.phpt'; + } + + $args = array(dirname($p), '-type', 'f', '-name', $p); + } + + if (!isset($options['recur'])) { + $args[] = '-maxdepth'; + $args[] = 1; + } + + $dir = System::find($args); + $tests = array_merge($tests, $dir); + } + + $ini_settings = ''; + if (isset($options['ini'])) { + $ini_settings .= $options['ini']; + } + + if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) { + $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}"; + } + + if ($ini_settings) { + $this->ui->outputData('Using INI settings: "' . $ini_settings . '"'); + } + + $skipped = $passed = $failed = array(); + $tests_count = count($tests); + $this->ui->outputData('Running ' . $tests_count . ' tests', $command); + $start = time(); + if (isset($options['realtimelog']) && file_exists('run-tests.log')) { + unlink('run-tests.log'); + } + + if (isset($options['tapoutput'])) { + $tap = '1..' . $tests_count . "\n"; + } + + require_once 'PEAR/RunTest.php'; + $run = new PEAR_RunTest($log, $options); + $run->tests_count = $tests_count; + + if (isset($options['coverage']) && extension_loaded('xdebug')){ + $run->xdebug_loaded = true; + } else { + $run->xdebug_loaded = false; + } + + $j = $i = 1; + foreach ($tests as $t) { + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "Running test [$i / $tests_count] $t..."); + fclose($fp); + } + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (isset($options['phpunit'])) { + $result = $run->runPHPUnit($t, $ini_settings); + } else { + $result = $run->run($t, $ini_settings, $j); + } + PEAR::staticPopErrorHandling(); + if (PEAR::isError($result)) { + $this->ui->log($result->getMessage()); + continue; + } + + if (isset($options['tapoutput'])) { + $tap .= $result[0] . ' ' . $i . $result[1] . "\n"; + continue; + } + + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "$result\n"); + fclose($fp); + } + } + + if ($result == 'FAILED') { + $failed[] = $t; + } + if ($result == 'PASSED') { + $passed[] = $t; + } + if ($result == 'SKIPPED') { + $skipped[] = $t; + } + + $j++; + } + + $total = date('i:s', time() - $start); + if (isset($options['tapoutput'])) { + $fp = @fopen('run-tests.log', 'w'); + if ($fp) { + fwrite($fp, $tap, strlen($tap)); + fclose($fp); + $this->ui->outputData('wrote TAP-format log to "' .realpath('run-tests.log') . + '"', $command); + } + } else { + if (count($failed)) { + $output = "TOTAL TIME: $total\n"; + $output .= count($passed) . " PASSED TESTS\n"; + $output .= count($skipped) . " SKIPPED TESTS\n"; + $output .= count($failed) . " FAILED TESTS:\n"; + foreach ($failed as $failure) { + $output .= $failure . "\n"; + } + + $mode = isset($options['realtimelog']) ? 'a' : 'w'; + $fp = @fopen('run-tests.log', $mode); + + if ($fp) { + fwrite($fp, $output, strlen($output)); + fclose($fp); + $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command); + } + } elseif (file_exists('run-tests.log') && !is_dir('run-tests.log')) { + @unlink('run-tests.log'); + } + } + $this->ui->outputData('TOTAL TIME: ' . $total); + $this->ui->outputData(count($passed) . ' PASSED TESTS', $command); + $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command); + if (count($failed)) { + $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command); + foreach ($failed as $failure) { + $this->ui->outputData($failure, $command); + } + } + + return true; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Command/Test.xml b/3rdparty/PEAR/Command/Test.xml new file mode 100644 index 0000000000..bbe9fcccc5 --- /dev/null +++ b/3rdparty/PEAR/Command/Test.xml @@ -0,0 +1,54 @@ + + + Run Regression Tests + doRunTests + rt + + + r + Run tests in child directories, recursively. 4 dirs deep maximum + + + i + actual string of settings to pass to php in format " -d setting=blah" + SETTINGS + + + l + Log test runs/results as they are run + + + q + Only display detail for failed tests + + + s + Display simple output for all tests + + + p + Treat parameters as installed packages from which to run tests + + + u + Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead. + + + t + Output run-tests.log in TAP-compliant format + + + c + CGI php executable (needed for tests with POST/GET section) + PHPCGI + + + x + Generate a code coverage report (requires Xdebug 2.0.0+) + + + [testfile|dir ...] +Run regression tests with PHP's regression testing script (run-tests.php). + + \ No newline at end of file diff --git a/3rdparty/PEAR/Common.php b/3rdparty/PEAR/Common.php index fba53a3779..3a8c7e80d3 100644 --- a/3rdparty/PEAR/Common.php +++ b/3rdparty/PEAR/Common.php @@ -1,44 +1,58 @@ | -// | Tomas V.V.Cox | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.126.2.2 2004/12/27 07:04:19 cellog Exp $ +/** + * PEAR_Common, the base class for the PEAR Installer + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1.0 + * @deprecated File deprecated since Release 1.4.0a1 + */ +/** + * Include error handling + */ require_once 'PEAR.php'; -require_once 'Archive/Tar.php'; -require_once 'System.php'; -require_once 'PEAR/Config.php'; - -// {{{ constants and globals /** * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode() */ define('PEAR_COMMON_ERROR_INVALIDPHP', 1); define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+'); -define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '$/'); +define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/'); // this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1 define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?'); -define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '$/i'); +define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i'); // XXX far from perfect :-) -define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^(' . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?$/'); +define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG . + ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG . + '\\z/'); + +define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+'); +define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/'); + +// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED +define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*'); +define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i'); + +define('_PEAR_CHANNELS_PACKAGE_PREG', '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')'); +define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i'); + +define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/'); /** * List of temporary files and directories registered by @@ -95,16 +109,35 @@ $GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function */ $GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup'); -// }}} - /** * Class providing common functionality for PEAR administration classes. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 * @deprecated This class will disappear, and its components will be spread - * into smaller classes, like the AT&T breakup + * into smaller classes, like the AT&T breakup, as of Release 1.4.0a1 */ class PEAR_Common extends PEAR { - // {{{ properties + /** + * User Interface object (PEAR_Frontend_* class). If null, + * the log() method uses print. + * @var object + */ + var $ui = null; + + /** + * Configuration object (PEAR_Config). + * @var PEAR_Config + */ + var $config = null; /** stack of elements, gives some sort of XML context */ var $element_stack = array(); @@ -118,26 +151,8 @@ class PEAR_Common extends PEAR /** assoc with information about a package */ var $pkginfo = array(); - /** - * User Interface object (PEAR_Frontend_* class). If null, - * the log() method uses print. - * @var object - */ - var $ui = null; - - /** - * Configuration object (PEAR_Config). - * @var object - */ - var $config = null; - var $current_path = null; - /** - * PEAR_SourceAnalyzer instance - * @var object - */ - var $source_analyzer = null; /** * Flag variable used to mark a valid package file * @var boolean @@ -145,10 +160,6 @@ class PEAR_Common extends PEAR */ var $_validPackageFile; - // }}} - - // {{{ constructor - /** * PEAR_Common constructor * @@ -161,9 +172,6 @@ class PEAR_Common extends PEAR $this->debug = $this->config->get('verbose'); } - // }}} - // {{{ destructor - /** * PEAR_Common destructor * @@ -176,6 +184,10 @@ class PEAR_Common extends PEAR $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles']; while ($file = array_shift($tempfiles)) { if (@is_dir($file)) { + if (!class_exists('System')) { + require_once 'System.php'; + } + System::rm(array('-rf', $file)); } elseif (file_exists($file)) { unlink($file); @@ -183,9 +195,6 @@ class PEAR_Common extends PEAR } } - // }}} - // {{{ addTempFile() - /** * Register a temporary file or directory. When the destructor is * executed, all registered temporary files and directories are @@ -199,12 +208,12 @@ class PEAR_Common extends PEAR */ function addTempFile($file) { - $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + PEAR_Frontend::addTempFile($file); } - // }}} - // {{{ mkDirHier() - /** * Wrapper to System::mkDir(), creates a directory as well as * any necessary parent directories. @@ -217,13 +226,14 @@ class PEAR_Common extends PEAR */ function mkDirHier($dir) { + // Only used in Installer - move it there ? $this->log(2, "+ create dir $dir"); + if (!class_exists('System')) { + require_once 'System.php'; + } return System::mkDir(array('-p', $dir)); } - // }}} - // {{{ log() - /** * Logging method. * @@ -233,21 +243,24 @@ class PEAR_Common extends PEAR * @return void * * @access public + * @static */ function log($level, $msg, $append_crlf = true) { if ($this->debug >= $level) { - if (is_object($this->ui)) { - $this->ui->log($msg, $append_crlf); + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + + $ui = &PEAR_Frontend::singleton(); + if (is_a($ui, 'PEAR_Frontend')) { + $ui->log($msg, $append_crlf); } else { print "$msg\n"; } } } - // }}} - // {{{ mkTempDir() - /** * Create and register a temporary directory. * @@ -261,22 +274,20 @@ class PEAR_Common extends PEAR */ function mkTempDir($tmpdir = '') { - if ($tmpdir) { - $topt = array('-t', $tmpdir); - } else { - $topt = array(); - } + $topt = $tmpdir ? array('-t', $tmpdir) : array(); $topt = array_merge($topt, array('-d', 'pear')); + if (!class_exists('System')) { + require_once 'System.php'; + } + if (!$tmpdir = System::mktemp($topt)) { return false; } + $this->addTempFile($tmpdir); return $tmpdir; } - // }}} - // {{{ setFrontendObject() - /** * Set object that represents the frontend to be used. * @@ -289,400 +300,181 @@ class PEAR_Common extends PEAR $this->ui = &$ui; } - // }}} - - // {{{ _unIndent() + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } /** - * Unindent given string (?) + * Get the valid roles for a PEAR package maintainer * - * @param string $str The string that has to be unindented. - * @return string - * @access private + * @return array + * @static */ - function _unIndent($str) + function getUserRoles() { - // remove leading newlines - $str = preg_replace('/^[\r\n]+/', '', $str); - // find whitespace at the beginning of the first line - $indent_len = strspn($str, " \t"); - $indent = substr($str, 0, $indent_len); - $data = ''; - // remove the same amount of whitespace from following lines - foreach (explode("\n", $str) as $line) { - if (substr($line, 0, $indent_len) == $indent) { - $data .= substr($line, $indent_len) . "\n"; + return $GLOBALS['_PEAR_Common_maintainer_roles']; + } + + /** + * Get the valid package release states of packages + * + * @return array + * @static + */ + function getReleaseStates() + { + return $GLOBALS['_PEAR_Common_release_states']; + } + + /** + * Get the implemented dependency types (php, ext, pkg etc.) + * + * @return array + * @static + */ + function getDependencyTypes() + { + return $GLOBALS['_PEAR_Common_dependency_types']; + } + + /** + * Get the implemented dependency relations (has, lt, ge etc.) + * + * @return array + * @static + */ + function getDependencyRelations() + { + return $GLOBALS['_PEAR_Common_dependency_relations']; + } + + /** + * Get the implemented file roles + * + * @return array + * @static + */ + function getFileRoles() + { + return $GLOBALS['_PEAR_Common_file_roles']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getReplacementTypes() + { + return $GLOBALS['_PEAR_Common_replacement_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getProvideTypes() + { + return $GLOBALS['_PEAR_Common_provide_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getScriptPhases() + { + return $GLOBALS['_PEAR_Common_script_phases']; + } + + /** + * Test whether a string contains a valid package name. + * + * @param string $name the package name to test + * + * @return bool + * + * @access public + */ + function validPackageName($name) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name); + } + + /** + * Test whether a string contains a valid package version. + * + * @param string $ver the package version to test + * + * @return bool + * + * @access public + */ + function validPackageVersion($ver) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $ipath = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($ipath as $include) { + $test = realpath($include . DIRECTORY_SEPARATOR . $path); + if (file_exists($test) && is_readable($test)) { + return true; } } - return $data; + + return false; } - // }}} - // {{{ _element_start() - - /** - * XML parser callback for starting elements. Used while package - * format version is not yet known. - * - * @param resource $xp XML parser resource - * @param string $name name of starting element - * @param array $attribs element attributes, name => value - * - * @return void - * - * @access private - */ - function _element_start($xp, $name, $attribs) + function _postProcessChecks($pf) { - array_push($this->element_stack, $name); - $this->current_element = $name; - $spos = sizeof($this->element_stack) - 2; - $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; - $this->current_attributes = $attribs; - switch ($name) { - case 'package': { - $this->_validPackageFile = true; - if (isset($attribs['version'])) { - $vs = preg_replace('/[^0-9a-z]/', '_', $attribs['version']); - } else { - $vs = '1_0'; - } - $elem_start = '_element_start_'. $vs; - $elem_end = '_element_end_'. $vs; - $cdata = '_pkginfo_cdata_'. $vs; - if (!method_exists($this, $elem_start) || - !method_exists($this, $elem_end) || - !method_exists($this, $cdata)) { - $this->raiseError("No handlers for package.xml version $attribs[version]"); - return; - } - xml_set_element_handler($xp, $elem_start, $elem_end); - xml_set_character_data_handler($xp, $cdata); - break; + if (!PEAR::isError($pf)) { + return $this->_postProcessValidPackagexml($pf); + } + + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); } } + + return $pf; } - // }}} - // {{{ _element_end() - - /** - * XML parser callback for ending elements. Used while package - * format version is not yet known. - * - * @param resource $xp XML parser resource - * @param string $name name of ending element - * - * @return void - * - * @access private - */ - function _element_end($xp, $name) - { - } - - // }}} - - // Support for package DTD v1.0: - // {{{ _element_start_1_0() - - /** - * XML parser callback for ending elements. Used for version 1.0 - * packages. - * - * @param resource $xp XML parser resource - * @param string $name name of ending element - * - * @return void - * - * @access private - */ - function _element_start_1_0($xp, $name, $attribs) - { - array_push($this->element_stack, $name); - $this->current_element = $name; - $spos = sizeof($this->element_stack) - 2; - $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; - $this->current_attributes = $attribs; - $this->cdata = ''; - switch ($name) { - case 'dir': - if ($this->in_changelog) { - break; - } - if ($attribs['name'] != '/') { - $this->dir_names[] = $attribs['name']; - } - if (isset($attribs['baseinstalldir'])) { - $this->dir_install = $attribs['baseinstalldir']; - } - if (isset($attribs['role'])) { - $this->dir_role = $attribs['role']; - } - break; - case 'file': - if ($this->in_changelog) { - break; - } - if (isset($attribs['name'])) { - $path = ''; - if (count($this->dir_names)) { - foreach ($this->dir_names as $dir) { - $path .= $dir . DIRECTORY_SEPARATOR; - } - } - $path .= $attribs['name']; - unset($attribs['name']); - $this->current_path = $path; - $this->filelist[$path] = $attribs; - // Set the baseinstalldir only if the file don't have this attrib - if (!isset($this->filelist[$path]['baseinstalldir']) && - isset($this->dir_install)) - { - $this->filelist[$path]['baseinstalldir'] = $this->dir_install; - } - // Set the Role - if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { - $this->filelist[$path]['role'] = $this->dir_role; - } - } - break; - case 'replace': - if (!$this->in_changelog) { - $this->filelist[$this->current_path]['replacements'][] = $attribs; - } - break; - case 'maintainers': - $this->pkginfo['maintainers'] = array(); - $this->m_i = 0; // maintainers array index - break; - case 'maintainer': - // compatibility check - if (!isset($this->pkginfo['maintainers'])) { - $this->pkginfo['maintainers'] = array(); - $this->m_i = 0; - } - $this->pkginfo['maintainers'][$this->m_i] = array(); - $this->current_maintainer =& $this->pkginfo['maintainers'][$this->m_i]; - break; - case 'changelog': - $this->pkginfo['changelog'] = array(); - $this->c_i = 0; // changelog array index - $this->in_changelog = true; - break; - case 'release': - if ($this->in_changelog) { - $this->pkginfo['changelog'][$this->c_i] = array(); - $this->current_release = &$this->pkginfo['changelog'][$this->c_i]; - } else { - $this->current_release = &$this->pkginfo; - } - break; - case 'deps': - if (!$this->in_changelog) { - $this->pkginfo['release_deps'] = array(); - } - break; - case 'dep': - // dependencies array index - if (!$this->in_changelog) { - $this->d_i++; - $this->pkginfo['release_deps'][$this->d_i] = $attribs; - } - break; - case 'configureoptions': - if (!$this->in_changelog) { - $this->pkginfo['configure_options'] = array(); - } - break; - case 'configureoption': - if (!$this->in_changelog) { - $this->pkginfo['configure_options'][] = $attribs; - } - break; - case 'provides': - if (empty($attribs['type']) || empty($attribs['name'])) { - break; - } - $attribs['explicit'] = true; - $this->pkginfo['provides']["$attribs[type];$attribs[name]"] = $attribs; - break; - } - } - - // }}} - // {{{ _element_end_1_0() - - /** - * XML parser callback for ending elements. Used for version 1.0 - * packages. - * - * @param resource $xp XML parser resource - * @param string $name name of ending element - * - * @return void - * - * @access private - */ - function _element_end_1_0($xp, $name) - { - $data = trim($this->cdata); - switch ($name) { - case 'name': - switch ($this->prev_element) { - case 'package': - // XXX should we check the package name here? - $this->pkginfo['package'] = ereg_replace('[^a-zA-Z0-9._]', '_', $data); - break; - case 'maintainer': - $this->current_maintainer['name'] = $data; - break; - } - break; - case 'summary': - $this->pkginfo['summary'] = $data; - break; - case 'description': - $data = $this->_unIndent($this->cdata); - $this->pkginfo['description'] = $data; - break; - case 'user': - $this->current_maintainer['handle'] = $data; - break; - case 'email': - $this->current_maintainer['email'] = $data; - break; - case 'role': - $this->current_maintainer['role'] = $data; - break; - case 'version': - $data = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $data); - if ($this->in_changelog) { - $this->current_release['version'] = $data; - } else { - $this->pkginfo['version'] = $data; - } - break; - case 'date': - if ($this->in_changelog) { - $this->current_release['release_date'] = $data; - } else { - $this->pkginfo['release_date'] = $data; - } - break; - case 'notes': - // try to "de-indent" release notes in case someone - // has been over-indenting their xml ;-) - $data = $this->_unIndent($this->cdata); - if ($this->in_changelog) { - $this->current_release['release_notes'] = $data; - } else { - $this->pkginfo['release_notes'] = $data; - } - break; - case 'warnings': - if ($this->in_changelog) { - $this->current_release['release_warnings'] = $data; - } else { - $this->pkginfo['release_warnings'] = $data; - } - break; - case 'state': - if ($this->in_changelog) { - $this->current_release['release_state'] = $data; - } else { - $this->pkginfo['release_state'] = $data; - } - break; - case 'license': - if ($this->in_changelog) { - $this->current_release['release_license'] = $data; - } else { - $this->pkginfo['release_license'] = $data; - } - break; - case 'dep': - if ($data && !$this->in_changelog) { - $this->pkginfo['release_deps'][$this->d_i]['name'] = $data; - } - break; - case 'dir': - if ($this->in_changelog) { - break; - } - array_pop($this->dir_names); - break; - case 'file': - if ($this->in_changelog) { - break; - } - if ($data) { - $path = ''; - if (count($this->dir_names)) { - foreach ($this->dir_names as $dir) { - $path .= $dir . DIRECTORY_SEPARATOR; - } - } - $path .= $data; - $this->filelist[$path] = $this->current_attributes; - // Set the baseinstalldir only if the file don't have this attrib - if (!isset($this->filelist[$path]['baseinstalldir']) && - isset($this->dir_install)) - { - $this->filelist[$path]['baseinstalldir'] = $this->dir_install; - } - // Set the Role - if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { - $this->filelist[$path]['role'] = $this->dir_role; - } - } - break; - case 'maintainer': - if (empty($this->pkginfo['maintainers'][$this->m_i]['role'])) { - $this->pkginfo['maintainers'][$this->m_i]['role'] = 'lead'; - } - $this->m_i++; - break; - case 'release': - if ($this->in_changelog) { - $this->c_i++; - } - break; - case 'changelog': - $this->in_changelog = false; - break; - } - array_pop($this->element_stack); - $spos = sizeof($this->element_stack) - 1; - $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : ''; - $this->cdata = ''; - } - - // }}} - // {{{ _pkginfo_cdata_1_0() - - /** - * XML parser callback for character data. Used for version 1.0 - * packages. - * - * @param resource $xp XML parser resource - * @param string $name character data - * - * @return void - * - * @access private - */ - function _pkginfo_cdata_1_0($xp, $data) - { - if (isset($this->cdata)) { - $this->cdata .= $data; - } - } - - // }}} - - // {{{ infoFromTgzFile() - /** * Returns information about a package file. Expects the name of * a gzipped tar file as input. @@ -692,48 +484,16 @@ class PEAR_Common extends PEAR * @return array array with package information * * @access public + * @deprecated use PEAR_PackageFile->fromTgzFile() instead * */ function infoFromTgzFile($file) { - if (!@is_file($file)) { - return $this->raiseError("could not open file \"$file\""); - } - $tar = new Archive_Tar($file); - if ($this->debug <= 1) { - $tar->pushErrorHandling(PEAR_ERROR_RETURN); - } - $content = $tar->listContent(); - if ($this->debug <= 1) { - $tar->popErrorHandling(); - } - if (!is_array($content)) { - $file = realpath($file); - return $this->raiseError("Could not get contents of package \"$file\"". - '. Invalid tgz file.'); - } - $xml = null; - foreach ($content as $file) { - $name = $file['filename']; - if ($name == 'package.xml') { - $xml = $name; - break; - } elseif (ereg('package.xml$', $name, $match)) { - $xml = $match[0]; - break; - } - } - $tmpdir = System::mkTemp(array('-d', 'pear')); - $this->addTempFile($tmpdir); - if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { - return $this->raiseError('could not extract the package.xml file'); - } - return $this->infoFromDescriptionFile("$tmpdir/$xml"); + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); } - // }}} - // {{{ infoFromDescriptionFile() - /** * Returns information about a package file. Expects the name of * a package xml file as input. @@ -743,85 +503,59 @@ class PEAR_Common extends PEAR * @return array array with package information * * @access public + * @deprecated use PEAR_PackageFile->fromPackageFile() instead * */ function infoFromDescriptionFile($descfile) { - if (!@is_file($descfile) || !is_readable($descfile) || - (!$fp = @fopen($descfile, 'r'))) { - return $this->raiseError("Unable to open $descfile"); - } - - // read the whole thing so we only get one cdata callback - // for each block of cdata - $data = fread($fp, filesize($descfile)); - return $this->infoFromString($data); + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); } - // }}} - // {{{ infoFromString() - /** * Returns information about a package file. Expects the contents * of a package xml file as input. * - * @param string $data name of package xml file + * @param string $data contents of package.xml file * * @return array array with package information * * @access public + * @deprecated use PEAR_PackageFile->fromXmlstring() instead * */ function infoFromString($data) { - require_once('PEAR/Dependency.php'); - if (PEAR_Dependency::checkExtension($error, 'xml')) { - return $this->raiseError($error); - } - $xp = @xml_parser_create(); - if (!$xp) { - return $this->raiseError('Unable to create XML parser'); - } - xml_set_object($xp, $this); - xml_set_element_handler($xp, '_element_start', '_element_end'); - xml_set_character_data_handler($xp, '_pkginfo_cdata'); - xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); - - $this->element_stack = array(); - $this->pkginfo = array('provides' => array()); - $this->current_element = false; - unset($this->dir_install); - $this->pkginfo['filelist'] = array(); - $this->filelist =& $this->pkginfo['filelist']; - $this->dir_names = array(); - $this->in_changelog = false; - $this->d_i = 0; - $this->cdata = ''; - $this->_validPackageFile = false; - - if (!xml_parse($xp, $data, 1)) { - $code = xml_get_error_code($xp); - $msg = sprintf("XML error: %s at line %d", - xml_error_string($code), - xml_get_current_line_number($xp)); - xml_parser_free($xp); - return $this->raiseError($msg, $code); - } - - xml_parser_free($xp); - - if (!$this->_validPackageFile) { - return $this->raiseError('Invalid Package File, no tag'); - } - foreach ($this->pkginfo as $k => $v) { - if (!is_array($v)) { - $this->pkginfo[$k] = trim($v); - } - } - return $this->pkginfo; + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false); + return $this->_postProcessChecks($pf); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return array + */ + function _postProcessValidPackagexml(&$pf) + { + if (!is_a($pf, 'PEAR_PackageFile_v2')) { + $this->pkginfo = $pf->toArray(); + return $this->pkginfo; + } + + // sort of make this into a package.xml 1.0-style array + // changelog is not converted to old format. + $arr = $pf->toArray(true); + $arr = array_merge($arr, $arr['old']); + unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'], + $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'], + $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'], + $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'], + $arr['helper'], $arr['contributor']); + $arr['filelist'] = $pf->getFilelist(); + $this->pkginfo = $arr; + return $arr; } - // }}} - // {{{ infoFromAny() /** * Returns package information from different sources @@ -832,35 +566,30 @@ class PEAR_Common extends PEAR * @access public * @param string Filename of the source ('package.xml', '.tgz') * @return string + * @deprecated use PEAR_PackageFile->fromAnyFile() instead */ function infoFromAny($info) { if (is_string($info) && file_exists($info)) { - $tmp = substr($info, -4); - if ($tmp == '.xml') { - $info = $this->infoFromDescriptionFile($info); - } elseif ($tmp == '.tar' || $tmp == '.tgz') { - $info = $this->infoFromTgzFile($info); - } else { - $fp = fopen($info, "r"); - $test = fread($fp, 5); - fclose($fp); - if ($test == "infoFromDescriptionFile($info); - } else { - $info = $this->infoFromTgzFile($info); + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } } + + return $pf; } - if (PEAR::isError($info)) { - return $this->raiseError($info); - } + + return $this->_postProcessValidPackagexml($pf); } + return $info; } - // }}} - // {{{ xmlFromInfo() - /** * Return an XML document based on the package info (as returned * by the PEAR_Common::infoFrom* methods). @@ -870,164 +599,17 @@ class PEAR_Common extends PEAR * @return string XML data * * @access public + * @deprecated use a PEAR_PackageFile_v* object's generator instead */ function xmlFromInfo($pkginfo) { - static $maint_map = array( - "handle" => "user", - "name" => "name", - "email" => "email", - "role" => "role", - ); - $ret = "\n"; - $ret .= "\n"; - $ret .= " - $pkginfo[package] - ".htmlspecialchars($pkginfo['summary'])." - ".htmlspecialchars($pkginfo['description'])." - -"; - foreach ($pkginfo['maintainers'] as $maint) { - $ret .= " \n"; - foreach ($maint_map as $idx => $elm) { - $ret .= " <$elm>"; - $ret .= htmlspecialchars($maint[$idx]); - $ret .= "\n"; - } - $ret .= " \n"; - } - $ret .= " \n"; - $ret .= $this->_makeReleaseXml($pkginfo); - if (@sizeof($pkginfo['changelog']) > 0) { - $ret .= " \n"; - foreach ($pkginfo['changelog'] as $oldrelease) { - $ret .= $this->_makeReleaseXml($oldrelease, true); - } - $ret .= " \n"; - } - $ret .= "\n"; - return $ret; + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + $pf = &$packagefile->fromArray($pkginfo); + $gen = &$pf->getDefaultGenerator(); + return $gen->toXml(PEAR_VALIDATE_PACKAGING); } - // }}} - // {{{ _makeReleaseXml() - - /** - * Generate part of an XML description with release information. - * - * @param array $pkginfo array with release information - * @param bool $changelog whether the result will be in a changelog element - * - * @return string XML data - * - * @access private - */ - function _makeReleaseXml($pkginfo, $changelog = false) - { - // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!! - $indent = $changelog ? " " : ""; - $ret = "$indent \n"; - if (!empty($pkginfo['version'])) { - $ret .= "$indent $pkginfo[version]\n"; - } - if (!empty($pkginfo['release_date'])) { - $ret .= "$indent $pkginfo[release_date]\n"; - } - if (!empty($pkginfo['release_license'])) { - $ret .= "$indent $pkginfo[release_license]\n"; - } - if (!empty($pkginfo['release_state'])) { - $ret .= "$indent $pkginfo[release_state]\n"; - } - if (!empty($pkginfo['release_notes'])) { - $ret .= "$indent ".htmlspecialchars($pkginfo['release_notes'])."\n"; - } - if (!empty($pkginfo['release_warnings'])) { - $ret .= "$indent ".htmlspecialchars($pkginfo['release_warnings'])."\n"; - } - if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) { - $ret .= "$indent \n"; - foreach ($pkginfo['release_deps'] as $dep) { - $ret .= "$indent $what) { - $ret .= "$indent $fa) { - @$ret .= "$indent $v) { - $ret .= " $k=\"" . htmlspecialchars($v) .'"'; - } - $ret .= "/>\n"; - } - @$ret .= "$indent \n"; - } - } - $ret .= "$indent \n"; - } - $ret .= "$indent \n"; - return $ret; - } - - // }}} - // {{{ validatePackageInfo() - /** * Validate XML package definition file. * @@ -1039,189 +621,38 @@ class PEAR_Common extends PEAR * may be found, or empty if they are not available * @access public * @return boolean + * @deprecated use the validation of PEAR_PackageFile objects */ function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '') { - if (PEAR::isError($info = $this->infoFromAny($info))) { - return $this->raiseError($info); + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (strpos($info, 'fromXmlString($info, PEAR_VALIDATE_NORMAL, ''); + } else { + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); } - if (!is_array($info)) { + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + if ($error['level'] == 'error') { + $errors[] = $error['message']; + } else { + $warnings[] = $error['message']; + } + } + } + return false; } - $errors = array(); - $warnings = array(); - if (!isset($info['package'])) { - $errors[] = 'missing package name'; - } elseif (!$this->validPackageName($info['package'])) { - $errors[] = 'invalid package name'; - } - $this->_packageName = $pn = $info['package']; - - if (empty($info['summary'])) { - $errors[] = 'missing summary'; - } elseif (strpos(trim($info['summary']), "\n") !== false) { - $warnings[] = 'summary should be on a single line'; - } - if (empty($info['description'])) { - $errors[] = 'missing description'; - } - if (empty($info['release_license'])) { - $errors[] = 'missing license'; - } - if (!isset($info['version'])) { - $errors[] = 'missing version'; - } elseif (!$this->validPackageVersion($info['version'])) { - $errors[] = 'invalid package release version'; - } - if (empty($info['release_state'])) { - $errors[] = 'missing release state'; - } elseif (!in_array($info['release_state'], PEAR_Common::getReleaseStates())) { - $errors[] = "invalid release state `$info[release_state]', should be one of: " - . implode(' ', PEAR_Common::getReleaseStates()); - } - if (empty($info['release_date'])) { - $errors[] = 'missing release date'; - } elseif (!preg_match('/^\d{4}-\d\d-\d\d$/', $info['release_date'])) { - $errors[] = "invalid release date `$info[release_date]', format is YYYY-MM-DD"; - } - if (empty($info['release_notes'])) { - $errors[] = "missing release notes"; - } - if (empty($info['maintainers'])) { - $errors[] = 'no maintainer(s)'; - } else { - $i = 1; - foreach ($info['maintainers'] as $m) { - if (empty($m['handle'])) { - $errors[] = "maintainer $i: missing handle"; - } - if (empty($m['role'])) { - $errors[] = "maintainer $i: missing role"; - } elseif (!in_array($m['role'], PEAR_Common::getUserRoles())) { - $errors[] = "maintainer $i: invalid role `$m[role]', should be one of: " - . implode(' ', PEAR_Common::getUserRoles()); - } - if (empty($m['name'])) { - $errors[] = "maintainer $i: missing name"; - } - if (empty($m['email'])) { - $errors[] = "maintainer $i: missing email"; - } - $i++; - } - } - if (!empty($info['release_deps'])) { - $i = 1; - foreach ($info['release_deps'] as $d) { - if (empty($d['type'])) { - $errors[] = "dependency $i: missing type"; - } elseif (!in_array($d['type'], PEAR_Common::getDependencyTypes())) { - $errors[] = "dependency $i: invalid type '$d[type]', should be one of: " . - implode(' ', PEAR_Common::getDependencyTypes()); - } - if (empty($d['rel'])) { - $errors[] = "dependency $i: missing relation"; - } elseif (!in_array($d['rel'], PEAR_Common::getDependencyRelations())) { - $errors[] = "dependency $i: invalid relation '$d[rel]', should be one of: " - . implode(' ', PEAR_Common::getDependencyRelations()); - } - if (!empty($d['optional'])) { - if (!in_array($d['optional'], array('yes', 'no'))) { - $errors[] = "dependency $i: invalid relation optional attribute '$d[optional]', should be one of: yes no"; - } else { - if (($d['rel'] == 'not' || $d['rel'] == 'ne') && $d['optional'] == 'yes') { - $errors[] = "dependency $i: 'not' and 'ne' dependencies cannot be " . - "optional"; - } - } - } - if ($d['rel'] != 'not' && $d['rel'] != 'has' && empty($d['version'])) { - $warnings[] = "dependency $i: missing version"; - } elseif (($d['rel'] == 'not' || $d['rel'] == 'has') && !empty($d['version'])) { - $warnings[] = "dependency $i: version ignored for `$d[rel]' dependencies"; - } - if ($d['rel'] == 'not' && !empty($d['version'])) { - $warnings[] = "dependency $i: 'not' defines a total conflict, to exclude " . - "specific versions, use 'ne'"; - } - if ($d['type'] == 'php' && !empty($d['name'])) { - $warnings[] = "dependency $i: name ignored for php type dependencies"; - } elseif ($d['type'] != 'php' && empty($d['name'])) { - $errors[] = "dependency $i: missing name"; - } - if ($d['type'] == 'php' && $d['rel'] == 'not') { - $errors[] = "dependency $i: PHP dependencies cannot use 'not' " . - "rel, use 'ne' to exclude versions"; - } - $i++; - } - } - if (!empty($info['configure_options'])) { - $i = 1; - foreach ($info['configure_options'] as $c) { - if (empty($c['name'])) { - $errors[] = "configure option $i: missing name"; - } - if (empty($c['prompt'])) { - $errors[] = "configure option $i: missing prompt"; - } - $i++; - } - } - if (empty($info['filelist'])) { - $errors[] = 'no files'; - } else { - foreach ($info['filelist'] as $file => $fa) { - if (empty($fa['role'])) { - $errors[] = "file $file: missing role"; - continue; - } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) { - $errors[] = "file $file: invalid role, should be one of: " - . implode(' ', PEAR_Common::getFileRoles()); - } - if ($fa['role'] == 'php' && $dir_prefix) { - $this->log(1, "Analyzing $file"); - $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); - if ($srcinfo) { - $this->buildProvidesArray($srcinfo); - } - } - - // (ssb) Any checks we can do for baseinstalldir? - // (cox) Perhaps checks that either the target dir and - // baseInstall doesn't cointain "../../" - } - } - $this->_packageName = $pn = $info['package']; - $pnl = strlen($pn); - foreach ((array)$this->pkginfo['provides'] as $key => $what) { - if (isset($what['explicit'])) { - // skip conformance checks if the provides entry is - // specified in the package.xml file - continue; - } - extract($what); - if ($type == 'class') { - if (!strncasecmp($name, $pn, $pnl)) { - continue; - } - $warnings[] = "in $file: class \"$name\" not prefixed with package name \"$pn\""; - } elseif ($type == 'function') { - if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { - continue; - } - $warnings[] = "in $file: function \"$name\" not prefixed with package name \"$pn\""; - } - } - - return true; } - // }}} - // {{{ buildProvidesArray() - /** * Build a "provides" array from data returned by * analyzeSourceCode(). The format of the built array is like @@ -1248,12 +679,14 @@ class PEAR_Common extends PEAR if (isset($this->_packageName)) { $pn = $this->_packageName; } + $pnl = strlen($pn); foreach ($srcinfo['declared_classes'] as $class) { $key = "class;$class"; if (isset($this->pkginfo['provides'][$key])) { continue; } + $this->pkginfo['provides'][$key] = array('file'=> $file, 'type' => 'class', 'name' => $class); if (isset($srcinfo['inheritance'][$class])) { @@ -1261,6 +694,7 @@ class PEAR_Common extends PEAR $srcinfo['inheritance'][$class]; } } + foreach ($srcinfo['declared_methods'] as $class => $methods) { foreach ($methods as $method) { $function = "$class::$method"; @@ -1269,6 +703,7 @@ class PEAR_Common extends PEAR isset($this->pkginfo['provides'][$key])) { continue; } + $this->pkginfo['provides'][$key] = array('file'=> $file, 'type' => 'function', 'name' => $function); } @@ -1279,17 +714,16 @@ class PEAR_Common extends PEAR if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) { continue; } + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; } + $this->pkginfo['provides'][$key] = array('file'=> $file, 'type' => 'function', 'name' => $function); } } - // }}} - // {{{ analyzeSourceCode() - /** * Analyze the source code of the given PHP file * @@ -1299,228 +733,28 @@ class PEAR_Common extends PEAR */ function analyzeSourceCode($file) { - if (!function_exists("token_get_all")) { - return false; + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; } - if (!defined('T_DOC_COMMENT')) { - define('T_DOC_COMMENT', T_COMMENT); - } - if (!defined('T_INTERFACE')) { - define('T_INTERFACE', -1); - } - if (!defined('T_IMPLEMENTS')) { - define('T_IMPLEMENTS', -1); - } - if (!$fp = @fopen($file, "r")) { - return false; - } - $contents = fread($fp, filesize($file)); - $tokens = token_get_all($contents); -/* - for ($i = 0; $i < sizeof($tokens); $i++) { - @list($token, $data) = $tokens[$i]; - if (is_string($token)) { - var_dump($token); - } else { - print token_name($token) . ' '; - var_dump(rtrim($data)); - } - } -*/ - $look_for = 0; - $paren_level = 0; - $bracket_level = 0; - $brace_level = 0; - $lastphpdoc = ''; - $current_class = ''; - $current_interface = ''; - $current_class_level = -1; - $current_function = ''; - $current_function_level = -1; - $declared_classes = array(); - $declared_interfaces = array(); - $declared_functions = array(); - $declared_methods = array(); - $used_classes = array(); - $used_functions = array(); - $extends = array(); - $implements = array(); - $nodeps = array(); - $inquote = false; - $interface = false; - for ($i = 0; $i < sizeof($tokens); $i++) { - if (is_array($tokens[$i])) { - list($token, $data) = $tokens[$i]; - } else { - $token = $tokens[$i]; - $data = ''; - } - if ($inquote) { - if ($token != '"') { - continue; - } else { - $inquote = false; - } - } - switch ($token) { - case T_WHITESPACE: - continue; - case ';': - if ($interface) { - $current_function = ''; - $current_function_level = -1; - } - break; - case '"': - $inquote = true; - break; - case T_CURLY_OPEN: - case T_DOLLAR_OPEN_CURLY_BRACES: - case '{': $brace_level++; continue 2; - case '}': - $brace_level--; - if ($current_class_level == $brace_level) { - $current_class = ''; - $current_class_level = -1; - } - if ($current_function_level == $brace_level) { - $current_function = ''; - $current_function_level = -1; - } - continue 2; - case '[': $bracket_level++; continue 2; - case ']': $bracket_level--; continue 2; - case '(': $paren_level++; continue 2; - case ')': $paren_level--; continue 2; - case T_INTERFACE: - $interface = true; - case T_CLASS: - if (($current_class_level != -1) || ($current_function_level != -1)) { - PEAR::raiseError("Parser error: Invalid PHP file $file", - PEAR_COMMON_ERROR_INVALIDPHP); - return false; - } - case T_FUNCTION: - case T_NEW: - case T_EXTENDS: - case T_IMPLEMENTS: - $look_for = $token; - continue 2; - case T_STRING: - if (version_compare(zend_version(), '2.0', '<')) { - if (in_array(strtolower($data), - array('public', 'private', 'protected', 'abstract', - 'interface', 'implements', 'clone', 'throw') - )) { - PEAR::raiseError('Error: PHP5 packages must be packaged by php 5 PEAR'); - return false; - } - } - if ($look_for == T_CLASS) { - $current_class = $data; - $current_class_level = $brace_level; - $declared_classes[] = $current_class; - } elseif ($look_for == T_INTERFACE) { - $current_interface = $data; - $current_class_level = $brace_level; - $declared_interfaces[] = $current_interface; - } elseif ($look_for == T_IMPLEMENTS) { - $implements[$current_class] = $data; - } elseif ($look_for == T_EXTENDS) { - $extends[$current_class] = $data; - } elseif ($look_for == T_FUNCTION) { - if ($current_class) { - $current_function = "$current_class::$data"; - $declared_methods[$current_class][] = $data; - } elseif ($current_interface) { - $current_function = "$current_interface::$data"; - $declared_methods[$current_interface][] = $data; - } else { - $current_function = $data; - $declared_functions[] = $current_function; - } - $current_function_level = $brace_level; - $m = array(); - } elseif ($look_for == T_NEW) { - $used_classes[$data] = true; - } - $look_for = 0; - continue 2; - case T_VARIABLE: - $look_for = 0; - continue 2; - case T_DOC_COMMENT: - case T_COMMENT: - if (preg_match('!^/\*\*\s!', $data)) { - $lastphpdoc = $data; - if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { - $nodeps = array_merge($nodeps, $m[1]); - } - } - continue 2; - case T_DOUBLE_COLON: - if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { - PEAR::raiseError("Parser error: Invalid PHP file $file", - PEAR_COMMON_ERROR_INVALIDPHP); - return false; - } - $class = $tokens[$i - 1][1]; - if (strtolower($class) != 'parent') { - $used_classes[$class] = true; - } - continue 2; - } - } - return array( - "source_file" => $file, - "declared_classes" => $declared_classes, - "declared_interfaces" => $declared_interfaces, - "declared_methods" => $declared_methods, - "declared_functions" => $declared_functions, - "used_classes" => array_diff(array_keys($used_classes), $nodeps), - "inheritance" => $extends, - "implements" => $implements, - ); + + $a = new PEAR_PackageFile_v2_Validator; + return $a->analyzeSourceCode($file); } - // }}} - // {{{ betterStates() - - /** - * Return an array containing all of the states that are more stable than - * or equal to the passed in state - * - * @param string Release state - * @param boolean Determines whether to include $state in the list - * @return false|array False if $state is not a valid release state - */ - function betterStates($state, $include = false) - { - static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); - $i = array_search($state, $states); - if ($i === false) { - return false; - } - if ($include) { - $i--; - } - return array_slice($states, $i + 1); - } - - // }}} - // {{{ detectDependencies() - function detectDependencies($any, $status_callback = null) { if (!function_exists("token_get_all")) { return false; } + if (PEAR::isError($info = $this->infoFromAny($any))) { return $this->raiseError($info); } + if (!is_array($info)) { return false; } + $deps = array(); $used_c = $decl_c = $decl_f = $decl_m = array(); foreach ($info['filelist'] as $file => $fa) { @@ -1531,9 +765,11 @@ class PEAR_Common extends PEAR $decl_m = @array_merge($decl_m, $tmp['declared_methods']); $inheri = @array_merge($inheri, $tmp['inheritance']); } + $used_c = array_unique($used_c); $decl_c = array_unique($decl_c); $undecl_c = array_diff($used_c, $decl_c); + return array('used_classes' => $used_c, 'declared_classes' => $decl_c, 'declared_methods' => $decl_m, @@ -1543,158 +779,6 @@ class PEAR_Common extends PEAR ); } - // }}} - // {{{ getUserRoles() - - /** - * Get the valid roles for a PEAR package maintainer - * - * @return array - * @static - */ - function getUserRoles() - { - return $GLOBALS['_PEAR_Common_maintainer_roles']; - } - - // }}} - // {{{ getReleaseStates() - - /** - * Get the valid package release states of packages - * - * @return array - * @static - */ - function getReleaseStates() - { - return $GLOBALS['_PEAR_Common_release_states']; - } - - // }}} - // {{{ getDependencyTypes() - - /** - * Get the implemented dependency types (php, ext, pkg etc.) - * - * @return array - * @static - */ - function getDependencyTypes() - { - return $GLOBALS['_PEAR_Common_dependency_types']; - } - - // }}} - // {{{ getDependencyRelations() - - /** - * Get the implemented dependency relations (has, lt, ge etc.) - * - * @return array - * @static - */ - function getDependencyRelations() - { - return $GLOBALS['_PEAR_Common_dependency_relations']; - } - - // }}} - // {{{ getFileRoles() - - /** - * Get the implemented file roles - * - * @return array - * @static - */ - function getFileRoles() - { - return $GLOBALS['_PEAR_Common_file_roles']; - } - - // }}} - // {{{ getReplacementTypes() - - /** - * Get the implemented file replacement types in - * - * @return array - * @static - */ - function getReplacementTypes() - { - return $GLOBALS['_PEAR_Common_replacement_types']; - } - - // }}} - // {{{ getProvideTypes() - - /** - * Get the implemented file replacement types in - * - * @return array - * @static - */ - function getProvideTypes() - { - return $GLOBALS['_PEAR_Common_provide_types']; - } - - // }}} - // {{{ getScriptPhases() - - /** - * Get the implemented file replacement types in - * - * @return array - * @static - */ - function getScriptPhases() - { - return $GLOBALS['_PEAR_Common_script_phases']; - } - - // }}} - // {{{ validPackageName() - - /** - * Test whether a string contains a valid package name. - * - * @param string $name the package name to test - * - * @return bool - * - * @access public - */ - function validPackageName($name) - { - return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name); - } - - - // }}} - // {{{ validPackageVersion() - - /** - * Test whether a string contains a valid package version. - * - * @param string $ver the package version to test - * - * @return bool - * - * @access public - */ - function validPackageVersion($ver) - { - return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); - } - - - // }}} - - // {{{ downloadHttp() - /** * Download a file through HTTP. Considers suggested file name in * Content-disposition: header and can run a callback function for @@ -1738,357 +822,16 @@ class PEAR_Common extends PEAR * getCode(). * * @access public + * @deprecated in favor of PEAR_Downloader::downloadHttp() */ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null) { - if ($callback) { - call_user_func($callback, 'setup', array(&$ui)); + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; } - if (preg_match('!^http://([^/:?#]*)(:(\d+))?(/.*)!', $url, $matches)) { - list(,$host,,$port,$path) = $matches; - } - if (isset($this)) { - $config = &$this->config; - } else { - $config = &PEAR_Config::singleton(); - } - $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; - if ($proxy = parse_url($config->get('http_proxy'))) { - $proxy_host = @$proxy['host']; - $proxy_port = @$proxy['port']; - $proxy_user = @$proxy['user']; - $proxy_pass = @$proxy['pass']; - - if ($proxy_port == '') { - $proxy_port = 8080; - } - if ($callback) { - call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); - } - } - if (empty($port)) { - $port = 80; - } - if ($proxy_host != '') { - $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); - if (!$fp) { - if ($callback) { - call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, - $errno, $errstr)); - } - return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); - } - $request = "GET $url HTTP/1.0\r\n"; - } else { - $fp = @fsockopen($host, $port, $errno, $errstr); - if (!$fp) { - if ($callback) { - call_user_func($callback, 'connfailed', array($host, $port, - $errno, $errstr)); - } - return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); - } - $request = "GET $path HTTP/1.0\r\n"; - } - $request .= "Host: $host:$port\r\n". - "User-Agent: PHP/".PHP_VERSION."\r\n"; - if ($proxy_host != '' && $proxy_user != '') { - $request .= 'Proxy-Authorization: Basic ' . - base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; - } - $request .= "\r\n"; - fwrite($fp, $request); - $headers = array(); - while (trim($line = fgets($fp, 1024))) { - if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) { - $headers[strtolower($matches[1])] = trim($matches[2]); - } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { - if ($matches[1] != 200) { - return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)"); - } - } - } - if (isset($headers['content-disposition']) && - preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/', $headers['content-disposition'], $matches)) { - $save_as = basename($matches[1]); - } else { - $save_as = basename($url); - } - if ($callback) { - $tmp = call_user_func($callback, 'saveas', $save_as); - if ($tmp) { - $save_as = $tmp; - } - } - $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; - if (!$wp = @fopen($dest_file, 'wb')) { - fclose($fp); - if ($callback) { - call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); - } - return PEAR::raiseError("could not open $dest_file for writing"); - } - if (isset($headers['content-length'])) { - $length = $headers['content-length']; - } else { - $length = -1; - } - $bytes = 0; - if ($callback) { - call_user_func($callback, 'start', array(basename($dest_file), $length)); - } - while ($data = @fread($fp, 1024)) { - $bytes += strlen($data); - if ($callback) { - call_user_func($callback, 'bytesread', $bytes); - } - if (!@fwrite($wp, $data)) { - fclose($fp); - if ($callback) { - call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); - } - return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); - } - } - fclose($fp); - fclose($wp); - if ($callback) { - call_user_func($callback, 'done', $bytes); - } - return $dest_file; + return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback); } - - // }}} - // {{{ sortPkgDeps() - - /** - * Sort a list of arrays of array(downloaded packagefilename) by dependency. - * - * It also removes duplicate dependencies - * @param array - * @param boolean Sort packages in reverse order if true - * @return array array of array(packagefilename, package.xml contents) - */ - function sortPkgDeps(&$packages, $uninstall = false) - { - $ret = array(); - if ($uninstall) { - foreach($packages as $packageinfo) { - $ret[] = array('info' => $packageinfo); - } - } else { - foreach($packages as $packagefile) { - if (!is_array($packagefile)) { - $ret[] = array('file' => $packagefile, - 'info' => $a = $this->infoFromAny($packagefile), - 'pkg' => $a['package']); - } else { - $ret[] = $packagefile; - } - } - } - $checkdupes = array(); - $newret = array(); - foreach($ret as $i => $p) { - if (!isset($checkdupes[$p['info']['package']])) { - $checkdupes[$p['info']['package']][] = $i; - $newret[] = $p; - } - } - $this->_packageSortTree = $this->_getPkgDepTree($newret); - - $func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps'; - usort($newret, array(&$this, $func)); - $this->_packageSortTree = null; - $packages = $newret; - } - - // }}} - // {{{ _sortPkgDeps() - - /** - * Compare two package's package.xml, and sort - * so that dependencies are installed first - * - * This is a crude compare, real dependency checking is done on install. - * The only purpose this serves is to make the command-line - * order-independent (you can list a dependent package first, and - * installation occurs in the order required) - * @access private - */ - function _sortPkgDeps($p1, $p2) - { - $p1name = $p1['info']['package']; - $p2name = $p2['info']['package']; - $p1deps = $this->_getPkgDeps($p1); - $p2deps = $this->_getPkgDeps($p2); - if (!count($p1deps) && !count($p2deps)) { - return 0; // order makes no difference - } - if (!count($p1deps)) { - return -1; // package 2 has dependencies, package 1 doesn't - } - if (!count($p2deps)) { - return 1; // package 1 has dependencies, package 2 doesn't - } - // both have dependencies - if (in_array($p1name, $p2deps)) { - return -1; // put package 1 first: package 2 depends on package 1 - } - if (in_array($p2name, $p1deps)) { - return 1; // put package 2 first: package 1 depends on package 2 - } - if ($this->_removedDependency($p1name, $p2name)) { - return -1; // put package 1 first: package 2 depends on packages that depend on package 1 - } - if ($this->_removedDependency($p2name, $p1name)) { - return 1; // put package 2 first: package 1 depends on packages that depend on package 2 - } - // doesn't really matter if neither depends on the other - return 0; - } - - // }}} - // {{{ _sortPkgDepsRev() - - /** - * Compare two package's package.xml, and sort - * so that dependencies are uninstalled last - * - * This is a crude compare, real dependency checking is done on uninstall. - * The only purpose this serves is to make the command-line - * order-independent (you can list a dependency first, and - * uninstallation occurs in the order required) - * @access private - */ - function _sortPkgDepsRev($p1, $p2) - { - $p1name = $p1['info']['package']; - $p2name = $p2['info']['package']; - $p1deps = $this->_getRevPkgDeps($p1); - $p2deps = $this->_getRevPkgDeps($p2); - if (!count($p1deps) && !count($p2deps)) { - return 0; // order makes no difference - } - if (!count($p1deps)) { - return 1; // package 2 has dependencies, package 1 doesn't - } - if (!count($p2deps)) { - return -1; // package 2 has dependencies, package 1 doesn't - } - // both have dependencies - if (in_array($p1name, $p2deps)) { - return 1; // put package 1 last - } - if (in_array($p2name, $p1deps)) { - return -1; // put package 2 last - } - if ($this->_removedDependency($p1name, $p2name)) { - return 1; // put package 1 last: package 2 depends on packages that depend on package 1 - } - if ($this->_removedDependency($p2name, $p1name)) { - return -1; // put package 2 last: package 1 depends on packages that depend on package 2 - } - // doesn't really matter if neither depends on the other - return 0; - } - - // }}} - // {{{ _getPkgDeps() - - /** - * get an array of package dependency names - * @param array - * @return array - * @access private - */ - function _getPkgDeps($p) - { - if (!isset($p['info']['releases'])) { - return $this->_getRevPkgDeps($p); - } - $rel = array_shift($p['info']['releases']); - if (!isset($rel['deps'])) { - return array(); - } - $ret = array(); - foreach($rel['deps'] as $dep) { - if ($dep['type'] == 'pkg') { - $ret[] = $dep['name']; - } - } - return $ret; - } - - // }}} - // {{{ _getPkgDeps() - - /** - * get an array representation of the package dependency tree - * @return array - * @access private - */ - function _getPkgDepTree($packages) - { - $tree = array(); - foreach ($packages as $p) { - $package = $p['info']['package']; - $deps = $this->_getPkgDeps($p); - $tree[$package] = $deps; - } - return $tree; - } - - // }}} - // {{{ _removedDependency($p1, $p2) - - /** - * get an array of package dependency names for uninstall - * @param string package 1 name - * @param string package 2 name - * @return bool - * @access private - */ - function _removedDependency($p1, $p2) - { - if (empty($this->_packageSortTree[$p2])) { - return false; - } - if (!in_array($p1, $this->_packageSortTree[$p2])) { - foreach ($this->_packageSortTree[$p2] as $potential) { - if ($this->_removedDependency($p1, $potential)) { - return true; - } - } - return false; - } - return true; - } - - // }}} - // {{{ _getRevPkgDeps() - - /** - * get an array of package dependency names for uninstall - * @param array - * @return array - * @access private - */ - function _getRevPkgDeps($p) - { - if (!isset($p['info']['release_deps'])) { - return array(); - } - $ret = array(); - foreach($p['info']['release_deps'] as $dep) { - if ($dep['type'] == 'pkg') { - $ret[] = $dep['name']; - } - } - return $ret; - } - - // }}} } -?> +require_once 'PEAR/Config.php'; +require_once 'PEAR/PackageFile.php'; \ No newline at end of file diff --git a/3rdparty/PEAR/Config.php b/3rdparty/PEAR/Config.php index ba64173525..86a7db3f32 100644 --- a/3rdparty/PEAR/Config.php +++ b/3rdparty/PEAR/Config.php @@ -1,24 +1,26 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Config.php,v 1.52 2004/01/08 17:33:12 sniper Exp $ +/** + * PEAR_Config, customized configuration handling for the PEAR Installer + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * Required for error handling + */ require_once 'PEAR.php'; +require_once 'PEAR/Registry.php'; +require_once 'PEAR/Installer/Role.php'; require_once 'System.php'; /** @@ -38,6 +40,18 @@ if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) { // PHP_ prefix is for some security, PHP protects environment // variables starting with PHP_*. +// default channel and preferred mirror is based on whether we are invoked through +// the "pear" or the "pecl" command +if (!defined('PEAR_RUNTYPE')) { + define('PEAR_RUNTYPE', 'pear'); +} + +if (PEAR_RUNTYPE == 'pear') { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net'); +} else { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net'); +} + if (getenv('PHP_PEAR_SYSCONF_DIR')) { define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR')); } elseif (getenv('SystemRoot')) { @@ -66,9 +80,8 @@ if (getenv('PHP_PEAR_HTTP_PROXY')) { if (getenv('PHP_PEAR_INSTALL_DIR')) { define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR')); } else { - if (@is_dir($PEAR_INSTALL_DIR)) { - define('PEAR_CONFIG_DEFAULT_PHP_DIR', - $PEAR_INSTALL_DIR); + if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); } else { define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); } @@ -80,7 +93,8 @@ if (getenv('PHP_PEAR_EXTENSION_DIR')) { } else { if (ini_get('extension_dir')) { define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir')); - } elseif (defined('PEAR_EXTENSION_DIR') && @is_dir(PEAR_EXTENSION_DIR)) { + } elseif (defined('PEAR_EXTENSION_DIR') && + file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) { define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR); } elseif (defined('PHP_EXTENSION_DIR')) { define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR); @@ -112,6 +126,22 @@ if (getenv('PHP_PEAR_DATA_DIR')) { $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data'); } +// Default for cfg_dir +if (getenv('PHP_PEAR_CFG_DIR')) { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg'); +} + +// Default for www_dir +if (getenv('PHP_PEAR_WWW_DIR')) { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www'); +} + // Default for test_dir if (getenv('PHP_PEAR_TEST_DIR')) { define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR')); @@ -120,6 +150,15 @@ if (getenv('PHP_PEAR_TEST_DIR')) { $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests'); } +// Default for temp_dir +if (getenv('PHP_PEAR_TEMP_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'temp'); +} + // Default for cache_dir if (getenv('PHP_PEAR_CACHE_DIR')) { define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR')); @@ -129,6 +168,15 @@ if (getenv('PHP_PEAR_CACHE_DIR')) { DIRECTORY_SEPARATOR . 'cache'); } +// Default for download_dir +if (getenv('PHP_PEAR_DOWNLOAD_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'download'); +} + // Default for php_bin if (getenv('PHP_PEAR_PHP_BIN')) { define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN')); @@ -192,11 +240,18 @@ if (getenv('PHP_PEAR_SIG_KEYDIR')) { /** * This is a class for storing configuration data, keeping track of * which are system-defined, user-defined or defaulted. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Config extends PEAR { - // {{{ properties - /** * Array of config files used. * @@ -227,6 +282,62 @@ class PEAR_Config extends PEAR 'default' => array(), ); + /** + * Configuration values that can be set for a channel + * + * All other configuration values can only have a global value + * @var array + * @access private + */ + var $_channelConfigInfo = array( + 'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir', + 'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username', + 'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini' + ); + + /** + * Channels that can be accessed + * @see setChannels() + * @var array + * @access private + */ + var $_channels = array('pear.php.net', 'pecl.php.net', '__uri'); + + /** + * This variable is used to control the directory values returned + * @see setInstallRoot(); + * @var string|false + * @access private + */ + var $_installRoot = false; + + /** + * If requested, this will always refer to the registry + * contained in php_dir + * @var PEAR_Registry + */ + var $_registry = array(); + + /** + * @var array + * @access private + */ + var $_regInitialized = array(); + + /** + * @var bool + * @access private + */ + var $_noRegistry = false; + + /** + * amount of errors found while parsing config + * @var integer + * @access private + */ + var $_errorsFound = 0; + var $_lastError = null; + /** * Information about the configuration data. Stores the type, * default value and a documentation string for each configuration @@ -235,12 +346,41 @@ class PEAR_Config extends PEAR * @var array layer => array(infotype => value, ...) */ var $configuration_info = array( + // Channels/Internet Access + 'default_channel' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default channel to use for all non explicit commands', + 'prompt' => 'Default Channel', + 'group' => 'Internet Access', + ), + 'preferred_mirror' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default server or mirror to use for channel actions', + 'prompt' => 'Default Channel Mirror', + 'group' => 'Internet Access', + ), + 'remote_config' => array( + 'type' => 'password', + 'default' => '', + 'doc' => 'ftp url of remote configuration file to use for synchronized install', + 'prompt' => 'Remote Configuration File', + 'group' => 'Internet Access', + ), + 'auto_discover' => array( + 'type' => 'integer', + 'default' => 0, + 'doc' => 'whether to automatically discover new channels', + 'prompt' => 'Auto-discover new Channels', + 'group' => 'Internet Access', + ), // Internet Access 'master_server' => array( 'type' => 'string', 'default' => 'pear.php.net', - 'doc' => 'name of the main PEAR server', - 'prompt' => 'PEAR server', + 'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]', + 'prompt' => 'PEAR server [DEPRECATED]', 'group' => 'Internet Access', ), 'http_proxy' => array( @@ -286,6 +426,20 @@ class PEAR_Config extends PEAR 'prompt' => 'PEAR data directory', 'group' => 'File Locations (Advanced)', ), + 'cfg_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CFG_DIR, + 'doc' => 'directory where modifiable configuration files are installed', + 'prompt' => 'PEAR configuration file directory', + 'group' => 'File Locations (Advanced)', + ), + 'www_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_WWW_DIR, + 'doc' => 'directory where www frontend files (html/js) are installed', + 'prompt' => 'PEAR www files directory', + 'group' => 'File Locations (Advanced)', + ), 'test_dir' => array( 'type' => 'directory', 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR, @@ -296,10 +450,24 @@ class PEAR_Config extends PEAR 'cache_dir' => array( 'type' => 'directory', 'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR, - 'doc' => 'directory which is used for XMLRPC cache', + 'doc' => 'directory which is used for web service cache', 'prompt' => 'PEAR Installer cache directory', 'group' => 'File Locations (Advanced)', ), + 'temp_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR, + 'doc' => 'directory which is used for all temp files', + 'prompt' => 'PEAR Installer temp directory', + 'group' => 'File Locations (Advanced)', + ), + 'download_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR, + 'doc' => 'directory which is used for all downloaded files', + 'prompt' => 'PEAR Installer download directory', + 'group' => 'File Locations (Advanced)', + ), 'php_bin' => array( 'type' => 'file', 'default' => PEAR_CONFIG_DEFAULT_PHP_BIN, @@ -307,6 +475,27 @@ class PEAR_Config extends PEAR 'prompt' => 'PHP CLI/CGI binary', 'group' => 'File Locations (Advanced)', ), + 'php_prefix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-prefix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_suffix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-suffix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_ini' => array( + 'type' => 'file', + 'default' => '', + 'doc' => 'location of php.ini in which to enable PECL extensions on install', + 'prompt' => 'php.ini location', + 'group' => 'File Locations (Advanced)', + ), // Maintainers 'username' => array( 'type' => 'string', @@ -380,31 +569,34 @@ class PEAR_Config extends PEAR 'group' => 'Maintainers', ), 'sig_keydir' => array( - 'type' => 'string', + 'type' => 'directory', 'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR, - 'doc' => 'which package signature mechanism to use', + 'doc' => 'directory where signature keys are located', 'prompt' => 'Signature Key Directory', 'group' => 'Maintainers', ), + // __channels is reserved - used for channel-specific configuration ); - // }}} - - // {{{ PEAR_Config([file], [defaults_file]) - /** * Constructor. * - * @param string (optional) file to read user-defined options from - * @param string (optional) file to read system-wide defaults from + * @param string file to read user-defined options from + * @param string file to read system-wide defaults from + * @param bool determines whether a registry object "follows" + * the value of php_dir (is automatically created + * and moved when php_dir is changed) + * @param bool if true, fails if configuration files cannot be loaded * * @access public * * @see PEAR_Config::singleton */ - function PEAR_Config($user_file = '', $system_file = '') + function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, + $strict = true) { $this->PEAR(); + PEAR_Installer_Role::initializeConfig($this); $sl = DIRECTORY_SEPARATOR; if (empty($user_file)) { if (OS_WINDOWS) { @@ -413,30 +605,73 @@ class PEAR_Config extends PEAR $user_file = getenv('HOME') . $sl . '.pearrc'; } } + if (empty($system_file)) { + $system_file = PEAR_CONFIG_SYSCONFDIR . $sl; if (OS_WINDOWS) { - $system_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini'; + $system_file .= 'pearsys.ini'; } else { - $system_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf'; + $system_file .= 'pear.conf'; } } + $this->layers = array_keys($this->configuration); - $this->files['user'] = $user_file; + $this->files['user'] = $user_file; $this->files['system'] = $system_file; if ($user_file && file_exists($user_file)) { - $this->readConfigFile($user_file); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->readConfigFile($user_file, 'user', $strict); + $this->popErrorHandling(); + if ($this->_errorsFound > 0) { + return; + } } - if ($system_file && file_exists($system_file)) { - $this->mergeConfigFile($system_file, false, 'system'); + + if ($system_file && @file_exists($system_file)) { + $this->mergeConfigFile($system_file, false, 'system', $strict); + if ($this->_errorsFound > 0) { + return; + } + } + + if (!$ftp_file) { + $ftp_file = $this->get('remote_config'); + } + + if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) { + $this->readFTPConfigFile($ftp_file); + } + foreach ($this->configuration_info as $key => $info) { $this->configuration['default'][$key] = $info['default']; } + + $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']); + $this->_registry['default']->setConfig($this, false); + $this->_regInitialized['default'] = false; //$GLOBALS['_PEAR_Config_instance'] = &$this; } - // }}} - // {{{ singleton([file], [defaults_file]) + /** + * Return the default locations of user and system configuration files + * @static + */ + function getDefaultConfigFiles() + { + $sl = DIRECTORY_SEPARATOR; + if (OS_WINDOWS) { + return array( + 'user' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini' + ); + } + + return array( + 'user' => getenv('HOME') . $sl . '.pearrc', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf' + ); + } /** * Static singleton method. If you want to keep only one instance @@ -453,113 +688,279 @@ class PEAR_Config extends PEAR * * @see PEAR_Config::PEAR_Config */ - function &singleton($user_file = '', $system_file = '') + function &singleton($user_file = '', $system_file = '', $strict = true) { if (is_object($GLOBALS['_PEAR_Config_instance'])) { return $GLOBALS['_PEAR_Config_instance']; } - $GLOBALS['_PEAR_Config_instance'] = - &new PEAR_Config($user_file, $system_file); + + $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict); + if ($t_conf->_errorsFound > 0) { + return $t_conf->lastError; + } + + $GLOBALS['_PEAR_Config_instance'] = &$t_conf; return $GLOBALS['_PEAR_Config_instance']; } - // }}} - // {{{ readConfigFile([file], [layer]) + /** + * Determine whether any configuration files have been detected, and whether a + * registry object can be retrieved from this configuration. + * @return bool + * @since PEAR 1.4.0a1 + */ + function validConfiguration() + { + if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) { + return true; + } + + return false; + } /** * Reads configuration data from a file. All existing values in * the config layer are discarded and replaced with data from the * file. - * - * @param string (optional) file to read from, if NULL or not - * specified, the last-used file for the same layer (second param) - * is used - * - * @param string (optional) config layer to insert data into - * ('user' or 'system') - * + * @param string file to read from, if NULL or not specified, the + * last-used file for the same layer (second param) is used + * @param string config layer to insert data into ('user' or 'system') * @return bool TRUE on success or a PEAR error on failure - * - * @access public */ - function readConfigFile($file = null, $layer = 'user') + function readConfigFile($file = null, $layer = 'user', $strict = true) { if (empty($this->files[$layer])) { - return $this->raiseError("unknown config file type `$layer'"); + return $this->raiseError("unknown config layer `$layer'"); } + if ($file === null) { $file = $this->files[$layer]; } + $data = $this->_readConfigDataFrom($file); if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + return $data; } + + $this->files[$layer] = $file; $this->_decodeInput($data); $this->configuration[$layer] = $data; + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } return true; } - // }}} - // {{{ mergeConfigFile(file, [override], [layer]) + /** + * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini + * @return true|PEAR_Error + */ + function readFTPConfigFile($path) + { + do { // poor man's try + if (!class_exists('PEAR_FTP')) { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (PEAR_Common::isIncludeable('PEAR/FTP.php')) { + require_once 'PEAR/FTP.php'; + } + } + + if (!class_exists('PEAR_FTP')) { + return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config'); + } + + $this->_ftp = &new PEAR_FTP; + $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN); + $e = $this->_ftp->init($path); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + $tmp = System::mktemp('-d'); + PEAR_Common::addTempFile($tmp); + $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR . + 'pear.ini', false, FTP_BINARY); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini'); + $this->_ftp->disconnect(); + $this->_ftp->popErrorHandling(); + $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini'; + $e = $this->readConfigFile(null, 'ftp'); + if (PEAR::isError($e)) { + return $e; + } + + $fail = array(); + foreach ($this->configuration_info as $key => $val) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + // any directory configs must be set for this to work + if (!isset($this->configuration['ftp'][$key])) { + $fail[] = $key; + } + } + } + + if (!count($fail)) { + return true; + } + + $fail = '"' . implode('", "', $fail) . '"'; + unset($this->files['ftp']); + unset($this->configuration['ftp']); + return PEAR::raiseError('ERROR: Ftp configuration file must set all ' . + 'directory configuration variables. These variables were not set: ' . + $fail); + } while (false); // poor man's catch + unset($this->files['ftp']); + return PEAR::raiseError('no remote host specified'); + } + + /** + * Reads the existing configurations and creates the _channels array from it + */ + function _setupChannels() + { + $set = array_flip(array_values($this->_channels)); + foreach ($this->configuration as $layer => $data) { + $i = 1000; + if (isset($data['__channels']) && is_array($data['__channels'])) { + foreach ($data['__channels'] as $channel => $info) { + $set[$channel] = $i++; + } + } + } + $this->_channels = array_values(array_flip($set)); + $this->setChannels($this->_channels); + } + + function deleteChannel($channel) + { + $ch = strtolower($channel); + foreach ($this->configuration as $layer => $data) { + if (isset($data['__channels']) && isset($data['__channels'][$ch])) { + unset($this->configuration[$layer]['__channels'][$ch]); + } + } + + $this->_channels = array_flip($this->_channels); + unset($this->_channels[$ch]); + $this->_channels = array_flip($this->_channels); + } /** * Merges data into a config layer from a file. Does the same * thing as readConfigFile, except it does not replace all * existing values in the config layer. - * * @param string file to read from - * - * @param bool (optional) whether to overwrite existing data - * (default TRUE) - * - * @param string config layer to insert data into ('user' or - * 'system') - * + * @param bool whether to overwrite existing data (default TRUE) + * @param string config layer to insert data into ('user' or 'system') + * @param string if true, errors are returned if file opening fails * @return bool TRUE on success or a PEAR error on failure - * - * @access public. */ - function mergeConfigFile($file, $override = true, $layer = 'user') + function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true) { if (empty($this->files[$layer])) { - return $this->raiseError("unknown config file type `$layer'"); + return $this->raiseError("unknown config layer `$layer'"); } + if ($file === null) { $file = $this->files[$layer]; } + $data = $this->_readConfigDataFrom($file); if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + return $data; } + $this->_decodeInput($data); if ($override) { - $this->configuration[$layer] = array_merge($this->configuration[$layer], $data); + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data); } else { - $this->configuration[$layer] = array_merge($data, $this->configuration[$layer]); + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]); + } + + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); } return true; } - // }}} - // {{{ writeConfigFile([file], [layer]) + /** + * @param array + * @param array + * @return array + * @static + */ + function arrayMergeRecursive($arr2, $arr1) + { + $ret = array(); + foreach ($arr2 as $key => $data) { + if (!isset($arr1[$key])) { + $ret[$key] = $data; + unset($arr1[$key]); + continue; + } + if (is_array($data)) { + if (!is_array($arr1[$key])) { + $ret[$key] = $arr1[$key]; + unset($arr1[$key]); + continue; + } + $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]); + unset($arr1[$key]); + } + } + + return array_merge($ret, $arr1); + } /** * Writes data into a config layer from a file. * - * @param string file to read from - * - * @param bool (optional) whether to overwrite existing data - * (default TRUE) - * + * @param string|null file to read from, or null for default * @param string config layer to insert data into ('user' or - * 'system') - * + * 'system') + * @param string|null data to write to config file or null for internal data [DEPRECATED] * @return bool TRUE on success or a PEAR error on failure - * - * @access public. */ function writeConfigFile($file = null, $layer = 'user', $data = null) { + $this->_lazyChannelSetup($layer); if ($layer == 'both' || $layer == 'all') { foreach ($this->files as $type => $file) { $err = $this->writeConfigFile($file, $type, $data); @@ -569,97 +970,215 @@ class PEAR_Config extends PEAR } return true; } + if (empty($this->files[$layer])) { return $this->raiseError("unknown config file type `$layer'"); } + if ($file === null) { $file = $this->files[$layer]; } + $data = ($data === null) ? $this->configuration[$layer] : $data; $this->_encodeOutput($data); $opt = array('-p', dirname($file)); if (!@System::mkDir($opt)) { return $this->raiseError("could not create directory: " . dirname($file)); } - if (@is_file($file) && !@is_writeable($file)) { + + if (file_exists($file) && is_file($file) && !is_writeable($file)) { return $this->raiseError("no write access to $file!"); } + $fp = @fopen($file, "w"); if (!$fp) { - return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed"); + return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)"); } + $contents = "#PEAR_Config 0.9\n" . serialize($data); if (!@fwrite($fp, $contents)) { - return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed"); + return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)"); } return true; } - // }}} - // {{{ _readConfigDataFrom(file) - /** * Reads configuration data from a file and returns the parsed data * in an array. * * @param string file to read from - * * @return array configuration data or a PEAR error on failure - * * @access private */ function _readConfigDataFrom($file) { - $fp = @fopen($file, "r"); + $fp = false; + if (file_exists($file)) { + $fp = @fopen($file, "r"); + } + if (!$fp) { return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed"); } + $size = filesize($file); $rt = get_magic_quotes_runtime(); set_magic_quotes_runtime(0); - $contents = fread($fp, $size); - set_magic_quotes_runtime($rt); fclose($fp); - $version = '0.1'; + $contents = file_get_contents($file); + if (empty($contents)) { + return $this->raiseError('Configuration file "' . $file . '" is empty'); + } + + set_magic_quotes_runtime($rt); + + $version = false; if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) { $version = $matches[1]; $contents = substr($contents, strlen($matches[0])); + } else { + // Museum config file + if (substr($contents,0,2) == 'a:') { + $version = '0.1'; + } } - if (version_compare("$version", '1', '<')) { + + if ($version && version_compare("$version", '1', '<')) { + // no '@', it is possible that unserialize + // raises a notice but it seems to block IO to + // STDOUT if a '@' is used and a notice is raise $data = unserialize($contents); + + if (!is_array($data) && !$data) { + if ($contents == serialize(false)) { + $data = array(); + } else { + $err = $this->raiseError("PEAR_Config: bad data in $file"); + return $err; + } + } if (!is_array($data)) { if (strlen(trim($contents)) > 0) { $error = "PEAR_Config: bad data in $file"; -// if (isset($this)) { - return $this->raiseError($error); -// } else { -// return PEAR::raiseError($error); - } else { - $data = array(); + $err = $this->raiseError($error); + return $err; } + + $data = array(); } // add parsing of newer formats here... } else { - return $this->raiseError("$file: unknown version `$version'"); + $err = $this->raiseError("$file: unknown version `$version'"); + return $err; } + return $data; } - // }}} - // {{{ getConfFile(layer) /** * Gets the file used for storing the config for a layer * * @param string $layer 'user' or 'system' */ - function getConfFile($layer) { return $this->files[$layer]; } - // }}} - // {{{ _encodeOutput(&data) + /** + * @param string Configuration class name, used for detecting duplicate calls + * @param array information on a role as parsed from its xml file + * @return true|PEAR_Error + * @access private + */ + function _addConfigVars($class, $vars) + { + static $called = array(); + if (isset($called[$class])) { + return; + } + + $called[$class] = 1; + if (count($vars) > 3) { + return $this->raiseError('Roles can only define 3 new config variables or less'); + } + + foreach ($vars as $name => $var) { + if (!is_array($var)) { + return $this->raiseError('Configuration information must be an array'); + } + + if (!isset($var['type'])) { + return $this->raiseError('Configuration information must contain a type'); + } elseif (!in_array($var['type'], + array('string', 'mask', 'password', 'directory', 'file', 'set'))) { + return $this->raiseError( + 'Configuration type must be one of directory, file, string, ' . + 'mask, set, or password'); + } + if (!isset($var['default'])) { + return $this->raiseError( + 'Configuration information must contain a default value ("default" index)'); + } + + if (is_array($var['default'])) { + $real_default = ''; + foreach ($var['default'] as $config_var => $val) { + if (strpos($config_var, 'text') === 0) { + $real_default .= $val; + } elseif (strpos($config_var, 'constant') === 0) { + if (!defined($val)) { + return $this->raiseError( + 'Unknown constant "' . $val . '" requested in ' . + 'default value for configuration variable "' . + $name . '"'); + } + + $real_default .= constant($val); + } elseif (isset($this->configuration_info[$config_var])) { + $real_default .= + $this->configuration_info[$config_var]['default']; + } else { + return $this->raiseError( + 'Unknown request for "' . $config_var . '" value in ' . + 'default value for configuration variable "' . + $name . '"'); + } + } + $var['default'] = $real_default; + } + + if ($var['type'] == 'integer') { + $var['default'] = (integer) $var['default']; + } + + if (!isset($var['doc'])) { + return $this->raiseError( + 'Configuration information must contain a summary ("doc" index)'); + } + + if (!isset($var['prompt'])) { + return $this->raiseError( + 'Configuration information must contain a simple prompt ("prompt" index)'); + } + + if (!isset($var['group'])) { + return $this->raiseError( + 'Configuration information must contain a simple group ("group" index)'); + } + + if (isset($this->configuration_info[$name])) { + return $this->raiseError('Configuration variable "' . $name . + '" already exists'); + } + + $this->configuration_info[$name] = $var; + // fix bug #7351: setting custom config variable in a channel fails + $this->_channelConfigInfo[] = $name; + } + + return true; + } /** * Encodes/scrambles configuration data before writing to files. @@ -667,17 +1186,22 @@ class PEAR_Config extends PEAR * that people spot cleartext passwords by accident. * * @param array (reference) array to encode values in - * * @return bool TRUE on success - * * @access private */ function _encodeOutput(&$data) { foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_encodeOutput($data['__channels'][$channel]); + } + } + if (!isset($this->configuration_info[$key])) { continue; } + $type = $this->configuration_info[$key]['type']; switch ($type) { // we base64-encode passwords so they are at least @@ -692,19 +1216,15 @@ class PEAR_Config extends PEAR } } } + return true; } - // }}} - // {{{ _decodeInput(&data) - /** * Decodes/unscrambles configuration data after reading from files. * * @param array (reference) array to encode values in - * * @return bool TRUE on success - * * @access private * * @see PEAR_Config::_encodeOutput @@ -714,10 +1234,18 @@ class PEAR_Config extends PEAR if (!is_array($data)) { return true; } + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_decodeInput($data['__channels'][$channel]); + } + } + if (!isset($this->configuration_info[$key])) { continue; } + $type = $this->configuration_info[$key]['type']; switch ($type) { case 'password': { @@ -730,38 +1258,202 @@ class PEAR_Config extends PEAR } } } + return true; } - // }}} - // {{{ get(key, [layer]) + /** + * Retrieve the default channel. + * + * On startup, channels are not initialized, so if the default channel is not + * pear.php.net, then initialize the config. + * @param string registry layer + * @return string|false + */ + function getDefaultChannel($layer = null) + { + $ret = false; + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + break; + } + } + } elseif (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + } + + if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') { + $ret = 'pecl.php.net'; + } + + if ($ret) { + if ($ret != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + + return $ret; + } + + return PEAR_CONFIG_DEFAULT_CHANNEL; + } /** * Returns a configuration value, prioritizing layers as per the * layers property. * * @param string config key - * * @return mixed the config value, or NULL if not found - * * @access public */ - function get($key, $layer = null) + function get($key, $layer = null, $channel = false) { + if (!isset($this->configuration_info[$key])) { + return null; + } + + if ($key == '__channels') { + return null; + } + + if ($key == 'default_channel') { + return $this->getDefaultChannel($layer); + } + + if (!$channel) { + $channel = $this->getDefaultChannel(); + } elseif ($channel != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + $channel = strtolower($channel); + + $test = (in_array($key, $this->_channelConfigInfo)) ? + $this->_getChannelValue($key, $layer, $channel) : + null; + if ($test !== null) { + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + return $test; + } + if ($layer === null) { foreach ($this->layers as $layer) { if (isset($this->configuration[$layer][$key])) { - return $this->configuration[$layer][$key]; + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + return $test; } } } elseif (isset($this->configuration[$layer][$key])) { - return $this->configuration[$layer][$key]; + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + + return $test; } + return null; } - // }}} - // {{{ set(key, value, [layer]) + /** + * Returns a channel-specific configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access private + */ + function _getChannelValue($key, $layer, $channel) + { + if ($key == '__channels' || $channel == 'pear.php.net') { + return null; + } + + $ret = null; + if ($layer === null) { + foreach ($this->layers as $ilayer) { + if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$ilayer]['__channels'][$channel][$key]; + break; + } + } + } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$layer]['__channels'][$channel][$key]; + } + + if ($key != 'preferred_mirror') { + return $ret; + } + + + if ($ret !== null) { + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($ret) && $chan->getName() != $ret) { + return $channel; // mirror does not exist + } + } + + return $ret; + } + + if ($channel != $this->getDefaultChannel($layer)) { + return $channel; // we must use the channel name as the preferred mirror + // if the user has not chosen an alternate + } + + return $this->getDefaultChannel($layer); + } /** * Set a config value in a specific layer (defaults to 'user'). @@ -770,20 +1462,51 @@ class PEAR_Config extends PEAR * variable will be validated against its legal values. * * @param string config key - * * @param string config value - * * @param string (optional) config layer - * + * @param string channel to set this value for, or null for global value * @return bool TRUE on success, FALSE on failure - * - * @access public */ - function set($key, $value, $layer = 'user') + function set($key, $value, $layer = 'user', $channel = false) { - if (empty($this->configuration_info[$key])) { + if ($key == '__channels') { return false; } + + if (!isset($this->configuration[$layer])) { + return false; + } + + if ($key == 'default_channel') { + // can only set this value globally + $channel = 'pear.php.net'; + if ($value != 'pear.php.net') { + $this->_lazyChannelSetup($layer); + } + } + + if ($key == 'preferred_mirror') { + if ($channel == '__uri') { + return false; // can't set the __uri pseudo-channel's mirror + } + + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net'); + if (PEAR::isError($chan)) { + return false; + } + + if (!$chan->getMirror($value) && $chan->getName() != $value) { + return false; // mirror does not exist + } + } + } + + if (!isset($this->configuration_info[$key])) { + return false; + } + extract($this->configuration_info[$key]); switch ($type) { case 'integer': @@ -804,12 +1527,138 @@ class PEAR_Config extends PEAR break; } } + + if (!$channel) { + $channel = $this->get('default_channel', null, 'pear.php.net'); + } + + if (!in_array($channel, $this->_channels)) { + $this->_lazyChannelSetup($layer); + $reg = &$this->getRegistry($layer); + if ($reg) { + $channel = $reg->channelName($channel); + } + + if (!in_array($channel, $this->_channels)) { + return false; + } + } + + if ($channel != 'pear.php.net') { + if (in_array($key, $this->_channelConfigInfo)) { + $this->configuration[$layer]['__channels'][$channel][$key] = $value; + return true; + } + + return false; + } + + if ($key == 'default_channel') { + if (!isset($reg)) { + $reg = &$this->getRegistry($layer); + if (!$reg) { + $reg = &$this->getRegistry(); + } + } + + if ($reg) { + $value = $reg->channelName($value); + } + + if (!$value) { + return false; + } + } + $this->configuration[$layer][$key] = $value; + if ($key == 'php_dir' && !$this->_noRegistry) { + if (!isset($this->_registry[$layer]) || + $value != $this->_registry[$layer]->install_dir) { + $this->_registry[$layer] = &new PEAR_Registry($value); + $this->_regInitialized[$layer] = false; + $this->_registry[$layer]->setConfig($this, false); + } + } + return true; } - // }}} - // {{{ getType(key) + function _lazyChannelSetup($uselayer = false) + { + if ($this->_noRegistry) { + return; + } + + $merge = false; + foreach ($this->_registry as $layer => $p) { + if ($uselayer && $uselayer != $layer) { + continue; + } + + if (!$this->_regInitialized[$layer]) { + if ($layer == 'default' && isset($this->_registry['user']) || + isset($this->_registry['system'])) { + // only use the default registry if there are no alternatives + continue; + } + + if (!is_object($this->_registry[$layer])) { + if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + return; + } + } + + $this->setChannels($this->_registry[$layer]->listChannels(), $merge); + $this->_regInitialized[$layer] = true; + $merge = true; + } + } + } + + /** + * Set the list of channels. + * + * This should be set via a call to {@link PEAR_Registry::listChannels()} + * @param array + * @param bool + * @return bool success of operation + */ + function setChannels($channels, $merge = false) + { + if (!is_array($channels)) { + return false; + } + + if ($merge) { + $this->_channels = array_merge($this->_channels, $channels); + } else { + $this->_channels = $channels; + } + + foreach ($channels as $channel) { + $channel = strtolower($channel); + if ($channel == 'pear.php.net') { + continue; + } + + foreach ($this->layers as $layer) { + if (!isset($this->configuration[$layer]['__channels'])) { + $this->configuration[$layer]['__channels'] = array(); + } + if (!isset($this->configuration[$layer]['__channels'][$channel]) + || !is_array($this->configuration[$layer]['__channels'][$channel])) { + $this->configuration[$layer]['__channels'][$channel] = array(); + } + } + } + + return true; + } /** * Get the type of a config value. @@ -830,14 +1679,10 @@ class PEAR_Config extends PEAR return false; } - // }}} - // {{{ getDocs(key) - /** * Get the documentation for a config value. * * @param string config key - * * @return string documentation string * * @access public @@ -848,16 +1693,14 @@ class PEAR_Config extends PEAR if (isset($this->configuration_info[$key])) { return $this->configuration_info[$key]['doc']; } + return false; } - // }}} - // {{{ getPrompt(key) /** * Get the short documentation for a config value. * * @param string config key - * * @return string short documentation string * * @access public @@ -868,16 +1711,14 @@ class PEAR_Config extends PEAR if (isset($this->configuration_info[$key])) { return $this->configuration_info[$key]['prompt']; } + return false; } - // }}} - // {{{ getGroup(key) /** * Get the parameter group for a config key. * * @param string config key - * * @return string parameter group * * @access public @@ -888,12 +1729,10 @@ class PEAR_Config extends PEAR if (isset($this->configuration_info[$key])) { return $this->configuration_info[$key]['group']; } + return false; } - // }}} - // {{{ getGroups() - /** * Get the list of parameter groups. * @@ -908,17 +1747,14 @@ class PEAR_Config extends PEAR foreach ($this->configuration_info as $key => $info) { $tmp[$info['group']] = 1; } + return array_keys($tmp); } - // }}} - // {{{ getGroupKeys() - /** * Get the list of the parameters in a group. * * @param string $group parameter group - * * @return array list of parameters in $group * * @access public @@ -932,18 +1768,15 @@ class PEAR_Config extends PEAR $keys[] = $key; } } + return $keys; } - // }}} - // {{{ getSetValues(key) - /** * Get the list of allowed set values for a config value. Returns * NULL for config values that are not sets. * * @param string config key - * * @return array enumerated array of set values, or NULL if the * config key is unknown or not a set * @@ -961,13 +1794,12 @@ class PEAR_Config extends PEAR if (key($valid_set) === 0) { return $valid_set; } + return array_keys($valid_set); } - return false; - } - // }}} - // {{{ getKeys() + return null; + } /** * Get all the current config keys. @@ -980,44 +1812,56 @@ class PEAR_Config extends PEAR { $keys = array(); foreach ($this->layers as $layer) { - $keys = array_merge($keys, $this->configuration[$layer]); + $test = $this->configuration[$layer]; + if (isset($test['__channels'])) { + foreach ($test['__channels'] as $channel => $configs) { + $keys = array_merge($keys, $configs); + } + } + + unset($test['__channels']); + $keys = array_merge($keys, $test); + } return array_keys($keys); } - // }}} - // {{{ remove(key, [layer]) - /** * Remove the a config key from a specific config layer. * * @param string config key - * * @param string (optional) config layer - * + * @param string (optional) channel (defaults to default channel) * @return bool TRUE on success, FALSE on failure * * @access public */ - function remove($key, $layer = 'user') + function remove($key, $layer = 'user', $channel = null) { + if ($channel === null) { + $channel = $this->getDefaultChannel(); + } + + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + unset($this->configuration[$layer]['__channels'][$channel][$key]); + return true; + } + } + if (isset($this->configuration[$layer][$key])) { unset($this->configuration[$layer][$key]); return true; } + return false; } - // }}} - // {{{ removeLayer(layer) - /** * Temporarily remove an entire config layer. USE WITH CARE! * * @param string config key - * * @param string (optional) config layer - * * @return bool TRUE on success, FALSE on failure * * @access public @@ -1028,17 +1872,14 @@ class PEAR_Config extends PEAR $this->configuration[$layer] = array(); return true; } + return false; } - // }}} - // {{{ store([layer]) - /** * Stores configuration data in a layer. * * @param string config layer to store - * * @return bool TRUE on success, or PEAR error on failure * * @access public @@ -1048,75 +1889,47 @@ class PEAR_Config extends PEAR return $this->writeConfigFile(null, $layer, $data); } - // }}} - // {{{ toDefault(key) - - /** - * Unset the user-defined value of a config key, reverting the - * value to the system-defined one. - * - * @param string config key - * - * @return bool TRUE on success, FALSE on failure - * - * @access public - */ - function toDefault($key) - { - trigger_error("PEAR_Config::toDefault() deprecated, use PEAR_Config::remove() instead", E_USER_NOTICE); - return $this->remove($key, 'user'); - } - - // }}} - // {{{ definedBy(key) - /** * Tells what config layer that gets to define a key. * * @param string config key + * @param boolean return the defining channel * - * @return string the config layer, or an empty string if not found + * @return string|array the config layer, or an empty string if not found. + * + * if $returnchannel, the return is an array array('layer' => layername, + * 'channel' => channelname), or an empty string if not found * * @access public */ - function definedBy($key) + function definedBy($key, $returnchannel = false) { foreach ($this->layers as $layer) { + $channel = $this->getDefaultChannel(); + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => $channel); + } + return $layer; + } + } + if (isset($this->configuration[$layer][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => 'pear.php.net'); + } return $layer; } } + return ''; } - // }}} - // {{{ isDefaulted(key) - - /** - * Tells whether a config value has a system-defined value. - * - * @param string config key - * - * @return bool - * - * @access public - * - * @deprecated - */ - function isDefaulted($key) - { - trigger_error("PEAR_Config::isDefaulted() deprecated, use PEAR_Config::definedBy() instead", E_USER_NOTICE); - return $this->definedBy($key) == 'system'; - } - - // }}} - // {{{ isDefined(key) - /** * Tells whether a given key exists as a config value. * * @param string config key - * * @return bool whether exists in this object * * @access public @@ -1128,17 +1941,14 @@ class PEAR_Config extends PEAR return true; } } + return false; } - // }}} - // {{{ isDefinedLayer(key) - /** * Tells whether a given config layer exists. * * @param string config layer - * * @return bool whether exists in this object * * @access public @@ -1148,9 +1958,6 @@ class PEAR_Config extends PEAR return isset($this->configuration[$layer]); } - // }}} - // {{{ getLayers() - /** * Returns the layers defined (except the 'default' one) * @@ -1163,7 +1970,128 @@ class PEAR_Config extends PEAR return array_keys($cf); } - // }}} -} + function apiVersion() + { + return '1.1'; + } -?> + /** + * @return PEAR_Registry + */ + function &getRegistry($use = null) + { + $layer = $use === null ? 'user' : $use; + if (isset($this->_registry[$layer])) { + return $this->_registry[$layer]; + } elseif ($use === null && isset($this->_registry['system'])) { + return $this->_registry['system']; + } elseif ($use === null && isset($this->_registry['default'])) { + return $this->_registry['default']; + } elseif ($use) { + $a = false; + return $a; + } + + // only go here if null was passed in + echo "CRITICAL ERROR: Registry could not be initialized from any value"; + exit(1); + } + + /** + * This is to allow customization like the use of installroot + * @param PEAR_Registry + * @return bool + */ + function setRegistry(&$reg, $layer = 'user') + { + if ($this->_noRegistry) { + return false; + } + + if (!in_array($layer, array('user', 'system'))) { + return false; + } + + $this->_registry[$layer] = &$reg; + if (is_object($reg)) { + $this->_registry[$layer]->setConfig($this, false); + } + + return true; + } + + function noRegistry() + { + $this->_noRegistry = true; + } + + /** + * @return PEAR_REST + */ + function &getREST($version, $options = array()) + { + $version = str_replace('.', '', $version); + if (!class_exists($class = 'PEAR_REST_' . $version)) { + require_once 'PEAR/REST/' . $version . '.php'; + } + + $remote = &new $class($this, $options); + return $remote; + } + + /** + * The ftp server is set in {@link readFTPConfigFile()}. It exists only if a + * remote configuration file has been specified + * @return PEAR_FTP|false + */ + function &getFTP() + { + if (isset($this->_ftp)) { + return $this->_ftp; + } + + $a = false; + return $a; + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string|false installation directory to prepend to all _dir variables, or false to + * disable + */ + function setInstallRoot($root) + { + if (substr($root, -1) == DIRECTORY_SEPARATOR) { + $root = substr($root, 0, -1); + } + $old = $this->_installRoot; + $this->_installRoot = $root; + if (($old != $root) && !$this->_noRegistry) { + foreach (array_keys($this->_registry) as $layer) { + if ($layer == 'ftp' || !isset($this->_registry[$layer])) { + continue; + } + $this->_registry[$layer] = + &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net')); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } + } + } +} diff --git a/3rdparty/PEAR/Dependency2.php b/3rdparty/PEAR/Dependency2.php new file mode 100644 index 0000000000..f3ddeb1cf1 --- /dev/null +++ b/3rdparty/PEAR/Dependency2.php @@ -0,0 +1,1358 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Dependency2.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Required for the PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; + +/** + * Dependency check for PEAR packages + * + * This class handles both version 1.0 and 2.0 dependencies + * WARNING: *any* changes to this class must be duplicated in the + * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc, + * or unit tests will not actually validate the changes + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Dependency2 +{ + /** + * One of the PEAR_VALIDATE_* states + * @see PEAR_VALIDATE_NORMAL + * @var integer + */ + var $_state; + + /** + * Command-line options to install/upgrade/uninstall commands + * @param array + */ + var $_options; + + /** + * @var OS_Guess + */ + var $_os; + + /** + * @var PEAR_Registry + */ + var $_registry; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencydb; + + /** + * Output of PEAR_Registry::parsedPackageName() + * @var array + */ + var $_currentPackage; + + /** + * @param PEAR_Config + * @param array installation options + * @param array format of PEAR_Registry::parsedPackageName() + * @param int installation state (one of PEAR_VALIDATE_*) + */ + function PEAR_Dependency2(&$config, $installoptions, $package, + $state = PEAR_VALIDATE_INSTALLING) + { + $this->_config = &$config; + if (!class_exists('PEAR_DependencyDB')) { + require_once 'PEAR/DependencyDB.php'; + } + + if (isset($installoptions['packagingroot'])) { + // make sure depdb is in the right location + $config->setInstallRoot($installoptions['packagingroot']); + } + + $this->_registry = &$config->getRegistry(); + $this->_dependencydb = &PEAR_DependencyDB::singleton($config); + if (isset($installoptions['packagingroot'])) { + $config->setInstallRoot(false); + } + + $this->_options = $installoptions; + $this->_state = $state; + if (!class_exists('OS_Guess')) { + require_once 'OS/Guess.php'; + } + + $this->_os = new OS_Guess; + $this->_currentPackage = $package; + } + + function _getExtraString($dep) + { + $extra = ' ('; + if (isset($dep['uri'])) { + return ''; + } + + if (isset($dep['recommended'])) { + $extra .= 'recommended version ' . $dep['recommended']; + } else { + if (isset($dep['min'])) { + $extra .= 'version >= ' . $dep['min']; + } + + if (isset($dep['max'])) { + if ($extra != ' (') { + $extra .= ', '; + } + $extra .= 'version <= ' . $dep['max']; + } + + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if ($extra != ' (') { + $extra .= ', '; + } + + $extra .= 'excluded versions: '; + foreach ($dep['exclude'] as $i => $exclude) { + if ($i) { + $extra .= ', '; + } + $extra .= $exclude; + } + } + } + + $extra .= ')'; + if ($extra == ' ()') { + $extra = ''; + } + + return $extra; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPHP_OS() + { + return PHP_OS; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getsysname() + { + return $this->_os->getSysname(); + } + + /** + * Specify a dependency on an OS. Use arch for detailed os/processor information + * + * There are two generic OS dependencies that will be the most common, unix and windows. + * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix + */ + function validateOsDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if ($dep['name'] == '*') { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + switch (strtolower($dep['name'])) { + case 'windows' : + if ($not) { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on Windows"); + } + + return $this->warning("warning: Cannot install %s on Windows"); + } + } else { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on Windows"); + } + + return $this->warning("warning: Can only install %s on Windows"); + } + } + break; + case 'unix' : + $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix'); + if ($not) { + if (in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on any Unix system"); + } + + return $this->warning( "warning: Cannot install %s on any Unix system"); + } + } else { + if (!in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on a Unix system"); + } + + return $this->warning("warning: Can only install %s on a Unix system"); + } + } + break; + default : + if ($not) { + if (strtolower($dep['name']) == strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . $dep['name'] . + ' operating system'); + } + + return $this->warning('warning: Cannot install %s on ' . + $dep['name'] . ' operating system'); + } + } else { + if (strtolower($dep['name']) != strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + + return $this->warning('warning: Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + } + } + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function matchSignature($pattern) + { + return $this->_os->matchSignature($pattern); + } + + /** + * Specify a complex dependency on an OS/processor/kernel version, + * Use OS for simple operating system dependency. + * + * This is the only dependency that accepts an eregable pattern. The pattern + * will be matched against the php_uname() output parsed by OS_Guess + */ + function validateArchDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING) { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + if (!$this->matchSignature($dep['pattern'])) { + if (!$not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, does not ' . + 'match "' . $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, does ' . + 'not match "' . $dep['pattern'] . '"'); + } + + return true; + } + + if ($not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, required "' . + $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, ' . + 'required "' . $dep['pattern'] . '"'); + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function extension_loaded($name) + { + return extension_loaded($name); + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function phpversion($name = null) + { + if ($name !== null) { + return phpversion($name); + } + + return phpversion(); + } + + function validateExtensionDependency($dep, $required = true) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $loaded = $this->extension_loaded($dep['name']); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($loaded) { + if (isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!$loaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if (!$required) { + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + $version = (string) $this->phpversion($dep['name']); + if (empty($version)) { + $version = '0'; + } + + $fail = false; + if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (isset($dep['conflicts'])) { + continue; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + } + } + + if (isset($dep['recommended'])) { + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] . + ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'] . + '", but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency: PHP extension ' . + $dep['name'] . ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'].'"'); + } + + return true; + } + + function validatePhpDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $version = $this->phpversion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (isset($dep['min'])) { + if (!version_compare($version, $dep['min'], '>=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['max'])) { + if (!version_compare($version, $dep['max'], '<=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP version ' . + $exclude); + } + + return $this->warning( + 'warning: %s is not compatible with PHP version ' . + $exclude); + } + } + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPEARVersion() + { + return '1.9.4'; + } + + function validatePearinstallerDependency($dep) + { + $pearversion = $this->getPEARVersion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (version_compare($pearversion, $dep['min'], '<')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + if (isset($dep['max'])) { + if (version_compare($pearversion, $dep['max'], '>')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + } + + if (isset($dep['exclude'])) { + if (!isset($dep['exclude'][0])) { + $dep['exclude'] = array($dep['exclude']); + } + + foreach ($dep['exclude'] as $exclude) { + if (version_compare($exclude, $pearversion, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PEAR Installer ' . + 'version ' . $exclude); + } + + return $this->warning('warning: %s is not compatible with PEAR ' . + 'Installer version ' . $exclude); + } + } + } + + return true; + } + + function validateSubpackageDependency($dep, $required, $params) + { + return $this->validatePackageDependency($dep, $required, $params); + } + + /** + * @param array dependency information (2.0 format) + * @param boolean whether this is a required dependency + * @param array a list of downloaded packages to be installed, if any + * @param boolean if true, then deps on pear.php.net that fail will also check + * against pecl.php.net packages to accomodate extensions that have + * moved to pecl.php.net from pear.php.net + */ + function validatePackageDependency($dep, $required, $params, $depv1 = false) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if (isset($dep['providesextension'])) { + if ($this->extension_loaded($dep['providesextension'])) { + $save = $dep; + $subdep = $dep; + $subdep['name'] = $subdep['providesextension']; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->validateExtensionDependency($subdep, $required); + PEAR::popErrorHandling(); + if (!PEAR::isError($ret)) { + return true; + } + } + } + + if ($this->_state == PEAR_VALIDATE_INSTALLING) { + return $this->_validatePackageInstall($dep, $required, $depv1); + } + + if ($this->_state == PEAR_VALIDATE_DOWNLOADING) { + return $this->_validatePackageDownload($dep, $required, $params, $depv1); + } + } + + function _validatePackageDownload($dep, $required, $params, $depv1 = false) + { + $dep['package'] = $dep['name']; + if (isset($dep['uri'])) { + $dep['channel'] = '__uri'; + } + + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $found = false; + foreach ($params as $param) { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => $dep['channel']))) { + $found = true; + break; + } + + if ($depv1 && $dep['channel'] == 'pear.php.net') { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => 'pecl.php.net'))) { + $found = true; + break; + } + } + } + + if (!$found && isset($dep['providesextension'])) { + foreach ($params as $param) { + if ($param->isExtension($dep['providesextension'])) { + $found = true; + break; + } + } + } + + if ($found) { + $version = $param->getVersion(); + $installed = false; + $downloaded = true; + } else { + if ($this->_registry->packageExists($dep['name'], $dep['channel'])) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + $dep['channel']); + } else { + if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'], + 'pear.php.net')) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + 'pear.php.net'); + } else { + $version = 'not installed or downloaded'; + $installed = false; + $downloaded = false; + } + } + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($installed || $downloaded) { + $installed = $installed ? 'installed' : 'downloaded'; + if (isset($dep['conflicts'])) { + $rest = ''; + if ($version) { + $rest = ", $installed version is " . $version; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + if (!$installed && !$downloaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + $dep['package'] = $dep['name']; + $dep = $this->_registry->parsedPackageNameToString($dep, true); + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && + isset($dep['conflicts']) && !isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . + ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + if (isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force']) + ) { + return $this->raiseError('%s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + } + } + + if (isset($dep['recommended'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!$found && $installed) { + $param = $this->_registry->getPackage($dep['name'], $dep['channel']); + } + + if ($param) { + $found = false; + foreach ($params as $parent) { + if ($parent->isEqual($this->_currentPackage)) { + $found = true; + break; + } + } + + if ($found) { + if ($param->isCompatible($parent)) { + return true; + } + } else { // this is for validPackage() calls + $parent = $this->_registry->getPackage($this->_currentPackage['package'], + $this->_currentPackage['channel']); + if ($parent !== null && $param->isCompatible($parent)) { + return true; + } + } + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) && + !isset($this->_options['loose']) + ) { + return $this->raiseError('%s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended'] . + ', but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended']); + } + + return true; + } + + function _validatePackageInstall($dep, $required, $depv1 = false) + { + return $this->_validatePackageDownload($dep, $required, array(), $depv1); + } + + /** + * Verify that uninstalling packages passed in to command line is OK. + * + * @param PEAR_Installer $dl + * @return PEAR_Error|true + */ + function validatePackageUninstall(&$dl) + { + if (PEAR::isError($this->_dependencydb)) { + return $this->_dependencydb; + } + + $params = array(); + // construct an array of "downloaded" packages to fool the package dependency checker + // into using these to validate uninstalls of circular dependencies + $downloaded = &$dl->getUninstallPackages(); + foreach ($downloaded as $i => $pf) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $dp = &new PEAR_Downloader_Package($dl); + $dp->setPackageFile($downloaded[$i]); + $params[$i] = &$dp; + } + + // check cache + $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' . + strtolower($this->_currentPackage['package']); + if (isset($dl->___uninstall_package_cache)) { + $badpackages = $dl->___uninstall_package_cache; + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + + return true; + } + + // first, list the immediate parents of each package to be uninstalled + $perpackagelist = array(); + $allparents = array(); + foreach ($params as $i => $param) { + $a = array( + 'channel' => strtolower($param->getChannel()), + 'package' => strtolower($param->getPackage()) + ); + + $deps = $this->_dependencydb->getDependentPackages($a); + if ($deps) { + foreach ($deps as $d) { + $pardeps = $this->_dependencydb->getDependencies($d); + foreach ($pardeps as $dep) { + if (strtolower($dep['dep']['channel']) == $a['channel'] && + strtolower($dep['dep']['name']) == $a['package']) { + if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) { + $perpackagelist[$a['channel'] . '/' . $a['package']] = array(); + } + $perpackagelist[$a['channel'] . '/' . $a['package']][] + = array($d['channel'] . '/' . $d['package'], $dep); + if (!isset($allparents[$d['channel'] . '/' . $d['package']])) { + $allparents[$d['channel'] . '/' . $d['package']] = array(); + } + if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) { + $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array(); + } + $allparents[$d['channel'] . '/' . $d['package']] + [$a['channel'] . '/' . $a['package']][] + = array($d, $dep); + } + } + } + } + } + + // next, remove any packages from the parents list that are not installed + $remove = array(); + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) { + continue; + } + $remove[$parent] = true; + } + } + + // next remove any packages from the parents list that are not passed in for + // uninstallation + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + foreach ($params as $param) { + if (strtolower($param->getChannel()) == $d[0][0]['channel'] && + strtolower($param->getPackage()) == $d[0][0]['package']) { + // found it + continue 3; + } + } + $remove[$parent] = true; + } + } + + // remove all packages whose dependencies fail + // save which ones failed for error reporting + $badchildren = array(); + do { + $fail = false; + foreach ($remove as $package => $unused) { + if (!isset($allparents[$package])) { + continue; + } + + foreach ($allparents[$package] as $kid => $d1) { + foreach ($d1 as $depinfo) { + if ($depinfo[1]['type'] != 'optional') { + if (isset($badchildren[$kid])) { + continue; + } + $badchildren[$kid] = true; + $remove[$kid] = true; + $fail = true; + continue 2; + } + } + } + if ($fail) { + // start over, we removed some children + continue 2; + } + } + } while ($fail); + + // next, construct the list of packages that can't be uninstalled + $badpackages = array(); + $save = $this->_currentPackage; + foreach ($perpackagelist as $package => $packagedeps) { + foreach ($packagedeps as $parent) { + if (!isset($remove[$parent[0]])) { + continue; + } + + $packagename = $this->_registry->parsePackageName($parent[0]); + $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']); + $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']); + $packagename['package'] = $pa->getPackage(); + $this->_currentPackage = $packagename; + // parent is not present in uninstall list, make sure we can actually + // uninstall it (parent dep is optional) + $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']); + $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']); + $parentname['package'] = $pa->getPackage(); + $parent[1]['dep']['package'] = $parentname['package']; + $parent[1]['dep']['channel'] = $parentname['channel']; + if ($parent[1]['type'] == 'optional') { + $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl); + if ($test !== true) { + $badpackages[$package]['warnings'][] = $test; + } + } else { + $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl); + if ($test !== true) { + $badpackages[$package]['errors'][] = $test; + } + } + } + } + + $this->_currentPackage = $save; + $dl->___uninstall_package_cache = $badpackages; + if (isset($badpackages[$memyselfandI])) { + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + } + + return true; + } + + function _validatePackageUninstall($dep, $required, $dl) + { + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']); + if (!$version) { + return true; + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (isset($dep['conflicts'])) { + return true; // uninstall OK - these packages conflict (probably installed with --force) + } + + if (!isset($dep['min']) && !isset($dep['max'])) { + if (!$required) { + return $this->warning('"' . $depname . '" can be optionally used by ' . + 'installed package %s' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('"' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + return $this->warning('warning: "' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + // we re-use this variable, preserve the original value + $saverequired = $required; + if (!$required) { + return $this->warning($depname . $extra . ' can be optionally used by installed package' . + ' "%s"'); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError($depname . $extra . ' is required by installed package' . + ' "%s"'); + } + + return $this->raiseError('warning: ' . $depname . $extra . + ' is required by installed package "%s"'); + } + + /** + * validate a downloaded package against installed packages + * + * As of PEAR 1.4.3, this will only validate + * + * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * $pkg package identifier (either + * array('package' => blah, 'channel' => blah) or an array with + * index 'info' referencing an object) + * @param PEAR_Downloader $dl + * @param array $params full list of packages to install + * @return true|PEAR_Error + */ + function validatePackage($pkg, &$dl, $params = array()) + { + if (is_array($pkg) && isset($pkg['info'])) { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']); + } else { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg); + } + + $fail = false; + if ($deps) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + + $dp = &new PEAR_Downloader_Package($dl); + if (is_object($pkg)) { + $dp->setPackageFile($pkg); + } else { + $dp->setDownloadURL($pkg); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($deps as $channel => $info) { + foreach ($info as $package => $ds) { + foreach ($params as $packd) { + if (strtolower($packd->getPackage()) == strtolower($package) && + $packd->getChannel() == $channel) { + $dl->log(3, 'skipping installed package check of "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $channel, 'package' => $package), + true) . + '", version "' . $packd->getVersion() . '" will be ' . + 'downloaded and installed'); + continue 2; // jump to next package + } + } + + foreach ($ds as $d) { + $checker = &new PEAR_Dependency2($this->_config, $this->_options, + array('channel' => $channel, 'package' => $package), $this->_state); + $dep = $d['dep']; + $required = $d['type'] == 'required'; + $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp)); + if (is_array($ret)) { + $dl->log(0, $ret[0]); + } elseif (PEAR::isError($ret)) { + $dl->log(0, $ret->getMessage()); + $fail = true; + } + } + } + } + PEAR::popErrorHandling(); + } + + if ($fail) { + return $this->raiseError( + '%s cannot be installed, conflicts with installed packages'); + } + + return true; + } + + /** + * validate a package.xml 1.0 dependency + */ + function validateDependency1($dep, $params = array()) + { + if (!isset($dep['optional'])) { + $dep['optional'] = 'no'; + } + + list($newdep, $type) = $this->normalizeDep($dep); + if (!$newdep) { + return $this->raiseError("Invalid Dependency"); + } + + if (method_exists($this, "validate{$type}Dependency")) { + return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no', + $params, true); + } + } + + /** + * Convert a 1.0 dep into a 2.0 dep + */ + function normalizeDep($dep) + { + $types = array( + 'pkg' => 'Package', + 'ext' => 'Extension', + 'os' => 'Os', + 'php' => 'Php' + ); + + if (!isset($types[$dep['type']])) { + return array(false, false); + } + + $type = $types[$dep['type']]; + + $newdep = array(); + switch ($type) { + case 'Package' : + $newdep['channel'] = 'pear.php.net'; + case 'Extension' : + case 'Os' : + $newdep['name'] = $dep['name']; + break; + } + + $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']); + switch ($dep['rel']) { + case 'has' : + return array($newdep, $type); + break; + case 'not' : + $newdep['conflicts'] = true; + break; + case '>=' : + case '>' : + $newdep['min'] = $dep['version']; + if ($dep['rel'] == '>') { + $newdep['exclude'] = $dep['version']; + } + break; + case '<=' : + case '<' : + $newdep['max'] = $dep['version']; + if ($dep['rel'] == '<') { + $newdep['exclude'] = $dep['version']; + } + break; + case 'ne' : + case '!=' : + $newdep['min'] = '0'; + $newdep['max'] = '100000'; + $newdep['exclude'] = $dep['version']; + break; + case '==' : + $newdep['min'] = $dep['version']; + $newdep['max'] = $dep['version']; + break; + } + if ($type == 'Php') { + if (!isset($newdep['min'])) { + $newdep['min'] = '4.4.0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '6.0.0'; + } + } + return array($newdep, $type); + } + + /** + * Converts text comparing operators to them sign equivalents + * + * Example: 'ge' to '>=' + * + * @access public + * @param string Operator + * @return string Sign equivalent + */ + function signOperator($operator) + { + switch($operator) { + case 'lt': return '<'; + case 'le': return '<='; + case 'gt': return '>'; + case 'ge': return '>='; + case 'eq': return '=='; + case 'ne': return '!='; + default: + return $operator; + } + } + + function raiseError($msg) + { + if (isset($this->_options['ignore-errors'])) { + return $this->warning($msg); + } + + return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } + + function warning($msg) + { + return array(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/DependencyDB.php b/3rdparty/PEAR/DependencyDB.php new file mode 100644 index 0000000000..948f0c9d70 --- /dev/null +++ b/3rdparty/PEAR/DependencyDB.php @@ -0,0 +1,769 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: DependencyDB.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array(); +/** + * Track dependency relationships between installed packages + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_DependencyDB +{ + // {{{ properties + + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Config + * @access private + */ + var $_config; + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Registry + * @access private + */ + var $_registry; + /** + * Filename of the dependency DB (usually .depdb) + * @var string + * @access private + */ + var $_depdb = false; + /** + * File name of the lockfile (usually .depdblock) + * @var string + * @access private + */ + var $_lockfile = false; + /** + * Open file resource for locking the lockfile + * @var resource|false + * @access private + */ + var $_lockFp = false; + /** + * API version of this class, used to validate a file on-disk + * @var string + * @access private + */ + var $_version = '1.0'; + /** + * Cached dependency database file + * @var array|null + * @access private + */ + var $_cache; + + // }}} + // {{{ & singleton() + + /** + * Get a raw dependency database. Calls setConfig() and assertDepsDB() + * @param PEAR_Config + * @param string|false full path to the dependency database, or false to use default + * @return PEAR_DependencyDB|PEAR_Error + * @static + */ + function &singleton(&$config, $depdb = false) + { + $phpdir = $config->get('php_dir', null, 'pear.php.net'); + if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) { + $a = new PEAR_DependencyDB; + $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a; + $a->setConfig($config, $depdb); + $e = $a->assertDepsDB(); + if (PEAR::isError($e)) { + return $e; + } + } + + return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir]; + } + + /** + * Set up the registry/location of dependency DB + * @param PEAR_Config|false + * @param string|false full path to the dependency database, or false to use default + */ + function setConfig(&$config, $depdb = false) + { + if (!$config) { + $this->_config = &PEAR_Config::singleton(); + } else { + $this->_config = &$config; + } + + $this->_registry = &$this->_config->getRegistry(); + if (!$depdb) { + $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'; + } else { + $this->_depdb = $depdb; + } + + $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; + } + // }}} + + function hasWriteAccess() + { + if (!file_exists($this->_depdb)) { + $dir = $this->_depdb; + while ($dir && $dir != '.') { + $dir = dirname($dir); // cd .. + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + } + + return false; + } + + return is_writeable($this->_depdb); + } + + // {{{ assertDepsDB() + + /** + * Create the dependency database, if it doesn't exist. Error if the database is + * newer than the code reading it. + * @return void|PEAR_Error + */ + function assertDepsDB() + { + if (!is_file($this->_depdb)) { + $this->rebuildDB(); + return; + } + + $depdb = $this->_getDepDB(); + // Datatype format has been changed, rebuild the Deps DB + if ($depdb['_version'] < $this->_version) { + $this->rebuildDB(); + } + + if ($depdb['_version']{0} > $this->_version{0}) { + return PEAR::raiseError('Dependency database is version ' . + $depdb['_version'] . ', and we are version ' . + $this->_version . ', cannot continue'); + } + } + + /** + * Get a list of installed packages that depend on this package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackages(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (isset($data['packages'][$channel][$package])) { + return $data['packages'][$channel][$package]; + } + + return false; + } + + /** + * Get a list of the actual dependencies of installed packages that depend on + * a package. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackageDependencies(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $depend = $this->getDependentPackages($pkg); + if (!$depend) { + return false; + } + + $dependencies = array(); + foreach ($depend as $info) { + $temp = $this->getDependencies($info); + foreach ($temp as $dep) { + if ( + isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) && + strtolower($dep['dep']['channel']) == $channel && + strtolower($dep['dep']['name']) == $package + ) { + if (!isset($dependencies[$info['channel']])) { + $dependencies[$info['channel']] = array(); + } + + if (!isset($dependencies[$info['channel']][$info['package']])) { + $dependencies[$info['channel']][$info['package']] = array(); + } + $dependencies[$info['channel']][$info['package']][] = $dep; + } + } + } + + return $dependencies; + } + + /** + * Get a list of dependencies of this installed package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependencies(&$pkg) + { + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $data = $this->_getDepDB(); + if (isset($data['dependencies'][$channel][$package])) { + return $data['dependencies'][$channel][$package]; + } + + return false; + } + + /** + * Determine whether $parent depends on $child, near or deep + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function dependsOn($parent, $child) + { + $c = array(); + $this->_getDepDB(); + return $this->_dependsOn($parent, $child, $c); + } + + function _dependsOn($parent, $child, &$checked) + { + if (is_object($parent)) { + $channel = strtolower($parent->getChannel()); + $package = strtolower($parent->getPackage()); + } else { + $channel = strtolower($parent['channel']); + $package = strtolower($parent['package']); + } + + if (is_object($child)) { + $depchannel = strtolower($child->getChannel()); + $deppackage = strtolower($child->getPackage()); + } else { + $depchannel = strtolower($child['channel']); + $deppackage = strtolower($child['package']); + } + + if (isset($checked[$channel][$package][$depchannel][$deppackage])) { + return false; // avoid endless recursion + } + + $checked[$channel][$package][$depchannel][$deppackage] = true; + if (!isset($this->_cache['dependencies'][$channel][$package])) { + return false; + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if (is_object($child)) { + if ($info['dep']['uri'] == $child->getURI()) { + return true; + } + } elseif (isset($child['uri'])) { + if ($info['dep']['uri'] == $child['uri']) { + return true; + } + } + return false; + } + + if (strtolower($info['dep']['channel']) == $depchannel && + strtolower($info['dep']['name']) == $deppackage) { + return true; + } + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if ($this->_dependsOn(array( + 'uri' => $info['dep']['uri'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } else { + if ($this->_dependsOn(array( + 'channel' => $info['dep']['channel'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } + } + + return false; + } + + /** + * Register dependencies of a package that is being installed or upgraded + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function installPackage(&$package) + { + $data = $this->_getDepDB(); + unset($this->_cache); + $this->_setPackageDeps($data, $package); + $this->_writeDepDB($data); + } + + /** + * Remove dependencies of a package that is being uninstalled, or upgraded. + * + * Upgraded packages first uninstall, then install + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have + * indices 'channel' and 'package' + */ + function uninstallPackage(&$pkg) + { + $data = $this->_getDepDB(); + unset($this->_cache); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (!isset($data['dependencies'][$channel][$package])) { + return true; + } + + foreach ($data['dependencies'][$channel][$package] as $dep) { + $found = false; + $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']); + $depname = strtolower($dep['dep']['name']); + if (isset($data['packages'][$depchannel][$depname])) { + foreach ($data['packages'][$depchannel][$depname] as $i => $info) { + if ($info['channel'] == $channel && $info['package'] == $package) { + $found = true; + break; + } + } + } + + if ($found) { + unset($data['packages'][$depchannel][$depname][$i]); + if (!count($data['packages'][$depchannel][$depname])) { + unset($data['packages'][$depchannel][$depname]); + if (!count($data['packages'][$depchannel])) { + unset($data['packages'][$depchannel]); + } + } else { + $data['packages'][$depchannel][$depname] = + array_values($data['packages'][$depchannel][$depname]); + } + } + } + + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + + if (!count($data['dependencies'])) { + unset($data['dependencies']); + } + + if (!count($data['packages'])) { + unset($data['packages']); + } + + $this->_writeDepDB($data); + } + + /** + * Rebuild the dependency DB by reading registry entries. + * @return true|PEAR_Error + */ + function rebuildDB() + { + $depdb = array('_version' => $this->_version); + if (!$this->hasWriteAccess()) { + // allow startup for read-only with older Registry + return $depdb; + } + + $packages = $this->_registry->listAllPackages(); + if (PEAR::isError($packages)) { + return $packages; + } + + foreach ($packages as $channel => $ps) { + foreach ($ps as $package) { + $package = $this->_registry->getPackage($package, $channel); + if (PEAR::isError($package)) { + return $package; + } + $this->_setPackageDeps($depdb, $package); + } + } + + $error = $this->_writeDepDB($depdb); + if (PEAR::isError($error)) { + return $error; + } + + $this->_cache = $depdb; + return true; + } + + /** + * Register usage of the dependency DB to prevent race conditions + * @param int one of the LOCK_* constants + * @return true|PEAR_Error + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->_lockFp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH) { + if (!file_exists($this->_lockfile)) { + touch($this->_lockfile); + } elseif (!is_file($this->_lockfile)) { + return PEAR::raiseError('could not create Dependency lock file, ' . + 'it exists and is not a regular file'); + } + $open_mode = 'r'; + } + + if (!is_resource($this->_lockFp)) { + $this->_lockFp = @fopen($this->_lockfile, $open_mode); + } + + if (!is_resource($this->_lockFp)) { + return PEAR::raiseError("could not create Dependency lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->_lockFp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); + } + + return true; + } + + /** + * Release usage of dependency DB + * @return true|PEAR_Error + * @access private + */ + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->_lockFp)) { + fclose($this->_lockFp); + } + $this->_lockFp = null; + return $ret; + } + + /** + * Load the dependency database from disk, or return the cache + * @return array|PEAR_Error + */ + function _getDepDB() + { + if (!$this->hasWriteAccess()) { + return array('_version' => $this->_version); + } + + if (isset($this->_cache)) { + return $this->_cache; + } + + if (!$fp = fopen($this->_depdb, 'r')) { + $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); + return $err; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + fclose($fp); + $data = unserialize(file_get_contents($this->_depdb)); + set_magic_quotes_runtime($rt); + $this->_cache = $data; + return $data; + } + + /** + * Write out the dependency database to disk + * @param array the database + * @return true|PEAR_Error + * @access private + */ + function _writeDepDB(&$deps) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + if (!$fp = fopen($this->_depdb, 'wb')) { + $this->_unlock(); + return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fwrite($fp, serialize($deps)); + set_magic_quotes_runtime($rt); + fclose($fp); + $this->_unlock(); + $this->_cache = $deps; + return true; + } + + /** + * Register all dependencies from a package in the dependencies database, in essence + * "installing" the package's dependency information + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @access private + */ + function _setPackageDeps(&$data, &$pkg) + { + $pkg->setConfig($this->_config); + if ($pkg->getPackagexmlVersion() == '1.0') { + $gen = &$pkg->getDefaultGenerator(); + $deps = $gen->dependenciesToV2(); + } else { + $deps = $pkg->getDeps(true); + } + + if (!$deps) { + return; + } + + if (!is_array($data)) { + $data = array(); + } + + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + $data['dependencies'][$channel][$package] = array(); + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['required']['subpackage'])) { + if (!isset($deps['required']['subpackage'][0])) { + $deps['required']['subpackage'] = array($deps['required']['subpackage']); + } + + foreach ($deps['required']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['subpackage'])) { + if (!isset($deps['optional']['subpackage'][0])) { + $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); + } + + foreach ($deps['optional']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + + foreach ($group['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + + foreach ($group['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + } + } + + if ($data['dependencies'][$channel][$package] == array()) { + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + } + } + + /** + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array the specific dependency + * @param required|optional whether this is a required or an optional dep + * @param string|false dependency group this dependency is from, or false for ordinary dep + */ + function _registerDep(&$data, &$pkg, $dep, $type, $group = false) + { + $info = array( + 'dep' => $dep, + 'type' => $type, + 'group' => $group + ); + + $dep = array_map('strtolower', $dep); + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + if (!isset($data['dependencies'][$channel][$package])) { + $data['dependencies'][$channel][$package] = array(); + } + + $data['dependencies'][$channel][$package][] = $info; + if (isset($data['packages'][$depchannel][$dep['name']])) { + $found = false; + foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) { + if ($p['channel'] == $channel && $p['package'] == $package) { + $found = true; + break; + } + } + } else { + if (!isset($data['packages'])) { + $data['packages'] = array(); + } + + if (!isset($data['packages'][$depchannel])) { + $data['packages'][$depchannel] = array(); + } + + if (!isset($data['packages'][$depchannel][$dep['name']])) { + $data['packages'][$depchannel][$dep['name']] = array(); + } + + $found = false; + } + + if (!$found) { + $data['packages'][$depchannel][$dep['name']][] = array( + 'channel' => $channel, + 'package' => $package + ); + } + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Downloader.php b/3rdparty/PEAR/Downloader.php index 5e16dc5ffb..730df0b738 100644 --- a/3rdparty/PEAR/Downloader.php +++ b/3rdparty/PEAR/Downloader.php @@ -1,31 +1,26 @@ | -// | Tomas V.V.Cox | -// | Martin Jansen | -// +----------------------------------------------------------------------+ -// -// $Id: Downloader.php,v 1.17.2.1 2004/10/22 22:54:03 cellog Exp $ +/** + * PEAR_Downloader, the PEAR Installer's download utility class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Downloader.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.0 + */ +/** + * Needed for constants, extending + */ require_once 'PEAR/Common.php'; -require_once 'PEAR/Registry.php'; -require_once 'PEAR/Dependency.php'; -require_once 'PEAR/Remote.php'; -require_once 'System.php'; - define('PEAR_INSTALLER_OK', 1); define('PEAR_INSTALLER_FAILED', 0); @@ -33,32 +28,29 @@ define('PEAR_INSTALLER_SKIPPED', -1); define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); /** - * Administration class used to download PEAR packages and maintain the - * installed package database. + * Administration class used to download anything from the internet (PEAR Packages, + * static URLs, xml files) * - * @since PEAR 1.4 - * @author Greg Beaver + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.0 */ class PEAR_Downloader extends PEAR_Common { - /** - * @var PEAR_Config - * @access private - */ - var $_config; - /** * @var PEAR_Registry * @access private */ var $_registry; - /** - * @var PEAR_Remote - * @access private - */ - var $_remote; - /** * Preferred Installation State (snapshot, devel, alpha, beta, stable) * @var string|null @@ -74,6 +66,7 @@ class PEAR_Downloader extends PEAR_Common * - alldeps : install all dependencies, including optional * - installroot : base relative path to install files in * - force : force a download even if warnings would prevent it + * - nocompress : download uncompressed tarballs * @see PEAR_Command_Install * @access private * @var array @@ -128,149 +121,962 @@ class PEAR_Downloader extends PEAR_Common */ var $_errorStack = array(); - // {{{ PEAR_Downloader() + /** + * @var boolean + * @access private + */ + var $_internalDownload = false; + /** + * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()} + * @var array + * @access private + */ + var $_packageSortTree; + + /** + * Temporary directory, or configuration value where downloads will occur + * @var string + */ + var $_downloadDir; + + /** + * @param PEAR_Frontend_* + * @param array + * @param PEAR_Config + */ function PEAR_Downloader(&$ui, $options, &$config) { + parent::PEAR_Common(); $this->_options = $options; - $this->_config = &$config; - $this->_preferredState = $this->_config->get('preferred_state'); + $this->config = &$config; + $this->_preferredState = $this->config->get('preferred_state'); $this->ui = &$ui; if (!$this->_preferredState) { // don't inadvertantly use a non-set preferred_state $this->_preferredState = null; } - $php_dir = $this->_config->get('php_dir'); if (isset($this->_options['installroot'])) { - if (substr($this->_options['installroot'], -1) == DIRECTORY_SEPARATOR) { - $this->_options['installroot'] = substr($this->_options['installroot'], 0, -1); - } - $php_dir = $this->_prependPath($php_dir, $this->_options['installroot']); + $this->config->setInstallRoot($this->_options['installroot']); } - $this->_registry = &new PEAR_Registry($php_dir); - $this->_remote = &new PEAR_Remote($config); + $this->_registry = &$config->getRegistry(); if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { - $this->_installed = $this->_registry->listPackages(); - array_walk($this->_installed, create_function('&$v,$k','$v = strtolower($v);')); - $this->_installed = array_flip($this->_installed); + $this->_installed = $this->_registry->listAllPackages(); + foreach ($this->_installed as $key => $unused) { + if (!count($unused)) { + continue; + } + $strtolower = create_function('$a','return strtolower($a);'); + array_walk($this->_installed[$key], $strtolower); + } } - parent::PEAR_Common(); } - // }}} - // {{{ configSet() - function configSet($key, $value, $layer = 'user') + /** + * Attempt to discover a channel's remote capabilities from + * its server name + * @param string + * @return boolean + */ + function discover($channel) { - $this->_config->set($key, $value, $layer); - $this->_preferredState = $this->_config->get('preferred_state'); + $this->log(1, 'Attempting to discover channel "' . $channel . '"...'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + if (!class_exists('System')) { + require_once 'System.php'; + } + + $tmpdir = $this->config->get('temp_dir'); + $tmp = System::mktemp('-d -t "' . $tmpdir . '"'); + $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + // Attempt to fallback to https automatically. + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...'); + $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + return false; + } + } + + list($a, $lastmodified) = $a; + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $b = new PEAR_ChannelFile; + if ($b->fromXmlFile($a)) { + unlink($a); + if ($this->config->get('auto_discover')) { + $this->_registry->addChannel($b, $lastmodified); + $alias = $b->getName(); + if ($b->getName() == $this->_registry->channelName($b->getAlias())) { + $alias = $b->getAlias(); + } + + $this->log(1, 'Auto-discovered channel "' . $channel . + '", alias "' . $alias . '", adding to registry'); + } + + return true; + } + + unlink($a); + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Downloader + * @return PEAR_Downloader_Package + */ + function &newDownloaderPackage(&$t) + { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $a = &new PEAR_Downloader_Package($t); + return $a; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param array + * @param array + * @param int + */ + function &getDependency2Object(&$c, $i, $p, $s) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $i, $p, $s); + return $z; + } + + function &download($params) + { + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channelschecked = array(); + // convert all parameters into PEAR_Downloader_Package objects + foreach ($params as $i => $param) { + $params[$i] = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $params[$i]->initialize($param); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + continue; + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft']) && $err->getMessage() !== '') { + $this->log(0, $err->getMessage()); + } + + $params[$i] = false; + if (is_object($param)) { + $param = $param->getChannel() . '/' . $param->getPackage(); + } + + if (!isset($this->_options['soft'])) { + $this->log(2, 'Package "' . $param . '" is not valid'); + } + + // Message logged above in a specific verbose mode, passing null to not show up on CLI + $this->pushError(null, PEAR_INSTALLER_SKIPPED); + } else { + do { + if ($params[$i] && $params[$i]->getType() == 'local') { + // bug #7090 skip channel.xml check for local packages + break; + } + + if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) && + !isset($this->_options['offline']) + ) { + $channelschecked[$params[$i]->getChannel()] = true; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('System')) { + require_once 'System.php'; + } + + $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); + if (PEAR::isError($curchannel)) { + PEAR::staticPopErrorHandling(); + return $this->raiseError($curchannel); + } + + if (PEAR::isError($dir = $this->getDownloadDir())) { + PEAR::staticPopErrorHandling(); + break; + } + + $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel()); + $url = 'http://' . $mirror . '/channel.xml'; + $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + // Attempt fallback to https automatically + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $a = $this->downloadHttp('https://' . $mirror . + '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + break; + } + } + $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() . + '" to update'); + } + } while (false); + + if ($params[$i] && !isset($this->_options['downloadonly'])) { + if (isset($this->_options['packagingroot'])) { + $checkdir = $this->_prependPath( + $this->config->get('php_dir', null, $params[$i]->getChannel()), + $this->_options['packagingroot']); + } else { + $checkdir = $this->config->get('php_dir', + null, $params[$i]->getChannel()); + } + + while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) { + $checkdir = dirname($checkdir); + } + + if ($checkdir == '.') { + $checkdir = '/'; + } + + if (!is_writeable($checkdir)) { + return PEAR::raiseError('Cannot install, php_dir for channel "' . + $params[$i]->getChannel() . '" is not writeable by the current user'); + } + } + } + } + + unset($channelschecked); + PEAR_Downloader_Package::removeDuplicates($params); + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) { + $reverify = true; + while ($reverify) { + $reverify = false; + foreach ($params as $i => $param) { + //PHP Bug 40768 / PEAR Bug #10944 + //Nested foreaches fail in PHP 5.2.1 + key($params); + $ret = $params[$i]->detectDependencies($params); + if (PEAR::isError($ret)) { + $reverify = true; + $params[$i] = false; + PEAR_Downloader_Package::removeDuplicates($params); + if (!isset($this->_options['soft'])) { + $this->log(0, $ret->getMessage()); + } + continue 2; + } + } + } + } + + if (isset($this->_options['offline'])) { + $this->log(3, 'Skipping dependency download check, --offline specified'); + } + + if (!count($params)) { + $a = array(); + return $a; + } + + while (PEAR_Downloader_Package::mergeDependencies($params)); + PEAR_Downloader_Package::removeDuplicates($params, true); + $errorparams = array(); + if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) { + if (count($errorparams)) { + foreach ($errorparams as $param) { + $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage()); + $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED); + } + $a = array(); + return $a; + } + } + + PEAR_Downloader_Package::removeInstalled($params); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($params); + PEAR::popErrorHandling(); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + $ret = array(); + $newparams = array(); + if (isset($this->_options['pretend'])) { + return $params; + } + + $somefailed = false; + foreach ($params as $i => $package) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$params[$i]->download(); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (!isset($this->_options['soft'])) { + $this->log(1, $pf->getMessage()); + $this->log(0, 'Error: cannot download "' . + $this->_registry->parsedPackageNameToString($package->getParsedPackage(), + true) . + '"'); + } + $somefailed = true; + continue; + } + + $newparams[] = &$params[$i]; + $ret[] = array( + 'file' => $pf->getArchiveFile(), + 'info' => &$pf, + 'pkg' => $pf->getPackage() + ); + } + + if ($somefailed) { + // remove params that did not download successfully + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($newparams, true); + PEAR::popErrorHandling(); + if (!count($newparams)) { + $this->pushError('Download failed', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + } + + $this->_downloadedPackages = $ret; + return $newparams; + } + + /** + * @param array all packages to be installed + */ + function analyzeDependencies(&$params, $force = false) + { + if (isset($this->_options['downloadonly'])) { + return; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $redo = true; + $reset = $hasfailed = $failed = false; + while ($redo) { + $redo = false; + foreach ($params as $i => $param) { + $deps = $param->getDeps(); + if (!$deps) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + continue; + } + + if (!$reset && $param->alreadyValidated() && !$force) { + continue; + } + + if (count($deps)) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + if ($send === null) { + $send = $param->getDownloadURL(); + } + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + + $failed = false; + if (isset($deps['required']) && is_array($deps['required'])) { + foreach ($deps['required'] as $type => $dep) { + // note: Dependency2 will never return a PEAR_Error if ignore-errors + // is specified, so soft is needed to turn off logging + if (!isset($dep[0])) { + if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + + if (isset($deps['optional']) && is_array($deps['optional'])) { + foreach ($deps['optional'] as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + + $groupname = $param->getGroup(); + if (isset($deps['group']) && $groupname) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + $found = false; + foreach ($deps['group'] as $group) { + if ($group['attribs']['name'] == $groupname) { + $found = true; + break; + } + } + + if ($found) { + unset($group['attribs']); + foreach ($group as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + } + } else { + foreach ($deps as $dep) { + if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + $params[$i]->setValidated(); + } + + if ($failed) { + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + } + } + + PEAR::staticPopErrorHandling(); + if ($hasfailed && (isset($this->_options['ignore-errors']) || + isset($this->_options['nodeps']))) { + // this is probably not needed, but just in case + if (!isset($this->_options['soft'])) { + $this->log(0, 'WARNING: dependencies failed'); + } + } + } + + /** + * Retrieve the directory that downloads will happen in + * @access private + * @return string + */ + function getDownloadDir() + { + if (isset($this->_downloadDir)) { + return $this->_downloadDir; + } + + $downloaddir = $this->config->get('download_dir'); + if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) { + if (is_dir($downloaddir) && !is_writable($downloaddir)) { + $this->log(0, 'WARNING: configuration download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir to avoid this warning'); + } + + if (!class_exists('System')) { + require_once 'System.php'; + } + + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(3, '+ tmp dir created at ' . $downloaddir); + } + + if (!is_writable($downloaddir)) { + if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) || + !is_writable($downloaddir)) { + return PEAR::raiseError('download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + + return $this->_downloadDir = $downloaddir; + } + + function setDownloadDir($dir) + { + if (!@is_writable($dir)) { + if (PEAR::isError(System::mkdir(array('-p', $dir)))) { + return PEAR::raiseError('download directory "' . $dir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + $this->_downloadDir = $dir; + } + + function configSet($key, $value, $layer = 'user', $channel = false) + { + $this->config->set($key, $value, $layer, $channel); + $this->_preferredState = $this->config->get('preferred_state', null, $channel); if (!$this->_preferredState) { // don't inadvertantly use a non-set preferred_state $this->_preferredState = null; } } - // }}} - // {{{ setOptions() function setOptions($options) { $this->_options = $options; } - // }}} - // {{{ _downloadFile() + function getOptions() + { + return $this->_options; + } + + /** - * @param string filename to download - * @param string version/state - * @param string original value passed to command-line - * @param string|null preferred state (snapshot/devel/alpha/beta/stable) - * Defaults to configuration preferred state - * @return null|PEAR_Error|string + * @param array output of {@link parsePackageName()} * @access private */ - function _downloadFile($pkgfile, $version, $origpkgfile, $state = null) + function _getPackageDownloadUrl($parr) { - if (is_null($state)) { - $state = $this->_preferredState; - } - // {{{ check the package filename, and whether it's already installed - $need_download = false; - if (preg_match('#^(http|ftp)://#', $pkgfile)) { - $need_download = true; - } elseif (!@is_file($pkgfile)) { - if ($this->validPackageName($pkgfile)) { - if ($this->_registry->packageExists($pkgfile)) { - if (empty($this->_options['upgrade']) && empty($this->_options['force'])) { - $errors[] = "$pkgfile already installed"; - return; - } + $curchannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $parr['channel']); + // getDownloadURL returns an array. On error, it only contains information + // on the latest release as array(version, info). On success it contains + // array(version, info, download url string) + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (!$this->_registry->channelExists($parr['channel'])) { + do { + if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) { + break; } - $pkgfile = $this->getPackageDownloadUrl($pkgfile, $version); - $need_download = true; - } else { - if (strlen($pkgfile)) { - $errors[] = "Could not open the package file: $pkgfile"; - } else { - $errors[] = "No package file given"; - } - return; - } - } - // }}} - // {{{ Download package ----------------------------------------------- - if ($need_download) { - $downloaddir = $this->_config->get('download_dir'); - if (empty($downloaddir)) { - if (PEAR::isError($downloaddir = System::mktemp('-d'))) { - return $downloaddir; - } - $this->log(3, '+ tmp dir created at ' . $downloaddir); - } - $callback = $this->ui ? array(&$this, '_downloadCallback') : null; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); - $this->popErrorHandling(); - if (PEAR::isError($file)) { - if ($this->validPackageName($origpkgfile)) { - if (!PEAR::isError($info = $this->_remote->call('package.info', - $origpkgfile))) { - if (!count($info['releases'])) { - return $this->raiseError('Package ' . $origpkgfile . - ' has no releases'); - } else { - return $this->raiseError('No releases of preferred state "' - . $state . '" exist for package ' . $origpkgfile . - '. Use ' . $origpkgfile . '-state to install another' . - ' state (like ' . $origpkgfile .'-beta)', - PEAR_INSTALLER_ERROR_NO_PREF_STATE); - } - } else { - return $pkgfile; - } - } else { - return $this->raiseError($file); - } - } - $pkgfile = $file; + $this->configSet('default_channel', $curchannel); + return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']); + } while (false); } - // }}} - return $pkgfile; + + $chan = &$this->_registry->getChannel($parr['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']); + $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']); + // package is installed - use the installed release stability level + if (!isset($parr['state']) && $stability !== null) { + $state = $stability['release']; + } + PEAR::staticPopErrorHandling(); + $base2 = false; + + $preferred_mirror = $this->config->get('preferred_mirror'); + if (!$chan->supportsREST($preferred_mirror) || + ( + !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror)) + && + !($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + if ($base2) { + $rest = &$this->config->getREST('1.3', $this->_options); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $downloadVersion = false; + if (!isset($parr['version']) && !isset($parr['state']) && $version + && !PEAR::isError($version) + && !isset($this->_options['downloadonly']) + ) { + $downloadVersion = $version; + } + + $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName()); + if (PEAR::isError($url)) { + $this->configSet('default_channel', $curchannel); + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (!isset($this->_options['force']) && + !isset($this->_options['downloadonly']) && + $version && + !PEAR::isError($version) && + !isset($parr['group']) + ) { + if (version_compare($version, $url['version'], '=')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is the same as the ' . + 'released version ' . $url['version'], -976); + } + + if (version_compare($version, $url['version'], '>')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is newer than detected ' . + 'released version ' . $url['version'], -976); + } + } + + if (isset($url['info']['required']) || $url['compatible']) { + require_once 'PEAR/PackageFile/v2.php'; + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($parr['channel']); + if ($url['compatible']) { + $pf->setRawCompatible($url['compatible']); + } + } else { + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + } + + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; } - // }}} - // {{{ getPackageDownloadUrl() - function getPackageDownloadUrl($package, $version = null) + /** + * @param array dependency array + * @access private + */ + function _getDepPackageDownloadUrl($dep, $parr) + { + $xsdversion = isset($dep['rel']) ? '1.0' : '2.0'; + $curchannel = $this->config->get('default_channel'); + if (isset($dep['uri'])) { + $xsdversion = '2.0'; + $chan = &$this->_registry->getChannel('__uri'); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri'); + $this->configSet('default_channel', '__uri'); + } else { + if (isset($dep['channel'])) { + $remotechannel = $dep['channel']; + } else { + $remotechannel = 'pear.php.net'; + } + + if (!$this->_registry->channelExists($remotechannel)) { + do { + if ($this->config->get('auto_discover')) { + if ($this->discover($remotechannel)) { + break; + } + } + return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); + } while (false); + } + + $chan = &$this->_registry->getChannel($remotechannel); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel); + $this->configSet('default_channel', $remotechannel); + } + + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (isset($parr['state']) && isset($parr['version'])) { + unset($parr['state']); + } + + if (isset($dep['uri'])) { + $info = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $info->initialize($dep); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + return PEAR::raiseError('Cannot initialize dependency'); + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $err->getMessage()); + } + + if (is_object($info)) { + $param = $info->getChannel() . '/' . $info->getPackage(); + } + return PEAR::raiseError('Package "' . $param . '" is not valid'); + } + return $info; + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) + && + ( + ($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror'))) + || + ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) + ) + ) { + if ($base2) { + $base = $base2; + $rest = &$this->config->getREST('1.3', $this->_options); + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr, + $state, $version, $chan->getName()); + if (PEAR::isError($url)) { + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (isset($url['info']['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($remotechannel); + } else { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + + } + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + /** + * @deprecated in favor of _getPackageDownloadUrl + */ + function getPackageDownloadUrl($package, $version = null, $channel = false) { if ($version) { $package .= "-$version"; } - if ($this === null || $this->_config === null) { + if ($this === null || $this->_registry === null) { $package = "http://pear.php.net/get/$package"; } else { - $package = "http://" . $this->_config->get('master_server') . - "/get/$package"; + $chan = $this->_registry->getChannel($channel); + if (PEAR::isError($chan)) { + return ''; + } + $package = "http://" . $chan->getServer() . "/get/$package"; } if (!extension_loaded("zlib")) { $package .= '?uncompress=yes'; @@ -278,28 +1084,6 @@ class PEAR_Downloader extends PEAR_Common return $package; } - // }}} - // {{{ extractDownloadFileName($pkgfile, &$version) - - function extractDownloadFileName($pkgfile, &$version) - { - if (@is_file($pkgfile)) { - return $pkgfile; - } - // regex defined in Common.php - if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) { - $version = (isset($m[3])) ? $m[3] : null; - return $m[1]; - } - $version = null; - return $pkgfile; - } - - // }}} - - // }}} - // {{{ getDownloadedPackages() - /** * Retrieve a list of downloaded packages after a call to {@link download()}. * @@ -314,298 +1098,6 @@ class PEAR_Downloader extends PEAR_Common return $ret; } - // }}} - // {{{ download() - - /** - * Download any files and their dependencies, if necessary - * - * BC-compatible method name - * @param array a mixed list of package names, local files, or package.xml - */ - function download($packages) - { - return $this->doDownload($packages); - } - - // }}} - // {{{ doDownload() - - /** - * Download any files and their dependencies, if necessary - * - * @param array a mixed list of package names, local files, or package.xml - */ - function doDownload($packages) - { - $mywillinstall = array(); - $state = $this->_preferredState; - - // {{{ download files in this list if necessary - foreach($packages as $pkgfile) { - $need_download = false; - if (!is_file($pkgfile)) { - if (preg_match('#^(http|ftp)://#', $pkgfile)) { - $need_download = true; - } - $pkgfile = $this->_downloadNonFile($pkgfile); - if (PEAR::isError($pkgfile)) { - return $pkgfile; - } - if ($pkgfile === false) { - continue; - } - } // end is_file() - - $tempinfo = $this->infoFromAny($pkgfile); - if ($need_download) { - $this->_toDownload[] = $tempinfo['package']; - } - if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { - // ignore dependencies if there are any errors - if (!PEAR::isError($tempinfo)) { - $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps']; - } - } - $this->_downloadedPackages[] = array('pkg' => $tempinfo['package'], - 'file' => $pkgfile, 'info' => $tempinfo); - } // end foreach($packages) - // }}} - - // {{{ extract dependencies from downloaded files and then download - // them if necessary - if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { - $deppackages = array(); - foreach ($mywillinstall as $package => $alldeps) { - if (!is_array($alldeps)) { - // there are no dependencies - continue; - } - $fail = false; - foreach ($alldeps as $info) { - if ($info['type'] != 'pkg') { - continue; - } - $ret = $this->_processDependency($package, $info, $mywillinstall); - if ($ret === false) { - continue; - } - if ($ret === 0) { - $fail = true; - continue; - } - if (PEAR::isError($ret)) { - return $ret; - } - $deppackages[] = $ret; - } // foreach($alldeps - if ($fail) { - $deppackages = array(); - } - } - - if (count($deppackages)) { - $this->doDownload($deppackages); - } - } // }}} if --alldeps or --onlyreqdeps - } - - // }}} - // {{{ _downloadNonFile($pkgfile) - - /** - * @return false|PEAR_Error|string false if loop should be broken out of, - * string if the file was downloaded, - * PEAR_Error on exception - * @access private - */ - function _downloadNonFile($pkgfile) - { - $origpkgfile = $pkgfile; - $state = null; - $pkgfile = $this->extractDownloadFileName($pkgfile, $version); - if (preg_match('#^(http|ftp)://#', $pkgfile)) { - return $this->_downloadFile($pkgfile, $version, $origpkgfile); - } - if (!$this->validPackageName($pkgfile)) { - return $this->raiseError("Package name '$pkgfile' not valid"); - } - // ignore packages that are installed unless we are upgrading - $curinfo = $this->_registry->packageInfo($pkgfile); - if ($this->_registry->packageExists($pkgfile) - && empty($this->_options['upgrade']) && empty($this->_options['force'])) { - $this->log(0, "Package '{$curinfo['package']}' already installed, skipping"); - return false; - } - if (in_array($pkgfile, $this->_toDownload)) { - return false; - } - $releases = $this->_remote->call('package.info', $pkgfile, 'releases', true); - if (!count($releases)) { - return $this->raiseError("No releases found for package '$pkgfile'"); - } - // Want a specific version/state - if ($version !== null) { - // Passed Foo-1.2 - if ($this->validPackageVersion($version)) { - if (!isset($releases[$version])) { - return $this->raiseError("No release with version '$version' found for '$pkgfile'"); - } - // Passed Foo-alpha - } elseif (in_array($version, $this->getReleaseStates())) { - $state = $version; - $version = 0; - foreach ($releases as $ver => $inf) { - if ($inf['state'] == $state && version_compare("$version", "$ver") < 0) { - $version = $ver; - break; - } - } - if ($version == 0) { - return $this->raiseError("No release with state '$state' found for '$pkgfile'"); - } - // invalid suffix passed - } else { - return $this->raiseError("Invalid suffix '-$version', be sure to pass a valid PEAR ". - "version number or release state"); - } - // Guess what to download - } else { - $states = $this->betterStates($this->_preferredState, true); - $possible = false; - $version = 0; - $higher_version = 0; - $prev_hi_ver = 0; - foreach ($releases as $ver => $inf) { - if (in_array($inf['state'], $states) && version_compare("$version", "$ver") < 0) { - $version = $ver; - break; - } else { - $ver = (string)$ver; - if (version_compare($prev_hi_ver, $ver) < 0) { - $prev_hi_ver = $higher_version = $ver; - } - } - } - if ($version === 0 && !isset($this->_options['force'])) { - return $this->raiseError('No release with state equal to: \'' . implode(', ', $states) . - "' found for '$pkgfile'"); - } elseif ($version === 0) { - $this->log(0, "Warning: $pkgfile is state '" . $releases[$higher_version]['state'] . "' which is less stable " . - "than state '$this->_preferredState'"); - } - } - // Check if we haven't already the version - if (empty($this->_options['force']) && !is_null($curinfo)) { - if ($curinfo['version'] == $version) { - $this->log(0, "Package '{$curinfo['package']}-{$curinfo['version']}' already installed, skipping"); - return false; - } elseif (version_compare("$version", "{$curinfo['version']}") < 0) { - $this->log(0, "Package '{$curinfo['package']}' version '{$curinfo['version']}' " . - " is installed and {$curinfo['version']} is > requested '$version', skipping"); - return false; - } - } - $this->_toDownload[] = $pkgfile; - return $this->_downloadFile($pkgfile, $version, $origpkgfile, $state); - } - - // }}} - // {{{ _processDependency($package, $info, $mywillinstall) - - /** - * Process a dependency, download if necessary - * @param array dependency information from PEAR_Remote call - * @param array packages that will be installed in this iteration - * @return false|string|PEAR_Error - * @access private - * @todo Add test for relation 'lt'/'le' -> make sure that the dependency requested is - * in fact lower than the required value. This will be very important for BC dependencies - */ - function _processDependency($package, $info, $mywillinstall) - { - $state = $this->_preferredState; - if (!isset($this->_options['alldeps']) && isset($info['optional']) && - $info['optional'] == 'yes') { - // skip optional deps - $this->log(0, "skipping Package '$package' optional dependency '$info[name]'"); - return false; - } - // {{{ get releases - $releases = $this->_remote->call('package.info', $info['name'], 'releases', true); - if (PEAR::isError($releases)) { - return $releases; - } - if (!count($releases)) { - if (!isset($this->_installed[strtolower($info['name'])])) { - $this->pushError("Package '$package' dependency '$info[name]' ". - "has no releases"); - } - return false; - } - $found = false; - $save = $releases; - while(count($releases) && !$found) { - if (!empty($state) && $state != 'any') { - list($release_version, $release) = each($releases); - if ($state != $release['state'] && - !in_array($release['state'], $this->betterStates($state))) - { - // drop this release - it ain't stable enough - array_shift($releases); - } else { - $found = true; - } - } else { - $found = true; - } - } - if (!count($releases) && !$found) { - $get = array(); - foreach($save as $release) { - $get = array_merge($get, - $this->betterStates($release['state'], true)); - } - $savestate = array_shift($get); - $this->pushError( "Release for '$package' dependency '$info[name]' " . - "has state '$savestate', requires '$state'"); - return 0; - } - if (in_array(strtolower($info['name']), $this->_toDownload) || - isset($mywillinstall[strtolower($info['name'])])) { - // skip upgrade check for packages we will install - return false; - } - if (!isset($this->_installed[strtolower($info['name'])])) { - // check to see if we can install the specific version required - if ($info['rel'] == 'eq') { - return $info['name'] . '-' . $info['version']; - } - // skip upgrade check for packages we don't have installed - return $info['name']; - } - // }}} - - // {{{ see if a dependency must be upgraded - $inst_version = $this->_registry->packageInfo($info['name'], 'version'); - if (!isset($info['version'])) { - // this is a rel='has' dependency, check against latest - if (version_compare($release_version, $inst_version, 'le')) { - return false; - } else { - return $info['name']; - } - } - if (version_compare($info['version'], $inst_version, 'le')) { - // installed version is up-to-date - return false; - } - return $info['name']; - } - - // }}} - // {{{ _downloadCallback() - function _downloadCallback($msg, $params = null) { switch ($msg) { @@ -626,29 +1118,34 @@ class PEAR_Downloader extends PEAR_Common $bytes += $params; break; case 'start': - $this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)"); + if($params[1] == -1) { + $length = "Unknown size"; + } else { + $length = number_format($params[1], 0, '', ',')." bytes"; + } + $this->log(1, "Starting to download {$params[0]} ($length)"); break; } if (method_exists($this->ui, '_downloadCallback')) $this->ui->_downloadCallback($msg, $params); } - // }}} - // {{{ _prependPath($path, $prepend) - function _prependPath($path, $prepend) { if (strlen($prepend) > 0) { if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { - $path = $prepend . substr($path, 2); + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); } else { $path = $prepend . $path; } } return $path; } - // }}} - // {{{ pushError($errmsg, $code) /** * @param string @@ -659,9 +1156,6 @@ class PEAR_Downloader extends PEAR_Common array_push($this->_errorStack, array($errmsg, $code)); } - // }}} - // {{{ getErrorMsgs() - function getErrorMsgs() { $msgs = array(); @@ -673,8 +1167,600 @@ class PEAR_Downloader extends PEAR_Common return $msgs; } - // }}} -} -// }}} + /** + * for BC + * + * @deprecated + */ + function sortPkgDeps(&$packages, $uninstall = false) + { + $uninstall ? + $this->sortPackagesForUninstall($packages) : + $this->sortPackagesForInstall($packages); + } -?> + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * This uses the topological sort method from graph theory, and the + * Structures_Graph package to properly sort dependencies for installation. + * @param array an array of downloaded PEAR_Downloader_Packages + * @return array array of array(packagefilename, package.xml contents) + */ + function sortPackagesForInstall(&$packages) + { + require_once 'Structures/Graph.php'; + require_once 'Structures/Graph/Node.php'; + require_once 'Structures/Graph/Manipulator/TopologicalSorter.php'; + $depgraph = new Structures_Graph(true); + $nodes = array(); + $reg = &$this->config->getRegistry(); + foreach ($packages as $i => $package) { + $pname = $reg->parsedPackageNameToString( + array( + 'channel' => $package->getChannel(), + 'package' => strtolower($package->getPackage()), + )); + $nodes[$pname] = new Structures_Graph_Node; + $nodes[$pname]->setData($packages[$i]); + $depgraph->addNode($nodes[$pname]); + } + + $deplinks = array(); + foreach ($nodes as $package => $node) { + $pf = &$node->getData(); + $pdeps = $pf->getDeps(true); + if (!$pdeps) { + continue; + } + + if ($pf->getPackagexmlVersion() == '1.0') { + foreach ($pdeps as $dep) { + if ($dep['type'] != 'pkg' || + (isset($dep['optional']) && $dep['optional'] == 'yes')) { + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pear.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pecl.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + } + } else { + // the only ordering we care about is: + // 1) subpackages must be installed before packages that depend on them + // 2) required deps must be installed before packages that depend on them + if (isset($pdeps['required']['subpackage'])) { + $t = $pdeps['required']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['subpackage'])) { + $t = $group['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + + if (isset($pdeps['optional']['subpackage'])) { + $t = $pdeps['optional']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['required']['package'])) { + $t = $pdeps['required']['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['package'])) { + $t = $group['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + } + } + + $this->_detectDepCycle($deplinks); + foreach ($deplinks as $dependent => $parents) { + foreach ($parents as $parent => $unused) { + $nodes[$dependent]->connectTo($nodes[$parent]); + } + } + + $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph); + $ret = array(); + for ($i = 0, $count = count($installOrder); $i < $count; $i++) { + foreach ($installOrder[$i] as $index => $sortedpackage) { + $data = &$installOrder[$i][$index]->getData(); + $ret[] = &$nodes[$reg->parsedPackageNameToString( + array( + 'channel' => $data->getChannel(), + 'package' => strtolower($data->getPackage()), + ))]->getData(); + } + } + + $packages = $ret; + return; + } + + /** + * Detect recursive links between dependencies and break the cycles + * + * @param array + * @access private + */ + function _detectDepCycle(&$deplinks) + { + do { + $keepgoing = false; + foreach ($deplinks as $dep => $parents) { + foreach ($parents as $parent => $unused) { + // reset the parent cycle detector + $this->_testCycle(null, null, null); + if ($this->_testCycle($dep, $deplinks, $parent)) { + $keepgoing = true; + unset($deplinks[$dep][$parent]); + if (count($deplinks[$dep]) == 0) { + unset($deplinks[$dep]); + } + + continue 3; + } + } + } + } while ($keepgoing); + } + + function _testCycle($test, $deplinks, $dep) + { + static $visited = array(); + if ($test === null) { + $visited = array(); + return; + } + + // this happens when a parent has a dep cycle on another dependency + // but the child is not part of the cycle + if (isset($visited[$dep])) { + return false; + } + + $visited[$dep] = 1; + if ($test == $dep) { + return true; + } + + if (isset($deplinks[$dep])) { + if (in_array($test, array_keys($deplinks[$dep]), true)) { + return true; + } + + foreach ($deplinks[$dep] as $parent => $unused) { + if ($this->_testCycle($test, $deplinks, $parent)) { + return true; + } + } + } + + return false; + } + + /** + * Set up the dependency for installation parsing + * + * @param array $t dependency information + * @param PEAR_Registry $reg + * @param array $deplinks list of dependency links already established + * @param array $nodes all existing package nodes + * @param string $package parent package name + * @access private + */ + function _setupGraph($t, $reg, &$deplinks, &$nodes, $package) + { + foreach ($t as $dep) { + $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel']; + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => $depchannel, + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + $deplinks[$dname][$package] = 1; + } + } + } + + function _dependsOn($a, $b) + { + return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b); + } + + function _checkDepTree($channel, $package, $b, $checked = array()) + { + $checked[$channel][$package] = true; + if (!isset($this->_depTree[$channel][$package])) { + return false; + } + + if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())] + [strtolower($b->getPackage())])) { + return true; + } + + foreach ($this->_depTree[$channel][$package] as $ch => $packages) { + foreach ($packages as $pa => $true) { + if ($this->_checkDepTree($ch, $pa, $b, $checked)) { + return true; + } + } + } + + return false; + } + + function _sortInstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return 1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return -1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependsOn($a, $b)) { + return 1; + } + if ($this->_dependsOn($b, $a)) { + return -1; + } + return 0; + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP/SSL connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir directory to save file in + * @param mixed $callback function/method to call for status + * updates + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @param false|string $channel Channel to use for retrieving authentication + * @return string|array Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + if ($callback) { + call_user_func($callback, 'setup', array(&$ui)); + } + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + + if (isset($this)) { + $config = &$this->config; + } else { + $config = &PEAR_Config::singleton(); + } + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($config->get('http_proxy') && + $proxy = parse_url($config->get('http_proxy'))) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + + if ($callback) { + call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); + } + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $url HTTP/1.1\r\n"; + $request .= "Host: $host\r\n"; + } else { + $request = "GET $url HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } else { + $network_host = $host; + if (isset($info['scheme']) && $info['scheme'] == 'https') { + $network_host = 'ssl://' . $host; + } + + $fp = @fsockopen($network_host, $port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($host, $port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $path HTTP/1.1\r\n"; + $request .= "Host: $host\r\n"; + } else { + $request = "GET $path HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } + + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n"; + + if (isset($this)) { // only pass in authentication for non-static calls + $username = $config->get('username', null, $channel); + $password = $config->get('password', null, $channel); + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + fwrite($fp, $request); + $headers = array(); + $reply = 0; + while (trim($line = fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], + $ui, $save_dir, $callback, $lastmodified, $accept); + } + + if (isset($headers['content-disposition']) && + preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) { + $save_as = basename($matches[1]); + } else { + $save_as = basename($url); + } + + if ($callback) { + $tmp = call_user_func($callback, 'saveas', $save_as); + if ($tmp) { + $save_as = $tmp; + } + } + + $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; + if (is_link($dest_file)) { + return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack'); + } + + if (!$wp = @fopen($dest_file, 'wb')) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("could not open $dest_file for writing"); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $bytes = 0; + if ($callback) { + call_user_func($callback, 'start', array(basename($dest_file), $length)); + } + + while ($data = fread($fp, 1024)) { + $bytes += strlen($data); + if ($callback) { + call_user_func($callback, 'bytesread', $bytes); + } + if (!@fwrite($wp, $data)) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); + } + } + + fclose($fp); + fclose($wp); + if ($callback) { + call_user_func($callback, 'done', $bytes); + } + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + return array($dest_file, $lastmodified, $headers); + } + return $dest_file; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Downloader/Package.php b/3rdparty/PEAR/Downloader/Package.php new file mode 100644 index 0000000000..987c965675 --- /dev/null +++ b/3rdparty/PEAR/Downloader/Package.php @@ -0,0 +1,1988 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Error code when parameter initialization fails because no releases + * exist within preferred_state, but releases do exist + */ +define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003); +/** + * Error code when parameter initialization fails because no releases + * exist that will work with the existing PHP version + */ +define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004); + +/** + * Coordinates download parameters and manages their dependencies + * prior to downloading them. + * + * Input can come from three sources: + * + * - local files (archives or package.xml) + * - remote files (downloadable urls) + * - abstract package names + * + * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires + * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the + * format returned of dependencies is slightly different from that used in package.xml. + * + * This class hides the differences between these elements, and makes automatic + * dependency resolution a piece of cake. It also manages conflicts when + * two classes depend on incompatible dependencies, or differing versions of the same + * package dependency. In addition, download will not be attempted if the php version is + * not supported, PEAR installer version is not supported, or non-PECL extensions are not + * installed. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Downloader_Package +{ + /** + * @var PEAR_Downloader + */ + var $_downloader; + /** + * @var PEAR_Config + */ + var $_config; + /** + * @var PEAR_Registry + */ + var $_registry; + /** + * Used to implement packagingroot properly + * @var PEAR_Registry + */ + var $_installRegistry; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2 + */ + var $_packagefile; + /** + * @var array + */ + var $_parsedname; + /** + * @var array + */ + var $_downloadURL; + /** + * @var array + */ + var $_downloadDeps = array(); + /** + * @var boolean + */ + var $_valid = false; + /** + * @var boolean + */ + var $_analyzed = false; + /** + * if this or a parent package was invoked with Package-state, this is set to the + * state variable. + * + * This allows temporary reassignment of preferred_state for a parent package and all of + * its dependencies. + * @var string|false + */ + var $_explicitState = false; + /** + * If this package is invoked with Package#group, this variable will be true + */ + var $_explicitGroup = false; + /** + * Package type local|url + * @var string + */ + var $_type; + /** + * Contents of package.xml, if downloaded from a remote channel + * @var string|false + * @access private + */ + var $_rawpackagefile; + /** + * @var boolean + * @access private + */ + var $_validated = false; + + /** + * @param PEAR_Downloader + */ + function PEAR_Downloader_Package(&$downloader) + { + $this->_downloader = &$downloader; + $this->_config = &$this->_downloader->config; + $this->_registry = &$this->_config->getRegistry(); + $options = $downloader->getOptions(); + if (isset($options['packagingroot'])) { + $this->_config->setInstallRoot($options['packagingroot']); + $this->_installRegistry = &$this->_config->getRegistry(); + $this->_config->setInstallRoot(false); + } else { + $this->_installRegistry = &$this->_registry; + } + $this->_valid = $this->_analyzed = false; + } + + /** + * Parse the input and determine whether this is a local file, a remote uri, or an + * abstract package name. + * + * This is the heart of the PEAR_Downloader_Package(), and is used in + * {@link PEAR_Downloader::download()} + * @param string + * @return bool|PEAR_Error + */ + function initialize($param) + { + $origErr = $this->_fromFile($param); + if ($this->_valid) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($options['offline'])) { + if (PEAR::isError($origErr) && !isset($options['soft'])) { + foreach ($origErr->getUserInfo() as $userInfo) { + if (isset($userInfo['message'])) { + $this->_downloader->log(0, $userInfo['message']); + } + } + + $this->_downloader->log(0, $origErr->getMessage()); + } + + return PEAR::raiseError('Cannot download non-local package "' . $param . '"'); + } + + $err = $this->_fromUrl($param); + if (PEAR::isError($err) || !$this->_valid) { + if ($this->_type == 'url') { + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError("Invalid or missing remote package file"); + } + + $err = $this->_fromString($param); + if (PEAR::isError($err) || !$this->_valid) { + if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) { + return false; // instruct the downloader to silently skip + } + + if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) { + if (is_array($origErr->getUserInfo())) { + foreach ($origErr->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $origErr->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + } + + return true; + } + + /** + * Retrieve any non-local packages + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error + */ + function &download() + { + if (isset($this->_packagefile)) { + return $this->_packagefile; + } + + if (isset($this->_downloadURL['url'])) { + $this->_isvalid = false; + $info = $this->getParsedPackage(); + foreach ($info as $i => $p) { + $info[$i] = strtolower($p); + } + + $err = $this->_fromUrl($this->_downloadURL['url'], + $this->_registry->parsedPackageNameToString($this->_parsedname, true)); + $newinfo = $this->getParsedPackage(); + foreach ($newinfo as $i => $p) { + $newinfo[$i] = strtolower($p); + } + + if ($info != $newinfo) { + do { + if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') { + $info['channel'] = 'pear.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') { + $info['channel'] = 'pecl.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + + return PEAR::raiseError('CRITICAL ERROR: We are ' . + $this->_registry->parsedPackageNameToString($info) . ', but the file ' . + 'downloaded claims to be ' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage())); + } while (false); + } + + if (PEAR::isError($err) || !$this->_valid) { + return $err; + } + } + + $this->_type = 'local'; + return $this->_packagefile; + } + + function &getPackageFile() + { + return $this->_packagefile; + } + + function &getDownloader() + { + return $this->_downloader; + } + + function getType() + { + return $this->_type; + } + + /** + * Like {@link initialize()}, but operates on a dependency + */ + function fromDepURL($dep) + { + $this->_downloadURL = $dep; + if (isset($dep['uri'])) { + $options = $this->_downloader->getOptions(); + if (!extension_loaded("zlib") || isset($options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->_fromUrl($dep['uri'] . $ext); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' . + 'cannot download'); + } + } else { + $this->_parsedname = + array( + 'package' => $dep['info']->getPackage(), + 'channel' => $dep['info']->getChannel(), + 'version' => $dep['version'] + ); + if (!isset($dep['nodefault'])) { + $this->_parsedname['group'] = 'default'; // download the default dependency group + $this->_explicitGroup = false; + } + + $this->_rawpackagefile = $dep['raw']; + } + } + + function detectDependencies($params) + { + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + return; + } + + if (isset($options['offline'])) { + $this->_downloader->log(3, 'Skipping dependency download check, --offline specified'); + return; + } + + $pname = $this->getParsedPackage(); + if (!$pname) { + return; + } + + $deps = $this->getDeps(); + if (!$deps) { + return; + } + + if (isset($deps['required'])) { // package.xml 2.0 + return $this->_detect2($deps, $pname, $options, $params); + } + + return $this->_detect1($deps, $pname, $options, $params); + } + + function setValidated() + { + $this->_validated = true; + } + + function alreadyValidated() + { + return $this->_validated; + } + + /** + * Remove packages to be downloaded that are already installed + * @param array of PEAR_Downloader_Package objects + * @static + */ + function removeInstalled(&$params) + { + if (!isset($params[0])) { + return; + } + + $options = $params[0]->_downloader->getOptions(); + if (!isset($options['downloadonly'])) { + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + // remove self if already installed with this version + // this does not need any pecl magic - we only remove exact matches + if ($param->_installRegistry->packageExists($package, $channel)) { + $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $param->getVersion(), '==')) { + if (!isset($options['force'])) { + $info = $param->getParsedPackage(); + unset($info['version']); + unset($info['state']); + if (!isset($options['soft'])) { + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + } + $params[$i] = false; + } + } elseif (!isset($options['force']) && !isset($options['upgrade']) && + !isset($options['soft'])) { + $info = $param->getParsedPackage(); + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + $params[$i] = false; + } + } + } + } + + PEAR_Downloader_Package::removeDuplicates($params); + } + + function _detect2($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $groupnotfound = false; + foreach (array('package', 'subpackage') as $packagetype) { + // get required dependency group + if (isset($deps['required'][$packagetype])) { + if (isset($deps['required'][$packagetype][0])) { + foreach ($deps['required'][$packagetype] as $dep) { + if (isset($dep['conflicts'])) { + // skip any package that this package conflicts with + continue; + } + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $dep = $deps['required'][$packagetype]; + if (!isset($dep['conflicts'])) { + // skip any package that this package conflicts with + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + + // get optional dependency group, if any + if (isset($deps['optional'][$packagetype])) { + $skipnames = array(); + if (!isset($deps['optional'][$packagetype][0])) { + $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]); + } + + foreach ($deps['optional'][$packagetype] as $dep) { + $skip = false; + if (!isset($options['alldeps'])) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage(), + true) . '" optional dependency "' . + $this->_registry->parsedPackageNameToString(array('package' => + $dep['name'], 'channel' => 'pear.php.net'), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true); + $skip = true; + unset($dep['package']); + } + + $ret = $this->_detect2Dep($dep, $pname, 'optional', $params); + if (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + if (!$ret) { + $dep['package'] = $dep['name']; + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + } + + if (!$skip && is_array($ret)) { + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download optional dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps to download automatically'); + } + } + } + + // get requested dependency group, if any + $groupname = $this->getGroup(); + $explicit = $this->_explicitGroup; + if (!$groupname) { + if (!$this->canDefault()) { + continue; + } + + $groupname = 'default'; // try the default dependency group + } + + if ($groupnotfound) { + continue; + } + + if (isset($deps['group'])) { + if (isset($deps['group']['attribs'])) { + if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) { + $group = $deps['group']; + } elseif ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + + $groupnotfound = true; + continue; + } + } else { + $found = false; + foreach ($deps['group'] as $group) { + if (strtolower($group['attribs']['name']) == strtolower($groupname)) { + $found = true; + break; + } + } + + if (!$found) { + if ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + } + + $groupnotfound = true; + continue; + } + } + } + + if (isset($group) && isset($group[$packagetype])) { + if (isset($group[$packagetype][0])) { + foreach ($group[$packagetype] as $dep) { + $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $ret = $this->_detect2Dep($group[$packagetype], $pname, + 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + } + + function _detect2Dep($dep, $pname, $group, $params) + { + if (isset($dep['conflicts'])) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($dep['uri'])) { + return array('uri' => $dep['uri'], 'dep' => $dep);; + } + + $testdep = $dep; + $testdep['package'] = $dep['name']; + if (PEAR_Downloader_Package::willDownload($testdep, $params)) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + } + return false; + } + + $options = $this->_downloader->getOptions(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + return $url; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + return false; + } + + // check to see if a dep is already installed and is the same or newer + if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) { + $oper = 'has'; + } else { + $oper = 'gt'; + } + + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled($ret, $oper)) { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']); + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '" version ' . $url['version'] . ', already installed as version ' . + $version); + } + + return false; + } + + if (isset($dep['nodefault'])) { + $ret['nodefault'] = true; + } + + return $ret; + } + + function _detect1($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $skipnames = array(); + foreach ($deps as $dep) { + $nodownload = false; + if (isset ($dep['type']) && $dep['type'] === 'pkg') { + $dep['channel'] = 'pear.php.net'; + $dep['package'] = $dep['name']; + switch ($dep['rel']) { + case 'not' : + continue 2; + case 'ge' : + case 'eq' : + case 'gt' : + case 'has' : + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + if (PEAR_Downloader_Package::willDownload($dep, $params)) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + continue 2; + } + $fakedp = new PEAR_PackageFile_v1; + $fakedp->setPackage($dep['name']); + // skip internet check if we are not upgrading (bug #5810) + if (!isset($options['upgrade']) && $this->isInstalled( + $fakedp, $dep['rel'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", is already installed'); + continue 2; + } + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + $chan = 'pear.php.net'; + if (PEAR::isError($url)) { + // check to see if this is a pecl package that has jumped + // from pear.php.net to pecl.php.net channel + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($dep); + $newdep = $newdep[0]; + $newdep['channel'] = 'pecl.php.net'; + $chan = 'pecl.php.net'; + $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname); + $obj = &$this->_installRegistry->getPackage($dep['name']); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . + ': Skipping ' . $group . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $obj->getVersion()); + } + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + continue; + } else { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + $this->_downloader->log(2, $this->getShortName() . + ': Skipping optional dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", no releases exist'); + continue; + } else { + return $url; + } + } + } + } + + PEAR::popErrorHandling(); + if (!isset($options['alldeps'])) { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" optional dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" required dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + // check to see if a dep is already installed + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled( + $url, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (isset($newdep)) { + $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']); + } else { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version'); + } + + $dep['version'] = $url['version']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $version); + } + + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + + continue; + } + + if ($nodownload) { + continue; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (isset($newdep)) { + $dep = $newdep; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, + isset($dep['optional']) && $dep['optional'] == 'yes' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + continue; + } + + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps or --onlyreqdeps to download automatically'); + } + } + } + + function setDownloadURL($pkg) + { + $this->_downloadURL = $pkg; + } + + /** + * Set the package.xml object for this downloaded package + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg + */ + function setPackageFile(&$pkg) + { + $this->_packagefile = &$pkg; + } + + function getShortName() + { + return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(), + 'package' => $this->getPackage()), true); + } + + function getParsedPackage() + { + if (isset($this->_packagefile) || isset($this->_parsedname)) { + return array('channel' => $this->getChannel(), + 'package' => $this->getPackage(), + 'version' => $this->getVersion()); + } + + return false; + } + + function getDownloadURL() + { + return $this->_downloadURL; + } + + function canDefault() + { + if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) { + return false; + } + + return true; + } + + function getPackage() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackage(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackage(); + } + + return false; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function isSubpackage(&$pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isSubpackage($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isSubpackage($pf); + } + + return false; + } + + function getPackageType() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackageType(); + } + + return false; + } + + function isBundle() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType() == 'bundle'; + } + + return false; + } + + function getPackageXmlVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackagexmlVersion(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackagexmlVersion(); + } + + return '1.0'; + } + + function getChannel() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getChannel(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getChannel(); + } + + return false; + } + + function getURI() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getURI(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getURI(); + } + + return false; + } + + function getVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getVersion(); + } elseif (isset($this->_downloadURL['version'])) { + return $this->_downloadURL['version']; + } + + return false; + } + + function isCompatible($pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isCompatible($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isCompatible($pf); + } + + return true; + } + + function setGroup($group) + { + $this->_parsedname['group'] = $group; + } + + function getGroup() + { + if (isset($this->_parsedname['group'])) { + return $this->_parsedname['group']; + } + + return ''; + } + + function isExtension($name) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isExtension($name); + } elseif (isset($this->_downloadURL['info'])) { + if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') { + return $this->_downloadURL['info']->getProvidesExtension() == $name; + } + + return false; + } + + return false; + } + + function getDeps() + { + if (isset($this->_packagefile)) { + $ver = $this->_packagefile->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_packagefile->getDeps(true); + } + + return $this->_packagefile->getDeps(); + } elseif (isset($this->_downloadURL['info'])) { + $ver = $this->_downloadURL['info']->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_downloadURL['info']->getDeps(true); + } + + return $this->_downloadURL['info']->getDeps(); + } + + return array(); + } + + /** + * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency + * returned from getDepDownloadURL() + */ + function isEqual($param) + { + if (is_object($param)) { + $channel = $param->getChannel(); + $package = $param->getPackage(); + if ($param->getURI()) { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + 'uri' => $param->getURI(), + ); + } else { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + ); + } + } else { + if (isset($param['uri'])) { + if ($this->getChannel() != '__uri') { + return false; + } + return $param['uri'] == $this->getURI(); + } + + $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage(); + $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel(); + if (isset($param['rel'])) { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($param); + $newdep = $newdep[0]; + } elseif (isset($param['min'])) { + $newdep = $param; + } + } + + if (isset($newdep)) { + if (!isset($newdep['min'])) { + $newdep['min'] = '0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '100000000000000000000'; + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + // we need to support both dependency possibilities + if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pecl.php.net'; + } + } + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pear.php.net'; + } + } + + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + version_compare($newdep['min'], $this->getVersion(), '<=') && + version_compare($newdep['max'], $this->getVersion(), '>=')); + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if (strtolower($package) == strtolower($this->getPackage())) { + $channel = 'pear.php.net'; + } + } + + if (isset($param['version'])) { + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + $param['version'] == $this->getVersion()); + } + + return strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel(); + } + + function isInstalled($dep, $oper = '==') + { + if (!$dep) { + return false; + } + + if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') { + return false; + } + + if (is_object($dep)) { + $package = $dep->getPackage(); + $channel = $dep->getChannel(); + if ($dep->getURI()) { + $dep = array( + 'uri' => $dep->getURI(), + 'version' => $dep->getVersion(), + ); + } else { + $dep = array( + 'version' => $dep->getVersion(), + ); + } + } else { + if (isset($dep['uri'])) { + $channel = '__uri'; + $package = $dep['dep']['name']; + } else { + $channel = $dep['info']->getChannel(); + $package = $dep['info']->getPackage(); + } + } + + $options = $this->_downloader->getOptions(); + $test = $this->_installRegistry->packageExists($package, $channel); + if (!$test && $channel == 'pecl.php.net') { + // do magic to allow upgrading from old pecl packages to new ones + $test = $this->_installRegistry->packageExists($package, 'pear.php.net'); + $channel = 'pear.php.net'; + } + + if ($test) { + if (isset($dep['uri'])) { + if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) { + return true; + } + } + + if (isset($options['upgrade'])) { + $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $dep['version'], '>=')) { + return true; + } + + return false; + } + + return true; + } + + return false; + } + + /** + * Detect duplicate package names with differing versions + * + * If a user requests to install Date 1.4.6 and Date 1.4.7, + * for instance, this is a logic error. This method + * detects this situation. + * + * @param array $params array of PEAR_Downloader_Package objects + * @param array $errorparams empty array + * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts + */ + function detectStupidDuplicates($params, &$errorparams) + { + $existing = array(); + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + $group = $param->getGroup(); + if (!isset($existing[$channel . '/' . $package])) { + $existing[$channel . '/' . $package] = array(); + } + + if (!isset($existing[$channel . '/' . $package][$group])) { + $existing[$channel . '/' . $package][$group] = array(); + } + + $existing[$channel . '/' . $package][$group][] = $i; + } + + $indices = array(); + foreach ($existing as $package => $groups) { + foreach ($groups as $group => $dupes) { + if (count($dupes) > 1) { + $indices = $indices + $dupes; + } + } + } + + $indices = array_unique($indices); + foreach ($indices as $index) { + $errorparams[] = $params[$index]; + } + + return count($errorparams); + } + + /** + * @param array + * @param bool ignore install groups - for final removal of dupe packages + * @static + */ + function removeDuplicates(&$params, $ignoreGroups = false) + { + $pnames = array(); + foreach ($params as $i => $param) { + if (!$param) { + continue; + } + + if ($param->getPackage()) { + $group = $ignoreGroups ? '' : $param->getGroup(); + $pnames[$i] = $param->getChannel() . '/' . + $param->getPackage() . '-' . $param->getVersion() . '#' . $group; + } + } + + $pnames = array_unique($pnames); + $unset = array_diff(array_keys($params), array_keys($pnames)); + $testp = array_flip($pnames); + foreach ($params as $i => $param) { + if (!$param) { + $unset[] = $i; + continue; + } + + if (!is_a($param, 'PEAR_Downloader_Package')) { + $unset[] = $i; + continue; + } + + $group = $ignoreGroups ? '' : $param->getGroup(); + if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' . + $param->getVersion() . '#' . $group])) { + $unset[] = $i; + } + } + + foreach ($unset as $i) { + unset($params[$i]); + } + + $ret = array(); + foreach ($params as $i => $param) { + $ret[] = &$params[$i]; + } + + $params = array(); + foreach ($ret as $i => $param) { + $params[] = &$ret[$i]; + } + } + + function explicitState() + { + return $this->_explicitState; + } + + function setExplicitState($s) + { + $this->_explicitState = $s; + } + + /** + * @static + */ + function mergeDependencies(&$params) + { + $bundles = $newparams = array(); + foreach ($params as $i => $param) { + if (!$param->isBundle()) { + continue; + } + + $bundles[] = $i; + $pf = &$param->getPackageFile(); + $newdeps = array(); + $contents = $pf->getBundledPackages(); + if (!is_array($contents)) { + $contents = array($contents); + } + + foreach ($contents as $file) { + $filecontents = $pf->getFileContents($file); + $dl = &$param->getDownloader(); + $options = $dl->getOptions(); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + return $dir; + } + + $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb'); + if (!$fp) { + continue; + } + + // FIXME do symlink check + + fwrite($fp, $filecontents, strlen($filecontents)); + fclose($fp); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + PEAR::popErrorHandling(); + return $dir; + } + + $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $dl->log(0, $e->getMessage()); + } + continue; + } + + $j = &$obj; + if (!PEAR_Downloader_Package::willDownload($j, + array_merge($params, $newparams)) && !$param->isInstalled($j)) { + $newparams[] = &$j; + } + } + } + + foreach ($bundles as $i) { + unset($params[$i]); // remove bundles - only their contents matter for installation + } + + PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices + if (count($newparams)) { // add in bundled packages for install + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + $newparams = array(); + } + + foreach ($params as $i => $param) { + $newdeps = array(); + foreach ($param->_downloadDeps as $dep) { + $merge = array_merge($params, $newparams); + if (!PEAR_Downloader_Package::willDownload($dep, $merge) + && !$param->isInstalled($dep) + ) { + $newdeps[] = $dep; + } else { + //var_dump($dep); + // detect versioning conflicts here + } + } + + // convert the dependencies into PEAR_Downloader_Package objects for the next time around + $params[$i]->_downloadDeps = array(); + foreach ($newdeps as $dep) { + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $e = $obj->fromDepURL($dep); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + continue; + } + + $e = $obj->detectDependencies($params); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + } + + $j = &$obj; + $newparams[] = &$j; + } + } + + if (count($newparams)) { + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + return true; + } + + return false; + } + + + /** + * @static + */ + function willDownload($param, $params) + { + if (!is_array($params)) { + return false; + } + + foreach ($params as $obj) { + if ($obj->isEqual($param)) { + return true; + } + } + + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d) + { + $a = &new PEAR_PackageFile($c, $d); + return $a; + } + + /** + * This will retrieve from a local file if possible, and parse out + * a group name as well. The original parameter will be modified to reflect this. + * @param string|array can be a parsed package name as well + * @access private + */ + function _fromFile(&$param) + { + $saveparam = $param; + if (is_string($param)) { + if (!@file_exists($param)) { + $test = explode('#', $param); + $group = array_pop($test); + if (@file_exists(implode('#', $test))) { + $this->setGroup($group); + $param = implode('#', $test); + $this->_explicitGroup = true; + } + } + + if (@is_file($param)) { + $this->_type = 'local'; + $options = $this->_downloader->getOptions(); + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->_debug); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + $this->_valid = false; + $param = $saveparam; + return $pf; + } + $this->_packagefile = &$pf; + if (!$this->getGroup()) { + $this->setGroup('default'); // install the default dependency group + } + return $this->_valid = true; + } + } + $param = $saveparam; + return $this->_valid = false; + } + + function _fromUrl($param, $saveparam = '') + { + if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) { + $options = $this->_downloader->getOptions(); + $this->_type = 'url'; + $callback = $this->_downloader->ui ? + array(&$this->_downloader, '_downloadCallback') : null; + $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + $this->_downloader->popErrorHandling(); + return $dir; + } + + $this->_downloader->log(3, 'Downloading "' . $param . '"'); + $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui, + $dir, $callback, null, false, $this->getChannel()); + $this->_downloader->popErrorHandling(); + if (PEAR::isError($file)) { + if (!empty($saveparam)) { + $saveparam = ", cannot download \"$saveparam\""; + } + $err = PEAR::raiseError('Could not download from "' . $param . + '"' . $saveparam . ' (' . $file->getMessage() . ')'); + return $err; + } + + if ($this->_rawpackagefile) { + require_once 'Archive/Tar.php'; + $tar = &new Archive_Tar($file); + $packagexml = $tar->extractInString('package2.xml'); + if (!$packagexml) { + $packagexml = $tar->extractInString('package.xml'); + } + + if (str_replace(array("\n", "\r"), array('',''), $packagexml) != + str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) { + if ($this->getChannel() != 'pear.php.net') { + return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' . + 'not match value returned from xml-rpc'); + } + + // be more lax for the existing PEAR packages that have not-ok + // characters in their package.xml + $this->_downloader->log(0, 'CRITICAL WARNING: The "' . + $this->getPackage() . '" package has invalid characters in its ' . + 'package.xml. The next version of PEAR may not be able to install ' . + 'this package for security reasons. Please open a bug report at ' . + 'http://pear.php.net/package/' . $this->getPackage() . '/bugs'); + } + } + + // whew, download worked! + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug); + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, "Validation Error: $err"); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pf->getMessage()); + } + + ///FIXME need to pass back some error code that we can use to match with to cancel all further operations + /// At least stop all deps of this package from being installed + $out = $saveparam ? $saveparam : $param; + $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive'); + $this->_valid = false; + return $err; + } + + $this->_packagefile = &$pf; + $this->setGroup('default'); // install the default dependency group + return $this->_valid = true; + } + + return $this->_valid = false; + } + + /** + * + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',]) + * or a string of format [channame/]pname[-version|-state] + */ + function _fromString($param) + { + $options = $this->_downloader->getOptions(); + $channel = $this->_config->get('default_channel'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($pname)) { + if ($pname->getCode() == 'invalid') { + $this->_valid = false; + return false; + } + + if ($pname->getCode() == 'channel') { + $parsed = $pname->getUserInfo(); + if ($this->_downloader->discover($parsed['channel'])) { + if ($this->_config->get('auto_discover')) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Channel "' . $parsed['channel'] . + '" is not initialized, use ' . + '"pear channel-discover ' . $parsed['channel'] . '" to initialize' . + 'or pear config-set auto_discover 1'); + } + } + } + + if (PEAR::isError($pname)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } + + if (!isset($this->_type)) { + $this->_type = 'rest'; + } + + $this->_parsedname = $pname; + $this->_explicitState = isset($pname['state']) ? $pname['state'] : false; + $this->_explicitGroup = isset($pname['group']) ? true : false; + + $info = $this->_downloader->_getPackageDownloadUrl($pname); + if (PEAR::isError($info)) { + if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') { + // try pecl + $pname['channel'] = 'pecl.php.net'; + if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) { + if (!PEAR::isError($test)) { + $info = PEAR::raiseError($info->getMessage() . ' - package ' . + $this->_registry->parsedPackageNameToString($pname, true) . + ' can be installed with "pecl install ' . $pname['package'] . + '"'); + } else { + $pname['channel'] = 'pear.php.net'; + } + } else { + $pname['channel'] = 'pear.php.net'; + } + } + + return $info; + } + + $this->_rawpackagefile = $info['raw']; + $ret = $this->_analyzeDownloadURL($info, $param, $pname); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($ret) { + $this->_downloadURL = $ret; + return $this->_valid = (bool) $ret; + } + } + + /** + * @param array output of package.getDownloadURL + * @param string|array|object information for detecting packages to be downloaded, and + * for errors + * @param array name information of the package + * @param array|null packages to be downloaded + * @param bool is this an optional dependency? + * @param bool is this any kind of dependency? + * @access private + */ + function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false, + $isdependency = false) + { + if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) { + return false; + } + + if ($info === false) { + $saveparam = !is_string($param) ? ", cannot download \"$param\"" : ''; + + // no releases exist + return PEAR::raiseError('No releases for package "' . + $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam); + } + + if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) { + $err = false; + if ($pname['channel'] == 'pecl.php.net') { + if ($info['info']->getChannel() != 'pear.php.net') { + $err = true; + } + } elseif ($info['info']->getChannel() == 'pecl.php.net') { + if ($pname['channel'] != 'pear.php.net') { + $err = true; + } + } else { + $err = true; + } + + if ($err) { + return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] . + '" retrieved another channel\'s name for download! ("' . + $info['info']->getChannel() . '")'); + } + } + + $preferred_state = $this->_config->get('preferred_state'); + if (!isset($info['url'])) { + $package_version = $this->_registry->packageInfo($info['info']->getPackage(), + 'version', $info['info']->getChannel()); + if ($this->isInstalled($info)) { + if ($isdependency && version_compare($info['version'], $package_version, '<=')) { + // ignore bogus errors of "failed to download dependency" + // if it is already installed and the one that would be + // downloaded is older or the same version (Bug #7219) + return false; + } + } + + if ($info['version'] === $package_version) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' . + ' (' . $package_version . ') is the same as the locally installed one.'); + } + + return false; + } + + if (version_compare($info['version'], $package_version, '<=')) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' . + ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').'); + } + + return false; + } + + $instead = ', will instead download version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '"'; + // releases exist, but we failed to get any + if (isset($this->_downloader->_options['force'])) { + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . + '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + $instead = ''; + } + } else { + $vs = ' within preferred state "' . $preferred_state . '"'; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . $vs . $instead); + } + + // download the latest release + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } else { + if (isset($info['php']) && $info['php']) { + $err = PEAR::raiseError('Failed to download ' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], + 'package' => $pname['package']), + true) . + ', latest release is version ' . $info['php']['v'] . + ', but it requires PHP version "' . + $info['php']['m'] . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['php']['v'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_PHPVERSION); + return $err; + } + + // construct helpful error message + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + } + } else { + $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"'; + } + + $options = $this->_downloader->getOptions(); + // this is only set by the "download-all" command + if (isset($options['ignorepreferred_state'])) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_STATE); + return $err; + } + + // Checks if the user has a package installed already and checks the release against + // the state against the installed package, this allows upgrades for packages + // with lower stability than the preferred_state + $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']); + if (!$this->isInstalled($info) + || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true)) + ) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install'); + return $err; + } + } + } + + if (isset($info['deprecated']) && $info['deprecated']) { + $this->_downloader->log(0, + 'WARNING: "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $info['info']->getChannel(), + 'package' => $info['info']->getPackage()), true) . + '" is deprecated in favor of "' . + $this->_registry->parsedPackageNameToString($info['deprecated'], true) . + '"'); + } + + return $info; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/ErrorStack.php b/3rdparty/PEAR/ErrorStack.php index da52f08d34..0303f5273a 100644 --- a/3rdparty/PEAR/ErrorStack.php +++ b/3rdparty/PEAR/ErrorStack.php @@ -1,24 +1,4 @@ | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: ErrorStack.php,v 1.7.2.5 2005/01/01 21:26:51 cellog Exp $ - /** * Error Stack Implementation * @@ -38,11 +18,13 @@ * * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can * still be done quite handily in an error callback or by manipulating the returned array - * @author Greg Beaver - * @version PEAR1.3.2 (beta) - * @package PEAR_ErrorStack - * @category Debugging - * @license http://www.php.net/license/3_0.txt PHP License v3.0 + * @category Debugging + * @package PEAR_ErrorStack + * @author Greg Beaver + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack */ /** @@ -149,9 +131,14 @@ define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2); * // local error stack * $local_stack = new PEAR_ErrorStack('MyPackage'); * - * @copyright 2004 Gregory Beaver - * @package PEAR_ErrorStack - * @license http://www.php.net/license/3_0.txt PHP License + * @author Greg Beaver + * @version 1.9.4 + * @package PEAR_ErrorStack + * @category Debugging + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack */ class PEAR_ErrorStack { /** @@ -281,8 +268,10 @@ class PEAR_ErrorStack { 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)', false, $trace); } - return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] = - &new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] = + new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; } /** @@ -487,19 +476,10 @@ class PEAR_ErrorStack { * @param array $backtrace Protected parameter: use this to pass in the * {@link debug_backtrace()} that should be used * to find error context - * @return PEAR_Error|array|Exception - * if compatibility mode is on, a PEAR_Error is also - * thrown. If the class Exception exists, then one - * is returned to allow code like: - * - * throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob'))); - * + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. If a PEAR_Error is returned, the userinfo + * property is set to the following array: * - * The errorData property of the exception class will be set to the array - * that would normally be returned. If a PEAR_Error is returned, the userinfo - * property is set to the array - * - * Otherwise, an array is returned in this format: * * array( * 'code' => $code, @@ -512,6 +492,8 @@ class PEAR_ErrorStack { * //['repackage' => $err] repackaged error array/Exception class * ); * + * + * Normally, the previous array is returned. */ function push($code, $level = 'error', $params = array(), $msg = false, $repackage = false, $backtrace = false) @@ -538,16 +520,16 @@ class PEAR_ErrorStack { 'message' => $msg, ); + if ($repackage) { + $err['repackage'] = $repackage; + } + // set up the error message, if necessary if ($this->_msgCallback) { $msg = call_user_func_array($this->_msgCallback, array(&$this, $err)); $err['message'] = $msg; } - - if ($repackage) { - $err['repackage'] = $repackage; - } $push = $log = true; $die = false; // try the overriding callback first @@ -586,6 +568,9 @@ class PEAR_ErrorStack { } if ($push) { array_unshift($this->_errors, $err); + if (!isset($this->_errorsByLevel[$err['level']])) { + $this->_errorsByLevel[$err['level']] = array(); + } $this->_errorsByLevel[$err['level']][] = &$this->_errors[0]; } if ($log) { @@ -617,13 +602,8 @@ class PEAR_ErrorStack { * @param array $backtrace Protected parameter: use this to pass in the * {@link debug_backtrace()} that should be used * to find error context - * @return PEAR_Error|null|Exception - * if compatibility mode is on, a PEAR_Error is also - * thrown. If the class Exception exists, then one - * is returned to allow code like: - * - * throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob'))); - * + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. see docs for {@link push()} * @static */ function staticPush($package, $code, $level = 'error', $params = array(), @@ -684,9 +664,33 @@ class PEAR_ErrorStack { */ function pop() { - return @array_shift($this->_errors); + $err = @array_shift($this->_errors); + if (!is_null($err)) { + @array_pop($this->_errorsByLevel[$err['level']]); + if (!count($this->_errorsByLevel[$err['level']])) { + unset($this->_errorsByLevel[$err['level']]); + } + } + return $err; } - + + /** + * Pop an error off of the error stack, static method + * + * @param string package name + * @return boolean + * @since PEAR1.5.0a1 + */ + function staticPop($package) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop(); + } + } + /** * Determine whether there are any errors on the stack * @param string|array Level name. Use to determine if any errors @@ -853,7 +857,7 @@ class PEAR_ErrorStack { 'line' => $filebacktrace['line']); // rearrange for eval'd code or create function errors if (strpos($filebacktrace['file'], '(') && - preg_match(';^(.*?)\((\d+)\) : (.*?)$;', $filebacktrace['file'], + preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'], $matches)) { $ret['file'] = $matches[1]; $ret['line'] = $matches[2] + 0; @@ -910,7 +914,7 @@ class PEAR_ErrorStack { $mainmsg = $stack->getErrorMessageTemplate($err['code']); } $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg); - if (count($err['params'])) { + if (is_array($err['params']) && count($err['params'])) { foreach ($err['params'] as $name => $val) { if (is_array($val)) { // @ is needed in case $val is a multi-dimensional array @@ -978,4 +982,4 @@ class PEAR_ErrorStack { } $stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack'); $stack->pushCallback(array('PEAR_ErrorStack', '_handleError')); -?> \ No newline at end of file +?> diff --git a/3rdparty/PEAR/Exception.php b/3rdparty/PEAR/Exception.php index c735c16b39..4a0e7b86fa 100644 --- a/3rdparty/PEAR/Exception.php +++ b/3rdparty/PEAR/Exception.php @@ -1,34 +1,27 @@ | -// | Hans Lellelid | -// | Bertrand Mansion | -// | Greg Beaver | -// +----------------------------------------------------------------------+ -// -// $Id: Exception.php,v 1.4.2.2 2004/12/31 19:01:52 cellog Exp $ +/** + * PEAR_Exception + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Tomas V. V. Cox + * @author Hans Lellelid + * @author Bertrand Mansion + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Exception.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 + */ /** * Base PEAR_Exception Class * - * WARNING: This code should be considered stable, but the API is - * subject to immediate and drastic change, so API stability is - * at best alpha - * * 1) Features: * * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception)) @@ -88,12 +81,17 @@ * } * * - * @since PHP 5 - * @package PEAR - * @version $Revision: 1.4.2.2 $ - * @author Tomas V.V.Cox - * @author Hans Lellelid - * @author Bertrand Mansion + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Hans Lellelid + * @author Bertrand Mansion + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 * */ class PEAR_Exception extends Exception @@ -108,19 +106,31 @@ class PEAR_Exception extends Exception /** * Supported signatures: - * PEAR_Exception(string $message); - * PEAR_Exception(string $message, int $code); - * PEAR_Exception(string $message, Exception $cause); - * PEAR_Exception(string $message, Exception $cause, int $code); - * PEAR_Exception(string $message, array $causes); - * PEAR_Exception(string $message, array $causes, int $code); + * - PEAR_Exception(string $message); + * - PEAR_Exception(string $message, int $code); + * - PEAR_Exception(string $message, Exception $cause); + * - PEAR_Exception(string $message, Exception $cause, int $code); + * - PEAR_Exception(string $message, PEAR_Error $cause); + * - PEAR_Exception(string $message, PEAR_Error $cause, int $code); + * - PEAR_Exception(string $message, array $causes); + * - PEAR_Exception(string $message, array $causes, int $code); + * @param string exception message + * @param int|Exception|PEAR_Error|array|null exception cause + * @param int|null exception code or null */ public function __construct($message, $p2 = null, $p3 = null) { if (is_int($p2)) { $code = $p2; $this->cause = null; - } elseif ($p2 instanceof Exception || is_array($p2)) { + } elseif (is_object($p2) || is_array($p2)) { + // using is_object allows both Exception and PEAR_Error + if (is_object($p2) && !($p2 instanceof Exception)) { + if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) { + throw new PEAR_Exception('exception cause must be Exception, ' . + 'array, or PEAR_Error'); + } + } $code = $p3; if (is_array($p2) && isset($p2['message'])) { // fix potential problem of passing in a single warning @@ -128,7 +138,7 @@ class PEAR_Exception extends Exception } $this->cause = $p2; } else { - $code = null; + $code = null; $this->cause = null; } parent::__construct($message, $code); @@ -234,13 +244,33 @@ class PEAR_Exception extends Exception $cause['line'] = $trace[0]['line']; } } + $causes[] = $cause; if ($this->cause instanceof PEAR_Exception) { $this->cause->getCauseMessage($causes); - } - if (is_array($this->cause)) { + } elseif ($this->cause instanceof Exception) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => $this->cause->getFile(), + 'line' => $this->cause->getLine()); + } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($this->cause)) { foreach ($this->cause as $cause) { if ($cause instanceof PEAR_Exception) { $cause->getCauseMessage($causes); + } elseif ($cause instanceof Exception) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => $cause->getFile(), + 'line' => $cause->getLine()); + } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); } elseif (is_array($cause) && isset($cause['message'])) { // PEAR_ErrorStack warning $causes[] = array( @@ -259,7 +289,7 @@ class PEAR_Exception extends Exception } public function getTraceSafe() - { + { if (!isset($this->_trace)) { $this->_trace = $this->getTrace(); if (empty($this->_trace)) { @@ -288,28 +318,28 @@ class PEAR_Exception extends Exception return $this->toHtml(); } return $this->toText(); - } + } public function toHtml() { $trace = $this->getTraceSafe(); $causes = array(); $this->getCauseMessage($causes); - $html = '' . "\n"; + $html = '
' . "\n"; foreach ($causes as $i => $cause) { - $html .= '\n"; } - $html .= '' . "\n" - . '' - . '' - . '' . "\n"; + $html .= '' . "\n" + . '' + . '' + . '' . "\n"; foreach ($trace as $k => $v) { - $html .= '' + $html .= '' . '' - . '' . "\n"; + . '' . "\n"; } - $html .= '' + $html .= '' . '' . '' . "\n" . '
' + $html .= '
' . str_repeat('-', $i) . ' ' . $cause['class'] . ': ' . htmlspecialchars($cause['message']) . ' in ' . $cause['file'] . ' ' . 'on line ' . $cause['line'] . '' . "
Exception trace
#FunctionLocation
Exception trace
#FunctionLocation
' . $k . '
' . $k . ''; if (!empty($v['class'])) { $html .= $v['class'] . $v['type']; @@ -333,9 +363,11 @@ class PEAR_Exception extends Exception } $html .= '(' . implode(', ',$args) . ')' . '' . $v['file'] . ':' . $v['line'] . '
' . (isset($v['file']) ? $v['file'] : 'unknown') + . ':' . (isset($v['line']) ? $v['line'] : 'unknown') + . '
' . ($k+1) . '
' . ($k+1) . '{main} 
'; @@ -354,6 +386,4 @@ class PEAR_Exception extends Exception } return $causeMsg . $this->getTraceAsString(); } -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/3rdparty/PEAR/FixPHP5PEARWarnings.php b/3rdparty/PEAR/FixPHP5PEARWarnings.php new file mode 100644 index 0000000000..be5dc3ce70 --- /dev/null +++ b/3rdparty/PEAR/FixPHP5PEARWarnings.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/3rdparty/PEAR/Frontend.php b/3rdparty/PEAR/Frontend.php new file mode 100644 index 0000000000..531e541f9e --- /dev/null +++ b/3rdparty/PEAR/Frontend.php @@ -0,0 +1,228 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Frontend.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +//require_once 'PEAR.php'; + +/** + * Which user interface class is being used. + * @var string class name + */ +$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI'; + +/** + * Instance of $_PEAR_Command_uiclass. + * @var object + */ +$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null; + +/** + * Singleton-based frontend for PEAR user input/output + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Frontend extends PEAR +{ + /** + * Retrieve the frontend object + * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk + * @static + */ + function &singleton($type = null) + { + if ($type === null) { + if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) { + $a = false; + return $a; + } + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + $a = PEAR_Frontend::setFrontendClass($type); + return $a; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to conform to the PEAR naming standard of + * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php) + * @param string $uiclass full class name + * @return PEAR_Frontend + * @static + */ + function &setFrontendClass($uiclass) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!class_exists($uiclass)) { + $file = str_replace('_', '/', $uiclass) . '.php'; + if (PEAR_Frontend::isIncludeable($file)) { + include_once $file; + } + } + + if (class_exists($uiclass)) { + $obj = &new $uiclass; + // quick test to see if this class implements a few of the most + // important frontend methods + if (is_a($obj, 'PEAR_Frontend')) { + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass; + return $obj; + } + + $err = PEAR::raiseError("not a frontend class: $uiclass"); + return $err; + } + + $err = PEAR::raiseError("no such class: $uiclass"); + return $err; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to be a descendant of PEAR_Frontend + * @param PEAR_Frontend + * @return PEAR_Frontend + * @static + */ + function &setFrontendObject($uiobject) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!is_a($uiobject, 'PEAR_Frontend')) { + $err = PEAR::raiseError('not a valid frontend class: (' . + get_class($uiobject) . ')'); + return $err; + } + + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject); + return $uiobject; + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $fp = @fopen($path, 'r', true); + if ($fp) { + fclose($fp); + return true; + } + + return false; + } + + /** + * @param PEAR_Config + */ + function setConfig(&$config) + { + } + + /** + * This can be overridden to allow session-based temporary file management + * + * By default, all files are deleted at the end of a session. The web installer + * needs to be able to sustain a list over many sessions in order to support + * user interaction with install scripts + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Log an action + * + * @param string $msg the message to log + * @param boolean $append_crlf + * @return boolean true + * @abstract + */ + function log($msg, $append_crlf = true) + { + } + + /** + * Run a post-installation script + * + * @param array $scripts array of post-install scripts + * @abstract + */ + function runPostinstallScripts(&$scripts) + { + } + + /** + * Display human-friendly output formatted depending on the + * $command parameter. + * + * This should be able to handle basic output data with no command + * @param mixed $data data structure containing the information to display + * @param string $command command from which this method was called + * @abstract + */ + function outputData($data, $command = '_default') + { + } + + /** + * Display a modal form dialog and return the given input + * + * A frontend that requires multiple requests to retrieve and process + * data must take these needs into account, and implement the request + * handling code. + * @param string $command command from which this method was called + * @param array $prompts associative array. keys are the input field names + * and values are the description + * @param array $types array of input field types (text, password, + * etc.) keys have to be the same like in $prompts + * @param array $defaults array of default values. again keys have + * to be the same like in $prompts. Do not depend + * on a default value being set. + * @return array input sent by the user + * @abstract + */ + function userDialog($command, $prompts, $types = array(), $defaults = array()) + { + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Frontend/CLI.php b/3rdparty/PEAR/Frontend/CLI.php index 832ddf09b3..340b99b79b 100644 --- a/3rdparty/PEAR/Frontend/CLI.php +++ b/3rdparty/PEAR/Frontend/CLI.php @@ -1,30 +1,38 @@ | - +----------------------------------------------------------------------+ +/** + * PEAR_Frontend_CLI + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: CLI.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ +require_once 'PEAR/Frontend.php'; - $Id: CLI.php,v 1.41 2004/02/17 05:49:16 ssb Exp $ -*/ - -require_once "PEAR.php"; - -class PEAR_Frontend_CLI extends PEAR +/** + * Command-line Frontend for the PEAR Installer + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Frontend_CLI extends PEAR_Frontend { - // {{{ properties - /** * What type of user interface this frontend is for. * @var string @@ -35,13 +43,9 @@ class PEAR_Frontend_CLI extends PEAR var $params = array(); var $term = array( - 'bold' => '', + 'bold' => '', 'normal' => '', - ); - - // }}} - - // {{{ constructor + ); function PEAR_Frontend_CLI() { @@ -50,121 +54,308 @@ class PEAR_Frontend_CLI extends PEAR if (function_exists('posix_isatty') && !posix_isatty(1)) { // output is being redirected to a file or through a pipe } elseif ($term) { - // XXX can use ncurses extension here, if available if (preg_match('/^(xterm|vt220|linux)/', $term)) { - $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109); - $this->term['normal']=sprintf("%c%c%c", 27, 91, 109); + $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109); + $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109); } elseif (preg_match('/^vt100/', $term)) { - $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); - $this->term['normal']=sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0); + $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); + $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0); } } elseif (OS_WINDOWS) { // XXX add ANSI codes here } } - // }}} - - // {{{ displayLine(text) - - function displayLine($text) - { - trigger_error("PEAR_Frontend_CLI::displayLine deprecated", E_USER_ERROR); - } - - function _displayLine($text) - { - print "$this->lp$text\n"; - } - - // }}} - // {{{ display(text) - - function display($text) - { - trigger_error("PEAR_Frontend_CLI::display deprecated", E_USER_ERROR); - } - - function _display($text) - { - print $text; - } - - // }}} - // {{{ displayError(eobj) - /** * @param object PEAR_Error object */ - function displayError($eobj) + function displayError($e) { - return $this->_displayLine($eobj->getMessage()); + return $this->_displayLine($e->getMessage()); } - // }}} - // {{{ displayFatalError(eobj) - /** * @param object PEAR_Error object */ function displayFatalError($eobj) { $this->displayError($eobj); + if (class_exists('PEAR_Config')) { + $config = &PEAR_Config::singleton(); + if ($config->get('verbose') > 5) { + if (function_exists('debug_print_backtrace')) { + debug_print_backtrace(); + exit(1); + } + + $raised = false; + foreach (debug_backtrace() as $i => $frame) { + if (!$raised) { + if (isset($frame['class']) + && strtolower($frame['class']) == 'pear' + && strtolower($frame['function']) == 'raiseerror' + ) { + $raised = true; + } else { + continue; + } + } + + $frame['class'] = !isset($frame['class']) ? '' : $frame['class']; + $frame['type'] = !isset($frame['type']) ? '' : $frame['type']; + $frame['function'] = !isset($frame['function']) ? '' : $frame['function']; + $frame['line'] = !isset($frame['line']) ? '' : $frame['line']; + $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]"); + } + } + } + exit(1); } - // }}} - // {{{ displayHeading(title) - - function displayHeading($title) + /** + * Instruct the runInstallScript method to skip a paramgroup that matches the + * id value passed in. + * + * This method is useful for dynamically configuring which sections of a post-install script + * will be run based on the user's setup, which is very useful for making flexible + * post-install scripts without losing the cross-Frontend ability to retrieve user input + * @param string + */ + function skipParamgroup($id) { - trigger_error("PEAR_Frontend_CLI::displayHeading deprecated", E_USER_ERROR); + $this->_skipSections[$id] = true; } - function _displayHeading($title) + function runPostinstallScripts(&$scripts) { - print $this->lp.$this->bold($title)."\n"; - print $this->lp.str_repeat("=", strlen($title))."\n"; + foreach ($scripts as $i => $script) { + $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj); + } } - // }}} - // {{{ userDialog(prompt, [type], [default]) - - function userDialog($command, $prompts, $types = array(), $defaults = array()) + /** + * @param array $xml contents of postinstallscript tag + * @param object $script post-installation script + * @param string install|upgrade + */ + function runInstallScript($xml, &$script) { - $result = array(); - if (is_array($prompts)) { - $fp = fopen("php://stdin", "r"); - foreach ($prompts as $key => $prompt) { - $type = $types[$key]; - $default = @$defaults[$key]; - if ($type == 'password') { - system('stty -echo'); + $this->_skipSections = array(); + if (!is_array($xml) || !isset($xml['paramgroup'])) { + $script->run(array(), '_default'); + return; + } + + $completedPhases = array(); + if (!isset($xml['paramgroup'][0])) { + $xml['paramgroup'] = array($xml['paramgroup']); + } + + foreach ($xml['paramgroup'] as $group) { + if (isset($this->_skipSections[$group['id']])) { + // the post-install script chose to skip this section dynamically + continue; + } + + if (isset($group['name'])) { + $paramname = explode('::', $group['name']); + if ($lastgroup['id'] != $paramname[0]) { + continue; } - print "$this->lp$prompt "; + + $group['name'] = $paramname[1]; + if (!isset($answers)) { + return; + } + + if (isset($answers[$group['name']])) { + switch ($group['conditiontype']) { + case '=' : + if ($answers[$group['name']] != $group['value']) { + continue 2; + } + break; + case '!=' : + if ($answers[$group['name']] == $group['value']) { + continue 2; + } + break; + case 'preg_match' : + if (!@preg_match('/' . $group['value'] . '/', + $answers[$group['name']])) { + continue 2; + } + break; + default : + return; + } + } + } + + $lastgroup = $group; + if (isset($group['instructions'])) { + $this->_display($group['instructions']); + } + + if (!isset($group['param'][0])) { + $group['param'] = array($group['param']); + } + + if (isset($group['param'])) { + if (method_exists($script, 'postProcessPrompts')) { + $prompts = $script->postProcessPrompts($group['param'], $group['id']); + if (!is_array($prompts) || count($prompts) != count($group['param'])) { + $this->outputData('postinstall', 'Error: post-install script did not ' . + 'return proper post-processed prompts'); + $prompts = $group['param']; + } else { + foreach ($prompts as $i => $var) { + if (!is_array($var) || !isset($var['prompt']) || + !isset($var['name']) || + ($var['name'] != $group['param'][$i]['name']) || + ($var['type'] != $group['param'][$i]['type']) + ) { + $this->outputData('postinstall', 'Error: post-install script ' . + 'modified the variables or prompts, severe security risk. ' . + 'Will instead use the defaults from the package.xml'); + $prompts = $group['param']; + } + } + } + + $answers = $this->confirmDialog($prompts); + } else { + $answers = $this->confirmDialog($group['param']); + } + } + + if ((isset($answers) && $answers) || !isset($group['param'])) { + if (!isset($answers)) { + $answers = array(); + } + + array_unshift($completedPhases, $group['id']); + if (!$script->run($answers, $group['id'])) { + $script->run($completedPhases, '_undoOnError'); + return; + } + } else { + $script->run($completedPhases, '_undoOnError'); + return; + } + } + } + + /** + * Ask for user input, confirm the answers and continue until the user is satisfied + * @param array an array of arrays, format array('name' => 'paramname', 'prompt' => + * 'text to display', 'type' => 'string'[, default => 'default value']) + * @return array + */ + function confirmDialog($params) + { + $answers = $prompts = $types = array(); + foreach ($params as $param) { + $prompts[$param['name']] = $param['prompt']; + $types[$param['name']] = $param['type']; + $answers[$param['name']] = isset($param['default']) ? $param['default'] : ''; + } + + $tried = false; + do { + if ($tried) { + $i = 1; + foreach ($answers as $var => $value) { + if (!strlen($value)) { + echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n"); + } + $i++; + } + } + + $answers = $this->userDialog('', $prompts, $types, $answers); + $tried = true; + } while (is_array($answers) && count(array_filter($answers)) != count($prompts)); + + return $answers; + } + + function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20) + { + if (!is_array($prompts)) { + return array(); + } + + $testprompts = array_keys($prompts); + $result = $defaults; + + reset($prompts); + if (count($prompts) === 1) { + foreach ($prompts as $key => $prompt) { + $type = $types[$key]; + $default = @$defaults[$key]; + print "$prompt "; if ($default) { print "[$default] "; } print ": "; - $line = fgets($fp, 2048); - if ($type == 'password') { - system('stty echo'); - print "\n"; + + $line = fgets(STDIN, 2048); + $result[$key] = ($default && trim($line) == '') ? $default : trim($line); + } + + return $result; + } + + $first_run = true; + while (true) { + $descLength = max(array_map('strlen', $prompts)); + $descFormat = "%-{$descLength}s"; + $last = count($prompts); + + $i = 0; + foreach ($prompts as $n => $var) { + $res = isset($result[$n]) ? $result[$n] : null; + printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res); + } + print "\n1-$last, 'all', 'abort', or Enter to continue: "; + + $tmp = trim(fgets(STDIN, 1024)); + if (empty($tmp)) { + break; + } + + if ($tmp == 'abort') { + return false; + } + + if (isset($testprompts[(int)$tmp - 1])) { + $var = $testprompts[(int)$tmp - 1]; + $desc = $prompts[$var]; + $current = @$result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if ($tmp !== '') { + $result[$var] = $tmp; } - if ($default && trim($line) == "") { - $result[$key] = $default; - } else { - $result[$key] = $line; + } elseif ($tmp == 'all') { + foreach ($prompts as $var => $desc) { + $current = $result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if (trim($tmp) !== '') { + $result[$var] = trim($tmp); + } } } - fclose($fp); + + $first_run = false; } + return $result; } - // }}} - // {{{ userConfirm(prompt, [default]) - function userConfirm($prompt, $default = 'yes') { trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR); @@ -190,199 +381,33 @@ class PEAR_Frontend_CLI extends PEAR return false; } - // }}} - // {{{ startTable([params]) - - function startTable($params = array()) - { - trigger_error("PEAR_Frontend_CLI::startTable deprecated", E_USER_ERROR); - } - - function _startTable($params = array()) - { - $params['table_data'] = array(); - $params['widest'] = array(); // indexed by column - $params['highest'] = array(); // indexed by row - $params['ncols'] = 0; - $this->params = $params; - } - - // }}} - // {{{ tableRow(columns, [rowparams], [colparams]) - - function tableRow($columns, $rowparams = array(), $colparams = array()) - { - trigger_error("PEAR_Frontend_CLI::tableRow deprecated", E_USER_ERROR); - } - - function _tableRow($columns, $rowparams = array(), $colparams = array()) - { - $highest = 1; - for ($i = 0; $i < sizeof($columns); $i++) { - $col = &$columns[$i]; - if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) { - $col = wordwrap($col, $colparams[$i]['wrap'], "\n", 0); - } - if (strpos($col, "\n") !== false) { - $multiline = explode("\n", $col); - $w = 0; - foreach ($multiline as $n => $line) { - if (strlen($line) > $w) { - $w = strlen($line); - } - } - $lines = sizeof($multiline); - } else { - $w = strlen($col); - } - if ($w > @$this->params['widest'][$i]) { - $this->params['widest'][$i] = $w; - } - $tmp = count_chars($columns[$i], 1); - // handle unix, mac and windows formats - $lines = (isset($tmp[10]) ? $tmp[10] : @$tmp[13]) + 1; - if ($lines > $highest) { - $highest = $lines; - } - } - if (sizeof($columns) > $this->params['ncols']) { - $this->params['ncols'] = sizeof($columns); - } - $new_row = array( - 'data' => $columns, - 'height' => $highest, - 'rowparams' => $rowparams, - 'colparams' => $colparams, - ); - $this->params['table_data'][] = $new_row; - } - - // }}} - // {{{ endTable() - - function endTable() - { - trigger_error("PEAR_Frontend_CLI::endTable deprecated", E_USER_ERROR); - } - - function _endTable() - { - extract($this->params); - if (!empty($caption)) { - $this->_displayHeading($caption); - } - if (count($table_data) == 0) { - return; - } - if (!isset($width)) { - $width = $widest; - } else { - for ($i = 0; $i < $ncols; $i++) { - if (!isset($width[$i])) { - $width[$i] = $widest[$i]; - } - } - } - $border = false; - if (empty($border)) { - $cellstart = ''; - $cellend = ' '; - $rowend = ''; - $padrowend = false; - $borderline = ''; - } else { - $cellstart = '| '; - $cellend = ' '; - $rowend = '|'; - $padrowend = true; - $borderline = '+'; - foreach ($width as $w) { - $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1); - $borderline .= '+'; - } - } - if ($borderline) { - $this->_displayLine($borderline); - } - for ($i = 0; $i < sizeof($table_data); $i++) { - extract($table_data[$i]); - if (!is_array($rowparams)) { - $rowparams = array(); - } - if (!is_array($colparams)) { - $colparams = array(); - } - $rowlines = array(); - if ($height > 1) { - for ($c = 0; $c < sizeof($data); $c++) { - $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]); - if (sizeof($rowlines[$c]) < $height) { - $rowlines[$c] = array_pad($rowlines[$c], $height, ''); - } - } - } else { - for ($c = 0; $c < sizeof($data); $c++) { - $rowlines[$c] = array($data[$c]); - } - } - for ($r = 0; $r < $height; $r++) { - $rowtext = ''; - for ($c = 0; $c < sizeof($data); $c++) { - if (isset($colparams[$c])) { - $attribs = array_merge($rowparams, $colparams); - } else { - $attribs = $rowparams; - } - $w = isset($width[$c]) ? $width[$c] : 0; - //$cell = $data[$c]; - $cell = $rowlines[$c][$r]; - $l = strlen($cell); - if ($l > $w) { - $cell = substr($cell, 0, $w); - } - if (isset($attribs['bold'])) { - $cell = $this->bold($cell); - } - if ($l < $w) { - // not using str_pad here because we may - // add bold escape characters to $cell - $cell .= str_repeat(' ', $w - $l); - } - - $rowtext .= $cellstart . $cell . $cellend; - } - if (!$border) { - $rowtext = rtrim($rowtext); - } - $rowtext .= $rowend; - $this->_displayLine($rowtext); - } - } - if ($borderline) { - $this->_displayLine($borderline); - } - } - - // }}} - // {{{ outputData() - function outputData($data, $command = '_default') { switch ($command) { + case 'channel-info': + foreach ($data as $type => $section) { + if ($type == 'main') { + $section['data'] = array_values($section['data']); + } + + $this->outputData($section); + } + break; case 'install': case 'upgrade': case 'upgrade-all': - if (isset($data['release_warnings'])) { + if (is_array($data) && isset($data['release_warnings'])) { $this->_displayLine(''); $this->_startTable(array( 'border' => false, 'caption' => 'Release Warnings' - )); + )); $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55))); $this->_endTable(); $this->_displayLine(''); } - $this->_displayLine($data['data']); + + $this->_displayLine(is_array($data) ? $data['data'] : $data); break; case 'search': $this->_startTable($data); @@ -390,63 +415,96 @@ class PEAR_Frontend_CLI extends PEAR $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); } + $packages = array(); foreach($data['data'] as $category) { - foreach($category as $pkg) { - $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + foreach($category as $name => $pkg) { + $packages[$pkg[0]] = $pkg; } - }; + } + + $p = array_keys($packages); + natcasesort($p); + foreach ($p as $name) { + $this->_tableRow($packages[$name], null, array(1 => array('wrap' => 55))); + } + $this->_endTable(); break; case 'list-all': + if (!isset($data['data'])) { + $this->_displayLine('No packages in channel'); + break; + } + $this->_startTable($data); if (isset($data['headline']) && is_array($data['headline'])) { $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); } + $packages = array(); foreach($data['data'] as $category) { - foreach($category as $pkg) { - unset($pkg[3]); - unset($pkg[4]); - $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + foreach($category as $name => $pkg) { + $packages[$pkg[0]] = $pkg; } - }; + } + + $p = array_keys($packages); + natcasesort($p); + foreach ($p as $name) { + $pkg = $packages[$name]; + unset($pkg[4], $pkg[5]); + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + $this->_endTable(); break; case 'config-show': $data['border'] = false; - $opts = array(0 => array('wrap' => 30), - 1 => array('wrap' => 20), - 2 => array('wrap' => 35)); + $opts = array( + 0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + $this->_startTable($data); if (isset($data['headline']) && is_array($data['headline'])) { - $this->_tableRow($data['headline'], - array('bold' => true), - $opts); + $this->_tableRow($data['headline'], array('bold' => true), $opts); } - foreach($data['data'] as $group) { - foreach($group as $value) { + + foreach ($data['data'] as $group) { + foreach ($group as $value) { if ($value[2] == '') { $value[2] = ""; } + $this->_tableRow($value, null, $opts); } } + $this->_endTable(); break; case 'remote-info': + $d = $data; $data = array( 'caption' => 'Package details:', - 'border' => false, - 'data' => array( - array("Latest", $data['stable']), - array("Installed", $data['installed']), - array("Package", $data['name']), - array("License", $data['license']), - array("Category", $data['category']), - array("Summary", $data['summary']), + 'border' => false, + 'data' => array( + array("Latest", $data['stable']), + array("Installed", $data['installed']), + array("Package", $data['name']), + array("License", $data['license']), + array("Category", $data['category']), + array("Summary", $data['summary']), array("Description", $data['description']), - ), - ); + ), + ); + + if (isset($d['deprecated']) && $d['deprecated']) { + $conf = &PEAR_Config::singleton(); + $reg = $conf->getRegistry(); + $name = $reg->parsedPackageNameToString($d['deprecated'], true); + $data['data'][] = array('Deprecated! use', $name); + } default: { if (is_array($data)) { $this->_startTable($data); @@ -468,9 +526,14 @@ class PEAR_Frontend_CLI extends PEAR array('bold' => true), $opts); } - foreach($data['data'] as $row) { - $this->_tableRow($row, null, $opts); - } + + if (is_array($data['data'])) { + foreach($data['data'] as $row) { + $this->_tableRow($row, null, $opts); + } + } else { + $this->_tableRow(array($data['data']), null, $opts); + } $this->_endTable(); } else { $this->_displayLine($data); @@ -479,31 +542,210 @@ class PEAR_Frontend_CLI extends PEAR } } - // }}} - // {{{ log(text) - - function log($text, $append_crlf = true) { if ($append_crlf) { return $this->_displayLine($text); } + return $this->_display($text); } - - // }}} - // {{{ bold($text) - function bold($text) { if (empty($this->term['bold'])) { return strtoupper($text); } + return $this->term['bold'] . $text . $this->term['normal']; } - // }}} -} + function _displayHeading($title) + { + print $this->lp.$this->bold($title)."\n"; + print $this->lp.str_repeat("=", strlen($title))."\n"; + } -?> + function _startTable($params = array()) + { + $params['table_data'] = array(); + $params['widest'] = array(); // indexed by column + $params['highest'] = array(); // indexed by row + $params['ncols'] = 0; + $this->params = $params; + } + + function _tableRow($columns, $rowparams = array(), $colparams = array()) + { + $highest = 1; + for ($i = 0; $i < count($columns); $i++) { + $col = &$columns[$i]; + if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) { + $col = wordwrap($col, $colparams[$i]['wrap']); + } + + if (strpos($col, "\n") !== false) { + $multiline = explode("\n", $col); + $w = 0; + foreach ($multiline as $n => $line) { + $len = strlen($line); + if ($len > $w) { + $w = $len; + } + } + $lines = count($multiline); + } else { + $w = strlen($col); + } + + if (isset($this->params['widest'][$i])) { + if ($w > $this->params['widest'][$i]) { + $this->params['widest'][$i] = $w; + } + } else { + $this->params['widest'][$i] = $w; + } + + $tmp = count_chars($columns[$i], 1); + // handle unix, mac and windows formats + $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1; + if ($lines > $highest) { + $highest = $lines; + } + } + + if (count($columns) > $this->params['ncols']) { + $this->params['ncols'] = count($columns); + } + + $new_row = array( + 'data' => $columns, + 'height' => $highest, + 'rowparams' => $rowparams, + 'colparams' => $colparams, + ); + $this->params['table_data'][] = $new_row; + } + + function _endTable() + { + extract($this->params); + if (!empty($caption)) { + $this->_displayHeading($caption); + } + + if (count($table_data) === 0) { + return; + } + + if (!isset($width)) { + $width = $widest; + } else { + for ($i = 0; $i < $ncols; $i++) { + if (!isset($width[$i])) { + $width[$i] = $widest[$i]; + } + } + } + + $border = false; + if (empty($border)) { + $cellstart = ''; + $cellend = ' '; + $rowend = ''; + $padrowend = false; + $borderline = ''; + } else { + $cellstart = '| '; + $cellend = ' '; + $rowend = '|'; + $padrowend = true; + $borderline = '+'; + foreach ($width as $w) { + $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1); + $borderline .= '+'; + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + + for ($i = 0; $i < count($table_data); $i++) { + extract($table_data[$i]); + if (!is_array($rowparams)) { + $rowparams = array(); + } + + if (!is_array($colparams)) { + $colparams = array(); + } + + $rowlines = array(); + if ($height > 1) { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]); + if (count($rowlines[$c]) < $height) { + $rowlines[$c] = array_pad($rowlines[$c], $height, ''); + } + } + } else { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = array($data[$c]); + } + } + + for ($r = 0; $r < $height; $r++) { + $rowtext = ''; + for ($c = 0; $c < count($data); $c++) { + if (isset($colparams[$c])) { + $attribs = array_merge($rowparams, $colparams); + } else { + $attribs = $rowparams; + } + + $w = isset($width[$c]) ? $width[$c] : 0; + //$cell = $data[$c]; + $cell = $rowlines[$c][$r]; + $l = strlen($cell); + if ($l > $w) { + $cell = substr($cell, 0, $w); + } + + if (isset($attribs['bold'])) { + $cell = $this->bold($cell); + } + + if ($l < $w) { + // not using str_pad here because we may + // add bold escape characters to $cell + $cell .= str_repeat(' ', $w - $l); + } + + $rowtext .= $cellstart . $cell . $cellend; + } + + if (!$border) { + $rowtext = rtrim($rowtext); + } + + $rowtext .= $rowend; + $this->_displayLine($rowtext); + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + } + + function _displayLine($text) + { + print "$this->lp$text\n"; + } + + function _display($text) + { + print $text; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Installer.php b/3rdparty/PEAR/Installer.php index 31e2cff81e..eb17ca7914 100644 --- a/3rdparty/PEAR/Installer.php +++ b/3rdparty/PEAR/Installer.php @@ -1,41 +1,44 @@ | -// | Tomas V.V.Cox | -// | Martin Jansen | -// +----------------------------------------------------------------------+ -// -// $Id: Installer.php,v 1.150.2.2 2005/02/17 17:47:55 cellog Exp $ +/** + * PEAR_Installer + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Installer.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * Used for installation groups in package.xml 2.0 and platform exceptions + */ +require_once 'OS/Guess.php'; require_once 'PEAR/Downloader.php'; +define('PEAR_INSTALLER_NOBINARY', -240); /** * Administration class used to install PEAR packages and maintain the * installed package database. * - * TODO: - * - Check dependencies break on package uninstall (when no force given) - * - add a guessInstallDest() method with the code from _installFile() and - * use that method in Registry::_rebuildFileMap() & Command_Registry::doList(), - * others.. - * - * @since PHP 4.0.2 - * @author Stig Bakken - * @author Martin Jansen - * @author Greg Beaver + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Installer extends PEAR_Downloader { @@ -77,11 +80,18 @@ class PEAR_Installer extends PEAR_Downloader */ var $tmpdir; - /** PEAR_Registry object used by the installer - * @var object + /** + * PEAR_Registry object used by the installer + * @var PEAR_Registry */ var $registry; + /** + * array of PEAR_Downloader_Packages + * @var array + */ + var $_downloadedPackages; + /** List of file transactions queued for an install/upgrade/uninstall. * * Format: @@ -111,38 +121,85 @@ class PEAR_Installer extends PEAR_Downloader parent::PEAR_Common(); $this->setFrontendObject($ui); $this->debug = $this->config->get('verbose'); - //$this->registry = &new PEAR_Registry($this->config->get('php_dir')); + } + + function setOptions($options) + { + $this->_options = $options; + } + + function setConfig(&$config) + { + $this->config = &$config; + $this->_registry = &$config->getRegistry(); } // }}} + function _removeBackups($files) + { + foreach ($files as $path) { + $this->addFileOperation('removebackup', array($path)); + } + } + // {{{ _deletePackageFiles() /** * Delete a package's installed files, does not remove empty directories. * - * @param string $package package name - * + * @param string package name + * @param string channel name + * @param bool if true, then files are backed up first * @return bool TRUE on success, or a PEAR error on failure - * - * @access private + * @access protected */ - function _deletePackageFiles($package) + function _deletePackageFiles($package, $channel = false, $backup = false) { + if (!$channel) { + $channel = 'pear.php.net'; + } + if (!strlen($package)) { return $this->raiseError("No package to uninstall given"); } - $filelist = $this->registry->packageInfo($package, 'filelist'); - if ($filelist == null) { - return $this->raiseError("$package not installed"); + + if (strtolower($package) == 'pear' && $channel == 'pear.php.net') { + // to avoid race conditions, include all possible needed files + require_once 'PEAR/Task/Common.php'; + require_once 'PEAR/Task/Replace.php'; + require_once 'PEAR/Task/Unixeol.php'; + require_once 'PEAR/Task/Windowseol.php'; + require_once 'PEAR/PackageFile/v1.php'; + require_once 'PEAR/PackageFile/v2.php'; + require_once 'PEAR/PackageFile/Generator/v1.php'; + require_once 'PEAR/PackageFile/Generator/v2.php'; } + + $filelist = $this->_registry->packageInfo($package, 'filelist', $channel); + if ($filelist == null) { + return $this->raiseError("$channel/$package not installed"); + } + + $ret = array(); foreach ($filelist as $file => $props) { if (empty($props['installed_as'])) { continue; } - $path = $this->_prependPath($props['installed_as'], $this->installroot); + + $path = $props['installed_as']; + if ($backup) { + $this->addFileOperation('backup', array($path)); + $ret[] = $path; + } + $this->addFileOperation('delete', array($path)); } + + if ($backup) { + return $ret; + } + return true; } @@ -160,18 +217,23 @@ class PEAR_Installer extends PEAR_Downloader { // {{{ return if this file is meant for another platform static $os; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + if (isset($atts['platform'])) { if (empty($os)) { - include_once "OS/Guess.php"; $os = new OS_Guess(); } + if (strlen($atts['platform']) && $atts['platform']{0} == '!') { - $negate = true; + $negate = true; $platform = substr($atts['platform'], 1); } else { - $negate = false; + $negate = false; $platform = $atts['platform']; } + if ((bool) $os->matchSignature($platform) === $negate) { $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); return PEAR_INSTALLER_SKIPPED; @@ -179,36 +241,40 @@ class PEAR_Installer extends PEAR_Downloader } // }}} + $channel = $this->pkginfo->getChannel(); // {{{ assemble the destination paths switch ($atts['role']) { - case 'doc': - case 'data': - case 'test': - $dest_dir = $this->config->get($atts['role'] . '_dir') . - DIRECTORY_SEPARATOR . $this->pkginfo['package']; - unset($atts['baseinstalldir']); - break; - case 'ext': - case 'php': - $dest_dir = $this->config->get($atts['role'] . '_dir'); - break; - case 'script': - $dest_dir = $this->config->get('bin_dir'); - break; case 'src': case 'extsrc': $this->source_files++; return; + case 'doc': + case 'data': + case 'test': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . + DIRECTORY_SEPARATOR . $this->pkginfo->getPackage(); + unset($atts['baseinstalldir']); + break; + case 'ext': + case 'php': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel); + break; + case 'script': + $dest_dir = $this->config->get('bin_dir', null, $channel); + break; default: return $this->raiseError("Invalid role `$atts[role]' for file $file"); } + $save_destdir = $dest_dir; if (!empty($atts['baseinstalldir'])) { $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; } + if (dirname($file) != '.' && empty($atts['install-as'])) { $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); } + if (empty($atts['install-as'])) { $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); } else { @@ -219,125 +285,411 @@ class PEAR_Installer extends PEAR_Downloader // Clean up the DIRECTORY_SEPARATOR mess $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), - DIRECTORY_SEPARATOR, + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), array($dest_file, $orig_file)); - $installed_as = $dest_file; - $final_dest_file = $this->_prependPath($dest_file, $this->installroot); - $dest_dir = dirname($final_dest_file); + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); + } else { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + } + + $dest_dir = dirname($final_dest_file); $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } // }}} - if (!@is_dir($dest_dir)) { + if (empty($this->_options['register-only']) && + (!file_exists($dest_dir) || !is_dir($dest_dir))) { if (!$this->mkDirHier($dest_dir)) { return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED); } $this->log(3, "+ mkdir $dest_dir"); } - if (empty($atts['replacements'])) { - if (!file_exists($orig_file)) { - return $this->raiseError("file does not exist", - PEAR_INSTALLER_FAILED); - } - if (!@copy($orig_file, $dest_file)) { - return $this->raiseError("failed to write $dest_file", - PEAR_INSTALLER_FAILED); - } - $this->log(3, "+ cp $orig_file $dest_file"); - if (isset($atts['md5sum'])) { - $md5sum = md5_file($dest_file); - } - } else { - // {{{ file with replacements - if (!file_exists($orig_file)) { - return $this->raiseError("file does not exist", - PEAR_INSTALLER_FAILED); - } - $fp = fopen($orig_file, "r"); - $contents = fread($fp, filesize($orig_file)); - fclose($fp); - if (isset($atts['md5sum'])) { - $md5sum = md5($contents); - } - $subst_from = $subst_to = array(); - foreach ($atts['replacements'] as $a) { - $to = ''; - if ($a['type'] == 'php-const') { - if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) { - eval("\$to = $a[to];"); - } else { - $this->log(0, "invalid php-const replacement: $a[to]"); - continue; - } - } elseif ($a['type'] == 'pear-config') { - $to = $this->config->get($a['to']); - if (is_null($to)) { - $this->log(0, "invalid pear-config replacement: $a[to]"); - continue; - } - } elseif ($a['type'] == 'package-info') { - if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) { - $to = $this->pkginfo[$a['to']]; - } else { - $this->log(0, "invalid package-info replacement: $a[to]"); - continue; - } - } - if (!is_null($to)) { - $subst_from[] = $a['from']; - $subst_to[] = $to; - } - } - $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); - if (sizeof($subst_from)) { - $contents = str_replace($subst_from, $subst_to, $contents); - } - $wp = @fopen($dest_file, "wb"); - if (!is_resource($wp)) { - return $this->raiseError("failed to create $dest_file: $php_errormsg", - PEAR_INSTALLER_FAILED); - } - if (!fwrite($wp, $contents)) { - return $this->raiseError("failed writing to $dest_file: $php_errormsg", - PEAR_INSTALLER_FAILED); - } - fclose($wp); - // }}} - } - // {{{ check the md5 - if (isset($md5sum)) { - if (strtolower($md5sum) == strtolower($atts['md5sum'])) { - $this->log(2, "md5sum ok: $final_dest_file"); - } else { - if (empty($options['force'])) { - // delete the file - @unlink($dest_file); - return $this->raiseError("bad md5sum for file $final_dest_file", + + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (empty($atts['replacements'])) { + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($atts['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { + // {{{ file with replacements + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($atts['md5sum'])) { + $md5sum = md5($contents); + } + + $subst_from = $subst_to = array(); + foreach ($atts['replacements'] as $a) { + $to = ''; + if ($a['type'] == 'php-const') { + if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) { + eval("\$to = $a[to];"); + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid php-const replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'pear-config') { + if ($a['to'] == 'master_server') { + $chan = $this->_registry->getChannel($channel); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $to = $this->config->get($a['to'], null, $channel); + } + } else { + $to = $this->config->get($a['to'], null, $channel); + } + if (is_null($to)) { + if (!isset($options['soft'])) { + $this->log(0, "invalid pear-config replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'package-info') { + if ($t = $this->pkginfo->packageInfo($a['to'])) { + $to = $t; + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid package-info replacement: $a[to]"); + } + continue; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + + $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (@fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + // }}} + } + + // {{{ check the md5 + if (isset($md5sum)) { + if (strtolower($md5sum) === strtolower($atts['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); } else { - $this->log(0, "warning : bad md5sum for file $final_dest_file"); + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } } } - } - // }}} - // {{{ set file permissions - if (!OS_WINDOWS) { - if ($atts['role'] == 'script') { - $mode = 0777 & ~(int)octdec($this->config->get('umask')); - $this->log(3, "+ chmod +x $dest_file"); + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($atts['role'] == 'script') { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($atts['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); } else { - $mode = 0666 & ~(int)octdec($this->config->get('umask')); - } - $this->addFileOperation("chmod", array($mode, $dest_file)); - if (!@chmod($dest_file, $mode)) { - $this->log(0, "failed to change mode of $dest_file"); + $this->addFileOperation("rename", array($dest_file, $final_dest_file, + $atts['role'] == 'ext')); } } - // }}} - $this->addFileOperation("rename", array($dest_file, $final_dest_file)); + // Store the full path where the file was installed for easy unistall - $this->addFileOperation("installed_as", array($file, $installed_as, - $save_destdir, dirname(substr($dest_file, strlen($save_destdir))))); + if ($atts['role'] != 'script') { + $loc = $this->config->get($atts['role'] . '_dir'); + } else { + $loc = $this->config->get('bin_dir'); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("installed_as", array($file, $installed_as, + $loc, + dirname(substr($installedas_dest_file, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ _installFile2() + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) + { + $atts = $real_atts; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channel = $pkg->getChannel(); + // {{{ assemble the destination paths + if (!in_array($atts['attribs']['role'], + PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . + "' for file $file"); + } + + $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config); + $err = $role->setup($this, $pkg, $atts['attribs'], $file); + if (PEAR::isError($err)) { + return $err; + } + + if (!$role->isInstallable()) { + return; + } + + $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path); + if (PEAR::isError($info)) { + return $info; + } + + list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info; + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $final_dest_file = $this->_prependPath($final_dest_file, + $this->_options['packagingroot']); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + // }}} + + if (empty($this->_options['register-only'])) { + if (!file_exists($dest_dir) || !is_dir($dest_dir)) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + } + + $attribs = $atts['attribs']; + unset($atts['attribs']); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!count($atts)) { // no tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($attribs['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { // file with tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($attribs['md5sum'])) { + $md5sum = md5($contents); + } + + foreach ($atts as $tag => $raw) { + $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->config, $this, PEAR_TASK_INSTALL); + if (!$task->isScript()) { // scripts are only handled after installation + $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); + $res = $task->startSession($pkg, $contents, $final_dest_file); + if ($res === false) { + continue; // skip this file + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + } + } + + // {{{ check the md5 + if (isset($md5sum)) { + // Make sure the original md5 sum matches with expected + if (strtolower($md5sum) === strtolower($attribs['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + + if (isset($contents)) { + // set md5 sum based on $content in case any tasks were run. + $real_atts['attribs']['md5sum'] = md5($contents); + } + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } else { + $real_atts['attribs']['md5sum'] = md5_file($dest_file); + } + + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($role->isExecutable()) { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($attribs['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($attribs['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension())); + } + } + + // Store the full path where the file was installed for easy uninstall + if ($attribs['role'] != 'src') { + $loc = $this->config->get($role->getLocationConfig(), null, $channel); + $this->addFileOperation('installed_as', array($file, $installed_as, + $loc, + dirname(substr($installed_as, strlen($loc))))); + } //$this->log(2, "installed: $dest_file"); return PEAR_INSTALLER_OK; @@ -350,16 +702,18 @@ class PEAR_Installer extends PEAR_Downloader * Add a file operation to the current file transaction. * * @see startFileTransaction() - * @var string $type This can be one of: - * - rename: rename a file ($data has 2 values) + * @param string $type This can be one of: + * - rename: rename a file ($data has 3 values) + * - backup: backup an existing file ($data has 1 value) + * - removebackup: clean up backups created during install ($data has 1 value) * - chmod: change permissions on a file ($data has 2 values) * - delete: delete a file ($data has 1 value) * - rmdir: delete a directory if empty ($data has 1 value) * - installed_as: mark a file as installed ($data has 4 values). - * @var array $data For all file operations, this array must contain the + * @param array $data For all file operations, this array must contain the * full path to the file or directory that is being operated on. For * the rename command, the first parameter must be the file to rename, - * the second its new name. + * the second its new name, the third whether this is a PHP extension. * * The installed_as operation contains 4 elements in this order: * 1. Filename as listed in the filelist element from package.xml @@ -374,6 +728,7 @@ class PEAR_Installer extends PEAR_Downloader return $this->raiseError('Internal Error: $data in addFileOperation' . ' must be an array, was ' . gettype($data)); } + if ($type == 'chmod') { $octmode = decoct($data[0]); $this->log(3, "adding to transaction: $type $octmode $data[1]"); @@ -399,17 +754,16 @@ class PEAR_Installer extends PEAR_Downloader function commitFileTransaction() { - $n = count($this->file_operations); - $this->log(2, "about to commit $n file operations"); // {{{ first, check permissions and such manually $errors = array(); - foreach ($this->file_operations as $tr) { + foreach ($this->file_operations as $key => $tr) { list($type, $data) = $tr; switch ($type) { case 'rename': if (!file_exists($data[0])) { $errors[] = "cannot rename file $data[0], doesn't exist"; } + // check that dest dir. is writable if (!is_writable(dirname($data[1]))) { $errors[] = "permission denied ($type): $data[1]"; @@ -426,51 +780,176 @@ class PEAR_Installer extends PEAR_Downloader $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); } // check that directory is writable - if (file_exists($data[0]) && !is_writable(dirname($data[0]))) { - $errors[] = "permission denied ($type): $data[0]"; + if (file_exists($data[0])) { + if (!is_writable(dirname($data[0]))) { + $errors[] = "permission denied ($type): $data[0]"; + } else { + // make sure the file to be deleted can be opened for writing + $fp = false; + if (!is_dir($data[0]) && + (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) { + $errors[] = "permission denied ($type): $data[0]"; + } elseif ($fp) { + fclose($fp); + } + } + + /* Verify we are not deleting a file owned by another package + * This can happen when a file moves from package A to B in + * an upgrade ala http://pear.php.net/17986 + */ + $info = array( + 'package' => strtolower($this->pkginfo->getName()), + 'channel' => strtolower($this->pkginfo->getChannel()), + ); + $result = $this->_registry->checkFileMap($data[0], $info, '1.1'); + if (is_array($result)) { + $res = array_diff($result, $info); + if (!empty($res)) { + $new = $this->_registry->getPackage($result[1], $result[0]); + $this->file_operations[$key] = false; + $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled."); + } + } } break; } } // }}} - $m = sizeof($errors); + + $n = count($this->file_operations); + $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName()); + + $m = count($errors); if ($m > 0) { foreach ($errors as $error) { - $this->log(1, $error); + if (!isset($this->_options['soft'])) { + $this->log(1, $error); + } + } + + if (!isset($this->_options['ignore-errors'])) { + return false; } - return false; } + + $this->_dirtree = array(); // {{{ really commit the transaction - foreach ($this->file_operations as $tr) { + foreach ($this->file_operations as $i => $tr) { + if (!$tr) { + // support removal of non-existing backups + continue; + } + list($type, $data) = $tr; switch ($type) { + case 'backup': + if (!file_exists($data[0])) { + $this->file_operations[$i] = false; + break; + } + + if (!@copy($data[0], $data[0] . '.bak')) { + $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . + '.bak ' . $php_errormsg); + return false; + } + $this->log(3, "+ backup $data[0] to $data[0].bak"); + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; case 'rename': - @unlink($data[1]); - @rename($data[0], $data[1]); + $test = file_exists($data[1]) ? @unlink($data[1]) : null; + if (!$test && file_exists($data[1])) { + if ($data[2]) { + $extra = ', this extension must be installed manually. Rename to "' . + basename($data[1]) . '"'; + } else { + $extra = ''; + } + + if (!isset($this->_options['soft'])) { + $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . + $data[0] . $extra); + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + // permissions issues with rename - copy() is far superior + $perms = @fileperms($data[0]); + if (!@copy($data[0], $data[1])) { + $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . + ' ' . $php_errormsg); + return false; + } + + // copy over permissions, otherwise they are lost + @chmod($data[1], $perms); + @unlink($data[0]); $this->log(3, "+ mv $data[0] $data[1]"); break; case 'chmod': - @chmod($data[1], $data[0]); + if (!@chmod($data[1], $data[0])) { + $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . + decoct($data[0]) . ' ' . $php_errormsg); + return false; + } + $octmode = decoct($data[0]); $this->log(3, "+ chmod $octmode $data[1]"); break; case 'delete': - @unlink($data[0]); - $this->log(3, "+ rm $data[0]"); + if (file_exists($data[0])) { + if (!@unlink($data[0])) { + $this->log(1, 'Could not delete ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rm $data[0]"); + } break; case 'rmdir': - @rmdir($data[0]); - $this->log(3, "+ rmdir $data[0]"); + if (file_exists($data[0])) { + do { + $testme = opendir($data[0]); + while (false !== ($entry = readdir($testme))) { + if ($entry == '.' || $entry == '..') { + continue; + } + closedir($testme); + break 2; // this directory is not empty and can't be + // deleted + } + + closedir($testme); + if (!@rmdir($data[0])) { + $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rmdir $data[0]"); + } while (false); + } break; case 'installed_as': - $this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1]; - if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) { - $this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true; - while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\' - && $data[3] != '.') { - $this->pkginfo['filelist']['dirtree'] - [$this->_prependPath($data[3], $data[2])] = true; + $this->pkginfo->setInstalledAs($data[0], $data[1]); + if (!isset($this->_dirtree[dirname($data[1])])) { + $this->_dirtree[dirname($data[1])] = true; + $this->pkginfo->setDirtree(dirname($data[1])); + + while(!empty($data[3]) && dirname($data[3]) != $data[3] && + $data[3] != '/' && $data[3] != '\\') { + $this->pkginfo->setDirtree($pp = + $this->_prependPath($data[3], $data[2])); + $this->_dirtree[$pp] = true; $data[3] = dirname($data[3]); } } @@ -493,6 +972,21 @@ class PEAR_Installer extends PEAR_Downloader foreach ($this->file_operations as $tr) { list($type, $data) = $tr; switch ($type) { + case 'backup': + if (file_exists($data[0] . '.bak')) { + if (file_exists($data[0] && is_writable($data[0]))) { + unlink($data[0]); + } + @copy($data[0] . '.bak', $data[0]); + $this->log(3, "+ restore $data[0] from $data[0].bak"); + } + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; case 'rename': @unlink($data[0]); $this->log(3, "+ rm $data[0]"); @@ -506,25 +1000,11 @@ class PEAR_Installer extends PEAR_Downloader case 'delete': break; case 'installed_as': - if (isset($this->pkginfo['filelist'])) { - unset($this->pkginfo['filelist'][$data[0]]['installed_as']); - } - if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) { - unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]); - while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\' - && $data[3] != '.') { - unset($this->pkginfo['filelist']['dirtree'] - [$this->_prependPath($data[3], $data[2])]); - $data[3] = dirname($data[3]); - } - } - if (isset($this->pkginfo['filelist']['dirtree']) - && !count($this->pkginfo['filelist']['dirtree'])) { - unset($this->pkginfo['filelist']['dirtree']); - } + $this->pkginfo->setInstalledAs($data[0], false); break; } } + $this->pkginfo->resetDirtree(); $this->file_operations = array(); } @@ -565,8 +1045,8 @@ class PEAR_Installer extends PEAR_Downloader { // trickiness: initialize here parent::PEAR_Downloader($this->ui, $options, $config); - $ret = parent::download($packages); - $errors = $this->getErrorMsgs(); + $ret = parent::download($packages); + $errors = $this->getErrorMsgs(); $installpackages = $this->getDownloadedPackages(); trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . "in favor of PEAR_Downloader class", E_USER_WARNING); @@ -574,12 +1054,70 @@ class PEAR_Installer extends PEAR_Downloader } // }}} + // {{{ _parsePackageXml() + + function _parsePackageXml(&$descfile) + { + // Parse xml file ----------------------------------------------- + $pkg = new PEAR_PackageFile($this->config, $this->debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($p)) { + if (is_array($p->getUserInfo())) { + foreach ($p->getUserInfo() as $err) { + $loglevel = $err['level'] == 'error' ? 0 : 1; + if (!isset($this->_options['soft'])) { + $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']); + } + } + } + return $this->raiseError('Installation failed: invalid package file'); + } + + $descfile = $p->getPackageFile(); + return $p; + } + + // }}} + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setDownloadedPackages(&$pkgs) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($pkgs); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return $err; + } + $this->_downloadedPackages = &$pkgs; + } + + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setUninstallPackages(&$pkgs) + { + $this->_downloadedPackages = &$pkgs; + } + + function getInstallPackages() + { + return $this->_downloadedPackages; + } + // {{{ install() /** * Installs the files within the package file specified. * - * @param string $pkgfile path to the package file + * @param string|PEAR_Downloader_Package $pkgfile path to the package file, + * or a pre-initialized packagefile object * @param array $options * recognized options: * - installroot : optional prefix directory for installation @@ -593,121 +1131,183 @@ class PEAR_Installer extends PEAR_Downloader * * @return array|PEAR_Error package info if successful */ - function install($pkgfile, $options = array()) { - $php_dir = $this->config->get('php_dir'); - if (isset($options['installroot'])) { - if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) { - $options['installroot'] = substr($options['installroot'], 0, -1); - } - $php_dir = $this->_prependPath($php_dir, $options['installroot']); - $this->installroot = $options['installroot']; + $this->_options = $options; + $this->_registry = &$this->config->getRegistry(); + if (is_object($pkgfile)) { + $dlpkg = &$pkgfile; + $pkg = $pkgfile->getPackageFile(); + $pkgfile = $pkg->getArchiveFile(); + $descfile = $pkg->getPackageFile(); } else { - $this->installroot = ''; - } - $this->registry = &new PEAR_Registry($php_dir); - // ==> XXX should be removed later on - $flag_old_format = false; - - if (substr($pkgfile, -4) == '.xml') { $descfile = $pkgfile; - } else { - // {{{ Decompress pack in tmp dir ------------------------------------- - - // To allow relative package file names - $pkgfile = realpath($pkgfile); - - if (PEAR::isError($tmpdir = System::mktemp('-d'))) { - return $tmpdir; + $pkg = $this->_parsePackageXml($descfile); + if (PEAR::isError($pkg)) { + return $pkg; } - $this->log(3, '+ tmp dir created at ' . $tmpdir); + } + + $tmpdir = dirname($descfile); + if (realpath($descfile) != realpath($pkgfile)) { + // Use the temp_dir since $descfile can contain the download dir path + $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net'); + $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"'); $tar = new Archive_Tar($pkgfile); - if (!@$tar->extract($tmpdir)) { + if (!$tar->extract($tmpdir)) { return $this->raiseError("unable to unpack $pkgfile"); } - - // {{{ Look for existing package file - $descfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml'; - - if (!is_file($descfile)) { - // ----- Look for old package archive format - // In this format the package.xml file was inside the - // Package-n.n directory - $dp = opendir($tmpdir); - do { - $pkgdir = readdir($dp); - } while ($pkgdir{0} == '.'); - - $descfile = $tmpdir . DIRECTORY_SEPARATOR . $pkgdir . DIRECTORY_SEPARATOR . 'package.xml'; - $flag_old_format = true; - $this->log(0, "warning : you are using an archive with an old format"); - } - // }}} - // <== XXX This part should be removed later on - // }}} } - if (!is_file($descfile)) { - return $this->raiseError("no package.xml file after extracting the archive"); + $pkgname = $pkg->getName(); + $channel = $pkg->getChannel(); + if (isset($this->_options['packagingroot'])) { + $regdir = $this->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $this->_options['packagingroot']); + + $packrootphp_dir = $this->_prependPath( + $this->config->get('php_dir', null, $channel), + $this->_options['packagingroot']); } - // Parse xml file ----------------------------------------------- - $pkginfo = $this->infoFromDescriptionFile($descfile); - if (PEAR::isError($pkginfo)) { - return $pkginfo; - } - $this->validatePackageInfo($pkginfo, $errors, $warnings); - // XXX We allow warnings, do we have to do it? - if (count($errors)) { - if (empty($options['force'])) { - return $this->raiseError("The following errors where found (use force option to install anyway):\n". - implode("\n", $errors)); + if (isset($options['installroot'])) { + $this->config->setInstallRoot($options['installroot']); + $this->_registry = &$this->config->getRegistry(); + $installregistry = &$this->_registry; + $this->installroot = ''; // all done automagically now + $php_dir = $this->config->get('php_dir', null, $channel); + } else { + $this->config->setInstallRoot(false); + $this->_registry = &$this->config->getRegistry(); + if (isset($this->_options['packagingroot'])) { + $installregistry = &new PEAR_Registry($regdir); + if (!$installregistry->channelExists($channel, true)) { + // we need to fake a channel-discover of this channel + $chanobj = $this->_registry->getChannel($channel, true); + $installregistry->addChannel($chanobj); + } + $php_dir = $packrootphp_dir; } else { - $this->log(0, "warning : the following errors were found:\n". - implode("\n", $errors)); + $installregistry = &$this->_registry; + $php_dir = $this->config->get('php_dir', null, $channel); } + $this->installroot = ''; } - $pkgname = $pkginfo['package']; - - // {{{ Check dependencies ------------------------------------------- - if (isset($pkginfo['release_deps']) && empty($options['nodeps'])) { - $dep_errors = ''; - $error = $this->checkDeps($pkginfo, $dep_errors); - if ($error == true) { - if (empty($options['soft'])) { - $this->log(0, substr($dep_errors, 1)); - } - return $this->raiseError("$pkgname: Dependencies failed"); - } else if (!empty($dep_errors)) { - // Print optional dependencies - if (empty($options['soft'])) { - $this->log(0, $dep_errors); - } - } - } - // }}} - // {{{ checks to do when not in "force" mode - if (empty($options['force'])) { - $test = $this->registry->checkFileMap($pkginfo); + if (empty($options['force']) && + (file_exists($this->config->get('php_dir')) && + is_dir($this->config->get('php_dir')))) { + $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname); + $instfilelist = $pkg->getInstallationFileList(true); + if (PEAR::isError($instfilelist)) { + return $instfilelist; + } + + // ensure we have the most accurate registry + $installregistry->flushFileMap(); + $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'); + if (PEAR::isError($test)) { + return $test; + } + if (sizeof($test)) { - $tmp = $test; - foreach ($tmp as $file => $pkg) { - if ($pkg == $pkgname) { - unset($test[$file]); + $pkgs = $this->getInstallPackages(); + $found = false; + foreach ($pkgs as $param) { + if ($pkg->isSubpackageOf($param)) { + $found = true; + break; } } - if (sizeof($test)) { - $msg = "$pkgname: conflicting files found:\n"; + + if ($found) { + // subpackages can conflict with earlier versions of parent packages + $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel()); + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_array($info)) { + if (strtolower($info[1]) == strtolower($param->getPackage()) && + strtolower($info[0]) == strtolower($param->getChannel()) + ) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } else { + if (strtolower($param->getChannel()) != 'pear.php.net') { + continue; + } + + if (strtolower($info) == strtolower($param->getPackage())) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } + } + + $pfk = &new PEAR_PackageFile($this->config); + $parentpkg = &$pfk->fromArray($parentreg); + $installregistry->updatePackage2($parentpkg); + } + + if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) { + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_string($info)) { + // pear.php.net packages are always stored as strings + if (strtolower($info) == strtolower($param->getPackage())) { + // upgrading existing package + unset($test[$file]); + } + } + } + } + + if (count($test)) { + $msg = "$channel/$pkgname: conflicting files found:\n"; $longest = max(array_map("strlen", array_keys($test))); $fmt = "%${longest}s (%s)\n"; - foreach ($test as $file => $pkg) { - $msg .= sprintf($fmt, $file, $pkg); + foreach ($test as $file => $info) { + if (!is_array($info)) { + $info = array('pear.php.net', $info); + } + $info = $info[0] . '/' . $info[1]; + $msg .= sprintf($fmt, $file, $info); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError($msg); + } + + if (!isset($options['soft'])) { + $this->log(0, "WARNING: $msg"); } - return $this->raiseError($msg); } } } @@ -715,165 +1315,310 @@ class PEAR_Installer extends PEAR_Downloader $this->startFileTransaction(); - if (empty($options['upgrade'])) { - // checks to do only when installing new packages - if (empty($options['force']) && $this->registry->packageExists($pkgname)) { - return $this->raiseError("$pkgname already installed"); + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; } } else { - if ($this->registry->packageExists($pkgname)) { - $v1 = $this->registry->packageInfo($pkgname, 'version'); - $v2 = $pkginfo['version']; + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (empty($options['upgrade']) && empty($options['soft'])) { + // checks to do only when installing new packages + if (empty($options['force']) && $test) { + return $this->raiseError("$channel/$pkgname is already installed"); + } + } else { + // Upgrade + if ($test) { + $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $v2 = $pkg->getVersion(); $cmp = version_compare("$v1", "$v2", 'gt'); if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); } - if (empty($options['register-only'])) { - // when upgrading, remove old release's files first: - if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) { - return $this->raiseError($err); - } + } + } + + // Do cleanups for upgrade and install, remove old release's files first + if ($test && empty($options['register-only'])) { + // when upgrading, remove old release's files first: + if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel, + true))) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + $backedup = $err; } } // {{{ Copy files to dest dir --------------------------------------- // info from the package it self we want to access from _installFile - $this->pkginfo = &$pkginfo; + $this->pkginfo = &$pkg; // used to determine whether we should build any C code $this->source_files = 0; - if (empty($options['register-only'])) { - if (!is_dir($php_dir)) { - return $this->raiseError("no script destination directory\n", - null, PEAR_ERROR_DIE); + $savechannel = $this->config->get('default_channel'); + if (empty($options['register-only']) && !is_dir($php_dir)) { + if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) { + return $this->raiseError("no installation destination directory '$php_dir'\n"); } + } - $tmp_path = dirname($descfile); - if (substr($pkgfile, -4) != '.xml') { - $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkginfo['version']; + if (substr($pkgfile, -4) != '.xml') { + $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion(); + } + + $this->configSet('default_channel', $channel); + // {{{ install files + + $ver = $pkg->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + $filelist = $pkg->getInstallationFilelist(); + } else { + $filelist = $pkg->getFileList(); + } + + if (PEAR::isError($filelist)) { + return $filelist; + } + + $p = &$installregistry->getPackage($pkgname, $channel); + $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false; + + $pkg->resetFilelist(); + $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(), + 'version', $pkg->getChannel())); + foreach ($filelist as $file => $atts) { + $this->expectError(PEAR_INSTALLER_FAILED); + if ($pkg->getPackagexmlVersion() == '1.0') { + $res = $this->_installFile($file, $atts, $tmpdir, $options); + } else { + $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options); } + $this->popExpect(); - // ==> XXX This part should be removed later on - if ($flag_old_format) { - $tmp_path = dirname($descfile); - } - // <== XXX This part should be removed later on - - // {{{ install files - foreach ($pkginfo['filelist'] as $file => $atts) { - $this->expectError(PEAR_INSTALLER_FAILED); - $res = $this->_installFile($file, $atts, $tmp_path, $options); - $this->popExpect(); - if (PEAR::isError($res)) { - if (empty($options['ignore-errors'])) { - $this->rollbackFileTransaction(); - if ($res->getMessage() == "file does not exist") { - $this->raiseError("file $file in package.xml does not exist"); - } - return $this->raiseError($res); - } else { - $this->log(0, "Warning: " . $res->getMessage()); - } - } - if ($res != PEAR_INSTALLER_OK) { - // Do not register files that were not installed - unset($pkginfo['filelist'][$file]); - } - } - // }}} - - // {{{ compile and install source files - if ($this->source_files > 0 && empty($options['nobuild'])) { - $this->log(1, "$this->source_files source files, building"); - $bob = &new PEAR_Builder($this->ui); - $bob->debug = $this->debug; - $built = $bob->build($descfile, array(&$this, '_buildCallback')); - if (PEAR::isError($built)) { + if (PEAR::isError($res)) { + if (empty($options['ignore-errors'])) { $this->rollbackFileTransaction(); - return $built; - } - $this->log(1, "\nBuild process completed successfully"); - foreach ($built as $ext) { - $bn = basename($ext['file']); - list($_ext_name, $_ext_suff) = explode('.', $bn); - if ($_ext_suff == '.so' || $_ext_suff == '.dll') { - if (extension_loaded($_ext_name)) { - $this->raiseError("Extension '$_ext_name' already loaded. " . - 'Please unload it in your php.ini file ' . - 'prior to install or upgrade'); - } - $role = 'ext'; - } else { - $role = 'src'; + if ($res->getMessage() == "file does not exist") { + $this->raiseError("file $file in package.xml does not exist"); } - $dest = $ext['dest']; - $this->log(1, "Installing '$ext[file]'"); - $copyto = $this->_prependPath($dest, $this->installroot); - $copydir = dirname($copyto); - if (!@is_dir($copydir)) { - if (!$this->mkDirHier($copydir)) { - return $this->raiseError("failed to mkdir $copydir", - PEAR_INSTALLER_FAILED); - } - $this->log(3, "+ mkdir $copydir"); - } - if (!@copy($ext['file'], $copyto)) { - return $this->raiseError("failed to write $copyto", PEAR_INSTALLER_FAILED); - } - $this->log(3, "+ cp $ext[file] $copyto"); - if (!OS_WINDOWS) { - $mode = 0666 & ~(int)octdec($this->config->get('umask')); - $this->addFileOperation('chmod', array($mode, $copyto)); - if (!@chmod($copyto, $mode)) { - $this->log(0, "failed to change mode of $copyto"); - } - } - $this->addFileOperation('rename', array($ext['file'], $copyto)); - $pkginfo['filelist'][$bn] = array( - 'role' => $role, - 'installed_as' => $dest, - 'php_api' => $ext['php_api'], - 'zend_mod_api' => $ext['zend_mod_api'], - 'zend_ext_api' => $ext['zend_ext_api'], - ); + return $this->raiseError($res); + } + + if (!isset($options['soft'])) { + $this->log(0, "Warning: " . $res->getMessage()); } } - // }}} + + $real = isset($atts['attribs']) ? $atts['attribs'] : $atts; + if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') { + // Register files that were installed + $pkg->installedFile($file, $atts); + } + } + // }}} + + // {{{ compile and install source files + if ($this->source_files > 0 && empty($options['nobuild'])) { + if (PEAR::isError($err = + $this->_compileSourceFiles($savechannel, $pkg))) { + return $err; + } + } + // }}} + + if (isset($backedup)) { + $this->_removeBackups($backedup); } if (!$this->commitFileTransaction()) { $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); } // }}} - $ret = false; + $ret = false; + $installphase = 'install'; + $oldversion = false; // {{{ Register that the package is installed ----------------------- if (empty($options['upgrade'])) { // if 'force' is used, replace the info in registry - if (!empty($options['force']) && $this->registry->packageExists($pkgname)) { - $this->registry->deletePackage($pkgname); - } - $ret = $this->registry->addPackage($pkgname, $pkginfo); - } else { - // new: upgrade installs a package if it isn't installed - if (!$this->registry->packageExists($pkgname)) { - $ret = $this->registry->addPackage($pkgname, $pkginfo); + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } } else { - $ret = $this->registry->updatePackage($pkgname, $pkginfo, false); + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (!empty($options['force']) && $test) { + $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $installregistry->deletePackage($pkgname, $usechannel); + } + $ret = $installregistry->addPackage2($pkg); + } else { + if ($dirtree) { + $this->startFileTransaction(); + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + $this->commitFileTransaction(); + } + + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + // new: upgrade installs a package if it isn't installed + if (!$test) { + $ret = $installregistry->addPackage2($pkg); + } else { + if ($usechannel != $channel) { + $installregistry->deletePackage($pkgname, $usechannel); + $ret = $installregistry->addPackage2($pkg); + } else { + $ret = $installregistry->updatePackage2($pkg); + } + $installphase = 'upgrade'; } } + if (!$ret) { - return $this->raiseError("Adding package $pkgname to registry failed"); + $this->configSet('default_channel', $savechannel); + return $this->raiseError("Adding package $channel/$pkgname to registry failed"); } // }}} - return $pkginfo; + + $this->configSet('default_channel', $savechannel); + if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist + if (PEAR_Task_Common::hasPostinstallTasks()) { + PEAR_Task_Common::runPostinstallTasks($installphase); + } + } + + return $pkg->toArray(true); } // }}} + + // {{{ _compileSourceFiles() + /** + * @param string + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function _compileSourceFiles($savechannel, &$filelist) + { + require_once 'PEAR/Builder.php'; + $this->log(1, "$this->source_files source files, building"); + $bob = &new PEAR_Builder($this->ui); + $bob->debug = $this->debug; + $built = $bob->build($filelist, array(&$this, '_buildCallback')); + if (PEAR::isError($built)) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $built; + } + + $this->log(1, "\nBuild process completed successfully"); + foreach ($built as $ext) { + $bn = basename($ext['file']); + list($_ext_name, $_ext_suff) = explode('.', $bn); + if ($_ext_suff == '.so' || $_ext_suff == '.dll') { + if (extension_loaded($_ext_name)) { + $this->raiseError("Extension '$_ext_name' already loaded. " . + 'Please unload it in your php.ini file ' . + 'prior to install or upgrade'); + } + $role = 'ext'; + } else { + $role = 'src'; + } + + $dest = $ext['dest']; + $packagingroot = ''; + if (isset($this->_options['packagingroot'])) { + $packagingroot = $this->_options['packagingroot']; + } + + $copyto = $this->_prependPath($dest, $packagingroot); + $extra = $copyto != $dest ? " as '$copyto'" : ''; + $this->log(1, "Installing '$dest'$extra"); + + $copydir = dirname($copyto); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!file_exists($copydir) || !is_dir($copydir)) { + if (!$this->mkDirHier($copydir)) { + return $this->raiseError("failed to mkdir $copydir", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ mkdir $copydir"); + } + + if (!@copy($ext['file'], $copyto)) { + return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $ext[file] $copyto"); + $this->addFileOperation('rename', array($ext['file'], $copyto)); + if (!OS_WINDOWS) { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + $this->addFileOperation('chmod', array($mode, $copyto)); + if (!@chmod($copyto, $mode)) { + $this->log(0, "failed to change mode of $copyto ($php_errormsg)"); + } + } + } + + + $data = array( + 'role' => $role, + 'name' => $bn, + 'installed_as' => $dest, + 'php_api' => $ext['php_api'], + 'zend_mod_api' => $ext['zend_mod_api'], + 'zend_ext_api' => $ext['zend_ext_api'], + ); + + if ($filelist->getPackageXmlVersion() == '1.0') { + $filelist->installedFile($bn, $data); + } else { + $filelist->installedFile($bn, array('attribs' => $data)); + } + } + } + + // }}} + function &getUninstallPackages() + { + return $this->_downloadedPackages; + } // {{{ uninstall() /** @@ -885,63 +1630,172 @@ class PEAR_Installer extends PEAR_Downloader * @param array Command-line options. Possibilities include: * * - installroot: base installation dir, if not the default + * - register-only : update registry but don't remove files * - nodeps: do not process dependencies of other packages to ensure * uninstallation does not break things */ function uninstall($package, $options = array()) { - $php_dir = $this->config->get('php_dir'); - if (isset($options['installroot'])) { - if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) { - $options['installroot'] = substr($options['installroot'], 0, -1); - } - $this->installroot = $options['installroot']; - $php_dir = $this->_prependPath($php_dir, $this->installroot); + $installRoot = isset($options['installroot']) ? $options['installroot'] : ''; + $this->config->setInstallRoot($installRoot); + + $this->installroot = ''; + $this->_registry = &$this->config->getRegistry(); + if (is_object($package)) { + $channel = $package->getChannel(); + $pkg = $package; + $package = $pkg->getPackage(); } else { - $this->installroot = ''; + $pkg = false; + $info = $this->_registry->parsePackageName($package, + $this->config->get('default_channel')); + $channel = $info['channel']; + $package = $info['package']; } - $this->registry = &new PEAR_Registry($php_dir); - $filelist = $this->registry->packageInfo($package, 'filelist'); - if ($filelist == null) { - return $this->raiseError("$package not installed"); + + $savechannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $channel); + if (!is_object($pkg)) { + $pkg = $this->_registry->getPackage($package, $channel); } - if (empty($options['nodeps'])) { - $depchecker = &new PEAR_Dependency($this->registry); - $error = $depchecker->checkPackageUninstall($errors, $warning, $package); - if ($error) { - return $this->raiseError($errors . 'uninstall failed'); + + if (!$pkg) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError($this->_registry->parsedPackageNameToString( + array( + 'channel' => $channel, + 'package' => $package + ), true) . ' not installed'); + } + + if ($pkg->getInstalledBinary()) { + // this is just an alias for a binary package + return $this->_registry->deletePackage($package, $channel); + } + + $filelist = $pkg->getFilelist(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $depchecker = &new PEAR_Dependency2($this->config, $options, + array('channel' => $channel, 'package' => $package), + PEAR_VALIDATE_UNINSTALLING); + $e = $depchecker->validatePackageUninstall($this); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($e); } - if ($warning) { - $this->log(0, $warning); + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $e->getMessage()); + } + } elseif (is_array($e)) { + if (!isset($options['soft'])) { + $this->log(0, $e[0]); } } - // {{{ Delete the files - $this->startFileTransaction(); - if (PEAR::isError($err = $this->_deletePackageFiles($package))) { - $this->rollbackFileTransaction(); - return $this->raiseError($err); - } - if (!$this->commitFileTransaction()) { - $this->rollbackFileTransaction(); - return $this->raiseError("uninstall failed"); - } else { + + $this->pkginfo = &$pkg; + // pretty much nothing happens if we are only registering the uninstall + if (empty($options['register-only'])) { + // {{{ Delete the files $this->startFileTransaction(); - if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) { - return $this->registry->deletePackage($package); - } - // attempt to delete empty directories - uksort($filelist['dirtree'], array($this, '_sortDirs')); - foreach($filelist['dirtree'] as $dir => $notused) { - $this->addFileOperation('rmdir', array($dir)); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) { + PEAR::popErrorHandling(); + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + PEAR::popErrorHandling(); } + if (!$this->commitFileTransaction()) { $this->rollbackFileTransaction(); - } - } - // }}} + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } else { + $this->startFileTransaction(); + $dirtree = $pkg->getDirTree(); + if ($dirtree === false) { + $this->configSet('default_channel', $savechannel); + return $this->_registry->deletePackage($package, $channel); + } + + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } + } + // }}} + } + + $this->configSet('default_channel', $savechannel); // Register that the package is no longer installed - return $this->registry->deletePackage($package); + return $this->_registry->deletePackage($package, $channel); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * It also removes duplicate dependencies + * @param array an array of PEAR_PackageFile_v[1/2] objects + * @return array|PEAR_Error array of array(packagefilename, package.xml contents) + */ + function sortPackagesForUninstall(&$packages) + { + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config); + if (PEAR::isError($this->_dependencyDB)) { + return $this->_dependencyDB; + } + usort($packages, array(&$this, '_sortUninstall')); + } + + function _sortUninstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return -1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return 1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependencyDB->dependsOn($a, $b)) { + return -1; + } + if ($this->_dependencyDB->dependsOn($b, $a)) { + return 1; + } + return 0; } // }}} @@ -954,92 +1808,7 @@ class PEAR_Installer extends PEAR_Downloader } // }}} - // {{{ checkDeps() - /** - * Check if the package meets all dependencies - * - * @param array Package information (passed by reference) - * @param string Error message (passed by reference) - * @return boolean False when no error occured, otherwise true - */ - function checkDeps(&$pkginfo, &$errors) - { - if (empty($this->registry)) { - $this->registry = &new PEAR_Registry($this->config->get('php_dir')); - } - $depchecker = &new PEAR_Dependency($this->registry); - $error = $errors = ''; - $failed_deps = $optional_deps = array(); - if (is_array($pkginfo['release_deps'])) { - foreach($pkginfo['release_deps'] as $dep) { - $code = $depchecker->callCheckMethod($error, $dep); - if ($code) { - if (isset($dep['optional']) && $dep['optional'] == 'yes') { - $optional_deps[] = array($dep, $code, $error); - } else { - $failed_deps[] = array($dep, $code, $error); - } - } - } - // {{{ failed dependencies - $n = count($failed_deps); - if ($n > 0) { - for ($i = 0; $i < $n; $i++) { - if (isset($failed_deps[$i]['type'])) { - $type = $failed_deps[$i]['type']; - } else { - $type = 'pkg'; - } - switch ($failed_deps[$i][1]) { - case PEAR_DEPENDENCY_MISSING: - if ($type == 'pkg') { - // install - } - $errors .= "\n" . $failed_deps[$i][2]; - break; - case PEAR_DEPENDENCY_UPGRADE_MINOR: - if ($type == 'pkg') { - // upgrade - } - $errors .= "\n" . $failed_deps[$i][2]; - break; - default: - $errors .= "\n" . $failed_deps[$i][2]; - break; - } - } - return true; - } - // }}} - - // {{{ optional dependencies - $count_optional = count($optional_deps); - if ($count_optional > 0) { - $errors = "Optional dependencies:"; - - for ($i = 0; $i < $count_optional; $i++) { - if (isset($optional_deps[$i]['type'])) { - $type = $optional_deps[$i]['type']; - } else { - $type = 'pkg'; - } - switch ($optional_deps[$i][1]) { - case PEAR_DEPENDENCY_MISSING: - case PEAR_DEPENDENCY_UPGRADE_MINOR: - default: - $errors .= "\n" . $optional_deps[$i][2]; - break; - } - } - return false; - } - // }}} - } - return false; - } - - // }}} // {{{ _buildCallback() function _buildCallback($what, $data) @@ -1051,18 +1820,4 @@ class PEAR_Installer extends PEAR_Downloader } // }}} -} - -// {{{ md5_file() utility function -if (!function_exists("md5_file")) { - function md5_file($filename) { - $fp = fopen($filename, "r"); - if (!$fp) return null; - $contents = fread($fp, filesize($filename)); - fclose($fp); - return md5($contents); - } -} -// }}} - -?> +} \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role.php b/3rdparty/PEAR/Installer/Role.php new file mode 100644 index 0000000000..0c50fa79c0 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role.php @@ -0,0 +1,276 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Role.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class for installer roles + */ +require_once 'PEAR/Installer/Role/Common.php'; +require_once 'PEAR/XMLParser.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role +{ + /** + * Set up any additional configuration variables that file roles require + * + * Never call this directly, it is called by the PEAR_Config constructor + * @param PEAR_Config + * @access private + * @static + */ + function initializeConfig(&$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $class => $info) { + if (!$info['config_vars']) { + continue; + } + + $config->_addConfigVars($class, $info['config_vars']); + } + } + + /** + * @param PEAR_PackageFile_v2 + * @param string role name + * @param PEAR_Config + * @return PEAR_Installer_Role_Common + * @static + */ + function &factory($pkg, $role, &$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + if (!in_array($role, PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + $a = false; + return $a; + } + + $a = 'PEAR_Installer_Role_' . ucfirst($role); + if (!class_exists($a)) { + require_once str_replace('_', '/', $a) . '.php'; + } + + $b = new $a($config); + return $b; + } + + /** + * Get a list of file roles that are valid for the particular release type. + * + * For instance, src files serve no purpose in regular php releases. + * @param string + * @param bool clear cache + * @return array + * @static + */ + function getValidRoles($release, $clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret = array(); + if ($clear) { + $ret = array(); + } + + if (isset($ret[$release])) { + return $ret[$release]; + } + + $ret[$release] = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if (in_array($release, $okreleases['releasetypes'])) { + $ret[$release][] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret[$release]; + } + + /** + * Get a list of roles that require their files to be installed + * + * Most roles must be installed, but src and package roles, for instance + * are pseudo-roles. src files are compiled into a new extension. Package + * roles are actually fully bundled releases of a package + * @param bool clear cache + * @return array + * @static + */ + function getInstallableRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['installable']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of roles that are affected by the baseinstalldir attribute + * + * Most roles ignore this attribute, and instead install directly into: + * PackageName/filepath + * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php + * @param bool clear cache + * @return array + * @static + */ + function getBaseinstallRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['honorsbaseinstall']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of file roles that should be analyzed for PHP content at package time, + * like the "php" role. + * @param bool clear cache + * @return array + * @static + */ + function getPhpRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['phpfile']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * @param string which directory to look for classes, defaults to + * the Installer/Roles subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * @access public + * @static + */ + function registerRoles($dir = null) + { + $GLOBALS['_PEAR_INSTALLER_ROLES'] = array(); + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Role'; + } + + if (!file_exists($dir) || !is_dir($dir)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory"); + } + + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg"); + } + + while ($entry = readdir($dp)) { + if ($entry{0} == '.' || substr($entry, -4) != '.xml') { + continue; + } + + $class = "PEAR_Installer_Role_".substr($entry, 0, -4); + // List of roles + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) { + $file = "$dir/$entry"; + $parser->parse(file_get_contents($file)); + $data = $parser->getData(); + if (!is_array($data['releasetypes'])) { + $data['releasetypes'] = array($data['releasetypes']); + } + + $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data; + } + } + + closedir($dp); + ksort($GLOBALS['_PEAR_INSTALLER_ROLES']); + PEAR_Installer_Role::getBaseinstallRoles(true); + PEAR_Installer_Role::getInstallableRoles(true); + PEAR_Installer_Role::getPhpRoles(true); + PEAR_Installer_Role::getValidRoles('****', true); + return true; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Cfg.php b/3rdparty/PEAR/Installer/Role/Cfg.php new file mode 100644 index 0000000000..762012248d --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Cfg.php @@ -0,0 +1,106 @@ + + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Cfg.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Cfg extends PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Installer + */ + var $installer; + + /** + * the md5 of the original file + * + * @var unknown_type + */ + var $md5 = null; + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + $this->installer = &$installer; + $reg = &$this->installer->config->getRegistry(); + $package = $reg->getPackage($pkg->getPackage(), $pkg->getChannel()); + if ($package) { + $filelist = $package->getFilelist(); + if (isset($filelist[$file]) && isset($filelist[$file]['md5sum'])) { + $this->md5 = $filelist[$file]['md5sum']; + } + } + } + + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $test = parent::processInstallation($pkg, $atts, $file, $tmp_path, $layer); + if (@file_exists($test[2]) && @file_exists($test[3])) { + $md5 = md5_file($test[2]); + // configuration has already been installed, check for mods + if ($md5 !== $this->md5 && $md5 !== md5_file($test[3])) { + // configuration has been modified, so save our version as + // configfile-version + $old = $test[2]; + $test[2] .= '.new-' . $pkg->getVersion(); + // backup original and re-install it + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $tmpcfg = $this->config->get('temp_dir'); + $newloc = System::mkdir(array('-p', $tmpcfg)); + if (!$newloc) { + // try temp_dir + $newloc = System::mktemp(array('-d')); + if (!$newloc || PEAR::isError($newloc)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + } else { + $newloc = $tmpcfg; + } + + $temp_file = $newloc . DIRECTORY_SEPARATOR . uniqid('savefile'); + if (!@copy($old, $temp_file)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + + PEAR::popErrorHandling(); + $this->installer->log(0, "WARNING: configuration file $old is being installed as $test[2], you should manually merge in changes to the existing configuration file"); + $this->installer->addFileOperation('rename', array($temp_file, $old, false)); + $this->installer->addFileOperation('delete', array($temp_file)); + } + } + + return $test; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Cfg.xml b/3rdparty/PEAR/Installer/Role/Cfg.xml new file mode 100644 index 0000000000..7a415dc466 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Cfg.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + cfg_dir + + 1 + + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Common.php b/3rdparty/PEAR/Installer/Role/Common.php new file mode 100644 index 0000000000..23e7348d70 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Common.php @@ -0,0 +1,174 @@ + + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class for all installation roles. + * + * This class allows extensibility of file roles. Packages with complex + * customization can now provide custom file roles along with the possibility of + * adding configuration values to match. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Config + * @access protected + */ + var $config; + + /** + * @param PEAR_Config + */ + function PEAR_Installer_Role_Common(&$config) + { + $this->config = $config; + } + + /** + * Retrieve configuration information about a file role from its XML info + * + * @param string $role Role Classname, as in "PEAR_Installer_Role_Data" + * @return array + */ + function getInfo($role) + { + if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) { + return PEAR::raiseError('Unknown Role class: "' . $role . '"'); + } + return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role]; + } + + /** + * This is called for each file to set up the directories and files + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array attributes from the tag + * @param string file name + * @return array an array consisting of: + * + * 1 the original, pre-baseinstalldir installation directory + * 2 the final installation directory + * 3 the full path to the final location of the file + * 4 the location of the pre-installation file + */ + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + if (!$roleInfo['locationconfig']) { + return false; + } + if ($roleInfo['honorsbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], $layer, + $pkg->getChannel()); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } elseif ($roleInfo['unusualbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } else { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + } + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + + list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_dir, $dest_file, $orig_file)); + return array($save_destdir, $dest_dir, $dest_file, $orig_file); + } + + /** + * Get the name of the configuration variable that specifies the location of this file + * @return string|false + */ + function getLocationConfig() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['locationconfig']; + } + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + } + + function isExecutable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['executable']; + } + + function isInstallable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['installable']; + } + + function isExtension() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['phpextension']; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Data.php b/3rdparty/PEAR/Installer/Role/Data.php new file mode 100644 index 0000000000..e3b7fa2fff --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Data.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Data.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Data extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Data.xml b/3rdparty/PEAR/Installer/Role/Data.xml new file mode 100644 index 0000000000..eae63720d3 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Data.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + data_dir + + + + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Doc.php b/3rdparty/PEAR/Installer/Role/Doc.php new file mode 100644 index 0000000000..d592ffff01 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Doc.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Doc.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Doc extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Doc.xml b/3rdparty/PEAR/Installer/Role/Doc.xml new file mode 100644 index 0000000000..173afba011 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Doc.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + doc_dir + + + + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Ext.php b/3rdparty/PEAR/Installer/Role/Ext.php new file mode 100644 index 0000000000..eceb0279ff --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Ext.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Ext.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Ext extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Ext.xml b/3rdparty/PEAR/Installer/Role/Ext.xml new file mode 100644 index 0000000000..e2940fe1f2 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Ext.xml @@ -0,0 +1,12 @@ + + extbin + zendextbin + 1 + ext_dir + 1 + + + + 1 + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Php.php b/3rdparty/PEAR/Installer/Role/Php.php new file mode 100644 index 0000000000..e2abf44eed --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Php.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Php.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Php extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Php.xml b/3rdparty/PEAR/Installer/Role/Php.xml new file mode 100644 index 0000000000..6b9a0e67af --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Php.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + php_dir + 1 + + 1 + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Script.php b/3rdparty/PEAR/Installer/Role/Script.php new file mode 100644 index 0000000000..b31469e4b1 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Script.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Script.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Script extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Script.xml b/3rdparty/PEAR/Installer/Role/Script.xml new file mode 100644 index 0000000000..e732cf2af6 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Script.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + bin_dir + 1 + + + 1 + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Src.php b/3rdparty/PEAR/Installer/Role/Src.php new file mode 100644 index 0000000000..503705313d --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Src.php @@ -0,0 +1,34 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Src.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Src extends PEAR_Installer_Role_Common +{ + function setup(&$installer, $pkg, $atts, $file) + { + $installer->source_files++; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Src.xml b/3rdparty/PEAR/Installer/Role/Src.xml new file mode 100644 index 0000000000..103483402f --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Src.xml @@ -0,0 +1,12 @@ + + extsrc + zendextsrc + 1 + temp_dir + + + + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Test.php b/3rdparty/PEAR/Installer/Role/Test.php new file mode 100644 index 0000000000..14c0e60919 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Test.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Test extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Test.xml b/3rdparty/PEAR/Installer/Role/Test.xml new file mode 100644 index 0000000000..51d5b894e0 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Test.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + test_dir + + + + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Www.php b/3rdparty/PEAR/Installer/Role/Www.php new file mode 100644 index 0000000000..11adeff829 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Www.php @@ -0,0 +1,28 @@ + + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Www.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Www extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Installer/Role/Www.xml b/3rdparty/PEAR/Installer/Role/Www.xml new file mode 100644 index 0000000000..7598be3889 --- /dev/null +++ b/3rdparty/PEAR/Installer/Role/Www.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + www_dir + 1 + + + + + + \ No newline at end of file diff --git a/3rdparty/PEAR/PackageFile.php b/3rdparty/PEAR/PackageFile.php new file mode 100644 index 0000000000..7ae3362844 --- /dev/null +++ b/3rdparty/PEAR/PackageFile.php @@ -0,0 +1,492 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PackageFile.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +/** + * Error code if the package.xml tag does not contain a valid version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); +/** + * Error code if the package.xml tag version is not supported (version 1.0 and 1.1 are the only supported versions, + * currently + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); +/** + * Abstraction for the package.xml package description file + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile +{ + /** + * @var PEAR_Config + */ + var $_config; + var $_debug; + + var $_logger = false; + /** + * @var boolean + */ + var $_rawReturn = false; + + /** + * helper for extracting Archive_Tar errors + * @var array + * @access private + */ + var $_extractErrors = array(); + + /** + * + * @param PEAR_Config $config + * @param ? $debug + * @param string @tmpdir Optional temporary directory for uncompressing + * files + */ + function PEAR_PackageFile(&$config, $debug = false) + { + $this->_config = $config; + $this->_debug = $debug; + } + + /** + * Turn off validation - return a parsed package.xml without checking it + * + * This is used by the package-validate command + */ + function rawReturn() + { + $this->_rawReturn = true; + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * Create a PEAR_PackageFile_Parser_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 + */ + function &parserFactory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php'; + $version = $version{0}; + $class = "PEAR_PackageFile_Parser_v$version"; + $a = new $class; + return $a; + } + + /** + * For simpler unit-testing + * @return string + */ + function getClassPrefix() + { + return 'PEAR_PackageFile_v'; + } + + /** + * Create a PEAR_PackageFile_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1 + */ + function &factory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/v' . $version{0} . '.php'; + $version = $version{0}; + $class = $this->getClassPrefix() . $version; + $a = new $class; + return $a; + } + + /** + * Create a PEAR_PackageFile_v* from its toArray() method + * + * WARNING: no validation is performed, the array is assumed to be valid, + * always parse from xml if you want validation. + * @param array $arr + * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2 + * @uses factory() to construct the returned object. + */ + function &fromArray($arr) + { + if (isset($arr['xsdversion'])) { + $obj = &$this->factory($arr['xsdversion']); + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + if (isset($arr['package']['attribs']['version'])) { + $obj = &$this->factory($arr['package']['attribs']['version']); + } else { + $obj = &$this->factory('1.0'); + } + + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + /** + * Create a PEAR_PackageFile_v* from an XML string. + * @access public + * @param string $data contents of package.xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string $file full path to the package.xml file (and the files + * it references) + * @param string $archive optional name of the archive that the XML was + * extracted from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses parserFactory() to construct a parser to load the package. + */ + function &fromXmlString($data, $state, $file, $archive = false) + { + if (preg_match('/]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) { + if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { + return PEAR::raiseError('package.xml version "' . $packageversion[1] . + '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); + } + + $object = &$this->parserFactory($packageversion[1]); + if ($this->_logger) { + $object->setLogger($this->_logger); + } + + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) {; + if ($this->_config->get('verbose') > 0 + && $this->_logger && $pf->getValidationWarnings(false) + ) { + foreach ($pf->getValidationWarnings(false) as $warning) { + $this->_logger->log(0, 'ERROR: ' . $warning['message']); + } + } + + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } elseif (preg_match('/]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) { + $a = PEAR::raiseError('package.xml file "' . $file . + '" has unsupported package.xml version "' . $packageversion[1] . '"'); + return $a; + } else { + if (!class_exists('PEAR_ErrorStack')) { + require_once 'PEAR/ErrorStack.php'; + } + + PEAR_ErrorStack::staticPush('PEAR_PackageFile', + PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, + 'warning', array('xml' => $data), 'package.xml "' . $file . + '" has no package.xml version'); + $object = &$this->parserFactory('1.0'); + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) { + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * @return void + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file. + * @access public + * @param string contents of package.xml file + * @param int package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @using Archive_Tar to extract the files + * @using fromPackageFile() to load the package after the package.xml + * file is extracted. + */ + function &fromTgzFile($file, $state) + { + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + + $tar = new Archive_Tar($file); + if ($this->_debug <= 1) { + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + } + + $content = $tar->listContent(); + if ($this->_debug <= 1) { + $tar->popErrorHandling(); + } + + if (!is_array($content)) { + if (is_string($file) && strlen($file < 255) && + (!file_exists($file) || !@is_file($file))) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $file = realpath($file); + $ret = PEAR::raiseError("Could not get contents of package \"$file\"". + '. Invalid tgz file.'); + return $ret; + } + + if (!count($content) && !@is_file($file)) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $xml = null; + $origfile = $file; + foreach ($content as $file) { + $name = $file['filename']; + if ($name == 'package2.xml') { // allow a .tgz to distribute both versions + $xml = $name; + break; + } + + if ($name == 'package.xml') { + $xml = $name; + break; + } elseif (preg_match('/package.xml$/', $name, $match)) { + $xml = $name; + break; + } + } + + $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear'); + if ($tmpdir === false) { + $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); + return $ret; + } + + PEAR_PackageFile::addTempFile($tmpdir); + + $this->_extractErrors(); + PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); + + if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { + $extra = implode("\n", $this->_extractErrors()); + if ($extra) { + $extra = ' ' . $extra; + } + + PEAR::staticPopErrorHandling(); + $ret = PEAR::raiseError('could not extract the package.xml file from "' . + $origfile . '"' . $extra); + return $ret; + } + + PEAR::staticPopErrorHandling(); + $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); + return $ret; + } + + /** + * helper callback for extracting Archive_Tar errors + * + * @param PEAR_Error|null $err + * @return array + * @access private + */ + function _extractErrors($err = null) + { + static $errors = array(); + if ($err === null) { + $e = $errors; + $errors = array(); + return $e; + } + $errors[] = $err->getMessage(); + } + + /** + * Create a PEAR_PackageFile_v* from a package.xml file. + * + * @access public + * @param string $descfile name of package xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string|false $archive name of the archive this package.xml came + * from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses PEAR_PackageFile::fromXmlString to create the oject after the + * XML is loaded from the package.xml file. + */ + function &fromPackageFile($descfile, $state, $archive = false) + { + $fp = false; + if (is_string($descfile) && strlen($descfile) < 255 && + ( + !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) + || (!$fp = @fopen($descfile, 'r')) + ) + ) { + $a = PEAR::raiseError("Unable to open $descfile"); + return $a; + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); + return $ret; + } + + /** + * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. + * + * This method is able to extract information about a package from a .tgz + * archive or from a XML package definition file. + * + * @access public + * @param string $info file name + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses fromPackageFile() if the file appears to be XML + * @uses fromTgzFile() to load all non-XML files + */ + function &fromAnyFile($info, $state) + { + if (is_dir($info)) { + $dir_name = realpath($info); + if (file_exists($dir_name . '/package.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); + } elseif (file_exists($dir_name . '/package2.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); + } else { + $info = PEAR::raiseError("No package definition found in '$info' directory"); + } + + return $info; + } + + $fp = false; + if (is_string($info) && strlen($info) < 255 && + (file_exists($info) || ($fp = @fopen($info, 'r'))) + ) { + + if ($fp) { + fclose($fp); + } + + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = &PEAR_PackageFile::fromPackageFile($info, $state); + } elseif ($tmp == '.tar' || $tmp == '.tgz') { + $info = &PEAR_PackageFile::fromTgzFile($info, $state); + } else { + $fp = fopen($info, 'r'); + $test = fread($fp, 5); + fclose($fp); + if ($test == ' + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +require_once 'System.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * This class converts a PEAR_PackageFile_v1 object into any output format. + * + * Supported output formats include array, XML string, and a PEAR_PackageFile_v2 + * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v1 +{ + /** + * @var PEAR_PackageFile_v1 + */ + var $_packagefile; + function PEAR_PackageFile_Generator_v1(&$packagefile) + { + $this->_packagefile = &$packagefile; + } + + function getPackagerVersion() + { + return '1.9.4'; + } + + /** + * @param PEAR_Packager + * @param bool if true, a .tgz is written, otherwise a .tar is written + * @param string|null directory in which to save the .tgz + * @return string|PEAR_Error location of package or error object + */ + function toTgz(&$packager, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' . + ' not be created'); + } + if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') && + !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' . + ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"'); + } + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file'); + } + $pkginfo = $this->_packagefile->getArray(); + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $pkginfo['package'] . '-' . $pkginfo['version']; + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) && + !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' . + getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"'); + } + if ($pkgfile = $this->_packagefile->getPackageFile()) { + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + } else { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' . + 'be created from a real file'); + } + // {{{ Create the package file list + $filelist = array(); + $i = 0; + + foreach ($this->_packagefile->getFilelist() as $fname => $atts) { + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return PEAR::raiseError("File does not exist: $fname"); + } else { + $filelist[$i++] = $file; + if (!isset($atts['md5sum'])) { + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file)); + } + $packager->log(2, "Adding file $fname"); + } + } + // }}} + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $ok; + } elseif (!$ok) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $pkgdir)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + return $dest_package; + } + } + + /** + * @param string|null directory to place the package.xml in, or null for a temporary dir + * @param int one of the PEAR_VALIDATE_* constants + * @param string name of the generated file + * @param bool if true, then no analysis will be performed on role="php" files + * @return string|PEAR_Error path to the created file on success + */ + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml', + $nofilechecking = false) + { + if (!$this->_packagefile->validate($state, $nofilechecking)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state, true)); + fclose($np); + return $newpkgfile; + } + + /** + * fix both XML encoding to be UTF8, and replace standard XML entities < > " & ' + * + * @param string $string + * @return string + * @access private + */ + function _fixXmlEncoding($string) + { + if (version_compare(phpversion(), '5.0.0', 'lt')) { + $string = utf8_encode($string); + } + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false) + { + $this->_packagefile->setDate(date('Y-m-d')); + if (!$this->_packagefile->validate($state, $nofilevalidation)) { + return false; + } + $pkginfo = $this->_packagefile->getArray(); + static $maint_map = array( + "handle" => "user", + "name" => "name", + "email" => "email", + "role" => "role", + ); + $ret = "\n"; + $ret .= "\n"; + $ret .= "\n" . +" $pkginfo[package]"; + if (isset($pkginfo['extends'])) { + $ret .= "\n$pkginfo[extends]"; + } + $ret .= + "\n ".$this->_fixXmlEncoding($pkginfo['summary'])."\n" . +" ".trim($this->_fixXmlEncoding($pkginfo['description']))."\n \n" . +" \n"; + foreach ($pkginfo['maintainers'] as $maint) { + $ret .= " \n"; + foreach ($maint_map as $idx => $elm) { + $ret .= " <$elm>"; + $ret .= $this->_fixXmlEncoding($maint[$idx]); + $ret .= "\n"; + } + $ret .= " \n"; + } + $ret .= " \n"; + $ret .= $this->_makeReleaseXml($pkginfo, false, $state); + if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) { + $ret .= " \n"; + foreach ($pkginfo['changelog'] as $oldrelease) { + $ret .= $this->_makeReleaseXml($oldrelease, true); + } + $ret .= " \n"; + } + $ret .= "\n"; + return $ret; + } + + // }}} + // {{{ _makeReleaseXml() + + /** + * Generate part of an XML description with release information. + * + * @param array $pkginfo array with release information + * @param bool $changelog whether the result will be in a changelog element + * + * @return string XML data + * + * @access private + */ + function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL) + { + // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!! + $indent = $changelog ? " " : ""; + $ret = "$indent \n"; + if (!empty($pkginfo['version'])) { + $ret .= "$indent $pkginfo[version]\n"; + } + if (!empty($pkginfo['release_date'])) { + $ret .= "$indent $pkginfo[release_date]\n"; + } + if (!empty($pkginfo['release_license'])) { + $ret .= "$indent $pkginfo[release_license]\n"; + } + if (!empty($pkginfo['release_state'])) { + $ret .= "$indent $pkginfo[release_state]\n"; + } + if (!empty($pkginfo['release_notes'])) { + $ret .= "$indent ".trim($this->_fixXmlEncoding($pkginfo['release_notes'])) + ."\n$indent \n"; + } + if (!empty($pkginfo['release_warnings'])) { + $ret .= "$indent ".$this->_fixXmlEncoding($pkginfo['release_warnings'])."\n"; + } + if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) { + $ret .= "$indent \n"; + foreach ($pkginfo['release_deps'] as $dep) { + $ret .= "$indent _fixXmlEncoding($c['name']) . "\""; + if (isset($c['default'])) { + $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\""; + } + $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\""; + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + if (isset($pkginfo['provides'])) { + foreach ($pkginfo['provides'] as $key => $what) { + $ret .= "$indent recursiveXmlFilelist($pkginfo['filelist']); + } else { + foreach ($pkginfo['filelist'] as $file => $fa) { + if (!isset($fa['role'])) { + $fa['role'] = ''; + } + $ret .= "$indent _fixXmlEncoding($fa['baseinstalldir']) . '"'; + } + if (isset($fa['md5sum'])) { + $ret .= " md5sum=\"$fa[md5sum]\""; + } + if (isset($fa['platform'])) { + $ret .= " platform=\"$fa[platform]\""; + } + if (!empty($fa['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($fa['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($fa['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($fa['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + } + } + $ret .= "$indent \n"; + } + $ret .= "$indent \n"; + return $ret; + } + + /** + * @param array + * @access protected + */ + function recursiveXmlFilelist($list) + { + $this->_dirs = array(); + foreach ($list as $file => $attributes) { + $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes); + } + return $this->_formatDir($this->_dirs); + } + + /** + * @param array + * @param array + * @param string|null + * @param array|null + * @access private + */ + function _addDir(&$dirs, $dir, $file = null, $attributes = null) + { + if ($dir == array() || $dir == array('.')) { + $dirs['files'][basename($file)] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dirs'][$curdir])) { + $dirs['dirs'][$curdir] = array(); + } + $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes); + } + + /** + * @param array + * @param string + * @param string + * @access private + */ + function _formatDir($dirs, $indent = '', $curdir = '') + { + $ret = ''; + if (!count($dirs)) { + return ''; + } + if (isset($dirs['dirs'])) { + uksort($dirs['dirs'], 'strnatcasecmp'); + foreach ($dirs['dirs'] as $dir => $contents) { + $usedir = "$curdir/$dir"; + $ret .= "$indent \n"; + $ret .= $this->_formatDir($contents, "$indent ", $usedir); + $ret .= "$indent \n"; + } + } + if (isset($dirs['files'])) { + uksort($dirs['files'], 'strnatcasecmp'); + foreach ($dirs['files'] as $file => $attribs) { + $ret .= $this->_formatFile($file, $attribs, $indent); + } + } + return $ret; + } + + /** + * @param string + * @param array + * @param string + * @access private + */ + function _formatFile($file, $attributes, $indent) + { + $ret = "$indent _fixXmlEncoding($attributes['baseinstalldir']) . '"'; + } + if (isset($attributes['md5sum'])) { + $ret .= " md5sum=\"$attributes[md5sum]\""; + } + if (isset($attributes['platform'])) { + $ret .= " platform=\"$attributes[platform]\""; + } + if (!empty($attributes['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($attributes['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($attributes['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($attributes['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + return $ret; + } + + // {{{ _unIndent() + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * @return array + */ + function dependenciesToV2() + { + $arr = array(); + $this->_convertDependencies2_0($arr); + return $arr['dependencies']; + } + + /** + * Convert a package.xml version 1.0 into version 2.0 + * + * Note that this does a basic conversion, to allow more advanced + * features like bundles and multiple releases + * @param string the classname to instantiate and return. This must be + * PEAR_PackageFile_v2 or a descendant + * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the + * strictest parameters will be converted + * @return PEAR_PackageFile_v2|PEAR_Error + */ + function &toV2($class = 'PEAR_PackageFile_v2', $strict = false) + { + if ($strict) { + if (!$this->_packagefile->validate()) { + $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' . + ' to version 2.0', null, null, null, + $this->_packagefile->getValidationWarnings(true)); + return $a; + } + } + + $arr = array( + 'attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" . +"http://pear.php.net/dtd/tasks-1.0.xsd\n" . +"http://pear.php.net/dtd/package-2.0\n" . +'http://pear.php.net/dtd/package-2.0.xsd', + ), + 'name' => $this->_packagefile->getPackage(), + 'channel' => 'pear.php.net', + ); + $arr['summary'] = $this->_packagefile->getSummary(); + $arr['description'] = $this->_packagefile->getDescription(); + $maintainers = $this->_packagefile->getMaintainers(); + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] != 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr['lead'][] = $new; + } + + if (!isset($arr['lead'])) { // some people... you know? + $arr['lead'] = array( + 'name' => 'unknown', + 'user' => 'unknown', + 'email' => 'noleadmaintainer@example.com', + 'active' => 'no', + ); + } + + if (count($arr['lead']) == 1) { + $arr['lead'] = $arr['lead'][0]; + } + + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] == 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr[$maintainer['role']][] = $new; + } + + if (isset($arr['developer']) && count($arr['developer']) == 1) { + $arr['developer'] = $arr['developer'][0]; + } + + if (isset($arr['contributor']) && count($arr['contributor']) == 1) { + $arr['contributor'] = $arr['contributor'][0]; + } + + if (isset($arr['helper']) && count($arr['helper']) == 1) { + $arr['helper'] = $arr['helper'][0]; + } + + $arr['date'] = $this->_packagefile->getDate(); + $arr['version'] = + array( + 'release' => $this->_packagefile->getVersion(), + 'api' => $this->_packagefile->getVersion(), + ); + $arr['stability'] = + array( + 'release' => $this->_packagefile->getState(), + 'api' => $this->_packagefile->getState(), + ); + $licensemap = + array( + 'php' => 'http://www.php.net/license', + 'php license' => 'http://www.php.net/license', + 'lgpl' => 'http://www.gnu.org/copyleft/lesser.html', + 'bsd' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'mit' => 'http://www.opensource.org/licenses/mit-license.php', + 'gpl' => 'http://www.gnu.org/copyleft/gpl.html', + 'apache' => 'http://www.opensource.org/licenses/apache2.0.php' + ); + + if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) { + $arr['license'] = array( + 'attribs' => array('uri' => + $licensemap[strtolower($this->_packagefile->getLicense())]), + '_content' => $this->_packagefile->getLicense() + ); + } else { + // don't use bogus uri + $arr['license'] = $this->_packagefile->getLicense(); + } + + $arr['notes'] = $this->_packagefile->getNotes(); + $temp = array(); + $arr['contents'] = $this->_convertFilelist2_0($temp); + $this->_convertDependencies2_0($arr); + $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ? + 'extsrcrelease' : 'phprelease'; + if ($release == 'extsrcrelease') { + $arr['channel'] = 'pecl.php.net'; + $arr['providesextension'] = $arr['name']; // assumption + } + + $arr[$release] = array(); + if ($this->_packagefile->getConfigureOptions()) { + $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions(); + foreach ($arr[$release]['configureoption'] as $i => $opt) { + $arr[$release]['configureoption'][$i] = array('attribs' => $opt); + } + if (count($arr[$release]['configureoption']) == 1) { + $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0]; + } + } + + $this->_convertRelease2_0($arr[$release], $temp); + if ($release == 'extsrcrelease' && count($arr[$release]) > 1) { + // multiple extsrcrelease tags added in PEAR 1.4.1 + $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1'; + } + + if ($cl = $this->_packagefile->getChangelog()) { + foreach ($cl as $release) { + $rel = array(); + $rel['version'] = + array( + 'release' => $release['version'], + 'api' => $release['version'], + ); + if (!isset($release['release_state'])) { + $release['release_state'] = 'stable'; + } + + $rel['stability'] = + array( + 'release' => $release['release_state'], + 'api' => $release['release_state'], + ); + if (isset($release['release_date'])) { + $rel['date'] = $release['release_date']; + } else { + $rel['date'] = date('Y-m-d'); + } + + if (isset($release['release_license'])) { + if (isset($licensemap[strtolower($release['release_license'])])) { + $uri = $licensemap[strtolower($release['release_license'])]; + } else { + $uri = 'http://www.example.com'; + } + $rel['license'] = array( + 'attribs' => array('uri' => $uri), + '_content' => $release['release_license'] + ); + } else { + $rel['license'] = $arr['license']; + } + + if (!isset($release['release_notes'])) { + $release['release_notes'] = 'no release notes'; + } + + $rel['notes'] = $release['release_notes']; + $arr['changelog']['release'][] = $rel; + } + } + + $ret = new $class; + $ret->setConfig($this->_packagefile->_config); + if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) { + $ret->setLogger($this->_packagefile->_logger); + } + + $ret->fromArray($arr); + return $ret; + } + + /** + * @param array + * @param bool + * @access private + */ + function _convertDependencies2_0(&$release, $internal = false) + { + $peardep = array('pearinstaller' => + array('min' => '1.4.0b1')); // this is a lot safer + $required = $optional = array(); + $release['dependencies'] = array('required' => array()); + if ($this->_packagefile->hasDeps()) { + foreach ($this->_packagefile->getDeps() as $dep) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + $required[] = $dep; + } else { + $optional[] = $dep; + } + } + foreach (array('required', 'optional') as $arr) { + $deps = array(); + foreach ($$arr as $dep) { + // organize deps by dependency type and name + if (!isset($deps[$dep['type']])) { + $deps[$dep['type']] = array(); + } + if (isset($dep['name'])) { + $deps[$dep['type']][$dep['name']][] = $dep; + } else { + $deps[$dep['type']][] = $dep; + } + } + do { + if (isset($deps['php'])) { + $php = array(); + if (count($deps['php']) > 1) { + $php = $this->_processPhpDeps($deps['php']); + } else { + if (!isset($deps['php'][0])) { + list($key, $blah) = each ($deps['php']); // stupid buggy versions + $deps['php'] = array($blah[0]); + } + $php = $this->_processDep($deps['php'][0]); + if (!$php) { + break; // poor mans throw + } + } + $release['dependencies'][$arr]['php'] = $php; + } + } while (false); + do { + if (isset($deps['pkg'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['pkg']); + if (!$pkg) { + break; // poor mans throw + } + $release['dependencies'][$arr]['package'] = $pkg; + } + } while (false); + do { + if (isset($deps['ext'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['ext']); + $release['dependencies'][$arr]['extension'] = $pkg; + } + } while (false); + // skip sapi - it's not supported so nobody will have used it + // skip os - it's not supported in 1.0 + } + } + if (isset($release['dependencies']['required'])) { + $release['dependencies']['required'] = + array_merge($peardep, $release['dependencies']['required']); + } else { + $release['dependencies']['required'] = $peardep; + } + if (!isset($release['dependencies']['required']['php'])) { + $release['dependencies']['required']['php'] = + array('min' => '4.0.0'); + } + $order = array(); + $bewm = $release['dependencies']['required']; + $order['php'] = $bewm['php']; + $order['pearinstaller'] = $bewm['pearinstaller']; + isset($bewm['package']) ? $order['package'] = $bewm['package'] :0; + isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0; + $release['dependencies']['required'] = $order; + } + + /** + * @param array + * @access private + */ + function _convertFilelist2_0(&$package) + { + $ret = array('dir' => + array( + 'attribs' => array('name' => '/'), + 'file' => array() + ) + ); + $package['platform'] = + $package['install-as'] = array(); + $this->_isExtension = false; + foreach ($this->_packagefile->getFilelist() as $name => $file) { + $file['name'] = $name; + if (isset($file['role']) && $file['role'] == 'src') { + $this->_isExtension = true; + } + if (isset($file['replacements'])) { + $repl = $file['replacements']; + unset($file['replacements']); + } else { + unset($repl); + } + if (isset($file['install-as'])) { + $package['install-as'][$name] = $file['install-as']; + unset($file['install-as']); + } + if (isset($file['platform'])) { + $package['platform'][$name] = $file['platform']; + unset($file['platform']); + } + $file = array('attribs' => $file); + if (isset($repl)) { + foreach ($repl as $replace ) { + $file['tasks:replace'][] = array('attribs' => $replace); + } + if (count($repl) == 1) { + $file['tasks:replace'] = $file['tasks:replace'][0]; + } + } + $ret['dir']['file'][] = $file; + } + return $ret; + } + + /** + * Post-process special files with install-as/platform attributes and + * make the release tag. + * + * This complex method follows this work-flow to create the release tags: + * + *
+     * - if any install-as/platform exist, create a generic release and fill it with
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     * - create a release for each platform encountered and fill with
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     *   o  tags for 
+     * 
+ * + * It does this by accessing the $package parameter, which contains an array with + * indices: + * + * - platform: mapping of file => OS the file should be installed on + * - install-as: mapping of file => installed name + * - osmap: mapping of OS => list of files that should be installed + * on that OS + * - notosmap: mapping of OS => list of files that should not be + * installed on that OS + * + * @param array + * @param array + * @access private + */ + function _convertRelease2_0(&$release, $package) + { + //- if any install-as/platform exist, create a generic release and fill it with + if (count($package['platform']) || count($package['install-as'])) { + $generic = array(); + $genericIgnore = array(); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} == '!') { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!') { + $genericIgnore[] = $file; + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + if ($platform{0} != '!') { + //o tags for + $genericIgnore[] = $file; + } + } + if (count($package['platform'])) { + $oses = $notplatform = $platform = array(); + foreach ($package['platform'] as $file => $os) { + // get a list of oses + if ($os{0} == '!') { + if (isset($oses[substr($os, 1)])) { + continue; + } + $oses[substr($os, 1)] = count($oses); + } else { + if (isset($oses[$os])) { + continue; + } + $oses[$os] = count($oses); + } + } + //- create a release for each platform encountered and fill with + foreach ($oses as $os => $releaseNum) { + $release[$releaseNum]['installconditions']['os']['name'] = $os; + $release[$releaseNum]['filelist'] = array('install' => array(), + 'ignore' => array()); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == $os) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] != "!$os" && + $package['platform'][$file]{0} == '!') { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!' && + $package['platform'][$file] != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + //o tags for + if ($platform == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + if ($platform{0} != '!' && $platform != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + } + } + if (!count($release[$releaseNum]['filelist']['install'])) { + unset($release[$releaseNum]['filelist']['install']); + } + if (!count($release[$releaseNum]['filelist']['ignore'])) { + unset($release[$releaseNum]['filelist']['ignore']); + } + } + if (count($generic) || count($genericIgnore)) { + $release[count($oses)] = array(); + if (count($generic)) { + foreach ($generic as $file) { + if (isset($package['install-as'][$file])) { + $installas = $package['install-as'][$file]; + } else { + $installas = $file; + } + $release[count($oses)]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $installas, + ) + ); + } + } + if (count($genericIgnore)) { + foreach ($genericIgnore as $file) { + $release[count($oses)]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ) + ); + } + } + } + // cleanup + foreach ($release as $i => $rel) { + if (isset($rel['filelist']['install']) && + count($rel['filelist']['install']) == 1) { + $release[$i]['filelist']['install'] = + $release[$i]['filelist']['install'][0]; + } + if (isset($rel['filelist']['ignore']) && + count($rel['filelist']['ignore']) == 1) { + $release[$i]['filelist']['ignore'] = + $release[$i]['filelist']['ignore'][0]; + } + } + if (count($release) == 1) { + $release = $release[0]; + } + } else { + // no platform atts, but some install-as atts + foreach ($package['install-as'] as $file => $value) { + $release['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $value + ) + ); + } + if (count($release['filelist']['install']) == 1) { + $release['filelist']['install'] = $release['filelist']['install'][0]; + } + } + } + } + + /** + * @param array + * @return array + * @access private + */ + function _processDep($dep) + { + if ($dep['type'] == 'php') { + if ($dep['rel'] == 'has') { + // come on - everyone has php! + return false; + } + } + $php = array(); + if ($dep['type'] != 'php') { + $php['name'] = $dep['name']; + if ($dep['type'] == 'pkg') { + $php['channel'] = 'pear.php.net'; + } + } + switch ($dep['rel']) { + case 'gt' : + $php['min'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'ge' : + if (!isset($dep['version'])) { + if ($dep['type'] == 'php') { + if (isset($dep['name'])) { + $dep['version'] = $dep['name']; + } + } + } + $php['min'] = $dep['version']; + break; + case 'lt' : + $php['max'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'le' : + $php['max'] = $dep['version']; + break; + case 'eq' : + $php['min'] = $dep['version']; + $php['max'] = $dep['version']; + break; + case 'ne' : + $php['exclude'] = $dep['version']; + break; + case 'not' : + $php['conflicts'] = 'yes'; + break; + } + return $php; + } + + /** + * @param array + * @return array + */ + function _processPhpDeps($deps) + { + $test = array(); + foreach ($deps as $dep) { + $test[] = $this->_processDep($dep); + } + $min = array(); + $max = array(); + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + return $php; + } + + /** + * process multiple dependencies that have a name, like package deps + * @param array + * @return array + * @access private + */ + function _processMultipleDepsName($deps) + { + $ret = $tests = array(); + foreach ($deps as $name => $dep) { + foreach ($dep as $d) { + $tests[$name][] = $this->_processDep($d); + } + } + + foreach ($tests as $name => $test) { + $max = $min = $php = array(); + $php['name'] = $name; + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['channel'])) { + $php['channel'] = 'pear.php.net'; + } + if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') { + $php['conflicts'] = 'yes'; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + $ret[] = $php; + } + return $ret; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/PackageFile/Generator/v2.php b/3rdparty/PEAR/PackageFile/Generator/v2.php new file mode 100644 index 0000000000..4d202df27d --- /dev/null +++ b/3rdparty/PEAR/PackageFile/Generator/v2.php @@ -0,0 +1,893 @@ + + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * file/dir manipulation routines + */ +require_once 'System.php'; +require_once 'XML/Util.php'; + +/** + * This class converts a PEAR_PackageFile_v2 object into any output format. + * + * Supported output formats include array, XML string (using S. Schmidt's + * XML_Serializer, slightly customized) + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v2 +{ + /** + * default options for the serialization + * @access private + * @var array $_defaultOptions + */ + var $_defaultOptions = array( + 'indent' => ' ', // string used for indentation + 'linebreak' => "\n", // string used for newlines + 'typeHints' => false, // automatically add type hin attributes + 'addDecl' => true, // add an XML declaration + 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names + 'classAsTagName' => false, // use classname for objects in indexed arrays + 'keyAttribute' => '_originalKey', // attribute where original key is stored + 'typeAttribute' => '_type', // attribute for type (only if typeHints => true) + 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true) + 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute + 'prependAttributes' => '', // prepend string for attributes + 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array + 'addDoctype' => false, // add a doctype declaration + 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + 'rootName' => 'package', // name of the root tag + 'rootAttributes' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd', + ), // attributes of the root tag + 'attributesArray' => 'attribs', // all values in this key will be treated as attributes + 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + 'beautifyFilelist' => false, + 'encoding' => 'UTF-8', + ); + + /** + * options for the serialization + * @access private + * @var array $options + */ + var $options = array(); + + /** + * current tag depth + * @var integer $_tagDepth + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * @var string $_serializedData + */ + var $_serializedData = null; + /** + * @var PEAR_PackageFile_v2 + */ + var $_packagefile; + /** + * @param PEAR_PackageFile_v2 + */ + function PEAR_PackageFile_Generator_v2(&$packagefile) + { + $this->_packagefile = &$packagefile; + if (isset($this->_packagefile->encoding)) { + $this->_defaultOptions['encoding'] = $this->_packagefile->encoding; + } + } + + /** + * @return string + */ + function getPackagerVersion() + { + return '1.9.4'; + } + + /** + * @param PEAR_Packager + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz(&$packager, $compress = true, $where = null) + { + $a = null; + return $this->toTgz2($packager, $a, $compress, $where); + } + + /** + * Package up both a package.xml and package2.xml for the same release + * @param PEAR_Packager + * @param PEAR_PackageFile_v1 + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz2(&$packager, &$pf1, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if (!$this->_packagefile->isEquivalent($pf1)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . + basename($pf1->getPackageFile()) . + '" is not equivalent to "' . basename($this->_packagefile->getPackageFile()) + . '"'); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' . + ' not be created'); + } + + $file = $where . DIRECTORY_SEPARATOR . 'package.xml'; + if (file_exists($file) && !is_file($file)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' . + ' "' . $file .'"'); + } + + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml'); + } + + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion(); + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists($dest_package) && !is_file($dest_package)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' . + $dest_package . '"'); + } + + $pkgfile = $this->_packagefile->getPackageFile(); + if (!$pkgfile) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' . + 'be created from a real file'); + } + + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + + // {{{ Create the package file list + $filelist = array(); + $i = 0; + $this->_packagefile->flattenFilelist(); + $contents = $this->_packagefile->getContents(); + if (isset($contents['bundledpackage'])) { // bundles of packages + $contents = $contents['bundledpackage']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $package) { + $fname = $package; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + $filelist[$i++] = $tfile; + $packager->log(2, "Adding package $fname"); + } + } else { // normal packages + $contents = $contents['dir']['file']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $file) { + $fname = $file['attribs']['name']; + $atts = $file['attribs']; + $orig = $file; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $origperms = fileperms($file); + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + unset($orig['attribs']); + if (count($orig)) { // file with tasks + // run any package-time tasks + $contents = file_get_contents($file); + foreach ($orig as $tag => $raw) { + $tag = str_replace( + array($this->_packagefile->getTasksNs() . ':', '-'), + array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->_packagefile->_config, + $this->_packagefile->_logger, + PEAR_TASK_PACKAGE); + $task->init($raw, $atts, null); + $res = $task->startSession($this->_packagefile, $contents, $tfile); + if (!$res) { + continue; // skip this task + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + System::mkdir(array('-p', dirname($tfile))); + $wp = fopen($tfile, "wb"); + fwrite($wp, $contents); + fclose($wp); + } + } + + if (!file_exists($tfile)) { + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + } + + chmod($tfile, $origperms); + $filelist[$i++] = $tfile; + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1); + $packager->log(2, "Adding file $fname"); + } + } + // }}} + + $name = $pf1 !== null ? 'package2.xml' : 'package.xml'; + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $packager->raiseError($ok); + } elseif (!$ok) { + return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name . + ' failed'); + } + + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): tarball creation failed'); + } + + // add the package.xml version 1.0 + if ($pf1 !== null) { + $pfgen = &$pf1->getDefaultGenerator(); + $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if (!$tar->addModify(array($packagexml1), '', $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed'); + } + } + + return $dest_package; + } + } + + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml') + { + if (!$this->_packagefile->validate($state)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state)); + fclose($np); + return $newpkgfile; + } + + function &toV2() + { + return $this->_packagefile; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $options = array()) + { + $this->_packagefile->setDate(date('Y-m-d')); + $this->_packagefile->setTime(date('H:i:s')); + if (!$this->_packagefile->validate($state)) { + return false; + } + + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + + $arr = $this->_packagefile->getArray(); + if (isset($arr['filelist'])) { + unset($arr['filelist']); + } + + if (isset($arr['_lastversion'])) { + unset($arr['_lastversion']); + } + + // Fix the notes a little bit + if (isset($arr['notes'])) { + // This trims out the indenting, needs fixing + $arr['notes'] = "\n" . trim($arr['notes']) . "\n"; + } + + if (isset($arr['changelog']) && !empty($arr['changelog'])) { + // Fix for inconsistency how the array is filled depending on the changelog release amount + if (!isset($arr['changelog']['release'][0])) { + $release = $arr['changelog']['release']; + unset($arr['changelog']['release']); + + $arr['changelog']['release'] = array(); + $arr['changelog']['release'][0] = $release; + } + + foreach (array_keys($arr['changelog']['release']) as $key) { + $c =& $arr['changelog']['release'][$key]; + if (isset($c['notes'])) { + // This trims out the indenting, needs fixing + $c['notes'] = "\n" . trim($c['notes']) . "\n"; + } + } + } + + if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) { + $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']); + unset($arr['contents']['dir']['file']); + if (isset($use['dir'])) { + $arr['contents']['dir']['dir'] = $use['dir']; + } + if (isset($use['file'])) { + $arr['contents']['dir']['file'] = $use['file']; + } + $this->options['beautifyFilelist'] = true; + } + + $arr['attribs']['packagerversion'] = '1.9.4'; + if ($this->serialize($arr, $options)) { + return $this->_serializedData . "\n"; + } + + return false; + } + + + function _recursiveXmlFilelist($list) + { + $dirs = array(); + if (isset($list['attribs'])) { + $file = $list['attribs']['name']; + unset($list['attribs']['name']); + $attributes = $list['attribs']; + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes); + } else { + foreach ($list as $a) { + $file = $a['attribs']['name']; + $attributes = $a['attribs']; + unset($a['attribs']); + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a); + } + } + $this->_formatDir($dirs); + $this->_deFormat($dirs); + return $dirs; + } + + function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null) + { + if (!$tasks) { + $tasks = array(); + } + if ($dir == array() || $dir == array('.')) { + $dirs['file'][basename($file)] = $tasks; + $attributes['name'] = basename($file); + $dirs['file'][basename($file)]['attribs'] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dir'][$curdir])) { + $dirs['dir'][$curdir] = array(); + } + $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks); + } + + function _formatDir(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + $newdirs['dir'] = $dirs['dir']; + } + if (isset($dirs['file'])) { + $newdirs['file'] = $dirs['file']; + } + $dirs = $newdirs; + if (isset($dirs['dir'])) { + uksort($dirs['dir'], 'strnatcasecmp'); + foreach ($dirs['dir'] as $dir => $contents) { + $this->_formatDir($dirs['dir'][$dir]); + } + } + if (isset($dirs['file'])) { + uksort($dirs['file'], 'strnatcasecmp'); + }; + } + + function _deFormat(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + foreach ($dirs['dir'] as $dir => $contents) { + $newdir = array(); + $newdir['attribs']['name'] = $dir; + $this->_deFormat($contents); + foreach ($contents as $tag => $val) { + $newdir[$tag] = $val; + } + $newdirs['dir'][] = $newdir; + } + if (count($newdirs['dir']) == 1) { + $newdirs['dir'] = $newdirs['dir'][0]; + } + } + if (isset($dirs['file'])) { + foreach ($dirs['file'] as $name => $file) { + $newdirs['file'][] = $file; + } + if (count($newdirs['file']) == 1) { + $newdirs['file'] = $newdirs['file'][0]; + } + } + $dirs = $newdirs; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @access public + * @param mixed $data data to serialize + * @return boolean true on success, pear error on failure + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array'; + $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']); + } + + // add doctype declaration + if ($this->options['addDoctype'] === true) { + $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype']) + . $this->options['linebreak'] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options['addDecl']) { + $atts = array(); + $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null; + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding) + . $this->options['linebreak'] + . $this->_serializedData; + } + + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return true; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData === null) { + return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @access private + * @param mixed $value + * @param string $tagName + * @param array $attributes + * @return string + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @access private + * @param array $array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * @return string $string serialized data + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + + /** + * check for special attributes + */ + if ($this->options['attributesArray'] !== null) { + if (isset($array[$this->options['attributesArray']])) { + $attributes = $array[$this->options['attributesArray']]; + unset($array[$this->options['attributesArray']]); + } + /** + * check for special content + */ + if ($this->options['contentName'] !== null) { + if (isset($array[$this->options['contentName']])) { + $_content = $array[$this->options['contentName']]; + unset($array[$this->options['contentName']]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && $this->options['mode'] == 'simplexml') { + $indexed = true; + if (!count($array)) { + $indexed = false; + } + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed && $this->options['mode'] == 'simplexml') { + $string = ''; + foreach ($array as $key => $val) { + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($val['attribs'])) { + if ($val['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + if ($this->_curdir == '/') { + $this->_curdir = ''; + } + $this->_curdir .= '/' . $val['attribs']['name']; + } + } + } + $string .= $this->_serializeValue( $val, $tagName, $attributes); + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + $string .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + + $string .= $this->options['linebreak']; + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $string .= str_repeat($this->options['indent'], $this->_tagDepth); + } + } + return rtrim($string); + } + } + + if ($this->options['scalarAsAttributes'] === true) { + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options['prependAttributes'].$key] = $value; + } + } + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + + } else { + $this->_tagDepth++; + $tmp = $this->options['linebreak']; + foreach ($array as $key => $value) { + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + // copy key + $origKey = $key; + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + if (PEAR::isError($valid)) { + if ($this->options['classAsTagName'] && is_object($value)) { + $key = get_class($value); + } else { + $key = $this->options['defaultTagName']; + } + } + $atts = array(); + if ($this->options['typeHints'] === true) { + $atts[$this->options['typeAttribute']] = gettype($value); + if ($key !== $origKey) { + $atts[$this->options['keyAttribute']] = (string)$origKey; + } + + } + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($value['attribs'])) { + if ($value['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + $this->_curdir .= '/' . $value['attribs']['name']; + } + } + } + + if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) { + $value .= str_repeat($this->options['indent'], $this->_tagDepth); + } + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value ) + ); + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (isset($value['attribs'])) { + $tmp .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + } + $tmp .= $this->options['linebreak']; + } + + $this->_tagDepth--; + if ($this->options['indent']!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options['typeHints'] === true) { + if (!isset($tag['attributes'][$this->options['typeAttribute']])) { + $tag['attributes'][$this->options['typeAttribute']] = 'array'; + } + } + + $string = $this->_createXMLTag($tag, false); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @access private + * @param array $tag tag definition + * @param boolean $replaceEntities whether to replace XML entities in content or not + * @return string $string XML tag + */ + function _createXMLTag($tag, $replaceEntities = true) + { + if ($this->options['indentAttributes'] !== false) { + $multiline = true; + $indent = str_repeat($this->options['indent'], $this->_tagDepth); + + if ($this->options['indentAttributes'] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options['indentAttributes']; + } + } else { + $indent = $multiline = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options['encoding'] == 'UTF-8' && + version_compare(phpversion(), '5.0.0', 'lt') + ) { + $tag['content'] = utf8_encode($tag['content']); + } + + if ($replaceEntities === true) { + $replaceEntities = XML_UTIL_ENTITIES_XML; + } + + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/PackageFile/Parser/v1.php b/3rdparty/PEAR/PackageFile/Parser/v1.php new file mode 100644 index 0000000000..23395dc757 --- /dev/null +++ b/3rdparty/PEAR/PackageFile/Parser/v1.php @@ -0,0 +1,459 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * package.xml abstraction class + */ +require_once 'PEAR/PackageFile/v1.php'; +/** + * Parser for package.xml version 1.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v1 +{ + var $_registry; + var $_config; + var $_logger; + /** + * BC hack to allow PEAR_Common::infoFromString() to sort of + * work with the version 2.0 format - there's no filelist though + * @param PEAR_PackageFile_v2 + */ + function fromV2($packagefile) + { + $info = $packagefile->getArray(true); + $ret = new PEAR_PackageFile_v1; + $ret->fromArray($info['old']); + } + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * @param string contents of package.xml file, version 1.0 + * @return bool success of parsing + */ + function &parse($data, $file, $archive = false) + { + if (!extension_loaded('xml')) { + return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension'); + } + $xp = xml_parser_create(); + if (!$xp) { + $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml'); + return $a; + } + xml_set_object($xp, $this); + xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0'); + xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0'); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); + + $this->element_stack = array(); + $this->_packageInfo = array('provides' => array()); + $this->current_element = false; + unset($this->dir_install); + $this->_packageInfo['filelist'] = array(); + $this->filelist =& $this->_packageInfo['filelist']; + $this->dir_names = array(); + $this->in_changelog = false; + $this->d_i = 0; + $this->cdata = ''; + $this->_isValid = true; + + if (!xml_parse($xp, $data, 1)) { + $code = xml_get_error_code($xp); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + $a = &PEAR::raiseError(sprintf("XML error: %s at line %d", + $str = xml_error_string($code), $line), 2); + return $a; + } + + xml_parser_free($xp); + + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + if (isset($this->_logger)) { + $pf->setLogger($this->_logger); + } + $pf->setPackagefile($file, $archive); + $pf->fromArray($this->_packageInfo); + return $pf; + } + // {{{ _unIndent() + + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } elseif (trim(substr($line, 0, $indent_len))) { + $data .= ltrim($line); + } + } + return $data; + } + + // Support for package DTD v1.0: + // {{{ _element_start_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_start_1_0($xp, $name, $attribs) + { + array_push($this->element_stack, $name); + $this->current_element = $name; + $spos = sizeof($this->element_stack) - 2; + $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; + $this->current_attributes = $attribs; + $this->cdata = ''; + switch ($name) { + case 'dir': + if ($this->in_changelog) { + break; + } + if (array_key_exists('name', $attribs) && $attribs['name'] != '/') { + $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) { + $attribs['name'] = substr($attribs['name'], 0, + strlen($attribs['name']) - 1); + } + if (strpos($attribs['name'], '/') === 0) { + $attribs['name'] = substr($attribs['name'], 1); + } + $this->dir_names[] = $attribs['name']; + } + if (isset($attribs['baseinstalldir'])) { + $this->dir_install = $attribs['baseinstalldir']; + } + if (isset($attribs['role'])) { + $this->dir_role = $attribs['role']; + } + break; + case 'file': + if ($this->in_changelog) { + break; + } + if (isset($attribs['name'])) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + unset($attribs['name']); + $this->current_path = $path; + $this->filelist[$path] = $attribs; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'replace': + if (!$this->in_changelog) { + $this->filelist[$this->current_path]['replacements'][] = $attribs; + } + break; + case 'maintainers': + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; // maintainers array index + break; + case 'maintainer': + // compatibility check + if (!isset($this->_packageInfo['maintainers'])) { + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; + } + $this->_packageInfo['maintainers'][$this->m_i] = array(); + $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i]; + break; + case 'changelog': + $this->_packageInfo['changelog'] = array(); + $this->c_i = 0; // changelog array index + $this->in_changelog = true; + break; + case 'release': + if ($this->in_changelog) { + $this->_packageInfo['changelog'][$this->c_i] = array(); + $this->current_release = &$this->_packageInfo['changelog'][$this->c_i]; + } else { + $this->current_release = &$this->_packageInfo; + } + break; + case 'deps': + if (!$this->in_changelog) { + $this->_packageInfo['release_deps'] = array(); + } + break; + case 'dep': + // dependencies array index + if (!$this->in_changelog) { + $this->d_i++; + isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false; + $this->_packageInfo['release_deps'][$this->d_i] = $attribs; + } + break; + case 'configureoptions': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'] = array(); + } + break; + case 'configureoption': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'][] = $attribs; + } + break; + case 'provides': + if (empty($attribs['type']) || empty($attribs['name'])) { + break; + } + $attribs['explicit'] = true; + $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs; + break; + case 'package' : + if (isset($attribs['version'])) { + $this->_packageInfo['xsdversion'] = trim($attribs['version']); + } else { + $this->_packageInfo['xsdversion'] = '1.0'; + } + if (isset($attribs['packagerversion'])) { + $this->_packageInfo['packagerversion'] = $attribs['packagerversion']; + } + break; + } + } + + // }}} + // {{{ _element_end_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_end_1_0($xp, $name) + { + $data = trim($this->cdata); + switch ($name) { + case 'name': + switch ($this->prev_element) { + case 'package': + $this->_packageInfo['package'] = $data; + break; + case 'maintainer': + $this->current_maintainer['name'] = $data; + break; + } + break; + case 'extends' : + $this->_packageInfo['extends'] = $data; + break; + case 'summary': + $this->_packageInfo['summary'] = $data; + break; + case 'description': + $data = $this->_unIndent($this->cdata); + $this->_packageInfo['description'] = $data; + break; + case 'user': + $this->current_maintainer['handle'] = $data; + break; + case 'email': + $this->current_maintainer['email'] = $data; + break; + case 'role': + $this->current_maintainer['role'] = $data; + break; + case 'version': + if ($this->in_changelog) { + $this->current_release['version'] = $data; + } else { + $this->_packageInfo['version'] = $data; + } + break; + case 'date': + if ($this->in_changelog) { + $this->current_release['release_date'] = $data; + } else { + $this->_packageInfo['release_date'] = $data; + } + break; + case 'notes': + // try to "de-indent" release notes in case someone + // has been over-indenting their xml ;-) + // Trim only on the right side + $data = rtrim($this->_unIndent($this->cdata)); + if ($this->in_changelog) { + $this->current_release['release_notes'] = $data; + } else { + $this->_packageInfo['release_notes'] = $data; + } + break; + case 'warnings': + if ($this->in_changelog) { + $this->current_release['release_warnings'] = $data; + } else { + $this->_packageInfo['release_warnings'] = $data; + } + break; + case 'state': + if ($this->in_changelog) { + $this->current_release['release_state'] = $data; + } else { + $this->_packageInfo['release_state'] = $data; + } + break; + case 'license': + if ($this->in_changelog) { + $this->current_release['release_license'] = $data; + } else { + $this->_packageInfo['release_license'] = $data; + } + break; + case 'dep': + if ($data && !$this->in_changelog) { + $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data; + } + break; + case 'dir': + if ($this->in_changelog) { + break; + } + array_pop($this->dir_names); + break; + case 'file': + if ($this->in_changelog) { + break; + } + if ($data) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= $data; + $this->filelist[$path] = $this->current_attributes; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'maintainer': + if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) { + $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead'; + } + $this->m_i++; + break; + case 'release': + if ($this->in_changelog) { + $this->c_i++; + } + break; + case 'changelog': + $this->in_changelog = false; + break; + } + array_pop($this->element_stack); + $spos = sizeof($this->element_stack) - 1; + $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : ''; + $this->cdata = ''; + } + + // }}} + // {{{ _pkginfo_cdata_1_0() + + /** + * XML parser callback for character data. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name character data + * + * @return void + * + * @access private + */ + function _pkginfo_cdata_1_0($xp, $data) + { + if (isset($this->cdata)) { + $this->cdata .= $data; + } + } + + // }}} +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/PackageFile/Parser/v2.php b/3rdparty/PEAR/PackageFile/Parser/v2.php new file mode 100644 index 0000000000..a3ba7063f2 --- /dev/null +++ b/3rdparty/PEAR/PackageFile/Parser/v2.php @@ -0,0 +1,113 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * Parser for package.xml version 2.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } else { + $data .= $line . "\n"; + } + } + return $data; + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + if ($element == 'notes') { + return trim($this->_unIndent($data)); + } + return trim($data); + } + + /** + * @param string + * @param string file name of the package.xml + * @param string|false name of the archive this package.xml came from, if any + * @param string class name to instantiate and return. This must be PEAR_PackageFile_v2 or + * a subclass + * @return PEAR_PackageFile_v2 + */ + function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2') + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new $class; + $ret->encoding = $this->encoding; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + $ret->setPackagefile($file, $archive); + return $ret; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/PackageFile/v1.php b/3rdparty/PEAR/PackageFile/v1.php new file mode 100644 index 0000000000..43e346bcda --- /dev/null +++ b/3rdparty/PEAR/PackageFile/v1.php @@ -0,0 +1,1612 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5); + +/** + * Error code used when there is no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6); + +/** + * Error code when a package name is not valid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7); + +/** + * Error code used when no summary is parsed + */ +define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8); + +/** + * Error code for summaries that are more than 1 line + */ +define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9); + +/** + * Error code used when no description is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10); + +/** + * Error code used when no license is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11); + +/** + * Error code used when a version number is not present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12); + +/** + * Error code used when a version number is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17); + +/** + * Error code when no release notes are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18); + +/** + * Error code when no maintainers are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21); + +/** + * Error code when a maintainer has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22); + +/** + * Error code when a maintainer has no email + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24); + +/** + * Error code when a dependency is not a PHP dependency, but has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25); + +/** + * Error code when a dependency has no type (pkg, php, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26); + +/** + * Error code when a dependency has no relation (lt, ge, has, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27); + +/** + * Error code when a dependency is not a 'has' relation, but has no version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28); + +/** + * Error code when a dependency has an invalid relation + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29); + +/** + * Error code when a dependency has an invalid type + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30); + +/** + * Error code when a dependency has an invalid optional option + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31); + +/** + * Error code when a dependency is a pkg dependency, and has an invalid package name + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32); + +/** + * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33); + +/** + * Error code when rel="has" and version attribute is present. + */ +define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34); + +/** + * Error code when type="php" and dependency name is present + */ +define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37); + +/** + * Error code when a file in the filelist has an invalid role + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38); + +/** + * Error code when a file in the filelist has no role + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39); + +/** + * Error code when analyzing a php source file that has parse errors + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40); + +/** + * Error code when analyzing a php source file reveals a source element + * without a package name prefix + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41); + +/** + * Error code when an unknown channel is specified + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42); + +/** + * Error code when no files are found in the filelist + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43); + +/** + * Error code when a file is not valid php according to _analyzeSourceCode() + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44); + +/** + * Error code when the channel validator returns an error or warning + */ +define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45); + +/** + * Error code when a php5 package is packaged in php4 (analysis doesn't work) + */ +define('PEAR_PACKAGEFILE_ERROR_PHP5', 46); + +/** + * Error code when a file is listed in package.xml but does not exist + */ +define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47); + +/** + * Error code when a + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v1 +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * A registry object, used to access the package name validation regex for non-standard channels + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * An object that contains a log method that matches PEAR_Common::log's signature + * @var object + * @access private + */ + var $_logger; + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo; + + /** + * path to package.xml + * @var string + * @access private + */ + var $_packageFile; + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string + * @access private + */ + var $_archiveFile; + + /** + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack + * @param string Name of Error Stack class to use. + */ + function PEAR_PackageFile_v1() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = 0; + } + + function installBinary($installer) + { + return false; + } + + function isExtension($name) + { + return false; + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setRequestedGroup() + { + // placeholder + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + function getInstalledBinary() + { + return false; + } + + function listPostinstallScripts() + { + return false; + } + + function initPostinstallScripts() + { + return false; + } + + function setLogger(&$logger) + { + if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + function getPackageFile() + { + return isset($this->_packageFile) ? $this->_packageFile : false; + } + + function getPackageType() + { + return 'php'; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + function packageInfo($field) + { + if (!is_string($field) || empty($field) || + !isset($this->_packageInfo[$field])) { + return false; + } + return $this->_packageInfo[$field]; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + function fromArray($pinfo) + { + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + function getChannel() + { + return 'pear.php.net'; + } + + function getUri() + { + return false; + } + + function getTime() + { + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + /** + * @return array + */ + function toArray() + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray(); + } + + function getArray() + { + return $this->_packageInfo; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['package'])) { + return $this->_packageInfo['package']; + } + return false; + } + + /** + * WARNING - don't use this unless you know what you are doing + */ + function setRawPackage($package) + { + $this->_packageInfo['package'] = $package; + } + + function setPackage($package) + { + $this->_packageInfo['package'] = $package; + $this->_isValid = false; + } + + function getVersion() + { + if (isset($this->_packageInfo['version'])) { + return $this->_packageInfo['version']; + } + return false; + } + + function setVersion($version) + { + $this->_packageInfo['version'] = $version; + $this->_isValid = false; + } + + function clearMaintainers() + { + unset($this->_packageInfo['maintainers']); + } + + function getMaintainers() + { + if (isset($this->_packageInfo['maintainers'])) { + return $this->_packageInfo['maintainers']; + } + return false; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email) + { + $this->_packageInfo['maintainers'][] = + array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name); + $this->_isValid = false; + } + + function updateMaintainer($role, $handle, $name, $email) + { + $found = false; + if (!isset($this->_packageInfo['maintainers']) || + !is_array($this->_packageInfo['maintainers'])) { + return $this->addMaintainer($role, $handle, $name, $email); + } + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + } + $this->addMaintainer($role, $handle, $name, $email); + } + + function deleteMaintainer($handle) + { + $found = false; + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + return true; + } + return false; + } + + function getState() + { + if (isset($this->_packageInfo['release_state'])) { + return $this->_packageInfo['release_state']; + } + return false; + } + + function setRawState($state) + { + $this->_packageInfo['release_state'] = $state; + } + + function setState($state) + { + $this->_packageInfo['release_state'] = $state; + $this->_isValid = false; + } + + function getDate() + { + if (isset($this->_packageInfo['release_date'])) { + return $this->_packageInfo['release_date']; + } + return false; + } + + function setDate($date) + { + $this->_packageInfo['release_date'] = $date; + $this->_isValid = false; + } + + function getLicense() + { + if (isset($this->_packageInfo['release_license'])) { + return $this->_packageInfo['release_license']; + } + return false; + } + + function setLicense($date) + { + $this->_packageInfo['release_license'] = $date; + $this->_isValid = false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function setSummary($summary) + { + $this->_packageInfo['summary'] = $summary; + $this->_isValid = false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function setDescription($desc) + { + $this->_packageInfo['description'] = $desc; + $this->_isValid = false; + } + + function getNotes() + { + if (isset($this->_packageInfo['release_notes'])) { + return $this->_packageInfo['release_notes']; + } + return false; + } + + function setNotes($notes) + { + $this->_packageInfo['release_notes'] = $notes; + $this->_isValid = false; + } + + function getDeps() + { + if (isset($this->_packageInfo['release_deps'])) { + return $this->_packageInfo['release_deps']; + } + return false; + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + unset($this->_packageInfo['release_deps']); + } + + function addPhpDep($version, $rel) + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'php', + 'rel' => $rel, + 'version' => $version); + } + + function addPackageDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $dep = + array('type' => 'pkg', + 'name' => $name, + 'rel' => $rel, + 'optional' => $optional); + if ($rel != 'has' && $rel != 'not') { + $dep['version'] = $version; + } + $this->_packageInfo['release_deps'][] = $dep; + } + + function addExtensionDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'ext', + 'name' => $name, + 'rel' => $rel, + 'version' => $version, + 'optional' => $optional); + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['release_deps'] = $deps; + } + + function hasDeps() + { + return isset($this->_packageInfo['release_deps']) && + count($this->_packageInfo['release_deps']); + } + + function getDependencyGroup($group) + { + return false; + } + + function isCompatible($pf) + { + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + function isSubpackage($p) + { + return false; + } + + function dependsOn($package, $channel) + { + if (strtolower($channel) != 'pear.php.net') { + return false; + } + if (!($deps = $this->getDeps())) { + return false; + } + foreach ($deps as $dep) { + if ($dep['type'] != 'pkg') { + continue; + } + if (strtolower($dep['name']) == strtolower($package)) { + return true; + } + } + return false; + } + + function getConfigureOptions() + { + if (isset($this->_packageInfo['configure_options'])) { + return $this->_packageInfo['configure_options']; + } + return false; + } + + function hasConfigureOptions() + { + return isset($this->_packageInfo['configure_options']) && + count($this->_packageInfo['configure_options']); + } + + function addConfigureOption($name, $prompt, $default = false) + { + $o = array('name' => $name, 'prompt' => $prompt); + if ($default !== false) { + $o['default'] = $default; + } + if (!isset($this->_packageInfo['configure_options'])) { + $this->_packageInfo['configure_options'] = array(); + } + $this->_packageInfo['configure_options'][] = $o; + } + + function clearConfigureOptions() + { + unset($this->_packageInfo['configure_options']); + } + + function getProvides() + { + if (isset($this->_packageInfo['provides'])) { + return $this->_packageInfo['provides']; + } + return false; + } + + function getProvidesExtension() + { + return false; + } + + function addFile($dir, $file, $attrs) + { + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $file = $dir . $file; + $file = preg_replace('![\\/]+!', '/', $file); + $this->_packageInfo['filelist'][$file] = $attrs; + } + + function getInstallationFilelist() + { + return $this->getFilelist(); + } + + function getFilelist() + { + if (isset($this->_packageInfo['filelist'])) { + return $this->_packageInfo['filelist']; + } + return false; + } + + function setFileAttribute($file, $attr, $value) + { + $this->_packageInfo['filelist'][$file][$attr] = $value; + } + + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts); + } else { + $this->_packageInfo['filelist'][$file] = $atts; + } + } + + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function getPackagexmlVersion() + { + return '1.0'; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + // }}} + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params, false, false, debug_backtrace()); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace()); + } + + /** + * @param integer error code + * @access protected + */ + function _getErrorMessage() + { + return array( + PEAR_PACKAGEFILE_ERROR_NO_NAME => + 'Missing Package Name', + PEAR_PACKAGEFILE_ERROR_NO_SUMMARY => + 'No summary found', + PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY => + 'Summary should be on one line', + PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION => + 'Missing description', + PEAR_PACKAGEFILE_ERROR_NO_LICENSE => + 'Missing license', + PEAR_PACKAGEFILE_ERROR_NO_VERSION => + 'No release version found', + PEAR_PACKAGEFILE_ERROR_NO_STATE => + 'No release state found', + PEAR_PACKAGEFILE_ERROR_NO_DATE => + 'No release date found', + PEAR_PACKAGEFILE_ERROR_NO_NOTES => + 'No release notes found', + PEAR_PACKAGEFILE_ERROR_NO_LEAD => + 'Package must have at least one lead maintainer', + PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS => + 'No maintainers found, at least one must be defined', + PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE => + 'Maintainer %index% has no handle (user ID at channel server)', + PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE => + 'Maintainer %index% has no role', + PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME => + 'Maintainer %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL => + 'Maintainer %index% has no email', + PEAR_PACKAGEFILE_ERROR_NO_DEPNAME => + 'Dependency %index% is not a php dependency, and has no name', + PEAR_PACKAGEFILE_ERROR_NO_DEPREL => + 'Dependency %index% has no relation (rel)', + PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE => + 'Dependency %index% has no type', + PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED => + 'PHP Dependency %index% has a name attribute of "%name%" which will be' . + ' ignored!', + PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION => + 'Dependency %index% is not a rel="has" or rel="not" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION => + 'Dependency %index% is a type="php" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED => + 'Dependency %index% is a rel="%rel%" dependency, versioning is ignored', + PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL => + 'Dependency %index% has invalid optional value "%opt%", should be yes or no', + PEAR_PACKAGEFILE_PHP_NO_NOT => + 'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' . + ' to exclude specific versions', + PEAR_PACKAGEFILE_ERROR_NO_CONFNAME => + 'Configure Option %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT => + 'Configure Option %index% has no prompt', + PEAR_PACKAGEFILE_ERROR_NO_FILES => + 'No files in section of package.xml', + PEAR_PACKAGEFILE_ERROR_NO_FILEROLE => + 'File "%file%" has no role, expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE => + 'File "%file%" has invalid role "%role%", expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME => + 'File "%file%" cannot start with ".", cannot package or install', + PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE => + 'Parser error: invalid PHP found in file "%file%"', + PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX => + 'in %file%: %type% "%name%" not prefixed with package name "%package%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILE => + 'Parser error: invalid PHP file "%file%"', + PEAR_PACKAGEFILE_ERROR_CHANNELVAL => + 'Channel validator error: field "%field%" - %reason%', + PEAR_PACKAGEFILE_ERROR_PHP5 => + 'Error, PHP5 token encountered in %file%, analysis should be in PHP5', + PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND => + 'File "%file%" in package.xml does not exist', + PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS => + 'Package.xml contains non-ISO-8859-1 characters, and may not validate', + ); + } + + /** + * Validate XML package definition file. + * + * @access public + * @return boolean + */ + function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false) + { + if (($this->_isValid & $state) == $state) { + return true; + } + $this->_isValid = true; + $info = $this->_packageInfo; + if (empty($info['package'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME); + $this->_packageName = $pn = 'unknown'; + } else { + $this->_packageName = $pn = $info['package']; + } + + if (empty($info['summary'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (empty($info['description'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION); + } + if (empty($info['release_license'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE); + } + if (empty($info['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION); + } + if (empty($info['release_state'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE); + } + if (empty($info['release_date'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE); + } + if (empty($info['release_notes'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES); + } + if (empty($info['maintainers'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS); + } else { + $haslead = false; + $i = 1; + foreach ($info['maintainers'] as $m) { + if (empty($m['handle'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE, + array('index' => $i)); + } + if (empty($m['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE, + array('index' => $i, 'roles' => PEAR_Common::getUserRoles())); + } elseif ($m['role'] == 'lead') { + $haslead = true; + } + if (empty($m['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME, + array('index' => $i)); + } + if (empty($m['email'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL, + array('index' => $i)); + } + $i++; + } + if (!$haslead) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD); + } + } + if (!empty($info['release_deps'])) { + $i = 1; + foreach ($info['release_deps'] as $d) { + if (!isset($d['type']) || empty($d['type'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE, + array('index' => $i, 'types' => PEAR_Common::getDependencyTypes())); + continue; + } + if (!isset($d['rel']) || empty($d['rel'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL, + array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations())); + continue; + } + if (!empty($d['optional'])) { + if (!in_array($d['optional'], array('yes', 'no'))) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL, + array('index' => $i, 'opt' => $d['optional'])); + } + } + if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION, + array('index' => $i)); + } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED, + array('index' => $i, 'rel' => $d['rel'])); + } + if ($d['type'] == 'php' && !empty($d['name'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED, + array('index' => $i, 'name' => $d['name'])); + } elseif ($d['type'] != 'php' && empty($d['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME, + array('index' => $i)); + } + if ($d['type'] == 'php' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION, + array('index' => $i)); + } + if (($d['rel'] == 'not') && ($d['type'] == 'php')) { + $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT, + array('index' => $i)); + } + $i++; + } + } + if (!empty($info['configure_options'])) { + $i = 1; + foreach ($info['configure_options'] as $c) { + if (empty($c['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME, + array('index' => $i)); + } + if (empty($c['prompt'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT, + array('index' => $i)); + } + $i++; + } + } + if (empty($info['filelist'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES); + $errors[] = 'no files'; + } else { + foreach ($info['filelist'] as $file => $fa) { + if (empty($fa['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE, + array('file' => $file, 'roles' => PEAR_Common::getFileRoles())); + continue; + } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE, + array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles())); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) { + // file contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file)); + } + if (isset($fa['install-as']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['install-as']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [installed as ' . $fa['install-as'] . ']')); + } + if (isset($fa['baseinstalldir']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['baseinstalldir']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']')); + } + } + } + if (isset($this->_registry) && $this->_isValid) { + $chan = $this->_registry->getChannel('pear.php.net'); + if (PEAR::isError($chan)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage()); + return $this->_isValid = 0; + } + $validator = $chan->getValidationObject(); + $validator->setPackageFile($this); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error); + } + foreach ($failures['warnings'] as $warning) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning); + } + } + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) { + if ($this->_analyzePhpFiles()) { + $this->_isValid = true; + } + } + if ($this->_isValid) { + return $this->_isValid = $state; + } + return $this->_isValid = 0; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_logger) ? array(&$this->_logger, 'log') : + array($common, 'log'); + $info = $this->getFilelist(); + foreach ($info as $file => $fa) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND, + array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file)); + continue; + } + if ($fa['role'] == 'php' && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $this->_buildProvidesArray($srcinfo); + } + } + } + $this->_packageName = $pn = $this->getPackage(); + $pnl = strlen($pn); + if (isset($this->_packageInfo['provides'])) { + foreach ((array) $this->_packageInfo['provides'] as $key => $what) { + if (isset($what['explicit'])) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } + } + } + return $this->_isValid; + } + + /** + * Get the default xml generator object + * + * @return PEAR_PackageFile_Generator_v1 + */ + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v1')) { + require_once 'PEAR/PackageFile/Generator/v1.php'; + } + $a = &new PEAR_PackageFile_Generator_v1($this); + return $a; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + // {{{ analyzeSourceCode() + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access private + */ + function _analyzeSourceCode($file) + { + if (!function_exists("token_get_all")) { + return false; + } + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + $tokens = token_get_all($contents); +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + )) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5, + array($file)); + } + } + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + continue 2; + } + } + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return false; + } + $file = basename($srcinfo['source_file']); + $pn = $this->getPackage(); + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->_packageInfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) { + continue; + } + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + // }}} +} +?> diff --git a/3rdparty/PEAR/PackageFile/v2.php b/3rdparty/PEAR/PackageFile/v2.php new file mode 100644 index 0000000000..1ca412dc8c --- /dev/null +++ b/3rdparty/PEAR/PackageFile/v2.php @@ -0,0 +1,2049 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v2 +{ + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo = array(); + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string|false + * @access private + */ + var $_archiveFile; + + /** + * path to package .xml or false if this is an abstract parsed-from-string xml + * @var string|false + * @access private + */ + var $_packageFile; + + /** + * This is used by file analysis routines to log progress information + * @var PEAR_Common + * @access protected + */ + var $_logger; + + /** + * This is set to the highest validation level that has been validated + * + * If the package.xml is invalid or unknown, this is set to 0. If + * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL. If + * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING + * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING. This allows validation + * "caching" to occur, which is particularly important for package validation, so + * that PHP files are not validated twice + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * True if the filelist has been validated + * @param bool + */ + var $_filesValid = false; + + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * @var PEAR_Config + * @access protected + */ + var $_config; + + /** + * Optional Dependency group requested for installation + * @var string + * @access private + */ + var $_requestedGroup = false; + + /** + * @var PEAR_ErrorStack + * @access protected + */ + var $_stack; + + /** + * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible + */ + var $_tasksNs; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @var PEAR_PackageFile_v2_Validator + */ + var $_v2Validator; + + /** + * The constructor merely sets up the private error stack + */ + function PEAR_PackageFile_v2() + { + $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null); + $this->_isValid = false; + } + + /** + * To make unit-testing easier + * @param PEAR_Frontend_* + * @param array options + * @param PEAR_Config + * @return PEAR_Downloader + * @access protected + */ + function &getPEARDownloader(&$i, $o, &$c) + { + $z = &new PEAR_Downloader($i, $o, $c); + return $z; + } + + /** + * To make unit-testing easier + * @param PEAR_Config + * @param array options + * @param array package name as returned from {@link PEAR_Registry::parsePackageName()} + * @param int PEAR_VALIDATE_* constant + * @return PEAR_Dependency2 + * @access protected + */ + function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $o, $p, $s); + return $z; + } + + function getInstalledBinary() + { + return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] : + false; + } + + /** + * Installation of source package has failed, attempt to download and install the + * binary version of this package. + * @param PEAR_Installer + * @return array|false + */ + function installBinary(&$installer) + { + if (!OS_WINDOWS) { + $a = false; + return $a; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $releasetype = $this->getPackageType() . 'release'; + if (!is_array($installer->getInstallPackages())) { + $a = false; + return $a; + } + foreach ($installer->getInstallPackages() as $p) { + if ($p->isExtension($this->_packageInfo['providesextension'])) { + if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') { + $a = false; + return $a; // the user probably downloaded it separately + } + } + } + if (isset($this->_packageInfo[$releasetype]['binarypackage'])) { + $installer->log(0, 'Attempting to download binary version of extension "' . + $this->_packageInfo['providesextension'] . '"'); + $params = $this->_packageInfo[$releasetype]['binarypackage']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + if (isset($this->_packageInfo['channel'])) { + foreach ($params as $i => $param) { + $params[$i] = array('channel' => $this->_packageInfo['channel'], + 'package' => $param, 'version' => $this->getVersion()); + } + } + $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(), + $installer->config); + $verbose = $dl->config->get('verbose'); + $dl->config->set('verbose', -1); + foreach ($params as $param) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $dl->download(array($param)); + PEAR::popErrorHandling(); + if (is_array($ret) && count($ret)) { + break; + } + } + $dl->config->set('verbose', $verbose); + if (is_array($ret)) { + if (count($ret) == 1) { + $pf = $ret[0]->getPackageFile(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $installer->install($ret[0]); + PEAR::popErrorHandling(); + if (is_array($err)) { + $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage(); + // "install" self, so all dependencies will work transparently + $this->_registry->addPackage2($this); + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" successful'); + $a = array($ret[0], $err); + return $a; + } + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" failed'); + } + } + } + } + $a = false; + return $a; + } + + /** + * @return string|false Extension name + */ + function getProvidesExtension() + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (isset($this->_packageInfo['providesextension'])) { + return $this->_packageInfo['providesextension']; + } + } + return false; + } + + /** + * @param string Extension name + * @return bool + */ + function isExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + return $this->_packageInfo['providesextension'] == $extension; + } + return false; + } + + /** + * Tests whether every part of the package.xml 1.0 is represented in + * this package.xml 2.0 + * @param PEAR_PackageFile_v1 + * @return bool + */ + function isEquivalent($pf1) + { + if (!$pf1) { + return true; + } + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_stack->getErrors(true); + if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + $pass = true; + if ($pf1->getPackage() != $this->getPackage()) { + $this->_differentPackage($pf1->getPackage()); + $pass = false; + } + if ($pf1->getVersion() != $this->getVersion()) { + $this->_differentVersion($pf1->getVersion()); + $pass = false; + } + if (trim($pf1->getSummary()) != $this->getSummary()) { + $this->_differentSummary($pf1->getSummary()); + $pass = false; + } + if (preg_replace('/\s+/', '', $pf1->getDescription()) != + preg_replace('/\s+/', '', $this->getDescription())) { + $this->_differentDescription($pf1->getDescription()); + $pass = false; + } + if ($pf1->getState() != $this->getState()) { + $this->_differentState($pf1->getState()); + $pass = false; + } + if (!strstr(preg_replace('/\s+/', '', $this->getNotes()), + preg_replace('/\s+/', '', $pf1->getNotes()))) { + $this->_differentNotes($pf1->getNotes()); + $pass = false; + } + $mymaintainers = $this->getMaintainers(); + $yourmaintainers = $pf1->getMaintainers(); + for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) { + $reset = false; + for ($i2 = 0; $i2 < count($mymaintainers); $i2++) { + if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) { + if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) { + $this->_differentRole($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']); + $pass = false; + } + if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) { + $this->_differentEmail($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']); + $pass = false; + } + if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) { + $this->_differentName($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']); + $pass = false; + } + unset($mymaintainers[$i2]); + $mymaintainers = array_values($mymaintainers); + unset($yourmaintainers[$i1]); + $yourmaintainers = array_values($yourmaintainers); + $reset = true; + break; + } + } + if ($reset) { + $i1 = -1; + } + } + $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers); + $filelist = $this->getFilelist(); + foreach ($pf1->getFilelist() as $file => $atts) { + if (!isset($filelist[$file])) { + $this->_missingFile($file); + $pass = false; + } + } + return $pass; + } + + function _differentPackage($package) + { + $this->_stack->push(__FUNCTION__, 'error', array('package' => $package, + 'self' => $this->getPackage()), + 'package.xml 1.0 package "%package%" does not match "%self%"'); + } + + function _differentVersion($version) + { + $this->_stack->push(__FUNCTION__, 'error', array('version' => $version, + 'self' => $this->getVersion()), + 'package.xml 1.0 version "%version%" does not match "%self%"'); + } + + function _differentState($state) + { + $this->_stack->push(__FUNCTION__, 'error', array('state' => $state, + 'self' => $this->getState()), + 'package.xml 1.0 state "%state%" does not match "%self%"'); + } + + function _differentRole($handle, $role, $selfrole) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'role' => $role, 'self' => $selfrole), + 'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"'); + } + + function _differentEmail($handle, $email, $selfemail) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'email' => $email, 'self' => $selfemail), + 'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"'); + } + + function _differentName($handle, $name, $selfname) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'name' => $name, 'self' => $selfname), + 'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"'); + } + + function _unmatchedMaintainers($my, $yours) + { + if ($my) { + array_walk($my, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my), + 'package.xml 2.0 has unmatched extra maintainers "%handles%"'); + } + if ($yours) { + array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours), + 'package.xml 1.0 has unmatched extra maintainers "%handles%"'); + } + } + + function _differentNotes($notes) + { + $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...'; + $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() : + substr($this->getNotes(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes, + 'self' => $truncmynotes), + 'package.xml 1.0 release notes "%notes%" do not match "%self%"'); + } + + function _differentSummary($summary) + { + $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...'; + $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() : + substr($this->getsummary(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary, + 'self' => $truncmysummary), + 'package.xml 1.0 summary "%summary%" does not match "%self%"'); + } + + function _differentDescription($description) + { + $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...'); + $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() : + substr($this->getdescription(), 0, 24) . '...'); + $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription, + 'self' => $truncmydescription), + 'package.xml 1.0 description "%description%" does not match "%self%"'); + } + + function _missingFile($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'package.xml 1.0 file "%file%" is not present in '); + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawState($state) + { + if (!isset($this->_packageInfo['stability'])) { + $this->_packageInfo['stability'] = array(); + } + $this->_packageInfo['stability']['release'] = $state; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawCompatible($compatible) + { + $this->_packageInfo['compatible'] = $compatible; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawPackage($package) + { + $this->_packageInfo['name'] = $package; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawChannel($channel) + { + $this->_packageInfo['channel'] = $channel; + } + + function setRequestedGroup($group) + { + $this->_requestedGroup = $group; + } + + function getRequestedGroup() + { + if (isset($this->_requestedGroup)) { + return $this->_requestedGroup; + } + return false; + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + /** + * Determines whether this package.xml has post-install scripts or not + * @return array|false + */ + function listPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + $ret = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // ignored files will not be in the filelist + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $task = $this->getTask($tag); + $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL); + if ($task->isScript()) { + $ret[] = $filelist[$name]['installed_as']; + } + } + } + if (count($ret)) { + return $ret; + } + return false; + } + + /** + * Initialize post-install scripts for running + * + * This method can be used to detect post-install scripts, as the return value + * indicates whether any exist + * @return bool + */ + function initPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // file was not installed due to installconditions + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $taskname = $this->getTask($tag); + $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL); + if (!$task->isScript()) { + continue; // scripts are only handled after installation + } + $lastversion = isset($this->_packageInfo['_lastversion']) ? + $this->_packageInfo['_lastversion'] : null; + $task->init($raw, $atts, $lastversion); + $res = $task->startSession($this, $atts['installed_as']); + if (!$res) { + continue; // skip this file + } + if (PEAR::isError($res)) { + return $res; + } + $assign = &$task; + $this->_scripts[] = &$assign; + } + } + if (count($this->_scripts)) { + return true; + } + return false; + } + + function runPostinstallScripts() + { + if ($this->initPostinstallScripts()) { + $ui = &PEAR_Frontend::singleton(); + if ($ui) { + $ui->runPostinstallScripts($this->_scripts, $this); + } + } + } + + + /** + * Convert a recursive set of and tags into a single tag with + * tags. + */ + function flattenFilelist() + { + if (isset($this->_packageInfo['bundle'])) { + return; + } + $filelist = array(); + if (isset($this->_packageInfo['contents']['dir']['dir'])) { + $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']); + if (!isset($filelist[1])) { + $filelist = $filelist[0]; + } + $this->_packageInfo['contents']['dir']['file'] = $filelist; + unset($this->_packageInfo['contents']['dir']['dir']); + } else { + // else already flattened but check for baseinstalldir propagation + if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) { + if (isset($this->_packageInfo['contents']['dir']['file'][0])) { + foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) { + if (isset($file['attribs']['baseinstalldir'])) { + continue; + } + $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } else { + if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) { + $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } + } + } + } + + /** + * @param array the final flattened file list + * @param array the current directory being processed + * @param string|false any recursively inherited baeinstalldir attribute + * @param string private recursion variable + * @return array + * @access protected + */ + function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '') + { + if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) { + $baseinstall = $dir['attribs']['baseinstalldir']; + } + if (isset($dir['dir'])) { + if (!isset($dir['dir'][0])) { + $dir['dir'] = array($dir['dir']); + } + foreach ($dir['dir'] as $subdir) { + if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) { + $name = '*unknown*'; + } else { + $name = $subdir['attribs']['name']; + } + $newpath = empty($path) ? $name : + $path . '/' . $name; + $this->_getFlattenedFilelist($files, $subdir, + $baseinstall, $newpath); + } + } + if (isset($dir['file'])) { + if (!isset($dir['file'][0])) { + $dir['file'] = array($dir['file']); + } + foreach ($dir['file'] as $file) { + $attrs = $file['attribs']; + $name = $attrs['name']; + if ($baseinstall && !isset($attrs['baseinstalldir'])) { + $attrs['baseinstalldir'] = $baseinstall; + } + $attrs['name'] = empty($path) ? $name : $path . '/' . $name; + $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attrs['name']); + $file['attribs'] = $attrs; + $files[] = $file; + } + } + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setLogger(&$logger) + { + if (!is_object($logger) || !method_exists($logger, 'log')) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['dependencies'] = $deps; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setCompatible($compat) + { + $this->_packageInfo['compatible'] = $compat; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + function getPackageFile() + { + return $this->_packageFile; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + + /** + * Directly set the array that defines this packagefile + * + * WARNING: no validation. This should only be performed by internal methods + * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2 + * @param array + */ + function fromArray($pinfo) + { + unset($pinfo['old']); + unset($pinfo['xsdversion']); + // If the changelog isn't an array then it was passed in as an empty tag + if (isset($pinfo['changelog']) && !is_array($pinfo['changelog'])) { + unset($pinfo['changelog']); + } + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + /** + * @return array + */ + function toArray($forreg = false) + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray($forreg); + } + + function getArray($forReg = false) + { + if ($forReg) { + $arr = $this->_packageInfo; + $arr['old'] = array(); + $arr['old']['version'] = $this->getVersion(); + $arr['old']['release_date'] = $this->getDate(); + $arr['old']['release_state'] = $this->getState(); + $arr['old']['release_license'] = $this->getLicense(); + $arr['old']['release_notes'] = $this->getNotes(); + $arr['old']['release_deps'] = $this->getDeps(); + $arr['old']['maintainers'] = $this->getMaintainers(); + $arr['xsdversion'] = '2.0'; + return $arr; + } else { + $info = $this->_packageInfo; + unset($info['dirtree']); + if (isset($info['_lastversion'])) { + unset($info['_lastversion']); + } + if (isset($info['#binarypackage'])) { + unset($info['#binarypackage']); + } + return $info; + } + } + + function packageInfo($field) + { + $arr = $this->getArray(true); + if ($field == 'state') { + return $arr['stability']['release']; + } + if ($field == 'api-version') { + return $arr['version']['api']; + } + if ($field == 'api-state') { + return $arr['stability']['api']; + } + if (isset($arr['old'][$field])) { + if (!is_string($arr['old'][$field])) { + return null; + } + return $arr['old'][$field]; + } + if (isset($arr[$field])) { + if (!is_string($arr[$field])) { + return null; + } + return $arr[$field]; + } + return null; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['name'])) { + return $this->_packageInfo['name']; + } + return false; + } + + function getChannel() + { + if (isset($this->_packageInfo['uri'])) { + return '__uri'; + } + if (isset($this->_packageInfo['channel'])) { + return strtolower($this->_packageInfo['channel']); + } + return false; + } + + function getUri() + { + if (isset($this->_packageInfo['uri'])) { + return $this->_packageInfo['uri']; + } + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function getMaintainers($raw = false) + { + if (!isset($this->_packageInfo['lead'])) { + return false; + } + if ($raw) { + $ret = array('lead' => $this->_packageInfo['lead']); + (isset($this->_packageInfo['developer'])) ? + $ret['developer'] = $this->_packageInfo['developer'] :null; + (isset($this->_packageInfo['contributor'])) ? + $ret['contributor'] = $this->_packageInfo['contributor'] :null; + (isset($this->_packageInfo['helper'])) ? + $ret['helper'] = $this->_packageInfo['helper'] :null; + return $ret; + } else { + $ret = array(); + $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] : + array($this->_packageInfo['lead']); + foreach ($leads as $lead) { + $s = $lead; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'lead'; + $ret[] = $s; + } + if (isset($this->_packageInfo['developer'])) { + $leads = isset($this->_packageInfo['developer'][0]) ? + $this->_packageInfo['developer'] : + array($this->_packageInfo['developer']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'developer'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['contributor'])) { + $leads = isset($this->_packageInfo['contributor'][0]) ? + $this->_packageInfo['contributor'] : + array($this->_packageInfo['contributor']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'contributor'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['helper'])) { + $leads = isset($this->_packageInfo['helper'][0]) ? + $this->_packageInfo['helper'] : + array($this->_packageInfo['helper']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'helper'; + $ret[] = $s; + } + } + return $ret; + } + return false; + } + + function getLeads() + { + if (isset($this->_packageInfo['lead'])) { + return $this->_packageInfo['lead']; + } + return false; + } + + function getDevelopers() + { + if (isset($this->_packageInfo['developer'])) { + return $this->_packageInfo['developer']; + } + return false; + } + + function getContributors() + { + if (isset($this->_packageInfo['contributor'])) { + return $this->_packageInfo['contributor']; + } + return false; + } + + function getHelpers() + { + if (isset($this->_packageInfo['helper'])) { + return $this->_packageInfo['helper']; + } + return false; + } + + function setDate($date) + { + if (!isset($this->_packageInfo['date'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), array(), 'date'); + } + $this->_packageInfo['date'] = $date; + $this->_isValid = 0; + } + + function setTime($time) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['time'])) { + // ensure that the time tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), $time, 'time'); + } + $this->_packageInfo['time'] = $time; + } + + function getDate() + { + if (isset($this->_packageInfo['date'])) { + return $this->_packageInfo['date']; + } + return false; + } + + function getTime() + { + if (isset($this->_packageInfo['time'])) { + return $this->_packageInfo['time']; + } + return false; + } + + /** + * @param package|api version category to return + */ + function getVersion($key = 'release') + { + if (isset($this->_packageInfo['version'][$key])) { + return $this->_packageInfo['version'][$key]; + } + return false; + } + + function getStability() + { + if (isset($this->_packageInfo['stability'])) { + return $this->_packageInfo['stability']; + } + return false; + } + + function getState($key = 'release') + { + if (isset($this->_packageInfo['stability'][$key])) { + return $this->_packageInfo['stability'][$key]; + } + return false; + } + + function getLicense($raw = false) + { + if (isset($this->_packageInfo['license'])) { + if ($raw) { + return $this->_packageInfo['license']; + } + if (is_array($this->_packageInfo['license'])) { + return $this->_packageInfo['license']['_content']; + } else { + return $this->_packageInfo['license']; + } + } + return false; + } + + function getLicenseLocation() + { + if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) { + return false; + } + return $this->_packageInfo['license']['attribs']; + } + + function getNotes() + { + if (isset($this->_packageInfo['notes'])) { + return $this->_packageInfo['notes']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + return $this->_packageInfo['usesrole']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + return $this->_packageInfo['usestask']; + } + return false; + } + + /** + * This should only be used to retrieve filenames and install attributes + */ + function getFilelist($preserve = false) + { + if (isset($this->_packageInfo['filelist']) && !$preserve) { + return $this->_packageInfo['filelist']; + } + $this->flattenFilelist(); + if ($contents = $this->getContents()) { + $ret = array(); + if (!isset($contents['dir'])) { + return false; + } + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + foreach ($contents['dir']['file'] as $file) { + $name = $file['attribs']['name']; + if (!$preserve) { + $file = $file['attribs']; + } + $ret[$name] = $file; + } + if (!$preserve) { + $this->_packageInfo['filelist'] = $ret; + } + return $ret; + } + return false; + } + + /** + * Return configure options array, if any + * + * @return array|false + */ + function getConfigureOptions() + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $releases = $this->getReleases(); + if (isset($releases[0])) { + $releases = $releases[0]; + } + + if (isset($releases['configureoption'])) { + if (!isset($releases['configureoption'][0])) { + $releases['configureoption'] = array($releases['configureoption']); + } + + for ($i = 0; $i < count($releases['configureoption']); $i++) { + $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs']; + } + + return $releases['configureoption']; + } + + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + /** + * Retrieve a list of files that should be installed on this computer + * @return array + */ + function getInstallationFilelist($forfilecheck = false) + { + $contents = $this->getFilelist(true); + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $base = $contents['dir']['attribs']['baseinstalldir']; + } + if (isset($this->_packageInfo['bundle'])) { + return PEAR::raiseError( + 'Exception: bundles should be handled in download code only'); + } + $release = $this->getReleases(); + if ($release) { + if (!isset($release[0])) { + if (!isset($release['installconditions']) && !isset($release['filelist'])) { + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + $release = array($release); + } + $depchecker = &$this->getPEARDependency2($this->_config, array(), + array('channel' => $this->getChannel(), 'package' => $this->getPackage()), + PEAR_VALIDATE_INSTALLING); + foreach ($release as $instance) { + if (isset($instance['installconditions'])) { + $installconditions = $instance['installconditions']; + if (is_array($installconditions)) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($installconditions as $type => $conditions) { + if (!isset($conditions[0])) { + $conditions = array($conditions); + } + foreach ($conditions as $condition) { + $ret = $depchecker->{"validate{$type}Dependency"}($condition); + if (PEAR::isError($ret)) { + PEAR::popErrorHandling(); + continue 3; // skip this release + } + } + } + PEAR::popErrorHandling(); + } + } + // this is the release to use + if (isset($instance['filelist'])) { + // ignore files + if (isset($instance['filelist']['ignore'])) { + $ignore = isset($instance['filelist']['ignore'][0]) ? + $instance['filelist']['ignore'] : + array($instance['filelist']['ignore']); + foreach ($ignore as $ig) { + unset ($contents[$ig['attribs']['name']]); + } + } + // install files as this name + if (isset($instance['filelist']['install'])) { + $installas = isset($instance['filelist']['install'][0]) ? + $instance['filelist']['install'] : + array($instance['filelist']['install']); + foreach ($installas as $as) { + $contents[$as['attribs']['name']]['attribs']['install-as'] = + $as['attribs']['as']; + } + } + } + if ($forfilecheck) { + foreach ($contents as $file => $attrs) { + $contents[$file] = $attrs['attribs']; + } + } + return $contents; + } + } else { // simple release - no installconditions or install-as + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + // no releases matched + return PEAR::raiseError('No releases in package.xml matched the existing operating ' . + 'system, extensions installed, or architecture, cannot install'); + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function getInstalledLocation($file) + { + if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) { + return $this->_packageInfo['filelist'][$file]['installed_as']; + } + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Retrieve the contents tag + */ + function getContents() + { + if (isset($this->_packageInfo['contents'])) { + return $this->_packageInfo['contents']; + } + return false; + } + + /** + * @param string full path to file + * @param string attribute name + * @param string attribute value + * @param int risky but fast - use this to choose a file based on its position in the list + * of files. Index is zero-based like PHP arrays. + * @return bool success of operation + */ + function setFileAttribute($filename, $attr, $value, $index = false) + { + $this->_isValid = 0; + if (in_array($attr, array('role', 'name', 'baseinstalldir'))) { + $this->_filesValid = false; + } + if ($index !== false && + isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) { + $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value; + return true; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value; + } + return true; + } + } + } + return false; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + /** + * Determines whether this package claims it is compatible with the version of + * the package that has a recommended version dependency + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package + * @return boolean + */ + function isCompatible($pf) + { + if (!isset($this->_packageInfo['compatible'])) { + return false; + } + if (!isset($this->_packageInfo['channel'])) { + return false; + } + $me = $pf->getVersion(); + $compatible = $this->_packageInfo['compatible']; + if (!isset($compatible[0])) { + $compatible = array($compatible); + } + $found = false; + foreach ($compatible as $info) { + if (strtolower($info['name']) == strtolower($pf->getPackage())) { + if (strtolower($info['channel']) == strtolower($pf->getChannel())) { + $found = true; + break; + } + } + } + if (!$found) { + return false; + } + if (isset($info['exclude'])) { + if (!isset($info['exclude'][0])) { + $info['exclude'] = array($info['exclude']); + } + foreach ($info['exclude'] as $exclude) { + if (version_compare($me, $exclude, '==')) { + return false; + } + } + } + if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) { + return true; + } + return false; + } + + /** + * @return array|false + */ + function getCompatible() + { + if (isset($this->_packageInfo['compatible'])) { + return $this->_packageInfo['compatible']; + } + return false; + } + + function getDependencies() + { + if (isset($this->_packageInfo['dependencies'])) { + return $this->_packageInfo['dependencies']; + } + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + /** + * Determines whether the passed in package is a subpackage of this package. + * + * No version checking is done, only name verification. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + */ + function isSubpackage($p) + { + $sub = array(); + if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) { + $sub = $this->_packageInfo['dependencies']['required']['subpackage']; + if (!isset($sub[0])) { + $sub = array($sub); + } + } + if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) { + $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage']; + if (!isset($sub1[0])) { + $sub1 = array($sub1); + } + $sub = array_merge($sub, $sub1); + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $group = $this->_packageInfo['dependencies']['group']; + if (!isset($group[0])) { + $group = array($group); + } + foreach ($group as $deps) { + if (isset($deps['subpackage'])) { + $sub2 = $deps['subpackage']; + if (!isset($sub2[0])) { + $sub2 = array($sub2); + } + $sub = array_merge($sub, $sub2); + } + } + } + foreach ($sub as $dep) { + if (strtolower($dep['name']) == strtolower($p->getPackage())) { + if (isset($dep['channel'])) { + if (strtolower($dep['channel']) == strtolower($p->getChannel())) { + return true; + } + } else { + if ($dep['uri'] == $p->getURI()) { + return true; + } + } + } + } + return false; + } + + function dependsOn($package, $channel) + { + if (!($deps = $this->getDependencies())) { + return false; + } + foreach (array('package', 'subpackage') as $type) { + foreach (array('required', 'optional') as $needed) { + if (isset($deps[$needed][$type])) { + if (!isset($deps[$needed][$type][0])) { + $deps[$needed][$type] = array($deps[$needed][$type]); + } + foreach ($deps[$needed][$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $dep['group'] = array($deps['group']); + } + foreach ($deps['group'] as $group) { + if (isset($group[$type])) { + if (!is_array($group[$type])) { + $group[$type] = array($group[$type]); + } + foreach ($group[$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + } + } + return false; + } + + /** + * Get the contents of a dependency group + * @param string + * @return array|false + */ + function getDependencyGroup($name) + { + $name = strtolower($name); + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + foreach ($groups as $group) { + if (strtolower($group['attribs']['name']) == $name) { + return $group; + } + } + return false; + } + + /** + * Retrieve a partial package.xml 1.0 representation of dependencies + * + * a very limited representation of dependencies is returned by this method. + * The tag for excluding certain versions of a dependency is + * completely ignored. In addition, dependency groups are ignored, with the + * assumption that all dependencies in dependency groups are also listed in + * the optional group that work with all dependency groups + * @param boolean return package.xml 2.0 tag + * @return array|false + */ + function getDeps($raw = false, $nopearinstaller = false) + { + if (isset($this->_packageInfo['dependencies'])) { + if ($raw) { + return $this->_packageInfo['dependencies']; + } + $ret = array(); + $map = array( + 'php' => 'php', + 'package' => 'pkg', + 'subpackage' => 'pkg', + 'extension' => 'ext', + 'os' => 'os', + 'pearinstaller' => 'pkg', + ); + foreach (array('required', 'optional') as $type) { + $optional = ($type == 'optional') ? 'yes' : 'no'; + if (!isset($this->_packageInfo['dependencies'][$type]) + || empty($this->_packageInfo['dependencies'][$type])) { + continue; + } + foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) { + if ($dtype == 'pearinstaller' && $nopearinstaller) { + continue; + } + if (!isset($deps[0])) { + $deps = array($deps); + } + foreach ($deps as $dep) { + if (!isset($map[$dtype])) { + // no support for arch type + continue; + } + if ($dtype == 'pearinstaller') { + $dep['name'] = 'PEAR'; + $dep['channel'] = 'pear.php.net'; + } + $s = array('type' => $map[$dtype]); + if (isset($dep['channel'])) { + $s['channel'] = $dep['channel']; + } + if (isset($dep['uri'])) { + $s['uri'] = $dep['uri']; + } + if (isset($dep['name'])) { + $s['name'] = $dep['name']; + } + if (isset($dep['conflicts'])) { + $s['rel'] = 'not'; + } else { + if (!isset($dep['min']) && + !isset($dep['max'])) { + $s['rel'] = 'has'; + $s['optional'] = $optional; + } elseif (isset($dep['min']) && + isset($dep['max'])) { + $s['rel'] = 'ge'; + $s1 = $s; + $s1['rel'] = 'le'; + $s['version'] = $dep['min']; + $s1['version'] = $dep['max']; + if (isset($dep['channel'])) { + $s1['channel'] = $dep['channel']; + } + if ($dtype != 'php') { + $s['name'] = $dep['name']; + $s1['name'] = $dep['name']; + } + $s['optional'] = $optional; + $s1['optional'] = $optional; + $ret[] = $s1; + } elseif (isset($dep['min'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['min']) { + $s['rel'] = 'gt'; + } else { + $s['rel'] = 'ge'; + } + $s['version'] = $dep['min']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } elseif (isset($dep['max'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['max']) { + $s['rel'] = 'lt'; + } else { + $s['rel'] = 'le'; + } + $s['version'] = $dep['max']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } + } + $ret[] = $s; + } + } + } + if (count($ret)) { + return $ret; + } + } + return false; + } + + /** + * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false + */ + function getPackageType() + { + if (isset($this->_packageInfo['phprelease'])) { + return 'php'; + } + if (isset($this->_packageInfo['extsrcrelease'])) { + return 'extsrc'; + } + if (isset($this->_packageInfo['extbinrelease'])) { + return 'extbin'; + } + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return 'zendextsrc'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return 'zendextbin'; + } + if (isset($this->_packageInfo['bundle'])) { + return 'bundle'; + } + return false; + } + + /** + * @return array|false + */ + function getReleases() + { + $type = $this->getPackageType(); + if ($type != 'bundle') { + $type .= 'release'; + } + if ($this->getPackageType() && isset($this->_packageInfo[$type])) { + return $this->_packageInfo[$type]; + } + return false; + } + + /** + * @return array + */ + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function hasDeps() + { + return isset($this->_packageInfo['dependencies']); + } + + function getPackagexmlVersion() + { + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return '2.1'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return '2.1'; + } + return '2.0'; + } + + /** + * @return array|false + */ + function getSourcePackage() + { + if (isset($this->_packageInfo['extbinrelease']) || + isset($this->_packageInfo['zendextbinrelease'])) { + return array('channel' => $this->_packageInfo['srcchannel'], + 'package' => $this->_packageInfo['srcpackage']); + } + return false; + } + + function getBundledPackages() + { + if (isset($this->_packageInfo['bundle'])) { + return $this->_packageInfo['contents']['bundledpackage']; + } + return false; + } + + function getLastModified() + { + if (isset($this->_packageInfo['_lastmodified'])) { + return $this->_packageInfo['_lastmodified']; + } + return false; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + function &getRW() + { + if (!class_exists('PEAR_PackageFile_v2_rw')) { + require_once 'PEAR/PackageFile/v2/rw.php'; + } + $a = new PEAR_PackageFile_v2_rw; + foreach (get_object_vars($this) as $name => $unused) { + if (!isset($this->$name)) { + continue; + } + if ($name == '_config' || $name == '_logger'|| $name == '_registry' || + $name == '_stack') { + $a->$name = &$this->$name; + } else { + $a->$name = $this->$name; + } + } + return $a; + } + + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v2')) { + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + $a = &new PEAR_PackageFile_Generator_v2($this); + return $a; + } + + function analyzeSourceCode($file, $string = false) + { + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + return $this->_v2Validator->analyzeSourceCode($file, $string); + } + + function validate($state = PEAR_VALIDATE_NORMAL) + { + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + if (isset($this->_packageInfo['xsdversion'])) { + unset($this->_packageInfo['xsdversion']); + } + return $this->_v2Validator->validate($this, $state); + } + + function getTasksNs() + { + if (!isset($this->_tasksNs)) { + if (isset($this->_packageInfo['attribs'])) { + foreach ($this->_packageInfo['attribs'] as $name => $value) { + if ($value == 'http://pear.php.net/dtd/tasks-1.0') { + $this->_tasksNs = str_replace('xmlns:', '', $name); + break; + } + } + } + } + return $this->_tasksNs; + } + + /** + * Determine whether a task name is a valid task. Custom tasks may be defined + * using subdirectories by putting a "-" in the name, as in + * + * Note that this method will auto-load the task class file and test for the existence + * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class + * PEAR_Task_mycustom_task + * @param string + * @return boolean + */ + function getTask($task) + { + $this->getTasksNs(); + // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace + $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task); + $taskfile = str_replace(' ', '/', ucwords($task)); + $task = str_replace(array(' ', '/'), '_', ucwords($task)); + if (class_exists("PEAR_Task_$task")) { + return "PEAR_Task_$task"; + } + $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true); + if ($fp) { + fclose($fp); + require_once "PEAR/Task/$taskfile.php"; + return "PEAR_Task_$task"; + } + return false; + } + + /** + * Key-friendly array_splice + * @param tagname to splice a value in before + * @param mixed the value to splice in + * @param string the new tag name + */ + function _ksplice($array, $key, $value, $newkey) + { + $offset = array_search($key, array_keys($array)); + $after = array_slice($array, $offset); + $before = array_slice($array, 0, $offset); + $before[$newkey] = $value; + return array_merge($before, $after); + } + + /** + * @param array a list of possible keys, in the order they may occur + * @param mixed contents of the new package.xml tag + * @param string tag name + * @access private + */ + function _insertBefore($array, $keys, $contents, $newkey) + { + foreach ($keys as $key) { + if (isset($array[$key])) { + return $array = $this->_ksplice($array, $key, $contents, $newkey); + } + } + $array[$newkey] = $contents; + return $array; + } + + /** + * @param subsection of {@link $_packageInfo} + * @param array|string tag contents + * @param array format: + *
+     * array(
+     *   tagname => array(list of tag names that follow this one),
+     *   childtagname => array(list of child tag names that follow this one),
+     * )
+     * 
+ * + * This allows construction of nested tags + * @access private + */ + function _mergeTag($manip, $contents, $order) + { + if (count($order)) { + foreach ($order as $tag => $curorder) { + if (!isset($manip[$tag])) { + // ensure that the tag is set up + $manip = $this->_insertBefore($manip, $curorder, array(), $tag); + } + if (count($order) > 1) { + $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1)); + return $manip; + } + } + } else { + return $manip; + } + if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) { + $manip[$tag][] = $contents; + } else { + if (!count($manip[$tag])) { + $manip[$tag] = $contents; + } else { + $manip[$tag] = array($manip[$tag]); + $manip[$tag][] = $contents; + } + } + return $manip; + } +} +?> diff --git a/3rdparty/PEAR/PackageFile/v2/Validator.php b/3rdparty/PEAR/PackageFile/v2/Validator.php new file mode 100644 index 0000000000..33c8eee387 --- /dev/null +++ b/3rdparty/PEAR/PackageFile/v2/Validator.php @@ -0,0 +1,2154 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validator.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its + * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + * @access private + */ +class PEAR_PackageFile_v2_Validator +{ + /** + * @var array + */ + var $_packageInfo; + /** + * @var PEAR_PackageFile_v2 + */ + var $_pf; + /** + * @var PEAR_ErrorStack + */ + var $_stack; + /** + * @var int + */ + var $_isValid = 0; + /** + * @var int + */ + var $_filesValid = 0; + /** + * @var int + */ + var $_curState = 0; + /** + * @param PEAR_PackageFile_v2 + * @param int + */ + function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) + { + $this->_pf = &$pf; + $this->_curState = $state; + $this->_packageInfo = $this->_pf->getArray(); + $this->_isValid = $this->_pf->_isValid; + $this->_filesValid = $this->_pf->_filesValid; + $this->_stack = &$pf->_stack; + $this->_stack->getErrors(true); + if (($this->_isValid & $state) == $state) { + return true; + } + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_packageInfo['attribs']['version']) || + ($this->_packageInfo['attribs']['version'] != '2.0' && + $this->_packageInfo['attribs']['version'] != '2.1') + ) { + $this->_noPackageVersion(); + } + $structure = + array( + 'name', + 'channel|uri', + '*extends', // can't be multiple, but this works fine + 'summary', + 'description', + '+lead', // these all need content checks + '*developer', + '*contributor', + '*helper', + 'date', + '*time', + 'version', + 'stability', + 'license->?uri->?filesource', + 'notes', + 'contents', //special validation needed + '*compatible', + 'dependencies', //special validation needed + '*usesrole', + '*usestask', // reserve these for 1.4.0a1 to implement + // this will allow a package.xml to gracefully say it + // needs a certain package installed in order to implement a role or task + '*providesextension', + '*srcpackage|*srcuri', + '+phprelease|+extsrcrelease|+extbinrelease|' . + '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed + '*changelog', + ); + $test = $this->_packageInfo; + if (isset($test['dependencies']) && + isset($test['dependencies']['required']) && + isset($test['dependencies']['required']['pearinstaller']) && + isset($test['dependencies']['required']['pearinstaller']['min']) && + version_compare('1.9.4', + $test['dependencies']['required']['pearinstaller']['min'], '<') + ) { + $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); + return false; + } + // ignore post-installation array fields + if (array_key_exists('filelist', $test)) { + unset($test['filelist']); + } + if (array_key_exists('_lastmodified', $test)) { + unset($test['_lastmodified']); + } + if (array_key_exists('#binarypackage', $test)) { + unset($test['#binarypackage']); + } + if (array_key_exists('old', $test)) { + unset($test['old']); + } + if (array_key_exists('_lastversion', $test)) { + unset($test['_lastversion']); + } + if (!$this->_stupidSchemaValidate($structure, $test, '')) { + return false; + } + if (empty($this->_packageInfo['name'])) { + $this->_tagCannotBeEmpty('name'); + } + $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel'; + if (empty($this->_packageInfo[$test])) { + $this->_tagCannotBeEmpty($test); + } + if (is_array($this->_packageInfo['license']) && + (!isset($this->_packageInfo['license']['_content']) || + empty($this->_packageInfo['license']['_content']))) { + $this->_tagCannotBeEmpty('license'); + } elseif (empty($this->_packageInfo['license'])) { + $this->_tagCannotBeEmpty('license'); + } + if (empty($this->_packageInfo['summary'])) { + $this->_tagCannotBeEmpty('summary'); + } + if (empty($this->_packageInfo['description'])) { + $this->_tagCannotBeEmpty('description'); + } + if (empty($this->_packageInfo['date'])) { + $this->_tagCannotBeEmpty('date'); + } + if (empty($this->_packageInfo['notes'])) { + $this->_tagCannotBeEmpty('notes'); + } + if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) { + $this->_tagCannotBeEmpty('time'); + } + if (isset($this->_packageInfo['dependencies'])) { + $this->_validateDependencies(); + } + if (isset($this->_packageInfo['compatible'])) { + $this->_validateCompatible(); + } + if (!isset($this->_packageInfo['bundle'])) { + if (empty($this->_packageInfo['contents'])) { + $this->_tagCannotBeEmpty('contents'); + } + if (!isset($this->_packageInfo['contents']['dir'])) { + $this->_filelistMustContainDir('contents'); + return false; + } + if (isset($this->_packageInfo['contents']['file'])) { + $this->_filelistCannotContainFile('contents'); + return false; + } + } + $this->_validateMaintainers(); + $this->_validateStabilityVersion(); + $fail = false; + if (array_key_exists('usesrole', $this->_packageInfo)) { + $roles = $this->_packageInfo['usesrole']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['role'])) { + $this->_usesroletaskMustHaveRoleTask('usesrole', 'role'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['role'], 'usesrole'); + $fail = true; + } + } + } + } + if (array_key_exists('usestask', $this->_packageInfo)) { + $roles = $this->_packageInfo['usestask']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['task'])) { + $this->_usesroletaskMustHaveRoleTask('usestask', 'task'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['task'], 'usestask'); + $fail = true; + } + } + } + } + + if ($fail) { + return false; + } + + $list = $this->_packageInfo['contents']; + if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) { + $this->_multipleToplevelDirNotAllowed(); + return $this->_isValid = 0; + } + + $this->_validateFilelist(); + $this->_validateRelease(); + if (!$this->_stack->hasErrors()) { + $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true); + if (PEAR::isError($chan)) { + $this->_unknownChannel($this->_pf->getChannel()); + } else { + $valpack = $chan->getValidationPackage(); + // for channel validator packages, always use the default PEAR validator. + // otherwise, they can't be installed or packaged + $validator = $chan->getValidationObject($this->_pf->getPackage()); + if (!$validator) { + $this->_stack->push(__FUNCTION__, 'error', + array('channel' => $chan->getName(), + 'package' => $this->_pf->getPackage(), + 'name' => $valpack['_content'], + 'version' => $valpack['attribs']['version']), + 'package "%channel%/%package%" cannot be properly validated without ' . + 'validation package "%channel%/%name%-%version%"'); + return $this->_isValid = 0; + } + $validator->setPackageFile($this->_pf); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_stack->push(__FUNCTION__, 'error', $error, + 'Channel validator error: field "%field%" - %reason%'); + } + foreach ($failures['warnings'] as $warning) { + $this->_stack->push(__FUNCTION__, 'warning', $warning, + 'Channel validator warning: field "%field%" - %reason%'); + } + } + } + + $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error'); + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) { + if ($this->_pf->getPackageType() == 'bundle') { + if ($this->_analyzeBundledPackages()) { + $this->_filesValid = $this->_pf->_filesValid = true; + } else { + $this->_pf->_isValid = $this->_isValid = 0; + } + } else { + if (!$this->_analyzePhpFiles()) { + $this->_pf->_isValid = $this->_isValid = 0; + } else { + $this->_filesValid = $this->_pf->_filesValid = true; + } + } + } + + if ($this->_isValid) { + return $this->_pf->_isValid = $this->_isValid = $state; + } + + return $this->_pf->_isValid = $this->_isValid = 0; + } + + function _stupidSchemaValidate($structure, $xml, $root) + { + if (!is_array($xml)) { + $xml = array(); + } + $keys = array_keys($xml); + reset($keys); + $key = current($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $ret = true; + $mismatch = false; + foreach ($structure as $struc) { + if ($key) { + $tag = $xml[$key]; + } + $test = $this->_processStructure($struc); + if (isset($test['choices'])) { + $loose = true; + foreach ($test['choices'] as $choice) { + if ($key == $choice['tag']) { + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $mismatch = false; + if ($key && $key != $choice['tag'] && isset($choice['multiple'])) { + $unfoundtags[] = $choice['tag']; + $optionaltags[] = $choice['tag']; + if ($key) { + $mismatch = true; + } + } + $ret &= $this->_processAttribs($choice, $tag, $root); + continue 2; + } else { + $unfoundtags[] = $choice['tag']; + $mismatch = true; + } + if (!isset($choice['multiple']) || $choice['multiple'] != '*') { + $loose = false; + } else { + $optionaltags[] = $choice['tag']; + } + } + if (!$loose) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + } else { + if ($key != $test['tag']) { + if (isset($test['multiple']) && $test['multiple'] != '*') { + $unfoundtags[] = $test['tag']; + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } else { + if ($key) { + $mismatch = true; + } + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + } + if (!isset($test['multiple'])) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + continue; + } else { + $unfoundtags = $optionaltags = array(); + $mismatch = false; + } + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + if ($key && $key != $test['tag'] && isset($test['multiple'])) { + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + $mismatch = true; + } + $ret &= $this->_processAttribs($test, $tag, $root); + continue; + } + } + if (!$mismatch && count($optionaltags)) { + // don't error out on any optional tags + $unfoundtags = array_diff($unfoundtags, $optionaltags); + } + if (count($unfoundtags)) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + } elseif ($key) { + // unknown tags + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + while ($key = next($keys)) { + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + } + } + return $ret; + } + + function _processAttribs($choice, $tag, $context) + { + if (isset($choice['attribs'])) { + if (!is_array($tag)) { + $tag = array($tag); + } + $tags = $tag; + if (!isset($tags[0])) { + $tags = array($tags); + } + $ret = true; + foreach ($tags as $i => $tag) { + if (!is_array($tag) || !isset($tag['attribs'])) { + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + $ret &= $this->_tagHasNoAttribs($choice['tag'], + $context); + continue 2; + } + } + } + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + if (!isset($tag['attribs'][$attrib])) { + $ret &= $this->_tagMissingAttribute($choice['tag'], + $attrib, $context); + } + } + } + } + return $ret; + } + return true; + } + + function _processStructure($key) + { + $ret = array(); + if (count($pieces = explode('|', $key)) > 1) { + $ret['choices'] = array(); + foreach ($pieces as $piece) { + $ret['choices'][] = $this->_processStructure($piece); + } + return $ret; + } + $multi = $key{0}; + if ($multi == '+' || $multi == '*') { + $ret['multiple'] = $key{0}; + $key = substr($key, 1); + } + if (count($attrs = explode('->', $key)) > 1) { + $ret['tag'] = array_shift($attrs); + $ret['attribs'] = $attrs; + } else { + $ret['tag'] = $key; + } + return $ret; + } + + function _validateStabilityVersion() + { + $structure = array('release', 'api'); + $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], ''); + $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], ''); + if ($a) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['release'])) { + $this->_invalidVersion('release', $this->_packageInfo['version']['release']); + } + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['api'])) { + $this->_invalidVersion('api', $this->_packageInfo['version']['api']); + } + if (!in_array($this->_packageInfo['stability']['release'], + array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('release', $this->_packageInfo['stability']['release']); + } + if (!in_array($this->_packageInfo['stability']['api'], + array('devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('api', $this->_packageInfo['stability']['api']); + } + } + } + + function _validateMaintainers() + { + $structure = + array( + 'name', + 'user', + 'email', + 'active', + ); + foreach (array('lead', 'developer', 'contributor', 'helper') as $type) { + if (!isset($this->_packageInfo[$type])) { + continue; + } + if (isset($this->_packageInfo[$type][0])) { + foreach ($this->_packageInfo[$type] as $lead) { + $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>'); + } + } else { + $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type], + '<' . $type . '>'); + } + } + } + + function _validatePhpDep($dep, $installcondition = false) + { + $structure = array( + 'min', + '*max', + '*exclude', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['min'])) { + $this->_invalidVersion($type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['max'])) { + $this->_invalidVersion($type . '', $dep['max']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match( + '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $exclude)) { + $this->_invalidVersion($type . '', $exclude); + } + } + } + } + + function _validatePearinstallerDep($dep) + { + $structure = array( + 'min', + '*max', + '*recommended', + '*exclude', + ); + $this->_stupidSchemaValidate($structure, $dep, ''); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('', + $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('', + $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('', + $exclude); + } + } + } + } + + function _validatePackageDep($dep, $group, $type = '') + { + if (isset($dep['uri'])) { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'uri', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'uri', + '*providesextension', + ); + } + } else { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*exclude', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*recommended', + '*exclude', + '*nodefault', + '*providesextension', + ); + } + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, '' . $group . $type); + if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) || + isset($dep['recommended']) || isset($dep['exclude']))) { + $this->_uriDepsCannotHaveVersioning('' . $group . $type); + } + if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') { + $this->_DepchannelCannotBeUri('' . $group . $type); + } + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('' . $group . $type . '', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('' . $group . $type . '', + $exclude); + } + } + } + } + + function _validateSubpackageDep($dep, $group) + { + $this->_validatePackageDep($dep, $group, ''); + if (isset($dep['providesextension'])) { + $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : ''); + } + if (isset($dep['conflicts'])) { + $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : ''); + } + } + + function _validateExtensionDep($dep, $group = false, $installcondition = false) + { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + '*min', + '*max', + '*exclude', + 'conflicts', + ); + } else { + $structure = array( + 'name', + '*min', + '*max', + '*recommended', + '*exclude', + ); + } + if ($installcondition) { + $type = ''; + } else { + $type = '' . $group . ''; + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '' : ''; + if ($this->_stupidSchemaValidate($structure, $dep, $type)) { + if ($dep['name'] == '*') { + if (array_key_exists('conflicts', $dep)) { + $this->_cannotConflictWithAllOs($type); + } + } + } + } + + function _validateArchDep($dep, $installcondition = false) + { + $structure = array( + 'pattern', + '*conflicts', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + } + + function _validateInstallConditions($cond, $release) + { + $structure = array( + '*php', + '*extension', + '*os', + '*arch', + ); + if (!$this->_stupidSchemaValidate($structure, + $cond, $release)) { + return false; + } + foreach (array('php', 'extension', 'os', 'arch') as $type) { + if (isset($cond[$type])) { + $iter = $cond[$type]; + if (!is_array($iter) || !isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type == 'extension') { + $this->{"_validate{$type}Dep"}($package, false, true); + } else { + $this->{"_validate{$type}Dep"}($package, true); + } + } + } + } + } + + function _validateDependencies() + { + $structure = array( + 'required', + '*optional', + '*group->name->hint' + ); + if (!$this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'], '')) { + return false; + } + foreach (array('required', 'optional') as $simpledep) { + if (isset($this->_packageInfo['dependencies'][$simpledep])) { + if ($simpledep == 'optional') { + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + } else { + $structure = array( + 'php', + 'pearinstaller', + '*package', + '*subpackage', + '*extension', + '*os', + '*arch', + ); + } + if ($this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'][$simpledep], + "<$simpledep>")) { + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannel($type, + $package['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannel($type, $package['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, "<$simpledep>"); + } + } + } + if ($simpledep == 'optional') { + continue; + } + foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + $this->{"_validate{$type}Dep"}($package); + } + } + } + } + } + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + foreach ($groups as $group) { + if ($this->_stupidSchemaValidate($structure, $group, '')) { + if (!PEAR_Validate::validGroupName($group['attribs']['name'])) { + $this->_invalidDepGroupName($group['attribs']['name']); + } + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($group[$type])) { + $iter = $group[$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannelGroup($type, + $package['name'], + $group['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannelGroup($type, + $package['name'], + $group['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, ''); + } + } + } + } + } + } + } + + function _validateCompatible() + { + $compat = $this->_packageInfo['compatible']; + if (!isset($compat[0])) { + $compat = array($compat); + } + $required = array('name', 'channel', 'min', 'max', '*exclude'); + foreach ($compat as $package) { + $type = ''; + if (is_array($package) && array_key_exists('name', $package)) { + $type .= '' . $package['name'] . ''; + } + $this->_stupidSchemaValidate($required, $package, $type); + if (is_array($package) && array_key_exists('min', $package)) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $package['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_NoBundledPackages(); + } + if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) { + return $this->_AtLeast2BundledPackages(); + } + foreach ($list['bundledpackage'] as $package) { + if (!is_string($package)) { + $this->_bundledPackagesMustBeFilename(); + } + } + } + + function _validateFilelist($list = false, $allowignore = false, $dirs = '') + { + $iscontents = false; + if (!$list) { + $iscontents = true; + $list = $this->_packageInfo['contents']; + if (isset($this->_packageInfo['bundle'])) { + return $this->_validateBundle($list); + } + } + if ($allowignore) { + $struc = array( + '*install->name->as', + '*ignore->name' + ); + } else { + $struc = array( + '*dir->name->?baseinstalldir', + '*file->name->role->?baseinstalldir->?md5sum' + ); + if (isset($list['dir']) && isset($list['file'])) { + // stave off validation errors without requiring a set order. + $_old = $list; + if (isset($list['attribs'])) { + $list = array('attribs' => $_old['attribs']); + } + $list['dir'] = $_old['dir']; + $list['file'] = $_old['file']; + } + } + if (!isset($list['attribs']) || !isset($list['attribs']['name'])) { + $unknown = $allowignore ? '' : ''; + $dirname = $iscontents ? '' : $unknown; + } else { + $dirname = ''; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $list['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidDirName($list['attribs']['name']); + } + } + $res = $this->_stupidSchemaValidate($struc, $list, $dirname); + if ($allowignore && $res) { + $ignored_or_installed = array(); + $this->_pf->getFilelist(); + $fcontents = $this->_pf->getContents(); + $filelist = array(); + if (!isset($fcontents['dir']['file'][0])) { + $fcontents['dir']['file'] = array($fcontents['dir']['file']); + } + foreach ($fcontents['dir']['file'] as $file) { + $filelist[$file['attribs']['name']] = true; + } + if (isset($list['install'])) { + if (!isset($list['install'][0])) { + $list['install'] = array($list['install']); + } + foreach ($list['install'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'install'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_multipleInstallAs($file['attribs']['name']); + } + if (!isset($ignored_or_installed[$file['attribs']['name']])) { + $ignored_or_installed[$file['attribs']['name']] = array(); + } + $ignored_or_installed[$file['attribs']['name']][] = 1; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['as']))) { + // file contains .. parent directory or . cur directory references + $this->_invalidFileInstallAs($file['attribs']['name'], + $file['attribs']['as']); + } + } + } + if (isset($list['ignore'])) { + if (!isset($list['ignore'][0])) { + $list['ignore'] = array($list['ignore']); + } + foreach ($list['ignore'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'ignore'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_ignoreAndInstallAs($file['attribs']['name']); + } + } + } + } + if (!$allowignore && isset($list['file'])) { + if (is_string($list['file'])) { + $this->_oldStyleFileNotAllowed(); + return false; + } + if (!isset($list['file'][0])) { + // single file + $list['file'] = array($list['file']); + } + foreach ($list['file'] as $i => $file) + { + if (isset($file['attribs']) && isset($file['attribs']['name'])) { + if ($file['attribs']['name']{0} == '.' && + $file['attribs']['name']{1} == '/') { + // name is something like "./doc/whatever.txt" + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + } + if (isset($file['attribs']) && isset($file['attribs']['role'])) { + if (!$this->_validateRole($file['attribs']['role'])) { + if (isset($this->_packageInfo['usesrole'])) { + $roles = $this->_packageInfo['usesrole']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['role'] = $file['attribs']['role']) { + $msg = 'This package contains role "%role%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('role' => $role['role'], + 'package' => $role['uri']); + } else { + $params = array('role' => $role['role'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallRole', 'error', $params, $msg); + } + } + } + $this->_invalidFileRole($file['attribs']['name'], + $dirname, $file['attribs']['role']); + } + } + if (!isset($file['attribs'])) { + continue; + } + $save = $file['attribs']; + if ($dirs) { + $save['name'] = $dirs . '/' . $save['name']; + } + unset($file['attribs']); + if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks + foreach ($file as $task => $value) { + if ($tagClass = $this->_pf->getTask($task)) { + if (!is_array($value) || !isset($value[0])) { + $value = array($value); + } + foreach ($value as $v) { + $ret = call_user_func(array($tagClass, 'validateXml'), + $this->_pf, $v, $this->_pf->_config, $save); + if (is_array($ret)) { + $this->_invalidTask($task, $ret, isset($save['name']) ? + $save['name'] : ''); + } + } + } else { + if (isset($this->_packageInfo['usestask'])) { + $roles = $this->_packageInfo['usestask']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['task'] = $task) { + $msg = 'This package contains task "%task%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('task' => $role['task'], + 'package' => $role['uri']); + } else { + $params = array('task' => $role['task'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallTask', 'error', + $params, $msg); + } + } + } + $this->_unknownTask($task, $save['name']); + } + } + } + } + } + if (isset($list['ignore'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('ignore'); + } + } + if (isset($list['install'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('install'); + } + } + if (isset($list['file'])) { + if ($allowignore) { + $this->_fileNotAllowed('file'); + } + } + if (isset($list['dir'])) { + if ($allowignore) { + $this->_fileNotAllowed('dir'); + } else { + if (!isset($list['dir'][0])) { + $list['dir'] = array($list['dir']); + } + foreach ($list['dir'] as $dir) { + if (isset($dir['attribs']) && isset($dir['attribs']['name'])) { + if ($dir['attribs']['name'] == '/' || + !isset($this->_packageInfo['contents']['dir']['dir'])) { + // always use nothing if the filelist has already been flattened + $newdirs = ''; + } elseif ($dirs == '') { + $newdirs = $dir['attribs']['name']; + } else { + $newdirs = $dirs . '/' . $dir['attribs']['name']; + } + } else { + $newdirs = $dirs; + } + $this->_validateFilelist($dir, $allowignore, $newdirs); + } + } + } + } + + function _validateRelease() + { + if (isset($this->_packageInfo['phprelease'])) { + $release = 'phprelease'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['phprelease']; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach (array('', 'zend') as $prefix) { + $releasetype = $prefix . 'extsrcrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*configureoption->name->prompt->?default', + '*binarypackage', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + if (isset($rel['binarypackage'])) { + if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) { + $rel['binarypackage'] = array($rel['binarypackage']); + } + foreach ($rel['binarypackage'] as $bin) { + if (!is_string($bin)) { + $this->_binaryPackageMustBePackagename(); + } + } + } + } + } + $releasetype = 'extbinrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['channel']) && + !isset($this->_packageInfo['srcpackage'])) { + $this->_mustSrcPackage($release); + } + if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) { + $this->_mustSrcuri($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + } + } + } + if (isset($this->_packageInfo['bundle'])) { + $release = 'bundle'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['bundle']; + if (!is_array($releases) || !isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach ($releases as $rel) { + if (is_array($rel) && array_key_exists('installconditions', $rel)) { + $this->_validateInstallConditions($rel['installconditions'], + "<$release>"); + } + if (is_array($rel) && array_key_exists('filelist', $rel)) { + if ($rel['filelist']) { + + $this->_validateFilelist($rel['filelist'], true); + } + } + } + } + + /** + * This is here to allow role extension through plugins + * @param string + */ + function _validateRole($role) + { + return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())); + } + + function _pearVersionTooLow($version) + { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $version), + 'This package.xml requires PEAR version %version% to parse properly, we are ' . + 'version 1.9.4'); + } + + function _invalidTagOrder($oktags, $actual, $root) + { + $this->_stack->push(__FUNCTION__, 'error', + array('oktags' => $oktags, 'actual' => $actual, 'root' => $root), + 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"'); + } + + function _ignoreNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside global , only inside ' . + '//, use and only'); + } + + function _fileNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside release , only inside ' . + ', use and only'); + } + + function _oldStyleFileNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Old-style name is not allowed. Use' . + ''); + } + + function _tagMissingAttribute($tag, $attr, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'attribute' => $attr, 'context' => $context), + 'tag <%tag%> in context "%context%" has no attribute "%attribute%"'); + } + + function _tagHasNoAttribs($tag, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'context' => $context), + 'tag <%tag%> has no attributes in context "%context%"'); + } + + function _invalidInternalStructure() + { + $this->_stack->push(__FUNCTION__, 'exception', array(), + 'internal array was not generated by compatible parser, or extreme parser error, cannot continue'); + } + + function _invalidFileRole($file, $dir, $role) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'dir' => $dir, 'role' => $role, + 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())), + 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%'); + } + + function _invalidFileName($file, $dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file), + 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _invalidFileInstallAs($file, $as) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'as' => $as), + 'File "%file%" cannot contain "./" or contain ".."'); + } + + function _invalidDirName($dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'dir' => $file), + 'Directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _filelistCannotContainFile($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> can only contain , contains . Use ' . + ' as the first dir element'); + } + + function _filelistMustContainDir($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> must contain . Use as the ' . + 'first dir element'); + } + + function _tagCannotBeEmpty($tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '<%tag%> cannot be empty (<%tag%/>)'); + } + + function _UrlOrChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _UrlOrChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _unknownChannel($channel) + { + $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel), + 'Unknown channel "%channel%"'); + } + + function _noPackageVersion() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'package.xml tag has no version attribute, or version is not 2.0'); + } + + function _NoBundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'No tag was found in , required for bundle packages'); + } + + function _AtLeast2BundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'At least 2 packages must be bundled in a bundle package'); + } + + function _ChannelOrUri($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Bundled package "%name%" can have either a uri or a channel, not both'); + } + + function _noChildTag($child, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag), + 'Tag <%tag%> is missing child tag <%child%>'); + } + + function _invalidVersion($type, $value) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value), + 'Version type <%type%> is not a valid version (%value%)'); + } + + function _invalidState($type, $value) + { + $states = array('stable', 'beta', 'alpha', 'devel'); + if ($type != 'api') { + $states[] = 'snapshot'; + } + if (strtolower($value) == 'rc') { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $this->_packageInfo['version']['release']), + 'RC is not a state, it is a version postfix, try %version%RC1, stability beta'); + } + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value, + 'types' => $states), + 'Stability type <%type%> is not a valid stability (%value%), must be one of ' . + '%types%'); + } + + function _invalidTask($task, $ret, $file) + { + switch ($ret[0]) { + case PEAR_TASK_ERROR_MISSING_ATTRIB : + $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%'; + break; + case PEAR_TASK_ERROR_NOATTRIBS : + $info = array('task' => $task, 'file' => $file); + $msg = 'task <%task%> has no attributes in file %file%'; + break; + case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE : + $info = array('attrib' => $ret[1], 'values' => $ret[3], + 'was' => $ret[2], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '. + 'in file %file%, expecting one of "%values%"'; + break; + case PEAR_TASK_ERROR_INVALID : + $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> in file %file% is invalid because of "%reason%"'; + break; + } + $this->_stack->push(__FUNCTION__, 'error', $info, $msg); + } + + function _unknownTask($task, $file) + { + $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file), + 'Unknown task "%task%" passed in file '); + } + + function _subpackageCannotProvideExtension($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _subpackagesCannotConflict($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _cannotProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot use , only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension'); + } + + function _mustProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages must use to indicate which PHP extension is provided'); + } + + function _cannotHaveSrcpackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot specify a source code package, only extension binaries may use the tag'); + } + + function _mustSrcPackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _mustSrcuri($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _uriDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: dependencies with a tag cannot have any versioning information'); + } + + function _conflictingDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: conflicting dependencies cannot have versioning info, use to ' . + 'exclude specific versions of a dependency'); + } + + function _DepchannelCannotBeUri($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' . + 'dependencies only'); + } + + function _bundledPackagesMustBeFilename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain only the filename of a package release ' . + 'in the bundle'); + } + + function _binaryPackageMustBePackagename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain the name of a package that is ' . + 'a compiled version of this extsrc/zendextsrc package'); + } + + function _fileNotFound($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'File "%file%" in package.xml does not exist'); + } + + function _notInContents($file, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag), + '<%tag% name="%file%"> is invalid, file is not in '); + } + + function _cannotValidateNoPathSet() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Cannot validate files, no path to package file is set (use setPackageFile())'); + } + + function _usesroletaskMustHaveChannelOrUri($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain either , or and '); + } + + function _usesroletaskMustHavePackage($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain '); + } + + function _usesroletaskMustHaveRoleTask($tag, $type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type), + '<%tag%> must contain <%type%> defining the %type% to be used'); + } + + function _cannotConflictWithAllOs($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '%tag% cannot conflict with all OSes'); + } + + function _invalidDepGroupName($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Invalid dependency group name "%name%"'); + } + + function _multipleToplevelDirNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Multiple top-level tags are not allowed. Enclose them ' . + 'in a '); + } + + function _multipleInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Only one tag is allowed for file "%file%"'); + } + + function _ignoreAndInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Cannot have both and tags for file "%file%"'); + } + + function _analyzeBundledPackages() + { + if (!$this->_isValid) { + return false; + } + if (!$this->_pf->getPackageType() == 'bundle') { + return false; + } + if (!isset($this->_pf->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array($common, 'log'); + $info = $this->_pf->getContents(); + $info = $info['bundledpackage']; + if (!is_array($info)) { + $info = array($info); + } + $pkg = &new PEAR_PackageFile($this->_pf->_config); + foreach ($info as $package) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { + $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); + $this->_isValid = 0; + continue; + } + call_user_func_array($log, array(1, "Analyzing bundled package $package")); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package, + PEAR_VALIDATE_NORMAL); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + call_user_func_array($log, array(0, "ERROR: package $package is not a valid " . + 'package')); + $inf = $ret->getUserInfo(); + if (is_array($inf)) { + foreach ($inf as $err) { + call_user_func_array($log, array(1, $err['message'])); + } + } + return false; + } + } + return true; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_pf->_packageFile)) { + $this->_cannotValidateNoPathSet(); + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array(&$common, 'log'); + $info = $this->_pf->getContents(); + if (!$info || !isset($info['dir']['file'])) { + $this->_tagCannotBeEmpty('contents>_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file); + $this->_isValid = 0; + continue; + } + if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo)); + } + } + } + $this->_packageName = $pn = $this->_pf->getPackage(); + $pnl = strlen($pn); + foreach ($provides as $key => $what) { + if (isset($what['explicit']) || !$what) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } + } + return $this->_isValid; + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @param boolean whether to analyze $file as the file contents + * @return mixed + */ + function analyzeSourceCode($file, $string = false) + { + if (!function_exists("token_get_all")) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer'); + return false; + } + + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + + if ($string) { + $contents = $file; + } else { + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + } + + // Silence this function so we can catch PHP Warnings and show our own custom message + $tokens = @token_get_all($contents); + if (isset($php_errormsg)) { + if (isset($this->_stack)) { + $pn = $this->_pf->getPackage(); + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'package' => $pn), + 'in %file%: Could not process file for unkown reasons,' . + ' possibly a PHP parse error in %file% from %package%'); + } + } +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + ) + ) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array( + 'file' => $file), + 'Error, PHP5 token encountered in %file%,' . + ' analysis should be in PHP5'); + } else { + PEAR::raiseError('Error: PHP5 token encountered in ' . $file . + 'packaging should be done in PHP 5'); + return false; + } + } + } + + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + $token = $tokens[$i - 1][0]; + if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + + continue 2; + } + } + + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return array(); + } + + $providesret = array(); + $file = basename($srcinfo['source_file']); + $pn = isset($this->_pf) ? $this->_pf->getPackage() : ''; + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $providesret[$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($providesret[$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + + return $providesret; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/PackageFile/v2/rw.php b/3rdparty/PEAR/PackageFile/v2/rw.php new file mode 100644 index 0000000000..58f76c5594 --- /dev/null +++ b/3rdparty/PEAR/PackageFile/v2/rw.php @@ -0,0 +1,1604 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * For base class + */ +require_once 'PEAR/PackageFile/v2.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + */ +class PEAR_PackageFile_v2_rw extends PEAR_PackageFile_v2 +{ + /** + * @param string Extension name + * @return bool success of operation + */ + function setProvidesExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (!isset($this->_packageInfo['providesextension'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $extension, 'providesextension'); + } + $this->_packageInfo['providesextension'] = $extension; + return true; + } + return false; + } + + function setPackage($package) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.0 + http://pear.php.net/dtd/package-2.0.xsd', + )), $this->_packageInfo); + } + if (!isset($this->_packageInfo['name'])) { + return $this->_packageInfo = array_merge(array('name' => $package), + $this->_packageInfo); + } + $this->_packageInfo['name'] = $package; + } + + /** + * set this as a package.xml version 2.1 + * @access private + */ + function _setPackageVersion2_1() + { + $info = array( + 'version' => '2.1', + 'xmlns' => 'http://pear.php.net/dtd/package-2.1', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.1 + http://pear.php.net/dtd/package-2.1.xsd', + ); + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => $info), $this->_packageInfo); + } else { + $this->_packageInfo['attribs'] = $info; + } + } + + function setUri($uri) + { + unset($this->_packageInfo['channel']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['uri'])) { + // ensure that the uri tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $uri, 'uri'); + } + $this->_packageInfo['uri'] = $uri; + } + + function setChannel($channel) + { + unset($this->_packageInfo['uri']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['channel'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $channel, 'channel'); + } + $this->_packageInfo['channel'] = $channel; + } + + function setExtends($extends) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['extends'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $extends, 'extends'); + } + $this->_packageInfo['extends'] = $extends; + } + + function setSummary($summary) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['summary'])) { + // ensure that the summary tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $summary, 'summary'); + } + $this->_packageInfo['summary'] = $summary; + } + + function setDescription($desc) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['description'])) { + // ensure that the description tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $desc, 'description'); + } + $this->_packageInfo['description'] = $desc; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email, $active = 'yes') + { + if (!in_array($role, array('lead', 'developer', 'contributor', 'helper'))) { + return false; + } + if (isset($this->_packageInfo[$role])) { + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + $this->_packageInfo[$role][] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } else { + $testarr = array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'); + foreach (array('lead', 'developer', 'contributor', 'helper') as $testrole) { + array_shift($testarr); + if ($role == $testrole) { + break; + } + } + if (!isset($this->_packageInfo[$role])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, $testarr, + array(), $role); + } + $this->_packageInfo[$role] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } + $this->_isValid = 0; + } + + function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes') + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + $info = $this->_packageInfo[$role]; + if (!isset($info[0])) { + if ($info['user'] == $handle) { + $found = true; + break; + } + } + foreach ($info as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break 2; + } + } + } + if ($found === false) { + return $this->addMaintainer($newrole, $handle, $name, $email, $active); + } + if ($found !== false) { + if ($found === true) { + unset($this->_packageInfo[$role]); + } else { + unset($this->_packageInfo[$role][$found]); + $this->_packageInfo[$role] = array_values($this->_packageInfo[$role]); + } + } + $this->addMaintainer($newrole, $handle, $name, $email, $active); + $this->_isValid = 0; + } + + function deleteMaintainer($handle) + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + foreach ($this->_packageInfo[$role] as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo[$role][$found]); + if (!count($this->_packageInfo[$role]) && $role == 'lead') { + $this->_isValid = 0; + } + if (!count($this->_packageInfo[$role])) { + unset($this->_packageInfo[$role]); + return true; + } + $this->_packageInfo[$role] = + array_values($this->_packageInfo[$role]); + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + return true; + } + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + } + return false; + } + + function setReleaseVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['release'])) { + unset($this->_packageInfo['version']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + function setAPIVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['api'])) { + unset($this->_packageInfo['version']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + /** + * snapshot|devel|alpha|beta|stable + */ + function setReleaseStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['release'])) { + unset($this->_packageInfo['stability']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + /** + * @param devel|alpha|beta|stable + */ + function setAPIStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['api'])) { + unset($this->_packageInfo['stability']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + function setLicense($license, $uri = false, $filesource = false) + { + if (!isset($this->_packageInfo['license'])) { + // ensure that the license tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), 0, 'license'); + } + if ($uri || $filesource) { + $attribs = array(); + if ($uri) { + $attribs['uri'] = $uri; + } + $uri = true; // for test below + if ($filesource) { + $attribs['filesource'] = $filesource; + } + } + $license = $uri ? array('attribs' => $attribs, '_content' => $license) : $license; + $this->_packageInfo['license'] = $license; + $this->_isValid = 0; + } + + function setNotes($notes) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['notes'])) { + // ensure that the notes tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $notes, 'notes'); + } + $this->_packageInfo['notes'] = $notes; + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Reset the listing of package contents + * @param string base installation dir for the whole package, if any + */ + function clearContents($baseinstall = false) + { + $this->_filesValid = false; + $this->_isValid = 0; + if (!isset($this->_packageInfo['contents'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if ($this->getPackageType() != 'bundle') { + $this->_packageInfo['contents'] = + array('dir' => array('attribs' => array('name' => '/'))); + if ($baseinstall) { + $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'] = $baseinstall; + } + } else { + $this->_packageInfo['contents'] = array('bundledpackage' => array()); + } + } + + /** + * @param string relative path of the bundled package. + */ + function addBundledPackage($path) + { + if ($this->getPackageType() != 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $path, array( + 'contents' => array('compatible', 'dependencies', 'providesextension', + 'usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + 'bundledpackage' => array())); + } + + /** + * @param string file name + * @param PEAR_Task_Common a read/write task + */ + function addTaskToFile($filename, $task) + { + if (!method_exists($task, 'getXml')) { + return false; + } + if (!method_exists($task, 'getName')) { + return false; + } + if (!method_exists($task, 'validate')) { + return false; + } + if (!$task->validate()) { + return false; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $this->getTasksNs(); // discover the tasks namespace if not done already + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $t = isset($this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? + $this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'][$i] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$i][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } else { + $t = isset($this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? $this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } + return true; + } + } + } + return false; + } + + /** + * @param string path to the file + * @param string filename + * @param array extra attributes + */ + function addFile($dir, $file, $attrs) + { + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $attrs['name'] = $dir . $file; + if (!isset($this->_packageInfo['contents'])) { + // ensure that the contents tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if (isset($this->_packageInfo['contents']['dir']['file'])) { + if (!isset($this->_packageInfo['contents']['dir']['file'][0])) { + $this->_packageInfo['contents']['dir']['file'] = + array($this->_packageInfo['contents']['dir']['file']); + } + $this->_packageInfo['contents']['dir']['file'][]['attribs'] = $attrs; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'] = $attrs; + } + } + + /** + * @param string Dependent package name + * @param string Dependent package's channel name + * @param string minimum version of specified package that this release is guaranteed to be + * compatible with + * @param string maximum version of specified package that this release is guaranteed to be + * compatible with + * @param string versions of specified package that this release is not compatible with + */ + function addCompatiblePackage($name, $channel, $min, $max, $exclude = false) + { + $this->_isValid = 0; + $set = array( + 'name' => $name, + 'channel' => $channel, + 'min' => $min, + 'max' => $max, + ); + if ($exclude) { + $set['exclude'] = $exclude; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'compatible' => array('dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the tag entirely + */ + function resetUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + unset($this->_packageInfo['usesrole']); + } + } + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsesrole($role, $packageOrUri, $channel = false) { + $set = array('role' => $role); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usesrole' => array('usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the tag entirely + */ + function resetUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + unset($this->_packageInfo['usestask']); + } + } + + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsestask($task, $packageOrUri, $channel = false) { + $set = array('task' => $task); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usestask' => array('srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Remove all compatible tags + */ + function clearCompatible() + { + unset($this->_packageInfo['compatible']); + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + if (!isset($this->_packageInfo['dependencies'])) { + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'))); + } + $this->_packageInfo['dependencies'] = array(); + } + + /** + * @param string minimum PHP version allowed + * @param string maximum PHP version allowed + * @param array $exclude incompatible PHP versions + */ + function setPhpDep($min, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['php'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['php']), + 'warning: PHP dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['php']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'php' => array('pearinstaller', 'package', 'subpackage', 'extension', 'os', 'arch') + )); + return true; + } + + /** + * @param string minimum allowed PEAR installer version + * @param string maximum allowed PEAR installer version + * @param string recommended PEAR installer version + * @param array incompatible version of the PEAR installer + */ + function setPearinstallerDep($min, $max = false, $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['pearinstaller'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['pearinstaller']), + 'warning: PEAR Installer dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['pearinstaller']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'pearinstaller' => array('package', 'subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param array|false versions to exclude from installation + */ + function addConflictingPackageDepWithChannel($name, $channel, + $providesextension = false, $min = false, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, $channel, false, $min, $max, false, + $exclude, $providesextension, false, true); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + */ + function addConflictingPackageDepWithUri($name, $uri, $providesextension = false) + { + $this->_isValid = 0; + $dep = + array( + 'name' => $name, + 'uri' => $uri, + 'conflicts' => '', + ); + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + function addDependencyGroup($name, $hint) + { + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, + array('attribs' => array('name' => $name, 'hint' => $hint)), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'group' => array(), + )); + } + + /** + * @param string package name + * @param string|false channel name, false if this is a uri + * @param string|false uri name, false if this is a channel + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param string|false recommended installation version + * @param array|false versions to exclude from installation + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param bool if true, tells the installer to negate this dependency (conflicts) + * @return array + * @access private + */ + function _constructDep($name, $channel, $uri, $min, $max, $recommended, $exclude, + $providesextension = false, $nodefault = false, + $conflicts = false) + { + $dep = + array( + 'name' => $name, + ); + if ($channel) { + $dep['channel'] = $channel; + } elseif ($uri) { + $dep['uri'] = $uri; + } + if ($min) { + $dep['min'] = $min; + } + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($nodefault) { + $dep['nodefault'] = ''; + } + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + return $dep; + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array|false optional excluded versions + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()}, or a subpackage is added with + * a providesextension + */ + function addGroupPackageDepWithChannel($type, $groupname, $name, $channel, $min = false, + $max = false, $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package uri + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()} + */ + function addGroupPackageDepWithURI($type, $groupname, $name, $uri, $providesextension = false, + $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param string group name (must be pre-existing) + * @param string extension name + * @param string minimum version allowed + * @param string maximum version allowed + * @param string recommended version + * @param array incompatible versions + */ + function addGroupExtensionDep($groupname, $name, $min = false, $max = false, + $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + return $this->_addGroupDependency('extension', $dep, $groupname); + } + + /** + * @param package|subpackage|extension + * @param array dependency contents + * @param string name of the dependency group to add this to + * @return boolean + * @access private + */ + function _addGroupDependency($type, $dep, $groupname) + { + $arr = array('subpackage', 'extension'); + if ($type != 'package') { + array_shift($arr); + } + if ($type == 'extension') { + array_shift($arr); + } + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } else { + if (!isset($this->_packageInfo['dependencies']['group'][0])) { + if ($this->_packageInfo['dependencies']['group']['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } else { + return false; + } + } else { + foreach ($this->_packageInfo['dependencies']['group'] as $i => $group) { + if ($group['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'][$i] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'][$i], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } + } + return false; + } + } + } + + /** + * @param optional|required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param array|false optional excluded versions + */ + function addPackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if (!in_array($type, array('optional', 'required'), true)) { + $type = 'required'; + } + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required + * @param string name of the package + * @param string uri of the package + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addPackageDepWithUri($type, $name, $uri, $providesextension = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package uri for download + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithUri($type, $name, $uri, $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionDep($type, $name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'extension' => array('os', 'arch') + )); + } + + /** + * @param string Operating system name + * @param boolean true if this package cannot be installed on this OS + */ + function addOsDep($name, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'os' => array('arch') + )); + } + + /** + * @param string Architecture matching pattern + * @param boolean true if this package cannot be installed on this architecture + */ + function addArchDep($pattern, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'arch' => array() + )); + } + + /** + * Set the kind of package, and erase all release tags + * + * - a php package is a PEAR-style package + * - an extbin package is a PECL-style extension binary + * - an extsrc package is a PECL-style source for a binary + * - an zendextbin package is a PECL-style zend extension binary + * - an zendextsrc package is a PECL-style source for a zend extension binary + * - a bundle package is a collection of other pre-packaged packages + * @param php|extbin|extsrc|zendextsrc|zendextbin|bundle + * @return bool success + */ + function setPackageType($type) + { + $this->_isValid = 0; + if (!in_array($type, array('php', 'extbin', 'extsrc', 'zendextsrc', + 'zendextbin', 'bundle'))) { + return false; + } + + if (in_array($type, array('zendextsrc', 'zendextbin'))) { + $this->_setPackageVersion2_1(); + } + + if ($type != 'bundle') { + $type .= 'release'; + } + + foreach (array('phprelease', 'extbinrelease', 'extsrcrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle') as $test) { + unset($this->_packageInfo[$test]); + } + + if (!isset($this->_packageInfo[$type])) { + // ensure that the release tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('changelog'), + array(), $type); + } + + $this->_packageInfo[$type] = array(); + return true; + } + + /** + * @return bool true if package type is set up + */ + function addRelease() + { + if ($type = $this->getPackageType()) { + if ($type != 'bundle') { + $type .= 'release'; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array($type => array('changelog'))); + return true; + } + return false; + } + + /** + * Get the current release tag in order to add to it + * @param bool returns only releases that have installcondition if true + * @return array|null + */ + function &_getCurrentRelease($strict = true) + { + if ($p = $this->getPackageType()) { + if ($strict) { + if ($p == 'extsrc' || $p == 'zendextsrc') { + $a = null; + return $a; + } + } + if ($p != 'bundle') { + $p .= 'release'; + } + if (isset($this->_packageInfo[$p][0])) { + return $this->_packageInfo[$p][count($this->_packageInfo[$p]) - 1]; + } else { + return $this->_packageInfo[$p]; + } + } else { + $a = null; + return $a; + } + } + + /** + * Add a file to the current release that should be installed under a different name + * @param string path to file + * @param string name the file should be installed as + */ + function addInstallAs($path, $as) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path, 'as' => $as)), + array( + 'filelist' => array(), + 'install' => array('ignore') + )); + } + + /** + * Add a file to the current release that should be ignored + * @param string path to file + * @return bool success of operation + */ + function addIgnore($path) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path)), + array( + 'filelist' => array(), + 'ignore' => array() + )); + } + + /** + * Add an extension binary package for this extension source code release + * + * Note that the package must be from the same channel as the extension source package + * @param string + */ + function addBinarypackage($package) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, $package, + array( + 'binarypackage' => array('filelist'), + )); + } + + /** + * Add a configureoption to an extension source package + * @param string + * @param string + * @param string + */ + function addConfigureOption($name, $prompt, $default = null) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + + $opt = array('attribs' => array('name' => $name, 'prompt' => $prompt)); + if ($default !== null) { + $opt['attribs']['default'] = $default; + } + + $this->_isValid = 0; + $r = $this->_mergeTag($r, $opt, + array( + 'configureoption' => array('binarypackage', 'filelist'), + )); + } + + /** + * Set an installation condition based on php version for the current release set + * @param string minimum version + * @param string maximum version + * @param false|array incompatible versions of PHP + */ + function setPhpInstallCondition($min, $max, $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['php'])) { + unset($r['installconditions']['php']); + } + $dep = array('min' => $min, 'max' => $max); + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionInstallCondition($name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'extension' => array('os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'extension' => array('os', 'arch') + )); + } + } + + /** + * Set an installation condition based on operating system for the current release set + * @param string OS name + * @param bool whether this OS is incompatible with the current release + */ + function setOsInstallCondition($name, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['os'])) { + unset($r['installconditions']['os']); + } + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'os' => array('arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'os' => array('arch') + )); + } + } + + /** + * Set an installation condition based on architecture for the current release set + * @param string architecture pattern + * @param bool whether this arch is incompatible with the current release + */ + function setArchInstallCondition($pattern, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['arch'])) { + unset($r['installconditions']['arch']); + } + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'arch' => array() + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'arch' => array() + )); + } + } + + /** + * For extension binary releases, this is used to specify either the + * static URI to a source package, or the package name and channel of the extsrc/zendextsrc + * package it is based on. + * @param string Package name, or full URI to source package (extsrc/zendextsrc type) + */ + function setSourcePackage($packageOrUri) + { + $this->_isValid = 0; + if (isset($this->_packageInfo['channel'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $packageOrUri, 'srcpackage'); + } else { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), $packageOrUri, 'srcuri'); + } + } + + /** + * Generate a valid change log entry from the current package.xml + * @param string|false + */ + function generateChangeLogEntry($notes = false) + { + return array( + 'version' => + array( + 'release' => $this->getVersion('release'), + 'api' => $this->getVersion('api'), + ), + 'stability' => + $this->getStability(), + 'date' => $this->getDate(), + 'license' => $this->getLicense(true), + 'notes' => $notes ? $notes : $this->getNotes() + ); + } + + /** + * @param string release version to set change log notes for + * @param array output of {@link generateChangeLogEntry()} + */ + function setChangelogEntry($releaseversion, $contents) + { + if (!isset($this->_packageInfo['changelog'])) { + $this->_packageInfo['changelog']['release'] = $contents; + return; + } + if (!isset($this->_packageInfo['changelog']['release'][0])) { + if ($this->_packageInfo['changelog']['release']['version']['release'] == $releaseversion) { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + } else { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + return $this->_packageInfo['changelog']['release'][] = $contents; + } + } + foreach($this->_packageInfo['changelog']['release'] as $index => $changelog) { + if (isset($changelog['version']) && + strnatcasecmp($changelog['version']['release'], $releaseversion) == 0) { + $curlog = $index; + } + } + if (isset($curlog)) { + $this->_packageInfo['changelog']['release'][$curlog] = $contents; + } else { + $this->_packageInfo['changelog']['release'][] = $contents; + } + } + + /** + * Remove the changelog entirely + */ + function clearChangeLog() + { + unset($this->_packageInfo['changelog']); + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Packager.php b/3rdparty/PEAR/Packager.php index 3ed209be8b..8995a167fc 100644 --- a/3rdparty/PEAR/Packager.php +++ b/3rdparty/PEAR/Packager.php @@ -1,165 +1,201 @@ | -// | Tomas V.V.Cox | -// +----------------------------------------------------------------------+ -// -// $Id: Packager.php,v 1.53 2004/06/13 14:06:01 pajoye Exp $ +/** + * PEAR_Packager for generating releases + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Packager.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; require_once 'System.php'; /** * Administration class used to make a PEAR release tarball. * - * TODO: - * - add an extra param the dir where to place the created package - * - * @since PHP 4.0.2 - * @author Stig Bakken + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class PEAR_Packager extends PEAR_Common { - // {{{ constructor + /** + * @var PEAR_Registry + */ + var $_registry; - function PEAR_Packager() - { - parent::PEAR_Common(); - } - - // }}} - // {{{ destructor - - function _PEAR_Packager() - { - parent::_PEAR_Common(); - } - - // }}} - - // {{{ package() - - function package($pkgfile = null, $compress = true) + function package($pkgfile = null, $compress = true, $pkg2 = null) { // {{{ validate supplied package.xml file if (empty($pkgfile)) { $pkgfile = 'package.xml'; } - // $this->pkginfo gets populated inside - $pkginfo = $this->infoFromDescriptionFile($pkgfile); - if (PEAR::isError($pkginfo)) { - return $this->raiseError($pkginfo); - } - $pkgdir = dirname(realpath($pkgfile)); - $pkgfile = basename($pkgfile); - - $errors = $warnings = array(); - $this->validatePackageInfo($pkginfo, $errors, $warnings, $pkgdir); - foreach ($warnings as $w) { - $this->log(1, "Warning: $w"); - } - foreach ($errors as $e) { - $this->log(0, "Error: $e"); - } - if (sizeof($errors) > 0) { - return $this->raiseError('Errors in package'); - } - // }}} - - $pkgver = $pkginfo['package'] . '-' . $pkginfo['version']; - - // {{{ Create the package file list - $filelist = array(); - $i = 0; - - foreach ($pkginfo['filelist'] as $fname => $atts) { - $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; - if (!file_exists($file)) { - return $this->raiseError("File does not exist: $fname"); - } else { - $filelist[$i++] = $file; - if (empty($pkginfo['filelist'][$fname]['md5sum'])) { - $md5sum = md5_file($file); - $pkginfo['filelist'][$fname]['md5sum'] = $md5sum; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pkg = &new PEAR_PackageFile($this->config, $this->debug); + $pf = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL); + $main = &$pf; + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); } - $this->log(2, "Adding file $fname"); + } + + $this->log(0, $pf->getMessage()); + return $this->raiseError("Cannot package, errors in package file"); + } + + foreach ($pf->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + // }}} + if ($pkg2) { + $this->log(0, 'Attempting to process the second package file'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf2 = &$pkg->fromPackageFile($pkg2, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf2)) { + if (is_array($pf2->getUserInfo())) { + foreach ($pf2->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + $this->log(0, $pf2->getMessage()); + return $this->raiseError("Cannot package, errors in second package file"); + } + + foreach ($pf2->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pf2->getPackagexmlVersion() == '2.0' || + $pf2->getPackagexmlVersion() == '2.1' + ) { + $main = &$pf2; + $other = &$pf; + } else { + $main = &$pf; + $other = &$pf2; + } + + if ($main->getPackagexmlVersion() != '2.0' && + $main->getPackagexmlVersion() != '2.1') { + return PEAR::raiseError('Error: cannot package two package.xml version 1.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + + if ($other->getPackagexmlVersion() != '1.0') { + return PEAR::raiseError('Error: cannot package two package.xml version 2.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); } } - // }}} - // {{{ regenerate package.xml - $new_xml = $this->xmlFromInfo($pkginfo); - if (PEAR::isError($new_xml)) { - return $this->raiseError($new_xml); + $main->setLogger($this); + if (!$main->validate(PEAR_VALIDATE_PACKAGING)) { + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + return $this->raiseError("Cannot package, errors in package"); } - if (!($tmpdir = System::mktemp(array('-d')))) { - return $this->raiseError("PEAR_Packager: mktemp failed"); - } - $newpkgfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml'; - $np = @fopen($newpkgfile, 'wb'); - if (!$np) { - return $this->raiseError("PEAR_Packager: unable to rewrite $pkgfile as $newpkgfile"); - } - fwrite($np, $new_xml); - fclose($np); - // }}} - // {{{ TAR the Package ------------------------------------------- - $ext = $compress ? '.tgz' : '.tar'; - $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; - $tar =& new Archive_Tar($dest_package, $compress); - $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors - // ----- Creates with the package.xml file - $ok = $tar->createModify(array($newpkgfile), '', $tmpdir); - if (PEAR::isError($ok)) { - return $this->raiseError($ok); - } elseif (!$ok) { - return $this->raiseError('PEAR_Packager: tarball creation failed'); + foreach ($main->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); } - // ----- Add the content of the package - if (!$tar->addModify($filelist, $pkgver, $pkgdir)) { - return $this->raiseError('PEAR_Packager: tarball creation failed'); + + if ($pkg2) { + $other->setLogger($this); + $a = false; + if (!$other->validate(PEAR_VALIDATE_NORMAL) || $a = !$main->isEquivalent($other)) { + foreach ($other->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + if ($a) { + return $this->raiseError('The two package.xml files are not equivalent!'); + } + + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($other->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + $gen = &$main->getDefaultGenerator(); + $tgzfile = $gen->toTgz2($this, $other, $compress); + if (PEAR::isError($tgzfile)) { + return $tgzfile; + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, 'Tag the released code with "pear cvstag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, 'Tag the released code with "pear svntag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } else { // this branch is executed for single packagefile packaging + $gen = &$pf->getDefaultGenerator(); + $tgzfile = $gen->toTgz($this, $compress); + if (PEAR::isError($tgzfile)) { + $this->log(0, $tgzfile->getMessage()); + return $this->raiseError("Cannot package, errors in package"); + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, "Tag the released code with `pear cvstag $pkgfile'"); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, "Tag the released code with `pear svntag $pkgfile'"); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } } - $this->log(1, "Package $dest_package done"); - if (file_exists("$pkgdir/CVS/Root")) { - $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pkginfo['version']); - $cvstag = "RELEASE_$cvsversion"; - $this->log(1, "Tag the released code with `pear cvstag $pkgfile'"); - $this->log(1, "(or set the CVS tag $cvstag by hand)"); - } - // }}} return $dest_package; } - - // }}} -} - -// {{{ md5_file() utility function -if (!function_exists('md5_file')) { - function md5_file($file) { - if (!$fd = @fopen($file, 'r')) { - return false; - } - $md5 = md5(fread($fd, filesize($file))); - fclose($fd); - return $md5; - } -} -// }}} - -?> +} \ No newline at end of file diff --git a/3rdparty/PEAR/REST.php b/3rdparty/PEAR/REST.php new file mode 100644 index 0000000000..34a804f2bd --- /dev/null +++ b/3rdparty/PEAR/REST.php @@ -0,0 +1,483 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: REST.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * For downloading xml files + */ +require_once 'PEAR.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * Intelligently retrieve data, following hyperlinks if necessary, and re-directing + * as well + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_REST +{ + var $config; + var $_options; + + function PEAR_REST(&$config, $options = array()) + { + $this->config = &$config; + $this->_options = $options; + } + + /** + * Retrieve REST data, but always retrieve the local cache if it is available. + * + * This is useful for elements that should never change, such as information on a particular + * release + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (file_exists($cachefile)) { + return unserialize(implode('', file($cachefile))); + } + + return $this->retrieveData($url, $accept, $forcestring, $channel); + } + + /** + * Retrieve a remote REST resource + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveData($url, $accept = false, $forcestring = false, $channel = false) + { + $cacheId = $this->getCacheId($url); + if ($ret = $this->useLocalCache($url, $cacheId)) { + return $ret; + } + + $file = $trieddownload = false; + if (!isset($this->_options['offline'])) { + $trieddownload = true; + $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel); + } + + if (PEAR::isError($file)) { + if ($file->getCode() !== -9276) { + return $file; + } + + $trieddownload = false; + $file = false; // use local copy if available on socket connect error + } + + if (!$file) { + $ret = $this->getCache($url); + if (!PEAR::isError($ret) && $trieddownload) { + // reset the age of the cache if the server says it was unmodified + $result = $this->saveCache($url, $ret, null, true, $cacheId); + if (PEAR::isError($result)) { + return PEAR::raiseError($result->getMessage()); + } + } + + return $ret; + } + + if (is_array($file)) { + $headers = $file[2]; + $lastmodified = $file[1]; + $content = $file[0]; + } else { + $headers = array(); + $lastmodified = false; + $content = $file; + } + + if ($forcestring) { + $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId); + if (PEAR::isError($result)) { + return PEAR::raiseError($result->getMessage()); + } + + return $content; + } + + if (isset($headers['content-type'])) { + switch ($headers['content-type']) { + case 'text/xml' : + case 'application/xml' : + case 'text/plain' : + if ($headers['content-type'] === 'text/plain') { + $check = substr($content, 0, 5); + if ($check !== 'parse($content); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' . + $err->getMessage()); + } + $content = $parser->getData(); + case 'text/html' : + default : + // use it as a string + } + } else { + // assume XML + $parser = new PEAR_XMLParser; + $parser->parse($content); + $content = $parser->getData(); + } + + $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId); + if (PEAR::isError($result)) { + return PEAR::raiseError($result->getMessage()); + } + + return $content; + } + + function useLocalCache($url, $cacheid = null) + { + if ($cacheid === null) { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + if (!file_exists($cacheidfile)) { + return false; + } + + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $cachettl = $this->config->get('cache_ttl'); + // If cache is newer than $cachettl seconds, we use the cache! + if (time() - $cacheid['age'] < $cachettl) { + return $this->getCache($url); + } + + return false; + } + + function getCacheId($url) + { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + + if (!file_exists($cacheidfile)) { + return false; + } + + $ret = unserialize(implode('', file($cacheidfile))); + return $ret; + } + + function getCache($url) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (!file_exists($cachefile)) { + return PEAR::raiseError('No cached content available for "' . $url . '"'); + } + + return unserialize(implode('', file($cachefile))); + } + + /** + * @param string full URL to REST resource + * @param string original contents of the REST resource + * @param array HTTP Last-Modified and ETag headers + * @param bool if true, then the cache id file should be regenerated to + * trigger a new time-to-live value + */ + function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null) + { + $cache_dir = $this->config->get('cache_dir'); + $d = $cache_dir . DIRECTORY_SEPARATOR . md5($url); + $cacheidfile = $d . 'rest.cacheid'; + $cachefile = $d . 'rest.cachefile'; + + if (!is_dir($cache_dir)) { + if (System::mkdir(array('-p', $cache_dir)) === false) { + return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory and attempts to create the directory failed."); + } + } + + if ($cacheid === null && $nochange) { + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $idData = serialize(array( + 'age' => time(), + 'lastChange' => ($nochange ? $cacheid['lastChange'] : $lastmodified), + )); + + $result = $this->saveCacheFile($cacheidfile, $idData); + if (PEAR::isError($result)) { + return $result; + } elseif ($nochange) { + return true; + } + + $result = $this->saveCacheFile($cachefile, serialize($contents)); + if (PEAR::isError($result)) { + if (file_exists($cacheidfile)) { + @unlink($cacheidfile); + } + + return $result; + } + + return true; + } + + function saveCacheFile($file, $contents) + { + $len = strlen($contents); + + $cachefile_fp = @fopen($file, 'xb'); // x is the O_CREAT|O_EXCL mode + if ($cachefile_fp !== false) { // create file + if (fwrite($cachefile_fp, $contents, $len) < $len) { + fclose($cachefile_fp); + return PEAR::raiseError("Could not write $file."); + } + } else { // update file + $cachefile_lstat = lstat($file); + $cachefile_fp = @fopen($file, 'wb'); + if (!$cachefile_fp) { + return PEAR::raiseError("Could not open $file for writing."); + } + + $cachefile_fstat = fstat($cachefile_fp); + if ( + $cachefile_lstat['mode'] == $cachefile_fstat['mode'] && + $cachefile_lstat['ino'] == $cachefile_fstat['ino'] && + $cachefile_lstat['dev'] == $cachefile_fstat['dev'] && + $cachefile_fstat['nlink'] === 1 + ) { + if (fwrite($cachefile_fp, $contents, $len) < $len) { + fclose($cachefile_fp); + return PEAR::raiseError("Could not write $file."); + } + } else { + fclose($cachefile_fp); + $link = function_exists('readlink') ? readlink($file) : $file; + return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $file . ' as it is symlinked to ' . $link . ' - Possible symlink attack'); + } + } + + fclose($cachefile_fp); + return true; + } + + /** + * Efficiently Download a file through HTTP. Returns downloaded file as a string in-memory + * This is best used for small files + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param string $save_dir directory to save file in + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @return string|array Returns the contents of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($this->config->get('http_proxy')&& + $proxy = parse_url($this->config->get('http_proxy')) + ) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if ($schema === 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http'; + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + if (isset($proxy['host'])) { + $request = "GET $url HTTP/1.1\r\n"; + } else { + $request = "GET $path HTTP/1.1\r\n"; + } + + $request .= "Host: $host\r\n"; + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n"; + + $username = $this->config->get('username', null, $channel); + $password = $this->config->get('password', null, $channel); + + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Accept-Encoding:\r\n"; + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15); + if (!$fp) { + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276); + } + } else { + if ($schema === 'https') { + $host = 'ssl://' . $host; + } + + $fp = @fsockopen($host, $port, $errno, $errstr); + if (!$fp) { + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + } + + fwrite($fp, $request); + + $headers = array(); + $reply = 0; + while ($line = trim(fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $data = ''; + while ($chunk = @fread($fp, 8192)) { + $data .= $chunk; + } + fclose($fp); + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + + return array($data, $lastmodified, $headers); + } + + return $data; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/REST/10.php b/3rdparty/PEAR/REST/10.php new file mode 100644 index 0000000000..6ded7aeace --- /dev/null +++ b/3rdparty/PEAR/REST/10.php @@ -0,0 +1,871 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 10.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.0 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_10 +{ + /** + * @var PEAR_REST + */ + var $_rest; + function PEAR_REST_10($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
+     *  array(
+     *   'package' => 'packagename',
+     *   'channel' => 'channelname',
+     *  ['state' => 'alpha' (or valid state),]
+     *  -or-
+     *  ['version' => '1.whatever']
+     * 
+ * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + $found = true; + break; + } + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + $found = true; + break; + } + } + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + $recommended = $release['v']; + break; + } + } + } + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } else { + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + } + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + if (in_array($release['s'], $states)) { // if in the preferred state... + $found = true; // ... then use it + break; + } + } + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + /** + * Take raw data and return the array needed for processing a download URL + * + * @param string $base REST base uri + * @param string $package Package name + * @param array $release an array of format array('v' => version, 's' => state) + * describing the release to download + * @param array $info list of all releases as defined by allreleases.xml + * @param bool|null $found determines whether the release was found or this is the next + * best alternative. If null, then versions were skipped because + * of PHP dependency + * @return array|PEAR_Error + * @access private + */ + function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false) + { + if (!$found) { + $release = $info['r'][0]; + } + + $packageLower = strtolower($package); + $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' . + 'info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + return PEAR::raiseError('Package "' . $package . + '" does not have REST info xml available'); + } + + $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($releaseinfo)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST xml available'); + } + + $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + 'deps.' . $release['v'] . '.txt', false, true, $channel); + if (PEAR::isError($packagexml)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST dependency information available'); + } + + $packagexml = unserialize($packagexml); + if (!$packagexml) { + $packagexml = array(); + } + + $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($allinfo)) { + return $allinfo; + } + + if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) { + $allinfo['r'] = array($allinfo['r']); + } + + $compatible = false; + foreach ($allinfo['r'] as $release) { + if ($release['v'] != $releaseinfo['v']) { + continue; + } + + if (!isset($release['co'])) { + break; + } + + $compatible = array(); + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + $comp = array(); + $comp['name'] = $entry['p']; + $comp['channel'] = $entry['c']; + $comp['min'] = $entry['min']; + $comp['max'] = $entry['max']; + if (isset($entry['x']) && !is_array($entry['x'])) { + $comp['exclude'] = $entry['x']; + } + + $compatible[] = $comp; + } + + if (count($compatible) == 1) { + $compatible = $compatible[0]; + } + + break; + } + + $deprecated = false; + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } + + $return = array( + 'version' => $releaseinfo['v'], + 'info' => $packagexml, + 'package' => $releaseinfo['p']['_content'], + 'stability' => $releaseinfo['st'], + 'compatible' => $compatible, + 'deprecated' => $deprecated, + ); + + if ($found) { + $return['url'] = $releaseinfo['g']; + return $return; + } + + $return['php'] = $phpversion; + return $return; + } + + function listPackages($base, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + return $packagelist['p']; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categories = array(); + + // c/categories.xml does not exist; + // check for every package its category manually + // This is SLOOOWWWW : /// + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + $ret = array(); + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist['p'] as $package) { + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + $cat = $inf['ca']['_content']; + if (!isset($categories[$cat])) { + $categories[$cat] = $inf['ca']; + } + } + + return array_values($categories); + } + + /** + * List a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + + if ($info == true) { + // get individual package info + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist as $i => $packageitem) { + $url = sprintf('%s'.'r/%s/latest.txt', + $base, + strtolower($packageitem['_content'])); + $version = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($version)) { + break; // skipit + } + $url = sprintf('%s'.'r/%s/%s.xml', + $base, + strtolower($packageitem['_content']), + $version); + $info = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($info)) { + break; // skipit + } + $packagelist[$i]['info'] = $info; + } + PEAR::popErrorHandling(); + } + + return $packagelist; + } + + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if ($this->_rest->config->get('verbose') > 0) { + $ui = &PEAR_Frontend::singleton(); + $ui->log('Retrieving data...0%', true); + } + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + // only search-packagename = quicksearch ! + if ($searchpackage && (!$searchsummary || empty($searchpackage))) { + $newpackagelist = array(); + foreach ($packagelist['p'] as $package) { + if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) { + $newpackagelist[] = $package; + } + } + $packagelist['p'] = $newpackagelist; + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $next = .1; + foreach ($packagelist['p'] as $progress => $package) { + if ($this->_rest->config->get('verbose') > 0) { + if ($progress / count($packagelist['p']) >= $next) { + if ($next == .5) { + $ui->log('50%', false); + } else { + $ui->log('.', false); + } + $next += .1; + } + } + + if ($basic) { // remote-list command + if ($dostable) { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/stable.txt', false, false, $channel); + } else { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/latest.txt', false, false, $channel); + } + if (PEAR::isError($latest)) { + $latest = false; + } + $info = array('stable' => $latest); + } else { // list-all command + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + if ($searchpackage) { + $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false); + if (!$found && !(isset($searchsummary) && !empty($searchsummary) + && (stristr($inf['s'], $searchsummary) !== false + || stristr($inf['d'], $searchsummary) !== false))) + { + continue; + }; + } + $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($releases)) { + continue; + } + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + unset($latest); + unset($unstable); + unset($stable); + unset($state); + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + if (!isset($unstable) && $release['s'] != 'stable') { + $latest = $unstable = $release['v']; + $state = $release['s']; + } + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + $deps = array(); + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + if (!isset($latest)) { + $latest = false; + } + if ($latest) { + $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $latest . '.txt', false, false, $channel); + if (!PEAR::isError($d)) { + $d = unserialize($d); + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + $deps[] = $dep; + } + } + } + } + if (!isset($stable)) { + $stable = '-n/a-'; + } + if (!$searchpackage) { + $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } else { + $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } + } + $ret[$package] = $info; + } + PEAR::popErrorHandling(); + return $ret; + } + + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package)])) { + continue; + } + + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($info)) { + continue; // no remote releases + } + + if (!isset($info['r'])) { + continue; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + // $info['r'] is sorted by version number + usort($info['r'], array($this, '_sortReleasesByVersionNumber')); + foreach ($info['r'] as $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + break; + } else { + $new_state = $release['s']; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($relinfo)) { + return $relinfo; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $relinfo['f'], + ); + } + + return $ret; + } + + function packageInfo($base, $package, $channel = false) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' . + $pinfo->getMessage()); + } + + $releases = array(); + $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (!PEAR::isError($allreleases)) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) { + $allreleases['r'] = array($allreleases['r']); + } + + $pf = new PEAR_PackageFile_v2; + foreach ($allreleases['r'] as $release) { + $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $release['v'] . '.txt', false, false, $channel); + if (PEAR::isError($ds)) { + continue; + } + + if (!isset($latest)) { + $latest = $release['v']; + } + + $pf->setDeps(unserialize($ds)); + $ds = $pf->getDeps(); + $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) + . '/' . $release['v'] . '.xml', false, false, $channel); + + if (PEAR::isError($info)) { + continue; + } + + $releases[$release['v']] = array( + 'doneby' => $info['m'], + 'license' => $info['l'], + 'summary' => $info['s'], + 'description' => $info['d'], + 'releasedate' => $info['da'], + 'releasenotes' => $info['n'], + 'state' => $release['s'], + 'deps' => $ds ? $ds : array(), + ); + } + } else { + $latest = ''; + } + + PEAR::popErrorHandling(); + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } else { + $deprecated = false; + } + + if (!isset($latest)) { + $latest = ''; + } + + return array( + 'name' => $pinfo['n'], + 'channel' => $pinfo['c'], + 'category' => $pinfo['ca']['_content'], + 'stable' => $latest, + 'license' => $pinfo['l'], + 'summary' => $pinfo['s'], + 'description' => $pinfo['d'], + 'releases' => $releases, + 'deprecated' => $deprecated, + ); + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + + if ($include) { + $i--; + } + + return array_slice($states, $i + 1); + } + + /** + * Sort releases by version number + * + * @access private + */ + function _sortReleasesByVersionNumber($a, $b) + { + if (version_compare($a['v'], $b['v'], '=')) { + return 0; + } + + if (version_compare($a['v'], $b['v'], '>')) { + return -1; + } + + if (version_compare($a['v'], $b['v'], '<')) { + return 1; + } + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/REST/11.php b/3rdparty/PEAR/REST/11.php new file mode 100644 index 0000000000..831cfccdb7 --- /dev/null +++ b/3rdparty/PEAR/REST/11.php @@ -0,0 +1,341 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 11.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.3 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.1 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.3 + */ +class PEAR_REST_11 +{ + /** + * @var PEAR_REST + */ + var $_rest; + + function PEAR_REST_11($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + $ret = array(); + if (!is_array($categorylist['c']) || !isset($categorylist['c'][0])) { + $categorylist['c'] = array($categorylist['c']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($categorylist['c'] as $progress => $category) { + $category = $category['_content']; + $packagesinfo = $this->_rest->retrieveData($base . + 'c/' . urlencode($category) . '/packagesinfo.xml', false, false, $channel); + + if (PEAR::isError($packagesinfo)) { + continue; + } + + if (!is_array($packagesinfo) || !isset($packagesinfo['pi'])) { + continue; + } + + if (!is_array($packagesinfo['pi']) || !isset($packagesinfo['pi'][0])) { + $packagesinfo['pi'] = array($packagesinfo['pi']); + } + + foreach ($packagesinfo['pi'] as $packageinfo) { + if (empty($packageinfo)) { + continue; + } + + $info = $packageinfo['p']; + $package = $info['n']; + $releases = isset($packageinfo['a']) ? $packageinfo['a'] : false; + unset($latest); + unset($unstable); + unset($stable); + unset($state); + + if ($releases) { + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + + if (!isset($unstable) && $release['s'] != 'stable') { + $unstable = $release['v']; + $state = $release['s']; + } + + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + } + + if ($basic) { // remote-list command + if (!isset($latest)) { + $latest = false; + } + + if ($dostable) { + // $state is not set if there are no releases + if (isset($state) && $state == 'stable') { + $ret[$package] = array('stable' => $latest); + } else { + $ret[$package] = array('stable' => '-n/a-'); + } + } else { + $ret[$package] = array('stable' => $latest); + } + + continue; + } + + // list-all command + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + + if (!isset($latest)) { + $latest = false; + } + + $deps = array(); + if ($latest && isset($packageinfo['deps'])) { + if (!is_array($packageinfo['deps']) || + !isset($packageinfo['deps'][0]) + ) { + $packageinfo['deps'] = array($packageinfo['deps']); + } + + $d = false; + foreach ($packageinfo['deps'] as $dep) { + if ($dep['v'] == $latest) { + $d = unserialize($dep['d']); + } + } + + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + + $deps[] = $dep; + } + } + } + + $info = array( + 'stable' => $latest, + 'summary' => $info['s'], + 'description' => $info['d'], + 'deps' => $deps, + 'category' => $info['ca']['_content'], + 'unstable' => $unstable, + 'state' => $state + ); + $ret[$package] = $info; + } + } + + PEAR::popErrorHandling(); + return $ret; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + if (!is_array($categorylist) || !isset($categorylist['c'])) { + return array(); + } + + if (isset($categorylist['c']['_content'])) { + // only 1 category + $categorylist['c'] = array($categorylist['c']); + } + + return $categorylist['c']; + } + + /** + * List packages in a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + if ($info == false) { + $url = '%s'.'c/%s/packages.xml'; + } else { + $url = '%s'.'c/%s/packagesinfo.xml'; + } + $url = sprintf($url, + $base, + urlencode($category)); + + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if (!is_array($packagelist)) { + return array(); + } + + if ($info == false) { + if (!isset($packagelist['p'])) { + return array(); + } + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + return $packagelist; + } + + // info == true + if (!isset($packagelist['pi'])) { + return array(); + } + + if (!is_array($packagelist['pi']) || + !isset($packagelist['pi'][0])) { // only 1 pkg + $packagelist_pre = array($packagelist['pi']); + } else { + $packagelist_pre = $packagelist['pi']; + } + + $packagelist = array(); + foreach ($packagelist_pre as $i => $item) { + // compatibility with r/.xml + if (isset($item['a']['r'][0])) { + // multiple releases + $item['p']['v'] = $item['a']['r'][0]['v']; + $item['p']['st'] = $item['a']['r'][0]['s']; + } elseif (isset($item['a'])) { + // first and only release + $item['p']['v'] = $item['a']['r']['v']; + $item['p']['st'] = $item['a']['r']['s']; + } + + $packagelist[$i] = array('attribs' => $item['p']['r'], + '_content' => $item['p']['n'], + 'info' => $item['p']); + } + + return $packagelist; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/REST/13.php b/3rdparty/PEAR/REST/13.php new file mode 100644 index 0000000000..722ae0de30 --- /dev/null +++ b/3rdparty/PEAR/REST/13.php @@ -0,0 +1,299 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 13.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; +require_once 'PEAR/REST/10.php'; + +/** + * Implement REST 1.3 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_13 extends PEAR_REST_10 +{ + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * This is smart enough to resolve the minimum PHP version dependency prior to download + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
+     *  array(
+     *   'package' => 'packagename',
+     *   'channel' => 'channelname',
+     *  ['state' => 'alpha' (or valid state),]
+     *  -or-
+     *  ['version' => '1.whatever']
+     * 
+ * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + $skippedphp = false; + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + if (!isset($this->_rest->_options['force']) && + !isset($version) && + version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + $pinfo['package'] = $dependency['name']; + $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $pinfo['package'] = $dependency['name']; + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + + $skippedphp = $found = $release = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $recommended = $release['v']; + break; + } + } + } + + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } + + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + + if (in_array($release['s'], $states)) { // if in the preferred state... + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $found = true; // ... then use it + break; + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Registry.php b/3rdparty/PEAR/Registry.php index f2510a38f1..35e17db495 100644 --- a/3rdparty/PEAR/Registry.php +++ b/3rdparty/PEAR/Registry.php @@ -1,44 +1,53 @@ | -// | Tomas V.V.Cox | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: Registry.php,v 1.50.4.3 2004/10/26 19:19:56 cellog Exp $ +/** + * PEAR_Registry + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -/* -TODO: - - Transform into singleton() - - Add application level lock (avoid change the registry from the cmdline - while using the GTK interface, for ex.) -*/ -require_once "System.php"; -require_once "PEAR.php"; +/** + * for PEAR_Error + */ +require_once 'PEAR.php'; +require_once 'PEAR/DependencyDB.php'; -define('PEAR_REGISTRY_ERROR_LOCK', -2); -define('PEAR_REGISTRY_ERROR_FORMAT', -3); -define('PEAR_REGISTRY_ERROR_FILE', -4); +define('PEAR_REGISTRY_ERROR_LOCK', -2); +define('PEAR_REGISTRY_ERROR_FORMAT', -3); +define('PEAR_REGISTRY_ERROR_FILE', -4); +define('PEAR_REGISTRY_ERROR_CONFLICT', -5); +define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6); /** * Administration class used to maintain the installed package database. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 */ class PEAR_Registry extends PEAR { - // {{{ properties + /** + * File containing all channel information. + * @var string + */ + var $channels = ''; /** Directory where registry files are stored. * @var string @@ -50,6 +59,11 @@ class PEAR_Registry extends PEAR */ var $filemap = ''; + /** Directory where registry files for channels are stored. + * @var string + */ + var $channelsdir = ''; + /** Name of file used for locking the registry * @var string */ @@ -79,44 +93,259 @@ class PEAR_Registry extends PEAR */ var $filemap_cache = array(); - // }}} + /** + * @var false|PEAR_ChannelFile + */ + var $_pearChannel; - // {{{ constructor + /** + * @var false|PEAR_ChannelFile + */ + var $_peclChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_docChannel; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencyDB; + + /** + * @var PEAR_Config + */ + var $_config; /** * PEAR_Registry constructor. * * @param string (optional) PEAR install directory (for .php files) + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized * * @access public */ - function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR) + function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, + $pecl_channel = false) { parent::PEAR(); + $this->setInstallDir($pear_install_dir); + $this->_pearChannel = $pear_channel; + $this->_peclChannel = $pecl_channel; + $this->_config = false; + } + + function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR) + { $ds = DIRECTORY_SEPARATOR; $this->install_dir = $pear_install_dir; - $this->statedir = $pear_install_dir.$ds.'.registry'; - $this->filemap = $pear_install_dir.$ds.'.filemap'; - $this->lockfile = $pear_install_dir.$ds.'.lock'; + $this->channelsdir = $pear_install_dir.$ds.'.channels'; + $this->statedir = $pear_install_dir.$ds.'.registry'; + $this->filemap = $pear_install_dir.$ds.'.filemap'; + $this->lockfile = $pear_install_dir.$ds.'.lock'; + } + function hasWriteAccess() + { + if (!file_exists($this->install_dir)) { + $dir = $this->install_dir; + while ($dir && $dir != '.') { + $olddir = $dir; + $dir = dirname($dir); + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + + if ($dir == $olddir) { // this can happen in safe mode + return @is_writable($dir); + } + } + + return false; + } + + return is_writeable($this->install_dir); + } + + function setConfig(&$config, $resetInstallDir = true) + { + $this->_config = &$config; + if ($resetInstallDir) { + $this->setInstallDir($config->get('php_dir')); + } + } + + function _initializeChannelDirs() + { + static $running = false; + if (!$running) { + $running = true; + $ds = DIRECTORY_SEPARATOR; + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $pear_channel = $this->_pearChannel; + if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setAlias('pear'); + $pear_channel->setServer('pear.php.net'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/'); + } else { + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + } + + $pear_channel->validate(); + $this->_addChannel($pear_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) { + $pecl_channel = $this->_peclChannel; + if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pecl_channel = new PEAR_ChannelFile; + $pecl_channel->setAlias('pecl'); + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setSummary('PHP Extension Community Library'); + $pecl_channel->setDefaultPEARProtocols(); + $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + } else { + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setAlias('pecl'); + } + + $pecl_channel->validate(); + $this->_addChannel($pecl_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) { + $doc_channel = $this->_docChannel; + if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setAlias('phpdocs'); + $doc_channel->setServer('doc.php.net'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + } else { + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('doc'); + } + + $doc_channel->validate(); + $this->_addChannel($doc_channel); + } + + if (!file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + $this->_addChannel($private); + } + $this->_rebuildFileMap(); + } + + $running = false; + } + } + + function _initializeDirs() + { + $ds = DIRECTORY_SEPARATOR; // XXX Compatibility code should be removed in the future // rename all registry files if any to lowercase - if (!OS_WINDOWS && $handle = @opendir($this->statedir)) { - $dest = $this->statedir . DIRECTORY_SEPARATOR; + if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) && + $handle = opendir($this->statedir)) { + $dest = $this->statedir . $ds; while (false !== ($file = readdir($handle))) { - if (preg_match('/^.*[A-Z].*\.reg$/', $file)) { + if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) { rename($dest . $file, $dest . strtolower($file)); } } closedir($handle); } + + $this->_initializeChannelDirs(); if (!file_exists($this->filemap)) { - $this->rebuildFileMap(); + $this->_rebuildFileMap(); } + $this->_initializeDepDB(); } - // }}} - // {{{ destructor + function _initializeDepDB() + { + if (!isset($this->_dependencyDB)) { + static $initializing = false; + if (!$initializing) { + $initializing = true; + if (!$this->_config) { // never used? + $file = OS_WINDOWS ? 'pear.ini' : '.pearrc'; + $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . + $file); + $this->_config->setRegistry($this); + $this->_config->set('php_dir', $this->install_dir); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + // attempt to recover by removing the dep db + if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb')) { + @unlink($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + echo $this->_dependencyDB->getMessage(); + echo 'Unrecoverable error'; + exit(1); + } + } + + $initializing = false; + } + } + } /** * PEAR_Registry destructor. Makes sure no locks are forgotten. @@ -131,10 +360,6 @@ class PEAR_Registry extends PEAR } } - // }}} - - // {{{ _assertStateDir() - /** * Make sure the directory where we keep registry files exists. * @@ -143,118 +368,435 @@ class PEAR_Registry extends PEAR * * @access private */ - function _assertStateDir() + function _assertStateDir($channel = false) { - if (!@is_dir($this->statedir)) { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_assertChannelStateDir($channel); + } + + static $init = false; + if (!file_exists($this->statedir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; if (!System::mkdir(array('-p', $this->statedir))) { return $this->raiseError("could not create directory '{$this->statedir}'"); } + $init = true; + } elseif (!is_dir($this->statedir)) { + return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' . + 'it already exists and is not a directory'); } + + $ds = DIRECTORY_SEPARATOR; + if (!file_exists($this->channelsdir)) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + $init = true; + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' . + 'it already exists and is not a directory'); + } + + if ($init) { + static $running = false; + if (!$running) { + $running = true; + $this->_initializeDirs(); + $running = false; + $init = false; + } + } else { + $this->_initializeDepDB(); + } + return true; } - // }}} - // {{{ _packageFileName() + /** + * Make sure the directory where we keep registry files exists for a non-standard channel. + * + * @param string channel name + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelStateDir($channel) + { + $ds = DIRECTORY_SEPARATOR; + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + return $this->_assertStateDir($channel); + } + + $channelDir = $this->_channelDirectoryName($channel); + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + + if (!file_exists($channelDir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $channelDir))) { + return $this->raiseError("could not create directory '" . $channelDir . + "'"); + } + } elseif (!is_dir($channelDir)) { + return $this->raiseError("could not create directory '" . $channelDir . + "', already exists and is not a directory"); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files for channels exists + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelDir() + { + if (!file_exists($this->channelsdir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir))) { + return $this->raiseError("could not create directory '{$this->channelsdir}'"); + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "', it already exists and is not a directory"); + } + + if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) { + return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'"); + } + } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "/.alias', it already exists and is not a directory"); + } + + return true; + } /** * Get the name of the file where data for a given package is stored. * + * @param string channel name, or false if this is a PEAR package * @param string package name * * @return string registry file name * * @access public */ - function _packageFileName($package) + function _packageFileName($package, $channel = false) { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR . + strtolower($package) . '.reg'; + } + return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg'; } - // }}} - // {{{ _openPackageFile() - - function _openPackageFile($package, $mode) + /** + * Get the name of the file where data for a given channel is stored. + * @param string channel name + * @return string registry file name + */ + function _channelFileName($channel, $noaliases = false) { - $this->_assertStateDir(); - $file = $this->_packageFileName($package); + if (!$noaliases) { + if (file_exists($this->_getChannelAliasFileName($channel))) { + $channel = implode('', file($this->_getChannelAliasFileName($channel))); + } + } + return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_', + strtolower($channel)) . '.reg'; + } + + /** + * @param string + * @return string + */ + function _getChannelAliasFileName($alias) + { + return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' . + DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt'; + } + + /** + * Get the name of a channel from its alias + */ + function _getChannelFromAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear.php.net'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl.php.net'; + } + + if ($channel == 'doc.php.net') { + return 'doc.php.net'; + } + + if ($channel == '__uri') { + return '__uri'; + } + + return false; + } + + $channel = strtolower($channel); + if (file_exists($this->_getChannelAliasFileName($channel))) { + // translate an alias to an actual channel + return implode('', file($this->_getChannelAliasFileName($channel))); + } + + return $channel; + } + + /** + * Get the alias of a channel from its alias or its name + */ + function _getAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl'; + } + + if ($channel == 'doc.php.net') { + return 'phpdocs'; + } + + return false; + } + + $channel = $this->_getChannel($channel); + if (PEAR::isError($channel)) { + return $channel; + } + + return $channel->getAlias(); + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _channelDirectoryName($channel) + { + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return $this->statedir; + } + + $ch = $this->_getChannelFromAlias($channel); + if (!$ch) { + $ch = $channel; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' . + str_replace('/', '_', $ch)); + } + + function _openPackageFile($package, $mode, $channel = false) + { + if (!$this->_assertStateDir($channel)) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_packageFileName($package, $channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + $fp = @fopen($file, $mode); if (!$fp) { return null; } + return $fp; } - // }}} - // {{{ _closePackageFile() - function _closePackageFile($fp) { fclose($fp); } - // }}} - // {{{ rebuildFileMap() - - function rebuildFileMap() + function _openChannelFile($channel, $mode) { - $packages = $this->listPackages(); + if (!$this->_assertChannelDir()) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_channelFileName($channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closeChannelFile($fp) + { + fclose($fp); + } + + function _rebuildFileMap() + { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + + $channels = $this->_listAllPackages(); $files = array(); - foreach ($packages as $package) { - $version = $this->packageInfo($package, 'version'); - $filelist = $this->packageInfo($package, 'filelist'); - if (!is_array($filelist)) { - continue; - } - foreach ($filelist as $name => $attrs) { - if (isset($attrs['role']) && $attrs['role'] != 'php') { + foreach ($channels as $channel => $packages) { + foreach ($packages as $package) { + $version = $this->_packageInfo($package, 'version', $channel); + $filelist = $this->_packageInfo($package, 'filelist', $channel); + if (!is_array($filelist)) { continue; } - if (isset($attrs['baseinstalldir'])) { - $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; - } else { - $file = $name; + + foreach ($filelist as $name => $attrs) { + if (isset($attrs['attribs'])) { + $attrs = $attrs['attribs']; + } + + // it is possible for conflicting packages in different channels to + // conflict with data files/doc files + if ($name == 'dirtree') { + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = $package; + } + + if (isset($attrs['baseinstalldir'])) { + $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; + } else { + $file = $name; + } + + $file = preg_replace(',^/+,', '', $file); + if ($channel != 'pear.php.net') { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = array(strtolower($channel), + strtolower($package)); + } else { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = strtolower($package); + } } - $file = preg_replace(',^/+,', '', $file); - $files[$file] = $package; } } + + $this->_assertStateDir(); + if (!$this->hasWriteAccess()) { + return false; + } + $fp = @fopen($this->filemap, 'wb'); if (!$fp) { return false; } + $this->filemap_cache = $files; fwrite($fp, serialize($files)); fclose($fp); return true; } - // }}} - // {{{ readFileMap() - - function readFileMap() + function _readFileMap() { + if (!file_exists($this->filemap)) { + return array(); + } + $fp = @fopen($this->filemap, 'r'); if (!$fp) { - return $this->raiseError('PEAR_Registry: could not open filemap', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg); + return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg); } - $fsize = filesize($this->filemap); + + clearstatcache(); $rt = get_magic_quotes_runtime(); set_magic_quotes_runtime(0); - $data = fread($fp, $fsize); - set_magic_quotes_runtime($rt); + $fsize = filesize($this->filemap); fclose($fp); + $data = file_get_contents($this->filemap); + set_magic_quotes_runtime($rt); $tmp = unserialize($data); if (!$tmp && $fsize > 7) { return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data); } + $this->filemap_cache = $tmp; return true; } - // }}} - // {{{ _lock() - /** * Lock the registry. * @@ -269,123 +811,822 @@ class PEAR_Registry extends PEAR */ function _lock($mode = LOCK_EX) { - if (!eregi('Windows 9', php_uname())) { - if ($mode != LOCK_UN && is_resource($this->lock_fp)) { - // XXX does not check type of lock (LOCK_SH/LOCK_EX) - return true; - } - if (PEAR::isError($err = $this->_assertStateDir())) { - return $err; - } - $open_mode = 'w'; - // XXX People reported problems with LOCK_SH and 'w' - if ($mode === LOCK_SH || $mode === LOCK_UN) { - if (@!is_file($this->lockfile)) { - touch($this->lockfile); - } - $open_mode = 'r'; - } - - if (!is_resource($this->lock_fp)) { - $this->lock_fp = @fopen($this->lockfile, $open_mode); - } - - if (!is_resource($this->lock_fp)) { - return $this->raiseError("could not create lock file" . - (isset($php_errormsg) ? ": " . $php_errormsg : "")); - } - if (!(int)flock($this->lock_fp, $mode)) { - switch ($mode) { - case LOCK_SH: $str = 'shared'; break; - case LOCK_EX: $str = 'exclusive'; break; - case LOCK_UN: $str = 'unlock'; break; - default: $str = 'unknown'; break; - } - return $this->raiseError("could not acquire $str lock ($this->lockfile)", - PEAR_REGISTRY_ERROR_LOCK); - } + if (stristr(php_uname(), 'Windows 9')) { + return true; } + + if ($mode != LOCK_UN && is_resource($this->lock_fp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + if (!$this->_assertStateDir()) { + if ($mode == LOCK_EX) { + return $this->raiseError('Registry directory is not writeable by the current user'); + } + + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH || $mode === LOCK_UN) { + if (!file_exists($this->lockfile)) { + touch($this->lockfile); + } + $open_mode = 'r'; + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = @fopen($this->lockfile, $open_mode); + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = null; + return $this->raiseError("could not create lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->lock_fp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + //is resource at this point, close it on error. + fclose($this->lock_fp); + $this->lock_fp = null; + return $this->raiseError("could not acquire $str lock ($this->lockfile)", + PEAR_REGISTRY_ERROR_LOCK); + } + return true; } - // }}} - // {{{ _unlock() - function _unlock() { $ret = $this->_lock(LOCK_UN); if (is_resource($this->lock_fp)) { fclose($this->lock_fp); } + $this->lock_fp = null; return $ret; } - // }}} - // {{{ _packageExists() - - function _packageExists($package) + function _packageExists($package, $channel = false) { - return file_exists($this->_packageFileName($package)); + return file_exists($this->_packageFileName($package, $channel)); } - // }}} - // {{{ _packageInfo() + /** + * Determine whether a channel exists in the registry + * + * @param string Channel name + * @param bool if true, then aliases will be ignored + * @return boolean + */ + function _channelExists($channel, $noaliases = false) + { + $a = file_exists($this->_channelFileName($channel, $noaliases)); + if (!$a && $channel == 'pear.php.net') { + return true; + } - function _packageInfo($package = null, $key = null) + if (!$a && $channel == 'pecl.php.net') { + return true; + } + + if (!$a && $channel == 'doc.php.net') { + return true; + } + + return $a; + } + + /** + * Determine whether a mirror exists within the deafult channel in the registry + * + * @param string Channel name + * @param string Mirror name + * + * @return boolean + */ + function _mirrorExists($channel, $mirror) + { + $data = $this->_channelInfo($channel); + if (!isset($data['servers']['mirror'])) { + return false; + } + + foreach ($data['servers']['mirror'] as $m) { + if ($m['attribs']['host'] == $mirror) { + return true; + } + } + + return false; + } + + /** + * @param PEAR_ChannelFile Channel object + * @param donotuse + * @param string Last-Modified HTTP tag from remote request + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function _addChannel($channel, $update = false, $lastmodified = false) + { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + + if (file_exists($this->_channelFileName($channel->getName()))) { + if (!$update) { + return false; + } + + $checker = $this->_getChannel($channel->getName()); + if (PEAR::isError($checker)) { + return $checker; + } + + if ($channel->getAlias() != $checker->getAlias()) { + if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) { + @unlink($this->_getChannelAliasFileName($checker->getAlias())); + } + } + } else { + if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) { + return false; + } + } + + $ret = $this->_assertChannelDir(); + if (PEAR::isError($ret)) { + return $ret; + } + + $ret = $this->_assertChannelStateDir($channel->getName()); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($channel->getAlias() != $channel->getName()) { + if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) && + $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) { + $channel->setAlias($channel->getName()); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w'); + if (!$fp) { + return false; + } + + fwrite($fp, $channel->getName()); + fclose($fp); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_channelFileName($channel->getName()), 'wb'); + if (!$fp) { + return false; + } + + $info = $channel->toArray(); + if ($lastmodified) { + $info['_lastmodified'] = $lastmodified; + } else { + $info['_lastmodified'] = date('r'); + } + + fwrite($fp, serialize($info)); + fclose($fp); + return true; + } + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function _deleteChannel($channel) + { + if (!is_string($channel)) { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + $channel = $channel->getName(); + } + + if ($this->_getChannelFromAlias($channel) == '__uri') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + return false; + } + + if (!$this->_channelExists($channel)) { + return false; + } + + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return false; + } + + $channel = $this->_getChannelFromAlias($channel); + if ($channel == 'pear.php.net') { + return false; + } + + $test = $this->_listChannelPackages($channel); + if (count($test)) { + return false; + } + + $test = @rmdir($this->_channelDirectoryName($channel)); + if (!$test) { + return false; + } + + $file = $this->_getChannelAliasFileName($this->_getAlias($channel)); + if (file_exists($file)) { + $test = @unlink($file); + if (!$test) { + return false; + } + } + + $file = $this->_channelFileName($channel); + $ret = true; + if (file_exists($file)) { + $ret = @unlink($file); + } + + return $ret; + } + + /** + * Determine whether a channel exists in the registry + * @param string Channel Alias + * @return boolean + */ + function _isChannelAlias($alias) + { + return file_exists($this->_getChannelAliasFileName($alias)); + } + + /** + * @param string|null + * @param string|null + * @param string|null + * @return array|null + * @access private + */ + function _packageInfo($package = null, $key = null, $channel = 'pear.php.net') { if ($package === null) { - return array_map(array($this, '_packageInfo'), - $this->_listPackages()); + if ($channel === null) { + $channels = $this->_listChannels(); + $ret = array(); + foreach ($channels as $channel) { + $channel = strtolower($channel); + $ret[$channel] = array(); + $packages = $this->_listPackages($channel); + foreach ($packages as $package) { + $ret[$channel][] = $this->_packageInfo($package, null, $channel); + } + } + + return $ret; + } + + $ps = $this->_listPackages($channel); + if (!count($ps)) { + return array(); + } + return array_map(array(&$this, '_packageInfo'), + $ps, array_fill(0, count($ps), null), + array_fill(0, count($ps), $channel)); } - $fp = $this->_openPackageFile($package, 'r'); + + $fp = $this->_openPackageFile($package, 'r', $channel); if ($fp === null) { return null; } + $rt = get_magic_quotes_runtime(); set_magic_quotes_runtime(0); - $data = fread($fp, filesize($this->_packageFileName($package))); - set_magic_quotes_runtime($rt); + clearstatcache(); $this->_closePackageFile($fp); + $data = file_get_contents($this->_packageFileName($package, $channel)); + set_magic_quotes_runtime($rt); $data = unserialize($data); if ($key === null) { return $data; } + + // compatibility for package.xml version 2.0 + if (isset($data['old'][$key])) { + return $data['old'][$key]; + } + if (isset($data[$key])) { return $data[$key]; } + return null; } - // }}} - // {{{ _listPackages() - - function _listPackages() + /** + * @param string Channel name + * @param bool whether to strictly retrieve info of channels, not just aliases + * @return array|null + */ + function _channelInfo($channel, $noaliases = false) { + if (!$this->_channelExists($channel, $noaliases)) { + return null; + } + + $fp = $this->_openChannelFile($channel, 'r'); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closeChannelFile($fp); + $data = file_get_contents($this->_channelFileName($channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + return $data; + } + + function _listChannels() + { + $channellist = array(); + if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) { + return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri'); + } + + $dp = opendir($this->channelsdir); + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + if ($ent == '__uri.reg') { + $channellist[] = '__uri'; + continue; + } + + $channellist[] = str_replace('_', '/', substr($ent, 0, -4)); + } + + closedir($dp); + if (!in_array('pear.php.net', $channellist)) { + $channellist[] = 'pear.php.net'; + } + + if (!in_array('pecl.php.net', $channellist)) { + $channellist[] = 'pecl.php.net'; + } + + if (!in_array('doc.php.net', $channellist)) { + $channellist[] = 'doc.php.net'; + } + + + if (!in_array('__uri', $channellist)) { + $channellist[] = '__uri'; + } + + natsort($channellist); + return $channellist; + } + + function _listPackages($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_listChannelPackages($channel); + } + + if (!file_exists($this->statedir) || !is_dir($this->statedir)) { + return array(); + } + $pkglist = array(); - $dp = @opendir($this->statedir); + $dp = opendir($this->statedir); if (!$dp) { return $pkglist; } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + $pkglist[] = substr($ent, 0, -4); + } + closedir($dp); + return $pkglist; + } + + function _listChannelPackages($channel) + { + $pkglist = array(); + if (!file_exists($this->_channelDirectoryName($channel)) || + !is_dir($this->_channelDirectoryName($channel))) { + return array(); + } + + $dp = opendir($this->_channelDirectoryName($channel)); + if (!$dp) { + return $pkglist; + } + while ($ent = readdir($dp)) { if ($ent{0} == '.' || substr($ent, -4) != '.reg') { continue; } $pkglist[] = substr($ent, 0, -4); } + + closedir($dp); return $pkglist; } - // }}} + function _listAllPackages() + { + $ret = array(); + foreach ($this->_listChannels() as $channel) { + $ret[$channel] = $this->_listPackages($channel); + } - // {{{ packageExists() + return $ret; + } - function packageExists($package) + /** + * Add an installed package to the registry + * @param string package name + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + * @access private + */ + function _addPackage($package, $info) + { + if ($this->_packageExists($package)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb'); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($info['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _addPackage2($info) + { + if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) { + return false; + } + + if (!$info->validate()) { + if (class_exists('PEAR_Common')) { + $ui = PEAR_Frontend::singleton(); + if ($ui) { + foreach ($info->getValidationWarnings() as $err) { + $ui->log($err['message'], true); + } + } + } + return false; + } + + $channel = $info->getChannel(); + $package = $info->getPackage(); + $save = $info; + if ($this->_packageExists($package, $channel)) { + return false; + } + + if (!$this->_channelExists($channel, true)) { + return false; + } + + $info = $info->toArray(true); + if (!$info) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb', $channel); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param array parsed package.xml 1.0 + * @param bool this parameter is only here for BC. Don't use it. + * @access private + */ + function _updatePackage($package, $info, $merge = true) + { + $oldinfo = $this->_packageInfo($package); + if (empty($oldinfo)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'w'); + if ($fp === null) { + return false; + } + + if (is_object($info)) { + $info = $info->toArray(); + } + $info['_lastmodified'] = time(); + + $newinfo = $info; + if ($merge) { + $info = array_merge($oldinfo, $info); + } else { + $diff = $info; + } + + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($newinfo['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _updatePackage2($info) + { + if (!$this->_packageExists($info->getPackage(), $info->getChannel())) { + return false; + } + + $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel()); + if ($fp === null) { + return false; + } + + $save = $info; + $info = $save->getArray(true); + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param string Channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + * @access private + */ + function &_getPackage($package, $channel = 'pear.php.net') + { + $info = $this->_packageInfo($package, null, $channel); + if ($info === null) { + return $info; + } + + $a = $this->_config; + if (!$a) { + $this->_config = &new PEAR_Config; + $this->_config->set('php_dir', $this->statedir); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->_config); + $pf = &$pkg->fromArray($info); + return $pf; + } + + /** + * @param string channel name + * @param bool whether to strictly retrieve channel names + * @return PEAR_ChannelFile|PEAR_Error + * @access private + */ + function &_getChannel($channel, $noaliases = false) + { + $ch = false; + if ($this->_channelExists($channel, $noaliases)) { + $chinfo = $this->_channelInfo($channel, $noaliases); + if ($chinfo) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo); + } + } + + if ($ch) { + if ($ch->validate()) { + return $ch; + } + + foreach ($ch->getErrors(true) as $err) { + $message = $err['message'] . "\n"; + } + + $ch = PEAR::raiseError($message); + return $ch; + } + + if ($this->_getChannelFromAlias($channel) == 'pear.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pecl.php.net'); + $pear_channel->setAlias('pecl'); + $pear_channel->setSummary('PHP Extension Community Library'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('phpdocs'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + return $doc_channel; + } + + + if ($this->_getChannelFromAlias($channel) == '__uri') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + return $private; + } + + return $ch; + } + + /** + * @param string Package name + * @param string Channel name + * @return bool + */ + function packageExists($package, $channel = 'pear.php.net') { if (PEAR::isError($e = $this->_lock(LOCK_SH))) { return $e; } - $ret = $this->_packageExists($package); + $ret = $this->_packageExists($package, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + + // {{{ channelExists() + + /** + * @param string channel name + * @param bool if true, then aliases will be ignored + * @return bool + */ + function channelExists($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelExists($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string channel name mirror is in + * @param string mirror name + * + * @return bool + */ + function mirrorExists($channel, $mirror) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + + $ret = $this->_mirrorExists($channel, $mirror); + $this->_unlock(); + return $ret; + } + + // {{{ isAlias() + + /** + * Determines whether the parameter is an alias of a channel + * @param string + * @return bool + */ + function isAlias($alias) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_isChannelAlias($alias); $this->_unlock(); return $ret; } @@ -393,25 +1634,105 @@ class PEAR_Registry extends PEAR // }}} // {{{ packageInfo() - function packageInfo($package = null, $key = null) + /** + * @param string|null + * @param string|null + * @param string + * @return array|null + */ + function packageInfo($package = null, $key = null, $channel = 'pear.php.net') { if (PEAR::isError($e = $this->_lock(LOCK_SH))) { return $e; } - $ret = $this->_packageInfo($package, $key); + $ret = $this->_packageInfo($package, $key, $channel); $this->_unlock(); return $ret; } // }}} - // {{{ listPackages() + // {{{ channelInfo() - function listPackages() + /** + * Retrieve a raw array of channel data. + * + * Do not use this, instead use {@link getChannel()} for normal + * operations. Array structure is undefined in this method + * @param string channel name + * @param bool whether to strictly retrieve information only on non-aliases + * @return array|null|PEAR_Error + */ + function channelInfo($channel = null, $noaliases = false) { if (PEAR::isError($e = $this->_lock(LOCK_SH))) { return $e; } - $ret = $this->_listPackages(); + $ret = $this->_channelInfo($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string + */ + function channelName($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getChannelFromAlias($channel); + $this->_unlock(); + return $ret; + } + + /** + * @param string + */ + function channelAlias($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getAlias($channel); + $this->_unlock(); + return $ret; + } + // {{{ listPackages() + + function listPackages($channel = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listPackages($channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listAllPackages() + + function listAllPackages() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listAllPackages(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listChannel() + + function listChannels() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listChannels(); $this->_unlock(); return $ret; } @@ -419,38 +1740,136 @@ class PEAR_Registry extends PEAR // }}} // {{{ addPackage() + /** + * Add an installed package to the registry + * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object + * that will be passed to {@link addPackage2()} + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + */ function addPackage($package, $info) { - if ($this->packageExists($package)) { - return false; + if (is_object($info)) { + return $this->addPackage2($info); } if (PEAR::isError($e = $this->_lock(LOCK_EX))) { return $e; } - $fp = $this->_openPackageFile($package, 'wb'); - if ($fp === null) { - $this->_unlock(); + $ret = $this->_addPackage($package, $info); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($info); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ addPackage2() + + function addPackage2($info) + { + if (!is_object($info)) { + return $this->addPackage($info['package'], $info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + return $ret; + } + + // }}} + // {{{ updateChannel() + + /** + * For future expandibility purposes, separate this + * @param PEAR_ChannelFile + */ + function updateChannel($channel, $lastmodified = null) + { + if ($channel->getName() == '__uri') { return false; } - $info['_lastmodified'] = time(); - fwrite($fp, serialize($info)); - $this->_closePackageFile($fp); + return $this->addChannel($channel, $lastmodified, true); + } + + // }}} + // {{{ deleteChannel() + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function deleteChannel($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_deleteChannel($channel); $this->_unlock(); - return true; + if ($ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ addChannel() + + /** + * @param PEAR_ChannelFile Channel object + * @param string Last-Modified header from HTTP for caching + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function addChannel($channel, $lastmodified = false, $update = false) + { + if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_addChannel($channel, $update, $lastmodified); + $this->_unlock(); + if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; } // }}} // {{{ deletePackage() - function deletePackage($package) + function deletePackage($package, $channel = 'pear.php.net') { if (PEAR::isError($e = $this->_lock(LOCK_EX))) { return $e; } - $file = $this->_packageFileName($package); - $ret = @unlink($file); - $this->rebuildFileMap(); + + $file = $this->_packageFileName($package, $channel); + $ret = file_exists($file) ? @unlink($file) : false; + $this->_rebuildFileMap(); $this->_unlock(); + $p = array('channel' => $channel, 'package' => $package); + $this->_dependencyDB->uninstallPackage($p); return $ret; } @@ -459,80 +1878,518 @@ class PEAR_Registry extends PEAR function updatePackage($package, $info, $merge = true) { - $oldinfo = $this->packageInfo($package); - if (empty($oldinfo)) { - return false; + if (is_object($info)) { + return $this->updatePackage2($info, $merge); } if (PEAR::isError($e = $this->_lock(LOCK_EX))) { return $e; } - $fp = $this->_openPackageFile($package, 'w'); - if ($fp === null) { - $this->_unlock(); + $ret = $this->_updatePackage($package, $info, $merge); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($this->packageInfo($package)); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ updatePackage2() + + function updatePackage2($info) + { + + if (!is_object($info)) { + return $this->updatePackage($info['package'], $info, $merge); + } + + if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) { return false; } - $info['_lastmodified'] = time(); - if ($merge) { - fwrite($fp, serialize(array_merge($oldinfo, $info))); - } else { - fwrite($fp, serialize($info)); + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; } - $this->_closePackageFile($fp); - if (isset($info['filelist'])) { - $this->rebuildFileMap(); + + $ret = $this->_updatePackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + + return $ret; + } + + // }}} + // {{{ getChannel() + /** + * @param string channel name + * @param bool whether to strictly return raw channels (no aliases) + * @return PEAR_ChannelFile|PEAR_Error + */ + function &getChannel($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = &$this->_getChannel($channel, $noaliases); + $this->_unlock(); + if (!$ret) { + return PEAR::raiseError('Unknown channel: ' . $channel); + } + return $ret; + } + + // }}} + // {{{ getPackage() + /** + * @param string package name + * @param string channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + */ + function &getPackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $pf = &$this->_getPackage($package, $channel); + $this->_unlock(); + return $pf; + } + + // }}} + + /** + * Get PEAR_PackageFile_v[1/2] objects representing the contents of + * a dependency group that are installed. + * + * This is used at uninstall-time + * @param array + * @return array|false + */ + function getInstalledGroup($group) + { + $ret = array(); + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + foreach ($group['package'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + foreach ($group['subpackage'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (!count($ret)) { + return false; + } + return $ret; + } + + // {{{ getChannelValidator() + /** + * @param string channel name + * @return PEAR_Validate|false + */ + function &getChannelValidator($channel) + { + $chan = $this->getChannel($channel); + if (PEAR::isError($chan)) { + return $chan; + } + $val = $chan->getValidationObject(); + return $val; + } + // }}} + // {{{ getChannels() + /** + * @param string channel name + * @return array an array of PEAR_ChannelFile objects representing every installed channel + */ + function &getChannels() + { + $ret = array(); + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + foreach ($this->_listChannels() as $channel) { + $e = &$this->_getChannel($channel); + if (!$e || PEAR::isError($e)) { + continue; + } + $ret[] = $e; } $this->_unlock(); - return true; + return $ret; } // }}} // {{{ checkFileMap() /** - * Test whether a file belongs to a package. + * Test whether a file or set of files belongs to a package. * - * @param string $path file path, absolute or relative to the pear - * install dir - * - * @return string which package the file belongs to, or an empty - * string if the file does not belong to an installed package - * - * @access public + * If an array is passed in + * @param string|array file path, absolute or relative to the pear + * install dir + * @param string|array name of PEAR package or array('package' => name, 'channel' => + * channel) of a package that will be ignored + * @param string API version - 1.1 will exclude any files belonging to a package + * @param array private recursion variable + * @return array|false which package and channel the file belongs to, or an empty + * string if the file does not belong to an installed package, + * or belongs to the second parameter's package */ - function checkFileMap($path) + function checkFileMap($path, $package = false, $api = '1.0', $attrs = false) { if (is_array($path)) { static $notempty; if (empty($notempty)) { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } $notempty = create_function('$a','return !empty($a);'); } + $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1])) + : strtolower($package); $pkgs = array(); foreach ($path as $name => $attrs) { - if (is_array($attrs) && isset($attrs['baseinstalldir'])) { - $name = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; + if (is_array($attrs)) { + if (isset($attrs['install-as'])) { + $name = $attrs['install-as']; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package; + } + if (isset($attrs['baseinstalldir'])) { + $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name; + } + } + $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs); + if (PEAR::isError($pkgs[$name])) { + return $pkgs[$name]; } - $pkgs[$name] = $this->checkFileMap($name); } return array_filter($pkgs, $notempty); } - if (empty($this->filemap_cache) && PEAR::isError($this->readFileMap())) { - return $err; + if (empty($this->filemap_cache)) { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $err = $this->_readFileMap(); + $this->_unlock(); + if (PEAR::isError($err)) { + return $err; + } } - if (isset($this->filemap_cache[$path])) { - return $this->filemap_cache[$path]; + if (!$attrs) { + $attrs = array('role' => 'php'); // any old call would be for PHP role only + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; } $l = strlen($this->install_dir); if (substr($path, 0, $l) == $this->install_dir) { $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l)); } - if (isset($this->filemap_cache[$path])) { - return $this->filemap_cache[$path]; + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; } - return ''; + return false; } // }}} + // {{{ flush() + /** + * Force a reload of the filemap + * @since 1.5.0RC3 + */ + function flushFileMap() + { + $this->filemap_cache = null; + clearstatcache(); // ensure that the next read gets the full, current filemap + } -} + // }}} + // {{{ apiVersion() + /** + * Get the expected API version. Channels API is version 1.1, as it is backwards + * compatible with 1.0 + * @return string + */ + function apiVersion() + { + return '1.1'; + } + // }}} -?> + + /** + * Parse a package name, or validate a parsed package name array + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',] + * ['group' => 'groupname']) + * or a string of format + * [channel://][channame/]pname[-version|-state][/group=groupname] + * @return array|PEAR_Error + */ + function parsePackageName($param, $defaultchannel = 'pear.php.net') + { + $saveparam = $param; + if (is_array($param)) { + // convert to string for error messages + $saveparam = $this->parsedPackageNameToString($param); + // process the array + if (!isset($param['package'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in index "param"', + 'package', null, null, $param); + } + if (!isset($param['uri'])) { + if (!isset($param['channel'])) { + $param['channel'] = $defaultchannel; + } + } else { + $param['channel'] = '__uri'; + } + } else { + $components = @parse_url((string) $param); + if (isset($components['scheme'])) { + if ($components['scheme'] == 'http') { + // uri package + $param = array('uri' => $param, 'channel' => '__uri'); + } elseif($components['scheme'] != 'channel') { + return PEAR::raiseError('parsePackageName(): only channel:// uris may ' . + 'be downloaded, not "' . $param . '"', 'invalid', null, null, $param); + } + } + if (!isset($components['path'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in "' . $param . '"', + 'package', null, null, $param); + } + if (isset($components['host'])) { + // remove the leading "/" + $components['path'] = substr($components['path'], 1); + } + if (!isset($components['scheme'])) { + if (strpos($components['path'], '/') !== false) { + if ($components['path']{0} == '/') { + return PEAR::raiseError('parsePackageName(): this is not ' . + 'a package name, it begins with "/" in "' . $param . '"', + 'invalid', null, null, $param); + } + $parts = explode('/', $components['path']); + $components['host'] = array_shift($parts); + if (count($parts) > 1) { + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } else { + $components['path'] = implode('/', $parts); + } + } else { + $components['host'] = $defaultchannel; + } + } else { + if (strpos($components['path'], '/')) { + $parts = explode('/', $components['path']); + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } + } + + if (is_array($param)) { + $param['package'] = $components['path']; + } else { + $param = array( + 'package' => $components['path'] + ); + if (isset($components['host'])) { + $param['channel'] = $components['host']; + } + } + if (isset($components['fragment'])) { + $param['group'] = $components['fragment']; + } + if (isset($components['user'])) { + $param['user'] = $components['user']; + } + if (isset($components['pass'])) { + $param['pass'] = $components['pass']; + } + if (isset($components['query'])) { + parse_str($components['query'], $param['opts']); + } + // check for extension + $pathinfo = pathinfo($param['package']); + if (isset($pathinfo['extension']) && + in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) { + $param['extension'] = $pathinfo['extension']; + $param['package'] = substr($pathinfo['basename'], 0, + strlen($pathinfo['basename']) - 4); + } + // check for version + if (strpos($param['package'], '-')) { + $test = explode('-', $param['package']); + if (count($test) != 2) { + return PEAR::raiseError('parsePackageName(): only one version/state ' . + 'delimiter "-" is allowed in "' . $saveparam . '"', + 'version', null, null, $param); + } + list($param['package'], $param['version']) = $test; + } + } + // validation + $info = $this->channelExists($param['channel']); + if (PEAR::isError($info)) { + return $info; + } + if (!$info) { + return PEAR::raiseError('unknown channel "' . $param['channel'] . + '" in "' . $saveparam . '"', 'channel', null, null, $param); + } + $chan = $this->getChannel($param['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + if (!$chan) { + return PEAR::raiseError("Exception: corrupt registry, could not " . + "retrieve channel " . $param['channel'] . " information", + 'registry', null, null, $param); + } + $param['channel'] = $chan->getName(); + $validate = $chan->getValidationObject(); + $vpackage = $chan->getValidationPackage(); + // validate package name + if (!$validate->validPackageName($param['package'], $vpackage['_content'])) { + return PEAR::raiseError('parsePackageName(): invalid package name "' . + $param['package'] . '" in "' . $saveparam . '"', + 'package', null, null, $param); + } + if (isset($param['group'])) { + if (!PEAR_Validate::validGroupName($param['group'])) { + return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] . + '" is not a valid group name in "' . $saveparam . '"', 'group', null, null, + $param); + } + } + if (isset($param['state'])) { + if (!in_array(strtolower($param['state']), $validate->getValidStates())) { + return PEAR::raiseError('parsePackageName(): state "' . $param['state'] + . '" is not a valid state in "' . $saveparam . '"', + 'state', null, null, $param); + } + } + if (isset($param['version'])) { + if (isset($param['state'])) { + return PEAR::raiseError('parsePackageName(): cannot contain both ' . + 'a version and a stability (state) in "' . $saveparam . '"', + 'version/state', null, null, $param); + } + // check whether version is actually a state + if (in_array(strtolower($param['version']), $validate->getValidStates())) { + $param['state'] = strtolower($param['version']); + unset($param['version']); + } else { + if (!$validate->validVersion($param['version'])) { + return PEAR::raiseError('parsePackageName(): "' . $param['version'] . + '" is neither a valid version nor a valid state in "' . + $saveparam . '"', 'version/state', null, null, $param); + } + } + } + return $param; + } + + /** + * @param array + * @return string + */ + function parsedPackageNameToString($parsed, $brief = false) + { + if (is_string($parsed)) { + return $parsed; + } + if (is_object($parsed)) { + $p = $parsed; + $parsed = array( + 'package' => $p->getPackage(), + 'channel' => $p->getChannel(), + 'version' => $p->getVersion(), + ); + } + if (isset($parsed['uri'])) { + return $parsed['uri']; + } + if ($brief) { + if ($channel = $this->channelAlias($parsed['channel'])) { + return $channel . '/' . $parsed['package']; + } + } + $upass = ''; + if (isset($parsed['user'])) { + $upass = $parsed['user']; + if (isset($parsed['pass'])) { + $upass .= ':' . $parsed['pass']; + } + $upass = "$upass@"; + } + $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package']; + if (isset($parsed['version']) || isset($parsed['state'])) { + $ver = isset($parsed['version']) ? $parsed['version'] : ''; + $ver .= isset($parsed['state']) ? $parsed['state'] : ''; + $ret .= '-' . $ver; + } + if (isset($parsed['extension'])) { + $ret .= '.' . $parsed['extension']; + } + if (isset($parsed['opts'])) { + $ret .= '?'; + foreach ($parsed['opts'] as $name => $value) { + $parsed['opts'][$name] = "$name=$value"; + } + $ret .= implode('&', $parsed['opts']); + } + if (isset($parsed['group'])) { + $ret .= '#' . $parsed['group']; + } + return $ret; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/RunTest.php b/3rdparty/PEAR/RunTest.php index 1aa02aab9d..5182490697 100644 --- a/3rdparty/PEAR/RunTest.php +++ b/3rdparty/PEAR/RunTest.php @@ -1,81 +1,830 @@ | -// | Greg Beaver | -// | | -// +----------------------------------------------------------------------+ -// -// $Id: RunTest.php,v 1.3.2.4 2005/02/17 17:47:55 cellog Exp $ -// - /** - * Simplified version of PHP's test suite - * -- EXPERIMENTAL -- - - Try it with: - - $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);' - - -TODO: - -Actually finish the development and testing - + * PEAR_RunTest + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: RunTest.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 */ +/** + * for error handling + */ require_once 'PEAR.php'; require_once 'PEAR/Config.php'; define('DETAILED', 1); putenv("PHP_PEAR_RUNTESTS=1"); +/** + * Simplified version of PHP's test suite + * + * Try it with: + * + * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);' + * + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 + */ class PEAR_RunTest { + var $_headers = array(); var $_logger; + var $_options; + var $_php; + var $tests_count; + var $xdebug_loaded; + /** + * Saved value of php executable, used to reset $_php when we + * have a test that uses cgi + * + * @var unknown_type + */ + var $_savephp; + var $ini_overwrites = array( + 'output_handler=', + 'open_basedir=', + 'safe_mode=0', + 'disable_functions=', + 'output_buffering=Off', + 'display_errors=1', + 'log_errors=0', + 'html_errors=0', + 'track_errors=1', + 'report_memleaks=0', + 'report_zend_debug=0', + 'docref_root=', + 'docref_ext=.html', + 'error_prepend_string=', + 'error_append_string=', + 'auto_prepend_file=', + 'auto_append_file=', + 'magic_quotes_runtime=0', + 'xdebug.default_enable=0', + 'allow_url_fopen=1', + ); /** * An object that supports the PEAR_Common->log() signature, or null * @param PEAR_Common|null */ - function PEAR_RunTest($logger = null) + function PEAR_RunTest($logger = null, $options = array()) { - $this->_logger = $logger; + if (!defined('E_DEPRECATED')) { + define('E_DEPRECATED', 0); + } + if (!defined('E_STRICT')) { + define('E_STRICT', 0); + } + $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT)); + if (is_null($logger)) { + require_once 'PEAR/Common.php'; + $logger = new PEAR_Common; + } + $this->_logger = $logger; + $this->_options = $options; + + $conf = &PEAR_Config::singleton(); + $this->_php = $conf->get('php_bin'); } - // - // Run an individual test case. - // - - function run($file, $ini_settings = '') + /** + * Taken from php-src/run-tests.php + * + * @param string $commandline command name + * @param array $env + * @param string $stdin standard input to pass to the command + * @return unknown + */ + function system_with_timeout($commandline, $env = null, $stdin = null) { + $data = ''; + if (version_compare(phpversion(), '5.0.0', '<')) { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes); + } else { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes, null, $env, array('suppress_errors' => true)); + } + + if (!$proc) { + return false; + } + + if (is_string($stdin)) { + fwrite($pipes[0], $stdin); + } + fclose($pipes[0]); + + while (true) { + /* hide errors from interrupted syscalls */ + $r = $pipes; + $e = $w = null; + $n = @stream_select($r, $w, $e, 60); + + if ($n === 0) { + /* timed out */ + $data .= "\n ** ERROR: process timed out **\n"; + proc_terminate($proc); + return array(1234567890, $data); + } else if ($n > 0) { + $line = fread($pipes[1], 8192); + if (strlen($line) == 0) { + /* EOF */ + break; + } + $data .= $line; + } + } + if (function_exists('proc_get_status')) { + $stat = proc_get_status($proc); + if ($stat['signaled']) { + $data .= "\nTermsig=".$stat['stopsig']; + } + } + $code = proc_close($proc); + if (function_exists('proc_get_status')) { + $code = $stat['exitcode']; + } + return array($code, $data); + } + + /** + * Turns a PHP INI string into an array + * + * Turns -d "include_path=/foo/bar" into this: + * array( + * 'include_path' => array( + * 'operator' => '-d', + * 'value' => '/foo/bar', + * ) + * ) + * Works both with quotes and without + * + * @param string an PHP INI string, -d "include_path=/foo/bar" + * @return array + */ + function iniString2array($ini_string) + { + if (!$ini_string) { + return array(); + } + $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY); + $key = $split[1][0] == '"' ? substr($split[1], 1) : $split[1]; + $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2]; + // FIXME review if this is really the struct to go with + $array = array($key => array('operator' => $split[0], 'value' => $value)); + return $array; + } + + function settings2array($settings, $ini_settings) + { + foreach ($settings as $setting) { + if (strpos($setting, '=') !== false) { + $setting = explode('=', $setting, 2); + $name = trim(strtolower($setting[0])); + $value = trim($setting[1]); + $ini_settings[$name] = $value; + } + } + return $ini_settings; + } + + function settings2params($ini_settings) + { + $settings = ''; + foreach ($ini_settings as $name => $value) { + if (is_array($value)) { + $operator = $value['operator']; + $value = $value['value']; + } else { + $operator = '-d'; + } + $value = addslashes($value); + $settings .= " $operator \"$name=$value\""; + } + return $settings; + } + + function _preparePhpBin($php, $file, $ini_settings) + { + $file = escapeshellarg($file); + // This was fixed in php 5.3 and is not needed after that + if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) { + $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"'; + } else { + $cmd = $php . $ini_settings . ' -f ' . $file; + } + + return $cmd; + } + + function runPHPUnit($file, $ini_settings = '') + { + if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) { + $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file); + } elseif (file_exists($file)) { + $file = realpath($file); + } + + $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings); + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + $savedir = getcwd(); // in case the test moves us around + chdir(dirname($file)); + echo `$cmd`; + chdir($savedir); + return 'PASSED'; // we have no way of knowing this information so assume passing + } + + /** + * Runs an individual test case. + * + * @param string The filename of the test + * @param array|string INI settings to be applied to the test run + * @param integer Number what the current running test is of the + * whole test suite being runned. + * + * @return string|object Returns PASSED, WARNED, FAILED depending on how the + * test came out. + * PEAR Error when the tester it self fails + */ + function run($file, $ini_settings = array(), $test_number = 1) + { + if (isset($this->_savephp)) { + $this->_php = $this->_savephp; + unset($this->_savephp); + } + if (empty($this->_options['cgi'])) { + // try to see if php-cgi is in the path + $res = $this->system_with_timeout('php-cgi -v'); + if (false !== $res && !(is_array($res) && in_array($res[0], array(-1, 127)))) { + $this->_options['cgi'] = 'php-cgi'; + } + } + if (1 < $len = strlen($this->tests_count)) { + $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT); + $test_nr = "[$test_number/$this->tests_count] "; + } else { + $test_nr = ''; + } + + $file = realpath($file); + $section_text = $this->_readFile($file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) { + return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file"); + } + $cwd = getcwd(); - $conf = &PEAR_Config::singleton(); - $php = $conf->get('php_bin'); - //var_dump($php);exit; - global $log_format, $info_params, $ini_overwrites; - $info_params = ''; - $log_format = 'LEOD'; + $pass_options = ''; + if (!empty($this->_options['ini'])) { + $pass_options = $this->_options['ini']; + } + if (is_string($ini_settings)) { + $ini_settings = $this->iniString2array($ini_settings); + } + + $ini_settings = $this->settings2array($this->ini_overwrites, $ini_settings); + if ($section_text['INI']) { + if (strpos($section_text['INI'], '{PWD}') !== false) { + $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); + } + $ini = preg_split( "/[\n\r]+/", $section_text['INI']); + $ini_settings = $this->settings2array($ini, $ini_settings); + } + $ini_settings = $this->settings2params($ini_settings); + $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file); + + $tested = trim($section_text['TEST']); + $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' '; + + if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) || + !empty($section_text['UPLOAD']) || !empty($section_text['GET']) || + !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { + if (empty($this->_options['cgi'])) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info'); + } + return 'SKIPPED'; + } + $this->_savephp = $this->_php; + $this->_php = $this->_options['cgi']; + } + + $temp_dir = realpath(dirname($file)); + $main_file_name = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + $this->_cleanupOldFiles($file); + + // Check if test should be skipped. + $res = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings); + if (count($res) != 2) { + return $res; + } + $info = $res['info']; + $warn = $res['warn']; + + // We've satisfied the preconditions - run the test! + if (isset($this->_options['coverage']) && $this->xdebug_loaded) { + $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug'; + $text = "\n" . 'function coverage_shutdown() {' . + "\n" . ' $xdebug = var_export(xdebug_get_code_coverage(), true);'; + if (!function_exists('file_put_contents')) { + $text .= "\n" . ' $fh = fopen(\'' . $xdebug_file . '\', "wb");' . + "\n" . ' if ($fh !== false) {' . + "\n" . ' fwrite($fh, $xdebug);' . + "\n" . ' fclose($fh);' . + "\n" . ' }'; + } else { + $text .= "\n" . ' file_put_contents(\'' . $xdebug_file . '\', $xdebug);'; + } + + // Workaround for http://pear.php.net/bugs/bug.php?id=17292 + $lines = explode("\n", $section_text['FILE']); + $numLines = count($lines); + $namespace = ''; + $coverage_shutdown = 'coverage_shutdown'; + + if ( + substr($lines[0], 0, 2) == 'save_text($temp_file, "save_text($temp_file, $section_text['FILE']); + } + + $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : ''; + $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings); + $cmd.= "$args 2>&1"; + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + // Reset environment from any previous test. + $env = $this->_resetEnv($section_text, $temp_file); + + $section_text = $this->_processUpload($section_text, $file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { + $post = trim($section_text['POST_RAW']); + $raw_lines = explode("\n", $post); + + $request = ''; + $started = false; + foreach ($raw_lines as $i => $line) { + if (empty($env['CONTENT_TYPE']) && + preg_match('/^Content-Type:(.*)/i', $line, $res)) { + $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); + continue; + } + if ($started) { + $request .= "\n"; + } + $started = true; + $request .= $line; + } + + $env['CONTENT_LENGTH'] = strlen($request); + $env['REQUEST_METHOD'] = 'POST'; + + $this->save_text($tmp_post, $request); + $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post"; + } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + $post = trim($section_text['POST']); + $this->save_text($tmp_post, $post); + $content_length = strlen($post); + + $env['REQUEST_METHOD'] = 'POST'; + $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $env['CONTENT_LENGTH'] = $content_length; + + $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post"; + } else { + $env['REQUEST_METHOD'] = 'GET'; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + } + + if (OS_WINDOWS && isset($section_text['RETURNS'])) { + ob_start(); + system($cmd, $return_value); + $out = ob_get_contents(); + ob_end_clean(); + $section_text['RETURNS'] = (int) trim($section_text['RETURNS']); + $returnfail = ($return_value != $section_text['RETURNS']); + } else { + $returnfail = false; + $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null; + $out = $this->system_with_timeout($cmd, $env, $stdin); + $return_value = $out[0]; + $out = $out[1]; + } + + $output = preg_replace('/\r\n/', "\n", trim($out)); + + if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) { + @unlink(realpath($tmp_post)); + } + chdir($cwd); // in case the test moves us around + + $this->_testCleanup($section_text, $temp_clean); + + /* when using CGI, strip the headers from the output */ + $output = $this->_stripHeadersCGI($output); + + if (isset($section_text['EXPECTHEADERS'])) { + $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']); + $missing = array_diff_assoc($testheaders, $this->_headers); + $changed = ''; + foreach ($missing as $header => $value) { + if (isset($this->_headers[$header])) { + $changed .= "-$header: $value\n+$header: "; + $changed .= $this->_headers[$header]; + } else { + $changed .= "-$header: $value\n"; + } + } + if ($missing) { + // tack on failed headers to output: + $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed"; + } + } + // Does the output match what is expected? + do { + if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { + if (isset($section_text['EXPECTF'])) { + $wanted = trim($section_text['EXPECTF']); + } else { + $wanted = trim($section_text['EXPECTREGEX']); + } + $wanted_re = preg_replace('/\r\n/', "\n", $wanted); + if (isset($section_text['EXPECTF'])) { + $wanted_re = preg_quote($wanted_re, '/'); + // Stick to basics + $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy + $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re); + $wanted_re = str_replace("%d", "[0-9]+", $wanted_re); + $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re); + $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re); + $wanted_re = str_replace("%c", ".", $wanted_re); + // %f allows two points "-.0.0" but that is the best *simple* expression + } + + /* DEBUG YOUR REGEX HERE + var_dump($wanted_re); + print(str_repeat('=', 80) . "\n"); + var_dump($output); + */ + if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } else { + if (isset($section_text['EXPECTFILE'])) { + $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']); + if (!($fp = @fopen($f, 'rb'))) { + return PEAR::raiseError('--EXPECTFILE-- section file ' . + $f . ' not found'); + } + fclose($fp); + $section_text['EXPECT'] = file_get_contents($f); + } + + if (isset($section_text['EXPECT'])) { + $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT'])); + } else { + $wanted = ''; + } + + // compare and leave on success + if (!$returnfail && 0 == strcmp($output, $wanted)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } + } while (false); + + if (array_key_exists('FAIL', $section_text)) { + // we expect a particular failure + // this is only used for testing PEAR_RunTest + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $faildiff = $this->generate_diff($wanted, $output, null, $expectf); + $faildiff = preg_replace('/\r/', '', $faildiff); + $wanted = preg_replace('/\r/', '', trim($section_text['FAIL'])); + if ($faildiff == $wanted) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + unset($section_text['EXPECTF']); + $output = $faildiff; + if (isset($section_text['RETURNS'])) { + return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' . + $file); + } + } + + // Test failed so we need to report details. + $txt = $warn ? 'WARN ' : 'FAIL '; + $this->_logger->log(0, $txt . $test_nr . $tested . $info); + + // write .exp + $res = $this->_writeLog($exp_filename, $wanted); + if (PEAR::isError($res)) { + return $res; + } + + // write .out + $res = $this->_writeLog($output_filename, $output); + if (PEAR::isError($res)) { + return $res; + } + + // write .diff + $returns = isset($section_text['RETURNS']) ? + array(trim($section_text['RETURNS']), $return_value) : null; + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $data = $this->generate_diff($wanted, $output, $returns, $expectf); + $res = $this->_writeLog($diff_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + // write .log + $data = " +---- EXPECTED OUTPUT +$wanted +---- ACTUAL OUTPUT +$output +---- FAILED +"; + + if ($returnfail) { + $data .= " +---- EXPECTED RETURN +$section_text[RETURNS] +---- ACTUAL RETURN +$return_value +"; + } + + $res = $this->_writeLog($log_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + if (isset($this->_options['tapoutput'])) { + $wanted = explode("\n", $wanted); + $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted); + $output = explode("\n", $output); + $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output); + return array($wanted . $output . 'not ok', ' - ' . $tested); + } + return $warn ? 'WARNED' : 'FAILED'; + } + + function generate_diff($wanted, $output, $rvalue, $wanted_re) + { + $w = explode("\n", $wanted); + $o = explode("\n", $output); + $wr = explode("\n", $wanted_re); + $w1 = array_diff_assoc($w, $o); + $o1 = array_diff_assoc($o, $w); + $o2 = $w2 = array(); + foreach ($w1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) { + $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val; + } + } + foreach ($o1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $val)) { + $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val; + } + } + $diff = array_merge($w2, $o2); + ksort($diff); + $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : ''; + return implode("\r\n", $diff) . $extra; + } + + // Write the given text to a temporary file, and return the filename. + function save_text($filename, $text) + { + if (!$fp = fopen($filename, 'w')) { + return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)"); + } + fwrite($fp, $text); + fclose($fp); + if (1 < DETAILED) echo " +FILE $filename {{{ +$text +}}} +"; + } + + function _cleanupOldFiles($file) + { + $temp_dir = realpath(dirname($file)); + $mainFileName = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + @unlink($diff_filename); + @unlink($log_filename); + @unlink($exp_filename); + @unlink($output_filename); + @unlink($memcheck_filename); + @unlink($temp_file); + @unlink($temp_skipif); + @unlink($tmp_post); + @unlink($temp_clean); + } + + function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings) + { + $info = ''; + $warn = false; + if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) { + $this->save_text($temp_skipif, $section_text['SKIPIF']); + $output = $this->system_with_timeout("$this->_php$ini_settings -f \"$temp_skipif\""); + $output = $output[1]; + $loutput = ltrim($output); + unlink($temp_skipif); + if (!strncasecmp('skip', $loutput, 4)) { + $skipreason = "SKIP $tested"; + if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { + $skipreason .= '(reason: ' . $m[1] . ')'; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, $skipreason); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip ' . $reason); + } + return 'SKIPPED'; + } + + if (!strncasecmp('info', $loutput, 4) + && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { + $info = " (info: $m[1])"; + } + + if (!strncasecmp('warn', $loutput, 4) + && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { + $warn = true; /* only if there is a reason */ + $info = " (warn: $m[1])"; + } + } + + return array('warn' => $warn, 'info' => $info); + } + + function _stripHeadersCGI($output) + { + $this->headers = array(); + if (!empty($this->_options['cgi']) && + $this->_php == $this->_options['cgi'] && + preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) { + $output = isset($match[2]) ? trim($match[2]) : ''; + $this->_headers = $this->_processHeaders($match[1]); + } + + return $output; + } + + /** + * Return an array that can be used with array_diff() to compare headers + * + * @param string $text + */ + function _processHeaders($text) + { + $headers = array(); + $rh = preg_split("/[\n\r]+/", $text); + foreach ($rh as $line) { + if (strpos($line, ':')!== false) { + $line = explode(':', $line, 2); + $headers[trim($line[0])] = trim($line[1]); + } + } + return $headers; + } + + function _readFile($file) + { // Load the sections of the test file. $section_text = array( - 'TEST' => '(unnamed test)', - 'SKIPIF' => '', - 'GET' => '', - 'ARGS' => '', + 'TEST' => '(unnamed test)', + 'SKIPIF' => '', + 'GET' => '', + 'COOKIE' => '', + 'POST' => '', + 'ARGS' => '', + 'INI' => '', + 'CLEAN' => '', ); if (!is_file($file) || !$fp = fopen($file, "r")) { @@ -87,10 +836,13 @@ class PEAR_RunTest $line = fgets($fp); // Match the beginning of a section. - if (ereg('^--([A-Z]+)--',$line,$r)) { + if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { $section = $r[1]; $section_text[$section] = ''; continue; + } elseif (empty($section)) { + fclose($fp); + return PEAR::raiseError("Invalid sections formats in test file: $file"); } // Add to the section text. @@ -98,266 +850,119 @@ class PEAR_RunTest } fclose($fp); - $shortname = str_replace($cwd.'/', '', $file); - $tested = trim($section_text['TEST'])." [$shortname]"; - - $tmp = realpath(dirname($file)); - $tmp_skipif = $tmp . uniqid('/phpt.'); - $tmp_file = ereg_replace('\.phpt$','.php',$file); - $tmp_post = $tmp . uniqid('/phpt.'); - - @unlink($tmp_skipif); - @unlink($tmp_file); - @unlink($tmp_post); - - // unlink old test results - @unlink(ereg_replace('\.phpt$','.diff',$file)); - @unlink(ereg_replace('\.phpt$','.log',$file)); - @unlink(ereg_replace('\.phpt$','.exp',$file)); - @unlink(ereg_replace('\.phpt$','.out',$file)); - - // Check if test should be skipped. - $info = ''; - $warn = false; - if (array_key_exists('SKIPIF', $section_text)) { - if (trim($section_text['SKIPIF'])) { - $this->save_text($tmp_skipif, $section_text['SKIPIF']); - //$extra = substr(PHP_OS, 0, 3) !== "WIN" ? - // "unset REQUEST_METHOD;": ""; - - //$output = `$extra $php $info_params -f $tmp_skipif`; - $output = `$php $info_params -f $tmp_skipif`; - unlink($tmp_skipif); - if (eregi("^skip", trim($output))) { - $skipreason = "SKIP $tested"; - $reason = (eregi("^skip[[:space:]]*(.+)\$", trim($output))) ? eregi_replace("^skip[[:space:]]*(.+)\$", "\\1", trim($output)) : FALSE; - if ($reason) { - $skipreason .= " (reason: $reason)"; - } - $this->_logger->log(0, $skipreason); - if (isset($old_php)) { - $php = $old_php; - } - return 'SKIPPED'; - } - if (eregi("^info", trim($output))) { - $reason = (ereg("^info[[:space:]]*(.+)\$", trim($output))) ? ereg_replace("^info[[:space:]]*(.+)\$", "\\1", trim($output)) : FALSE; - if ($reason) { - $info = " (info: $reason)"; - } - } - if (eregi("^warn", trim($output))) { - $reason = (ereg("^warn[[:space:]]*(.+)\$", trim($output))) ? ereg_replace("^warn[[:space:]]*(.+)\$", "\\1", trim($output)) : FALSE; - if ($reason) { - $warn = true; /* only if there is a reason */ - $info = " (warn: $reason)"; - } - } - } - } - - // We've satisfied the preconditions - run the test! - $this->save_text($tmp_file,$section_text['FILE']); - - $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : ''; - - $cmd = "$php$ini_settings -f $tmp_file$args 2>&1"; - if (isset($this->_logger)) { - $this->_logger->log(2, 'Running command "' . $cmd . '"'); - } - - $savedir = getcwd(); // in case the test moves us around - if (isset($section_text['RETURNS'])) { - ob_start(); - system($cmd, $return_value); - $out = ob_get_contents(); - ob_end_clean(); - @unlink($tmp_post); - $section_text['RETURNS'] = (int) trim($section_text['RETURNS']); - $returnfail = ($return_value != $section_text['RETURNS']); - } else { - $out = `$cmd`; - $returnfail = false; - } - chdir($savedir); - // Does the output match what is expected? - $output = trim($out); - $output = preg_replace('/\r\n/', "\n", $output); - - if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { - if (isset($section_text['EXPECTF'])) { - $wanted = trim($section_text['EXPECTF']); - } else { - $wanted = trim($section_text['EXPECTREGEX']); - } - $wanted_re = preg_replace('/\r\n/',"\n",$wanted); - if (isset($section_text['EXPECTF'])) { - $wanted_re = preg_quote($wanted_re, '/'); - // Stick to basics - $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy - $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re); - $wanted_re = str_replace("%d", "[0-9]+", $wanted_re); - $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re); - $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re); - $wanted_re = str_replace("%c", ".", $wanted_re); - // %f allows two points "-.0.0" but that is the best *simple* expression - } - /* DEBUG YOUR REGEX HERE - var_dump($wanted_re); - print(str_repeat('=', 80) . "\n"); - var_dump($output); - */ - if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) { - @unlink($tmp_file); - $this->_logger->log(0, "PASS $tested$info"); - if (isset($old_php)) { - $php = $old_php; - } - return 'PASSED'; - } - - } else { - $wanted = trim($section_text['EXPECT']); - $wanted = preg_replace('/\r\n/',"\n",$wanted); - // compare and leave on success - $ok = (0 == strcmp($output,$wanted)); - if (!$returnfail && $ok) { - @unlink($tmp_file); - $this->_logger->log(0, "PASS $tested$info"); - if (isset($old_php)) { - $php = $old_php; - } - return 'PASSED'; - } - } - - // Test failed so we need to report details. - if ($warn) { - $this->_logger->log(0, "WARN $tested$info"); - } else { - $this->_logger->log(0, "FAIL $tested$info"); - } - - if (isset($section_text['RETURNS'])) { - $GLOBALS['__PHP_FAILED_TESTS__'][] = array( - 'name' => $file, - 'test_name' => $tested, - 'output' => ereg_replace('\.phpt$','.log', $file), - 'diff' => ereg_replace('\.phpt$','.diff', $file), - 'info' => $info, - 'return' => $return_value - ); - } else { - $GLOBALS['__PHP_FAILED_TESTS__'][] = array( - 'name' => $file, - 'test_name' => $tested, - 'output' => ereg_replace('\.phpt$','.log', $file), - 'diff' => ereg_replace('\.phpt$','.diff', $file), - 'info' => $info, - ); - } - - // write .exp - if (strpos($log_format,'E') !== FALSE) { - $logname = ereg_replace('\.phpt$','.exp',$file); - if (!$log = fopen($logname,'w')) { - return PEAR::raiseError("Cannot create test log - $logname"); - } - fwrite($log,$wanted); - fclose($log); - } - - // write .out - if (strpos($log_format,'O') !== FALSE) { - $logname = ereg_replace('\.phpt$','.out',$file); - if (!$log = fopen($logname,'w')) { - return PEAR::raiseError("Cannot create test log - $logname"); - } - fwrite($log,$output); - fclose($log); - } - - // write .diff - if (strpos($log_format,'D') !== FALSE) { - $logname = ereg_replace('\.phpt$','.diff',$file); - if (!$log = fopen($logname,'w')) { - return PEAR::raiseError("Cannot create test log - $logname"); - } - fwrite($log, $this->generate_diff($wanted, $output, - isset($section_text['RETURNS']) ? array(trim($section_text['RETURNS']), - $return_value) : null)); - fclose($log); - } - - // write .log - if (strpos($log_format,'L') !== FALSE) { - $logname = ereg_replace('\.phpt$','.log',$file); - if (!$log = fopen($logname,'w')) { - return PEAR::raiseError("Cannot create test log - $logname"); - } - fwrite($log," ----- EXPECTED OUTPUT -$wanted ----- ACTUAL OUTPUT -$output ----- FAILED -"); - if ($returnfail) { - fwrite($log," ----- EXPECTED RETURN -$section_text[RETURNS] ----- ACTUAL RETURN -$return_value -"); - } - fclose($log); - //error_report($file,$logname,$tested); - } - - if (isset($old_php)) { - $php = $old_php; - } - - return $warn ? 'WARNED' : 'FAILED'; + return $section_text; } - function generate_diff($wanted, $output, $return_value) + function _writeLog($logname, $data) { - $w = explode("\n", $wanted); - $o = explode("\n", $output); - $w1 = array_diff_assoc($w,$o); - $o1 = array_diff_assoc($o,$w); - $w2 = array(); - $o2 = array(); - foreach($w1 as $idx => $val) $w2[sprintf("%03d<",$idx)] = sprintf("%03d- ", $idx+1).$val; - foreach($o1 as $idx => $val) $o2[sprintf("%03d>",$idx)] = sprintf("%03d+ ", $idx+1).$val; - $diff = array_merge($w2, $o2); - ksort($diff); - if ($return_value) { - $extra = "##EXPECTED: $return_value[0]\r\n##RETURNED: $return_value[1]"; - } else { - $extra = ''; + if (!$log = fopen($logname, 'w')) { + return PEAR::raiseError("Cannot create test log - $logname"); } - return implode("\r\n", $diff) . $extra; + fwrite($log, $data); + fclose($log); } - // - // Write the given text to a temporary file, and return the filename. - // - - function save_text($filename, $text) + function _resetEnv($section_text, $temp_file) { - if (!$fp = fopen($filename, 'w')) { - return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)"); + $env = $_ENV; + $env['REDIRECT_STATUS'] = ''; + $env['QUERY_STRING'] = ''; + $env['PATH_TRANSLATED'] = ''; + $env['SCRIPT_FILENAME'] = ''; + $env['REQUEST_METHOD'] = ''; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + if (!empty($section_text['ENV'])) { + if (strpos($section_text['ENV'], '{PWD}') !== false) { + $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']); + } + foreach (explode("\n", trim($section_text['ENV'])) as $e) { + $e = explode('=', trim($e), 2); + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } } - fwrite($fp,$text); - fclose($fp); - if (1 < DETAILED) echo " -FILE $filename {{{ -$text -}}} -"; + if (array_key_exists('GET', $section_text)) { + $env['QUERY_STRING'] = trim($section_text['GET']); + } else { + $env['QUERY_STRING'] = ''; + } + if (array_key_exists('COOKIE', $section_text)) { + $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); + } else { + $env['HTTP_COOKIE'] = ''; + } + $env['REDIRECT_STATUS'] = '1'; + $env['PATH_TRANSLATED'] = $temp_file; + $env['SCRIPT_FILENAME'] = $temp_file; + + return $env; } + function _processUpload($section_text, $file) + { + if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) { + $upload_files = trim($section_text['UPLOAD']); + $upload_files = explode("\n", $upload_files); + + $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" . + "-----------------------------20896060251896012921717172737\n"; + foreach ($upload_files as $fileinfo) { + $fileinfo = explode('=', $fileinfo); + if (count($fileinfo) != 2) { + return PEAR::raiseError("Invalid UPLOAD section in test file: $file"); + } + if (!realpath(dirname($file) . '/' . $fileinfo[1])) { + return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " . + "in test file: $file"); + } + $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]); + $fileinfo[1] = basename($fileinfo[1]); + $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n"; + $request .= "Content-Type: text/plain\n\n"; + $request .= $file_contents . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + + if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + // encode POST raw + $post = trim($section_text['POST']); + $post = explode('&', $post); + foreach ($post as $i => $post_info) { + $post_info = explode('=', $post_info); + if (count($post_info) != 2) { + return PEAR::raiseError("Invalid POST data in test file: $file"); + } + $post_info[0] = rawurldecode($post_info[0]); + $post_info[1] = rawurldecode($post_info[1]); + $post[$i] = $post_info; + } + foreach ($post as $post_info) { + $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n"; + $request .= $post_info[1] . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + unset($section_text['POST']); + } + $section_text['POST_RAW'] = $request; + } + + return $section_text; + } + + function _testCleanup($section_text, $temp_clean) + { + if ($section_text['CLEAN']) { + // perform test cleanup + $this->save_text($temp_clean, $section_text['CLEAN']); + $output = $this->system_with_timeout("$this->_php $temp_clean 2>&1"); + if (strlen($output[1])) { + echo "BORKED --CLEAN-- section! output:\n", $output[1]; + } + if (file_exists($temp_clean)) { + unlink($temp_clean); + } + } + } } -?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Common.php b/3rdparty/PEAR/Task/Common.php new file mode 100644 index 0000000000..5b99c2e434 --- /dev/null +++ b/3rdparty/PEAR/Task/Common.php @@ -0,0 +1,202 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Error codes for task validation routines + */ +define('PEAR_TASK_ERROR_NOATTRIBS', 1); +define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2); +define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3); +define('PEAR_TASK_ERROR_INVALID', 4); +/**#@-*/ +define('PEAR_TASK_PACKAGE', 1); +define('PEAR_TASK_INSTALL', 2); +define('PEAR_TASK_PACKAGEANDINSTALL', 3); +/** + * A task is an operation that manipulates the contents of a file. + * + * Simple tasks operate on 1 file. Multiple tasks are executed after all files have been + * processed and installed, and are designed to operate on all files containing the task. + * The Post-install script task simply takes advantage of the fact that it will be run + * after installation, replace is a simple task. + * + * Combining tasks is possible, but ordering is significant. + * + * + * + * + * + * + * This will first replace any instance of @data-dir@ in the test.php file + * with the path to the current data directory. Then, it will include the + * test.php file and run the script it contains to configure the package post-installation. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @abstract + */ +class PEAR_Task_Common +{ + /** + * Valid types for this version are 'simple' and 'multiple' + * + * - simple tasks operate on the contents of a file and write out changes to disk + * - multiple tasks operate on the contents of many files and write out the + * changes directly to disk + * + * Child task classes must override this property. + * @access protected + */ + var $type = 'simple'; + /** + * Determines which install phase this task is executed under + */ + var $phase = PEAR_TASK_INSTALL; + /** + * @access protected + */ + var $config; + /** + * @access protected + */ + var $registry; + /** + * @access protected + */ + var $logger; + /** + * @access protected + */ + var $installphase; + /** + * @param PEAR_Config + * @param PEAR_Common + */ + function PEAR_Task_Common(&$config, &$logger, $phase) + { + $this->config = &$config; + $this->registry = &$config->getRegistry(); + $this->logger = &$logger; + $this->installphase = $phase; + if ($this->type == 'multiple') { + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this; + } + } + + /** + * Validate the basic contents of a task tag. + * @param PEAR_PackageFile_v2 + * @param array + * @param PEAR_Config + * @param array the entire parsed tag + * @return true|array On error, return an array in format: + * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * + * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in + * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array + * of legal values in + * @static + * @abstract + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package + * @abstract + */ + function init($xml, $fileAttributes, $lastVersion) + { + } + + /** + * Begin a task processing session. All multiple tasks will be processed after each file + * has been successfully installed, all simple tasks should perform their task here and + * return any errors using the custom throwError() method to allow forward compatibility + * + * This method MUST NOT write out any changes to disk + * @param PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + * @abstract + */ + function startSession($pkg, $contents, $dest) + { + } + + /** + * This method is used to process each of the tasks for a particular multiple class + * type. Simple tasks need not implement this method. + * @param array an array of tasks + * @access protected + * @static + * @abstract + */ + function run($tasks) + { + } + + /** + * @static + * @final + */ + function hasPostinstallTasks() + { + return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * @static + * @final + */ + function runPostinstallTasks() + { + foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { + $err = call_user_func(array($class, 'run'), + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]); + if ($err) { + return PEAR_Task_Common::throwError($err); + } + } + unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * Determines whether a role is a script + * @return bool + */ + function isScript() + { + return $this->type == 'script'; + } + + function throwError($msg, $code = -1) + { + include_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Postinstallscript.php b/3rdparty/PEAR/Task/Postinstallscript.php new file mode 100644 index 0000000000..e43ecca4b3 --- /dev/null +++ b/3rdparty/PEAR/Task/Postinstallscript.php @@ -0,0 +1,323 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Postinstallscript.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the postinstallscript file task. + * + * Note that post-install scripts are handled separately from installation, by the + * "pear run-scripts" command + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Postinstallscript extends PEAR_Task_Common +{ + var $type = 'script'; + var $_class; + var $_params; + var $_obj; + /** + * + * @var PEAR_PackageFile_v2 + */ + var $_pkg; + var $_contents; + var $phase = PEAR_TASK_INSTALL; + + /** + * Validate the raw xml at parsing-time. + * + * This also attempts to validate the script to make sure it meets the criteria + * for a post-install script + * @param PEAR_PackageFile_v2 + * @param array The XML contents of the tag + * @param PEAR_Config + * @param array the entire parsed tag + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($fileXml['role'] != 'php') { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must be role="php"'); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $file = $pkg->getFileContents($fileXml['name']); + if (PEAR::isError($file)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" is not valid: ' . + $file->getMessage()); + } elseif ($file === null) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" could not be retrieved for processing!'); + } else { + $analysis = $pkg->analyzeSourceCode($file, true); + if (!$analysis) { + PEAR::popErrorHandling(); + $warnings = ''; + foreach ($pkg->getValidationWarnings() as $warn) { + $warnings .= $warn['message'] . "\n"; + } + return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' . + $fileXml['name'] . '" failed: ' . $warnings); + } + if (count($analysis['declared_classes']) != 1) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare exactly 1 class'); + } + $class = $analysis['declared_classes'][0]; + if ($class != str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall') { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" class "' . $class . '" must be named "' . + str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall"'); + } + if (!isset($analysis['declared_methods'][$class])) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + $methods = array('init' => 0, 'run' => 1); + foreach ($analysis['declared_methods'][$class] as $method) { + if (isset($methods[$method])) { + unset($methods[$method]); + } + } + if (count($methods)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + } + PEAR::popErrorHandling(); + $definedparams = array(); + $tasksNamespace = $pkg->getTasksNs() . ':'; + if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) { + // in order to support the older betas, which did not expect internal tags + // to also use the namespace + $tasksNamespace = ''; + } + if (isset($xml[$tasksNamespace . 'paramgroup'])) { + $params = $xml[$tasksNamespace . 'paramgroup']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + foreach ($params as $param) { + if (!isset($param[$tasksNamespace . 'id'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must have ' . + 'an ' . $tasksNamespace . 'id> tag'); + } + if (isset($param[$tasksNamespace . 'name'])) { + if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" parameter "' . $param[$tasksNamespace . 'name'] . + '" has not been previously defined'); + } + if (!isset($param[$tasksNamespace . 'conditiontype'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!in_array($param[$tasksNamespace . 'conditiontype'], + array('=', '!=', 'preg_match'))) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!isset($param[$tasksNamespace . 'value'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'value> tag containing expected parameter value'); + } + } + if (isset($param[$tasksNamespace . 'instructions'])) { + if (!is_string($param[$tasksNamespace . 'instructions'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" ' . $tasksNamespace . 'instructions> must be simple text'); + } + } + if (!isset($param[$tasksNamespace . 'param'])) { + continue; // is no longer required + } + $subparams = $param[$tasksNamespace . 'param']; + if (!is_array($subparams) || !isset($subparams[0])) { + $subparams = array($subparams); + } + foreach ($subparams as $subparam) { + if (!isset($subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter for ' . + $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . '" must have ' . + 'a ' . $tasksNamespace . 'name> tag'); + } + if (!preg_match('/[a-zA-Z0-9]+/', + $subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" is not a valid name. Must contain only alphanumeric characters'); + } + if (!isset($subparam[$tasksNamespace . 'prompt'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'prompt> tag'); + } + if (!isset($subparam[$tasksNamespace . 'type'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'type> tag'); + } + $definedparams[] = $param[$tasksNamespace . 'id'] . '::' . + $subparam[$tasksNamespace . 'name']; + } + } + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package, if any (useful for upgrades) + */ + function init($xml, $fileattribs, $lastversion) + { + $this->_class = str_replace('/', '_', $fileattribs['name']); + $this->_filename = $fileattribs['name']; + $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall'; + $this->_params = $xml; + $this->_lastversion = $lastversion; + } + + /** + * Strip the tasks: namespace from internal params + * + * @access private + */ + function _stripNamespace($params = null) + { + if ($params === null) { + $params = array(); + if (!is_array($this->_params)) { + return; + } + foreach ($this->_params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + $this->_params = $params; + } else { + $newparams = array(); + foreach ($params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + return $newparams; + } + } + + /** + * Unlike other tasks, the installed file name is passed in instead of the file contents, + * because this task is handled post-installation + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file name + * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError) + */ + function startSession($pkg, $contents) + { + if ($this->installphase != PEAR_TASK_INSTALL) { + return false; + } + // remove the tasks: namespace if present + $this->_pkg = $pkg; + $this->_stripNamespace(); + $this->logger->log(0, 'Including external post-installation script "' . + $contents . '" - any errors are in this script'); + include_once $contents; + if (class_exists($this->_class)) { + $this->logger->log(0, 'Inclusion succeeded'); + } else { + return $this->throwError('init of post-install script class "' . $this->_class + . '" failed'); + } + $this->_obj = new $this->_class; + $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $res = $this->_obj->init($this->config, $pkg, $this->_lastversion); + PEAR::popErrorHandling(); + if ($res) { + $this->logger->log(0, 'init succeeded'); + } else { + return $this->throwError('init of post-install script "' . $this->_class . + '->init()" failed'); + } + $this->_contents = $contents; + return true; + } + + /** + * No longer used + * @see PEAR_PackageFile_v2::runPostinstallScripts() + * @param array an array of tasks + * @param string install or upgrade + * @access protected + * @static + */ + function run() + { + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Postinstallscript/rw.php b/3rdparty/PEAR/Task/Postinstallscript/rw.php new file mode 100644 index 0000000000..8f358bf22b --- /dev/null +++ b/3rdparty/PEAR/Task/Postinstallscript/rw.php @@ -0,0 +1,169 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Postinstallscript.php'; +/** + * Abstracts the postinstallscript file task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript +{ + /** + * parent package file object + * + * @var PEAR_PackageFile_v2_rw + */ + var $_pkg; + /** + * Enter description here... + * + * @param PEAR_PackageFile_v2_rw $pkg + * @param PEAR_Config $config + * @param PEAR_Frontend $logger + * @param array $fileXml + * @return PEAR_Task_Postinstallscript_rw + */ + function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function getName() + { + return 'postinstallscript'; + } + + /** + * add a simple to the post-install script + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addConditionTypeGroup()} to add a containing + * a tag + * @param string $id id as seen by the script + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addParamGroup($id, $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = + array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + /** + * add a complex to the post-install script with conditions + * + * This inserts a with + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addParamGroup()} to add a simple + * + * @param string $id id as seen by the script + * @param string $oldgroup id of the section referenced by + * + * @param string $param name of the from the older section referenced + * by + * @param string $value value to match of the parameter + * @param string $conditiontype one of '=', '!=', 'preg_match' + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=', + $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param; + $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype; + $stuff[$this->_pkg->getTasksNs() . ':value'] = $value; + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + function getXml() + { + return $this->_params; + } + + /** + * Use to set up a param tag for use in creating a paramgroup + * @static + */ + function getParam($name, $prompt, $type = 'string', $default = null) + { + if ($default !== null) { + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + $this->_pkg->getTasksNs() . ':default' => $default + ); + } + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + ); + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Replace.php b/3rdparty/PEAR/Task/Replace.php new file mode 100644 index 0000000000..376df64df6 --- /dev/null +++ b/3rdparty/PEAR/Task/Replace.php @@ -0,0 +1,176 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Replace.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the replace file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Replace extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGEANDINSTALL; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if (!isset($xml['attribs'])) { + return array(PEAR_TASK_ERROR_NOATTRIBS); + } + if (!isset($xml['attribs']['type'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type'); + } + if (!isset($xml['attribs']['to'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to'); + } + if (!isset($xml['attribs']['from'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from'); + } + if ($xml['attribs']['type'] == 'pear-config') { + if (!in_array($xml['attribs']['to'], $config->getKeys())) { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + $config->getKeys()); + } + } elseif ($xml['attribs']['type'] == 'php-const') { + if (defined($xml['attribs']['to'])) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('valid PHP constant')); + } + } elseif ($xml['attribs']['type'] == 'package-info') { + if (in_array($xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time'))) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time')); + } + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'], + array('pear-config', 'package-info', 'php-const')); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml; + } + + /** + * Do a package.xml 1.0 replacement, with additional package-info fields available + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $subst_from = $subst_to = array(); + foreach ($this->_replacements as $a) { + $a = $a['attribs']; + $to = ''; + if ($a['type'] == 'pear-config') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if ($a['to'] == 'master_server') { + $chan = $this->registry->getChannel($pkg->getChannel()); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } else { + if ($this->config->isDefinedLayer('ftp')) { + // try the remote config file first + $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel()); + if (is_null($to)) { + // then default to local + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } else { + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } + if (is_null($to)) { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } elseif ($a['type'] == 'php-const') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if (defined($a['to'])) { + $to = constant($a['to']); + } else { + $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]"); + return false; + } + } else { + if ($t = $pkg->packageInfo($a['to'])) { + $to = $t; + } else { + $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]"); + return false; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + $this->logger->log(3, "doing " . sizeof($subst_from) . + " substitution(s) for $dest"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + return $contents; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Replace/rw.php b/3rdparty/PEAR/Task/Replace/rw.php new file mode 100644 index 0000000000..32dad58629 --- /dev/null +++ b/3rdparty/PEAR/Task/Replace/rw.php @@ -0,0 +1,61 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Replace.php'; +/** + * Abstracts the replace task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Replace_rw extends PEAR_Task_Replace +{ + function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function setInfo($from, $to, $type) + { + $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type)); + } + + function getName() + { + return 'replace'; + } + + function getXml() + { + return $this->_params; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Unixeol.php b/3rdparty/PEAR/Task/Unixeol.php new file mode 100644 index 0000000000..89ca81be34 --- /dev/null +++ b/3rdparty/PEAR/Task/Unixeol.php @@ -0,0 +1,77 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Unixeol.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the unix line endings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Unixeol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with line endings customized for the current OS + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents); + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Unixeol/rw.php b/3rdparty/PEAR/Task/Unixeol/rw.php new file mode 100644 index 0000000000..b2ae5fa5cb --- /dev/null +++ b/3rdparty/PEAR/Task/Unixeol/rw.php @@ -0,0 +1,56 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Unixeol.php'; +/** + * Abstracts the unixeol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol +{ + function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'unixeol'; + } + + function getXml() + { + return ''; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Windowseol.php b/3rdparty/PEAR/Task/Windowseol.php new file mode 100644 index 0000000000..8ba4171159 --- /dev/null +++ b/3rdparty/PEAR/Task/Windowseol.php @@ -0,0 +1,77 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Windowseol.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the windows line endsings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Windowseol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with windows line endings + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\r\\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents); + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Task/Windowseol/rw.php b/3rdparty/PEAR/Task/Windowseol/rw.php new file mode 100644 index 0000000000..f0f1149c83 --- /dev/null +++ b/3rdparty/PEAR/Task/Windowseol/rw.php @@ -0,0 +1,56 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Windowseol.php'; +/** + * Abstracts the windowseol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol +{ + function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'windowseol'; + } + + function getXml() + { + return ''; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/Validate.php b/3rdparty/PEAR/Validate.php new file mode 100644 index 0000000000..176560bc23 --- /dev/null +++ b/3rdparty/PEAR/Validate.php @@ -0,0 +1,629 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validate.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Constants for install stage + */ +define('PEAR_VALIDATE_INSTALLING', 1); +define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others +define('PEAR_VALIDATE_NORMAL', 3); +define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others +define('PEAR_VALIDATE_PACKAGING', 7); +/**#@-*/ +require_once 'PEAR/Common.php'; +require_once 'PEAR/Validator/PECL.php'; + +/** + * Validation class for package.xml - channel-level advanced validation + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Validate +{ + var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + var $_packagexml; + /** + * @var int one of the PEAR_VALIDATE_* constants + */ + var $_state = PEAR_VALIDATE_NORMAL; + /** + * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same) + * @var array + * @access private + */ + var $_failures = array('error' => array(), 'warning' => array()); + + /** + * Override this method to handle validation of normal package names + * @param string + * @return bool + * @access protected + */ + function _validPackageName($name) + { + return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name); + } + + /** + * @param string package name to validate + * @param string name of channel-specific validation package + * @final + */ + function validPackageName($name, $validatepackagename = false) + { + if ($validatepackagename) { + if (strtolower($name) == strtolower($validatepackagename)) { + return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name); + } + } + return $this->_validPackageName($name); + } + + /** + * This validates a bundle name, and bundle names must conform + * to the PEAR naming convention, so the method is final and static. + * @param string + * @final + * @static + */ + function validGroupName($name) + { + return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); + } + + /** + * Determine whether $state represents a valid stability level + * @param string + * @return bool + * @static + * @final + */ + function validState($state) + { + return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); + } + + /** + * Get a list of valid stability levels + * @return array + * @static + * @final + */ + function getValidStates() + { + return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + } + + /** + * Determine whether a version is a properly formatted version number that can be used + * by version_compare + * @param string + * @return bool + * @static + * @final + */ + function validVersion($ver) + { + return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function setPackageFile(&$pf) + { + $this->_packagexml = &$pf; + } + + /** + * @access private + */ + function _addFailure($field, $reason) + { + $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason); + } + + /** + * @access private + */ + function _addWarning($field, $reason) + { + $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason); + } + + function getFailures() + { + $failures = $this->_failures; + $this->_failures = array('warnings' => array(), 'errors' => array()); + return $failures; + } + + /** + * @param int one of the PEAR_VALIDATE_* constants + */ + function validate($state = null) + { + if (!isset($this->_packagexml)) { + return false; + } + if ($state !== null) { + $this->_state = $state; + } + $this->_failures = array('warnings' => array(), 'errors' => array()); + $this->validatePackageName(); + $this->validateVersion(); + $this->validateMaintainers(); + $this->validateDate(); + $this->validateSummary(); + $this->validateDescription(); + $this->validateLicense(); + $this->validateNotes(); + if ($this->_packagexml->getPackagexmlVersion() == '1.0') { + $this->validateState(); + $this->validateFilelist(); + } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') { + $this->validateTime(); + $this->validateStability(); + $this->validateDeps(); + $this->validateMainFilelist(); + $this->validateReleaseFilelist(); + //$this->validateGlobalTasks(); + $this->validateChangelog(); + } + return !((bool) count($this->_failures['errors'])); + } + + /** + * @access protected + */ + function validatePackageName() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING || + $this->_state == PEAR_VALIDATE_NORMAL) { + if (($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') && + $this->_packagexml->getExtends()) { + $version = $this->_packagexml->getVersion() . ''; + $name = $this->_packagexml->getPackage(); + $test = array_shift($a = explode('.', $version)); + if ($test == '0') { + return true; + } + $vlen = strlen($test); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if ($majver != $test) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name should ' . + 'have a postfix equal to the major version like "' . + $this->_packagexml->getExtends() . $test . '"'); + return true; + } elseif (substr($name, 0, strlen($name) - $vlen) != + $this->_packagexml->getExtends()) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name must ' . + 'be an extension like "' . $this->_packagexml->getExtends() . + $test . '"'); + return true; + } + } + } + if (!$this->validPackageName($this->_packagexml->getPackage())) { + $this->_addFailure('name', 'package name "' . + $this->_packagexml->getPackage() . '" is invalid'); + return false; + } + } + + /** + * @access protected + */ + function validateVersion() + { + if ($this->_state != PEAR_VALIDATE_PACKAGING) { + if (!$this->validVersion($this->_packagexml->getVersion())) { + $this->_addFailure('version', + 'Invalid version number "' . $this->_packagexml->getVersion() . '"'); + } + return false; + } + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + if (count($versioncomponents) != 3) { + $this->_addWarning('version', + 'A version number should have 3 decimals (x.y.z)'); + return true; + } + $name = $this->_packagexml->getPackage(); + // version must be based upon state + switch ($this->_packagexml->getState()) { + case 'snapshot' : + return true; + case 'devel' : + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } else { + $this->_addWarning('version', + 'packages with devel stability must be < version 1.0.0'); + } + return true; + break; + case 'alpha' : + case 'beta' : + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + if (substr($versioncomponents[2], 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions ' . + 'must have capital RC, not lower-case rc'); + return false; + } + } + if (!$this->_packagexml->getExtends()) { + if ($versioncomponents[0] == '1') { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 1.*.0000 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 1.*.0RC1 or 1.*.0beta24 etc. + return true; + } else { + // version 1.*.0 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + 'bugfix versions (1.3.x where x > 0) probably should ' . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + 'major versions greater than 1 are not allowed for packages ' . + 'without an tag or an identical postfix (foo2 v2.0.0)'); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } else { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + if ($versioncomponents[0] == $majver) { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 2.*.0000 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return false; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 2.*.0RC1 or 2.*.0beta24 etc. + return true; + } else { + // version 2.*.0 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 cannot be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + "bugfix versions ($majver.x.y where y > 0) should " . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases"); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } + return true; + break; + case 'stable' : + if ($versioncomponents[0] == '0') { + $this->_addWarning('version', 'versions less than 1.0.0 cannot ' . + 'be stable'); + return true; + } + if (!is_numeric($versioncomponents[2])) { + if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i', + $versioncomponents[2])) { + $this->_addWarning('version', 'version "' . $version . '" or any ' . + 'RC/beta/alpha version cannot be stable'); + return true; + } + } + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_packagexml->getExtends()) { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + } elseif ($versioncomponents[0] > 1) { + $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' . + '1 for any package that does not have an tag'); + } + return true; + break; + default : + return false; + break; + } + } + + /** + * @access protected + */ + function validateMaintainers() + { + // maintainers can only be truly validated server-side for most channels + // but allow this customization for those who wish it + return true; + } + + /** + * @access protected + */ + function validateDate() + { + if ($this->_state == PEAR_VALIDATE_NORMAL || + $this->_state == PEAR_VALIDATE_PACKAGING) { + + if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/', + $this->_packagexml->getDate(), $res) || + count($res) < 4 + || !checkdate($res[2], $res[3], $res[1]) + ) { + $this->_addFailure('date', 'invalid release date "' . + $this->_packagexml->getDate() . '"'); + return false; + } + + if ($this->_state == PEAR_VALIDATE_PACKAGING && + $this->_packagexml->getDate() != date('Y-m-d')) { + $this->_addWarning('date', 'Release Date "' . + $this->_packagexml->getDate() . '" is not today'); + } + } + return true; + } + + /** + * @access protected + */ + function validateTime() + { + if (!$this->_packagexml->getTime()) { + // default of no time value set + return true; + } + + // packager automatically sets time, so only validate if pear validate is called + if ($this->_state = PEAR_VALIDATE_NORMAL) { + if (!preg_match('/\d\d:\d\d:\d\d/', + $this->_packagexml->getTime())) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + + $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches); + if ($result === false || empty($matches)) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + } + + return true; + } + + /** + * @access protected + */ + function validateState() + { + // this is the closest to "final" php4 can get + if (!PEAR_Validate::validState($this->_packagexml->getState())) { + if (strtolower($this->_packagexml->getState() == 'rc')) { + $this->_addFailure('state', 'RC is not a state, it is a version ' . + 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta'); + } + $this->_addFailure('state', 'invalid release state "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + return false; + } + return true; + } + + /** + * @access protected + */ + function validateStability() + { + $ret = true; + $packagestability = $this->_packagexml->getState(); + $apistability = $this->_packagexml->getState('api'); + if (!PEAR_Validate::validState($packagestability)) { + $this->_addFailure('state', 'invalid release stability "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + $ret = false; + } + $apistates = PEAR_Validate::getValidStates(); + array_shift($apistates); // snapshot is not allowed + if (!in_array($apistability, $apistates)) { + $this->_addFailure('state', 'invalid API stability "' . + $this->_packagexml->getState('api') . '", must be one of: ' . + implode(', ', $apistates)); + $ret = false; + } + return $ret; + } + + /** + * @access protected + */ + function validateSummary() + { + return true; + } + + /** + * @access protected + */ + function validateDescription() + { + return true; + } + + /** + * @access protected + */ + function validateLicense() + { + return true; + } + + /** + * @access protected + */ + function validateNotes() + { + return true; + } + + /** + * for package.xml 2.0 only - channels can't use package.xml 1.0 + * @access protected + */ + function validateDependencies() + { + return true; + } + + /** + * for package.xml 1.0 only + * @access private + */ + function _validateFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateMainFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateReleaseFilelist() + { + return true; // placeholder for now + } + + /** + * @access protected + */ + function validateChangelog() + { + return true; + } + + /** + * @access protected + */ + function validateFilelist() + { + return true; + } + + /** + * @access protected + */ + function validateDeps() + { + return true; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR/Validator/PECL.php b/3rdparty/PEAR/Validator/PECL.php new file mode 100644 index 0000000000..89b951f7b6 --- /dev/null +++ b/3rdparty/PEAR/Validator/PECL.php @@ -0,0 +1,63 @@ + + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PECL.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a5 + */ +/** + * This is the parent class for all validators + */ +require_once 'PEAR/Validate.php'; +/** + * Channel Validator for the pecl.php.net channel + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a5 + */ +class PEAR_Validator_PECL extends PEAR_Validate +{ + function validateVersion() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + $last = array_pop($versioncomponents); + if (substr($last, 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions must have ' . + 'upper-case RC, not lower-case rc'); + return false; + } + } + return true; + } + + function validatePackageName() + { + $ret = parent::validatePackageName(); + if ($this->_packagexml->getPackageType() == 'extsrc' || + $this->_packagexml->getPackageType() == 'zendextsrc') { + if (strtolower($this->_packagexml->getPackage()) != + strtolower($this->_packagexml->getProvidesExtension())) { + $this->_addWarning('providesextension', 'package name "' . + $this->_packagexml->getPackage() . '" is different from extension name "' . + $this->_packagexml->getProvidesExtension() . '"'); + } + } + return $ret; + } +} +?> \ No newline at end of file diff --git a/3rdparty/PEAR/XMLParser.php b/3rdparty/PEAR/XMLParser.php new file mode 100644 index 0000000000..7f091c16fa --- /dev/null +++ b/3rdparty/PEAR/XMLParser.php @@ -0,0 +1,253 @@ + + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: XMLParser.php 313023 2011-07-06 19:17:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Parser for any xml file + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.9.4 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_XMLParser +{ + /** + * unserilialized data + * @var string $_serializedData + */ + var $_unserializedData = null; + + /** + * name of the root tag + * @var string $_root + */ + var $_root = null; + + /** + * stack for all data that is found + * @var array $_dataStack + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * @var array $_valStack + */ + var $_valStack = array(); + + /** + * current tag depth + * @var int $_depth + */ + var $_depth = 0; + + /** + * The XML encoding to use + * @var string $encoding + */ + var $encoding = 'ISO-8859-1'; + + /** + * @return array + */ + function getData() + { + return $this->_unserializedData; + } + + /** + * @param string xml content + * @return true|PEAR_Error + */ + function parse($data) + { + if (!extension_loaded('xml')) { + include_once 'PEAR.php'; + return PEAR::raiseError("XML Extension not found", 1); + } + $this->_dataStack = $this->_valStack = array(); + $this->_depth = 0; + + if ( + strpos($data, 'encoding="UTF-8"') + || strpos($data, 'encoding="utf-8"') + || strpos($data, "encoding='UTF-8'") + || strpos($data, "encoding='utf-8'") + ) { + $this->encoding = 'UTF-8'; + } + + if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') { + $data = utf8_decode($data); + $this->encoding = 'ISO-8859-1'; + } + + $xp = xml_parser_create($this->encoding); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xp, $this); + xml_set_element_handler($xp, 'startHandler', 'endHandler'); + xml_set_character_data_handler($xp, 'cdataHandler'); + if (!xml_parse($xp, $data)) { + $msg = xml_error_string(xml_get_error_code($xp)); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + include_once 'PEAR.php'; + return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2); + } + xml_parser_free($xp); + return true; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => 'string', + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if (count($attribs) > 0) { + $val['children'] = array(); + $val['type'] = 'array'; + $val['children']['attribs'] = $attribs; + } + + array_push($this->_valStack, $val); + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + return trim($data); + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + $data = $this->postProcess($this->_dataStack[$this->_depth], $element); + + // adjust type of the value + switch (strtolower($value['type'])) { + // unserialize an array + case 'array': + if ($data !== '') { + $value['children']['_content'] = $data; + } + + $value['value'] = isset($value['children']) ? $value['children'] : array(); + break; + + /* + * unserialize a null value + */ + case 'null': + $data = null; + break; + + /* + * unserialize any scalar value + */ + default: + settype($data, $value['type']); + $value['value'] = $data; + break; + } + + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } + + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if ($parent['type'] != 'array') { + $parent['type'] = 'array'; + } + } + + if (!empty($value['name'])) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys'])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'],$value['value']); + } + array_push($this->_valStack, $parent); + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + $this->_dataStack[$this->_depth] .= $cdata; + } +} \ No newline at end of file diff --git a/3rdparty/PEAR5.php b/3rdparty/PEAR5.php new file mode 100644 index 0000000000..428606780b --- /dev/null +++ b/3rdparty/PEAR5.php @@ -0,0 +1,33 @@ +pdo->prepare("SELECT " . $fields . " FROM `".$this->calendarTableName."` WHERE principaluri = ?"); + $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM ".$this->calendarTableName." WHERE principaluri = ? ORDER BY calendarorder ASC"); $stmt->execute(array($principalUri)); $calendars = array(); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $components = explode(',',$row['components']); + $components = array(); + if ($row['components']) { + $components = explode(',',$row['components']); + } $calendar = array( 'id' => $row['id'], @@ -106,7 +111,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $row['ctag']?$row['ctag']:'0', '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet($components), ); - + foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; @@ -129,8 +134,9 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { * @param string $principalUri * @param string $calendarUri * @param array $properties + * @return string */ - public function createCalendar($principalUri,$calendarUri, array $properties) { + public function createCalendar($principalUri, $calendarUri, array $properties) { $fieldNames = array( 'principaluri', @@ -158,13 +164,12 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { foreach($this->propertyMap as $xmlName=>$dbName) { if (isset($properties[$xmlName])) { - $myValue = $properties[$xmlName]; $values[':' . $dbName] = $properties[$xmlName]; $fieldNames[] = $dbName; } } - $stmt = $this->pdo->prepare("INSERT INTO `".$this->calendarTableName."` (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")"); + $stmt = $this->pdo->prepare("INSERT INTO ".$this->calendarTableName." (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")"); $stmt->execute($values); return $this->pdo->lastInsertId(); @@ -184,7 +189,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { * If the operation was successful, true can be returned. * If the operation failed, false can be returned. * - * Deletion of a non-existant property is always succesful. + * Deletion of a non-existent property is always successful. * * Lastly, it is optional to return detailed information about any * failures. In this case an array should be returned with the following @@ -199,13 +204,13 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { * ) * ) * - * In this example it was forbidden to update {DAV:}displayname. + * In this example it was forbidden to update {DAV:}displayname. * (403 Forbidden), which in turn also caused {DAV:}owner to fail * (424 Failed Dependency) because the request needs to be atomic. * * @param string $calendarId - * @param array $mutations - * @return bool|array + * @param array $mutations + * @return bool|array */ public function updateCalendar($calendarId, array $mutations) { @@ -220,7 +225,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { foreach($mutations as $propertyName=>$propertyValue) { - // We don't know about this property. + // We don't know about this property. if (!isset($this->propertyMap[$propertyName])) { $hasError = true; $result[403][$propertyName] = null; @@ -230,7 +235,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { $fieldName = $this->propertyMap[$propertyName]; $newValues[$fieldName] = $propertyValue; - + } // If there were any errors we need to fail the request @@ -258,55 +263,60 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { } $valuesSql[] = 'ctag = ctag + 1'; - $stmt = $this->pdo->prepare("UPDATE `" . $this->calendarTableName . "` SET " . implode(', ',$valuesSql) . " WHERE id = ?"); - $newValues['id'] = $calendarId; + $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ',$valuesSql) . " WHERE id = ?"); + $newValues['id'] = $calendarId; $stmt->execute(array_values($newValues)); - return true; + return true; } /** - * Delete a calendar and all it's objects - * - * @param string $calendarId + * Delete a calendar and all it's objects + * + * @param string $calendarId * @return void */ public function deleteCalendar($calendarId) { - $stmt = $this->pdo->prepare('DELETE FROM `'.$this->calendarObjectTableName.'` WHERE calendarid = ?'); + $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?'); $stmt->execute(array($calendarId)); - $stmt = $this->pdo->prepare('DELETE FROM `'.$this->calendarTableName.'` WHERE id = ?'); + $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?'); $stmt->execute(array($calendarId)); } /** - * Returns all calendar objects within a calendar. + * Returns all calendar objects within a calendar. * * Every item contains an array with the following keys: * * id - unique identifier which will be used for subsequent updates - * * calendardata - The iCalendar-compatible calnedar data + * * calendardata - The iCalendar-compatible calendar data * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. * * lastmodified - a timestamp of the last modification time - * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: * ' "abcdef"') * * calendarid - The calendarid as it was passed to this function. + * * size - The size of the calendar objects, in bytes. * - * Note that the etag is optional, but it's highly encouraged to return for + * Note that the etag is optional, but it's highly encouraged to return for * speed reasons. * - * The calendardata is also optional. If it's not returned - * 'getCalendarObject' will be called later, which *is* expected to return + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return * calendardata. - * - * @param string $calendarId - * @return array + * + * If neither etag or size are specified, the calendardata will be + * used/fetched to determine these numbers. If both are specified the + * amount of times this is needed is reduced by a great degree. + * + * @param string $calendarId + * @return array */ public function getCalendarObjects($calendarId) { - $stmt = $this->pdo->prepare('SELECT * FROM `'.$this->calendarObjectTableName.'` WHERE calendarid = ?'); + $stmt = $this->pdo->prepare('SELECT * FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?'); $stmt->execute(array($calendarId)); return $stmt->fetchAll(); @@ -316,68 +326,68 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract { * Returns information from a single calendar object, based on it's object * uri. * - * The returned array must have the same keys as getCalendarObjects. The - * 'calendardata' object is required here though, while it's not required + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required * for getCalendarObjects. - * - * @param string $calendarId - * @param string $objectUri - * @return array + * + * @param string $calendarId + * @param string $objectUri + * @return array */ public function getCalendarObject($calendarId,$objectUri) { - $stmt = $this->pdo->prepare('SELECT * FROM `'.$this->calendarObjectTableName.'` WHERE calendarid = ? AND uri = ?'); + $stmt = $this->pdo->prepare('SELECT * FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?'); $stmt->execute(array($calendarId, $objectUri)); return $stmt->fetch(); } /** - * Creates a new calendar object. - * - * @param string $calendarId - * @param string $objectUri - * @param string $calendarData + * Creates a new calendar object. + * + * @param string $calendarId + * @param string $objectUri + * @param string $calendarData * @return void */ public function createCalendarObject($calendarId,$objectUri,$calendarData) { - $stmt = $this->pdo->prepare('INSERT INTO `'.$this->calendarObjectTableName.'` (calendarid, uri, calendardata, lastmodified) VALUES (?,?,?,?)'); + $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified) VALUES (?,?,?,?)'); $stmt->execute(array($calendarId,$objectUri,$calendarData,time())); - $stmt = $this->pdo->prepare('UPDATE `'.$this->calendarTableName.'` SET ctag = ctag + 1 WHERE id = ?'); + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?'); $stmt->execute(array($calendarId)); } /** - * Updates an existing calendarobject, based on it's uri. - * - * @param string $calendarId - * @param string $objectUri - * @param string $calendarData + * Updates an existing calendarobject, based on it's uri. + * + * @param string $calendarId + * @param string $objectUri + * @param string $calendarData * @return void */ public function updateCalendarObject($calendarId,$objectUri,$calendarData) { - $stmt = $this->pdo->prepare('UPDATE `'.$this->calendarObjectTableName.'` SET calendardata = ?, lastmodified = ? WHERE calendarid = ? AND uri = ?'); + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ? WHERE calendarid = ? AND uri = ?'); $stmt->execute(array($calendarData,time(),$calendarId,$objectUri)); - $stmt = $this->pdo->prepare('UPDATE `'.$this->calendarTableName.'` SET ctag = ctag + 1 WHERE id = ?'); + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?'); $stmt->execute(array($calendarId)); } /** - * Deletes an existing calendar object. - * - * @param string $calendarId - * @param string $objectUri + * Deletes an existing calendar object. + * + * @param string $calendarId + * @param string $objectUri * @return void */ public function deleteCalendarObject($calendarId,$objectUri) { - $stmt = $this->pdo->prepare('DELETE FROM `'.$this->calendarObjectTableName.'` WHERE calendarid = ? AND uri = ?'); + $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?'); $stmt->execute(array($calendarId,$objectUri)); - $stmt = $this->pdo->prepare('UPDATE `'. $this->calendarTableName .'` SET ctag = ctag + 1 WHERE id = ?'); + $stmt = $this->pdo->prepare('UPDATE '. $this->calendarTableName .' SET ctag = ctag + 1 WHERE id = ?'); $stmt->execute(array($calendarId)); } diff --git a/3rdparty/Sabre/CalDAV/Calendar.php b/3rdparty/Sabre/CalDAV/Calendar.php index 0d2b387577..623df2dd1b 100644 --- a/3rdparty/Sabre/CalDAV/Calendar.php +++ b/3rdparty/Sabre/CalDAV/Calendar.php @@ -5,42 +5,42 @@ * * A calendar can contain multiple TODO and or Events. These are represented * as Sabre_CalDAV_CalendarObject objects. - * + * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProperties, Sabre_DAVACL_IACL { /** - * This is an array with calendar information - * - * @var array + * This is an array with calendar information + * + * @var array */ protected $calendarInfo; /** - * CalDAV backend - * - * @var Sabre_CalDAV_Backend_Abstract + * CalDAV backend + * + * @var Sabre_CalDAV_Backend_Abstract */ protected $caldavBackend; /** * Principal backend - * + * * @var Sabre_DAVACL_IPrincipalBackend */ protected $principalBackend; /** - * Constructor - * - * @param Sabre_CalDAV_Backend_Abstract $caldavBackend - * @param array $calendarInfo - * @return void + * Constructor + * + * @param Sabre_DAVACL_IPrincipalBackend $principalBackend + * @param Sabre_CalDAV_Backend_Abstract $caldavBackend + * @param array $calendarInfo */ public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $calendarInfo) { @@ -52,9 +52,9 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper } /** - * Returns the name of the calendar - * - * @return string + * Returns the name of the calendar + * + * @return string */ public function getName() { @@ -63,10 +63,10 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper } /** - * Updates properties such as the display name and description - * - * @param array $mutations - * @return array + * Updates properties such as the display name and description + * + * @param array $mutations + * @return array */ public function updateProperties($mutations) { @@ -75,10 +75,10 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper } /** - * Returns the list of properties - * - * @param array $properties - * @return array + * Returns the list of properties + * + * @param array $requestedProperties + * @return array */ public function getProperties($requestedProperties) { @@ -86,16 +86,16 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper foreach($requestedProperties as $prop) switch($prop) { - case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' : - $response[$prop] = new Sabre_CalDAV_Property_SupportedCalendarData(); + case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' : + $response[$prop] = new Sabre_CalDAV_Property_SupportedCalendarData(); break; - case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' : - $response[$prop] = new Sabre_CalDAV_Property_SupportedCollationSet(); + case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' : + $response[$prop] = new Sabre_CalDAV_Property_SupportedCollationSet(); break; case '{DAV:}owner' : $response[$prop] = new Sabre_DAVACL_Property_Principal(Sabre_DAVACL_Property_Principal::HREF,$this->calendarInfo['principaluri']); break; - default : + default : if (isset($this->calendarInfo[$prop])) $response[$prop] = $this->calendarInfo[$prop]; break; @@ -108,22 +108,22 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper * Returns a calendar object * * The contained calendar objects are for example Events or Todo's. - * - * @param string $name - * @return Sabre_DAV_ICalendarObject + * + * @param string $name + * @return Sabre_DAV_ICalendarObject */ public function getChild($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name); - if (!$obj) throw new Sabre_DAV_Exception_FileNotFound('Calendar object not found'); + if (!$obj) throw new Sabre_DAV_Exception_NotFound('Calendar object not found'); return new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj); } /** - * Returns the full list of calendar objects - * - * @return array + * Returns the full list of calendar objects + * + * @return array */ public function getChildren() { @@ -137,17 +137,17 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper } /** - * Checks if a child-node exists. - * - * @param string $name - * @return bool + * Checks if a child-node exists. + * + * @param string $name + * @return bool */ public function childExists($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name); - if (!$obj) + if (!$obj) return false; - else + else return true; } @@ -156,8 +156,8 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper * Creates a new directory * * We actually block this, as subdirectories are not allowed in calendars. - * - * @param string $name + * + * @param string $name * @return void */ public function createDirectory($name) { @@ -170,32 +170,23 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper * Creates a new file * * The contents of the new file must be a valid ICalendar string. - * - * @param string $name - * @param resource $calendarData - * @return void + * + * @param string $name + * @param resource $calendarData + * @return string|null */ public function createFile($name,$calendarData = null) { - $calendarData = stream_get_contents($calendarData); - // Converting to UTF-8, if needed - $calendarData = Sabre_DAV_StringUtil::ensureUTF8($calendarData); - - $supportedComponents = $this->calendarInfo['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set']; - if ($supportedComponents) { - $supportedComponents = $supportedComponents->getValue(); - } else { - $supportedComponents = null; + if (is_resource($calendarData)) { + $calendarData = stream_get_contents($calendarData); } - Sabre_CalDAV_ICalendarUtil::validateICalendarObject($calendarData, $supportedComponents); - - $this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData); + return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData); } /** - * Deletes the calendar. - * + * Deletes the calendar. + * * @return void */ public function delete() { @@ -205,10 +196,10 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper } /** - * Renames the calendar. Note that most calendars use the - * {DAV:}displayname to display a name to display a name. - * - * @param string $newName + * Renames the calendar. Note that most calendars use the + * {DAV:}displayname to display a name to display a name. + * + * @param string $newName * @return void */ public function setName($newName) { @@ -219,7 +210,7 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper /** * Returns the last modification date as a unix timestamp. - * + * * @return void */ public function getLastModified() { @@ -231,8 +222,8 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -245,8 +236,8 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -258,13 +249,13 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { @@ -294,6 +285,11 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read', 'protected' => true, ), + array( + 'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), ); @@ -302,9 +298,9 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -313,6 +309,35 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + $default = Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet(); + + // We need to inject 'read-free-busy' in the tree, aggregated under + // {DAV:}read. + foreach($default['aggregates'] as &$agg) { + + if ($agg['privilege'] !== '{DAV:}read') continue; + + $agg['aggregates'][] = array( + 'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy', + ); + + } + return $default; + + } } diff --git a/3rdparty/Sabre/CalDAV/CalendarObject.php b/3rdparty/Sabre/CalDAV/CalendarObject.php index 0c99f18deb..72f0a578d1 100644 --- a/3rdparty/Sabre/CalDAV/CalendarObject.php +++ b/3rdparty/Sabre/CalDAV/CalendarObject.php @@ -1,43 +1,43 @@ calendarInfo['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set']; - if ($supportedComponents) { - $supportedComponents = $supportedComponents->getValue(); - } else { - $supportedComponents = null; } - Sabre_CalDAV_ICalendarUtil::validateICalendarObject($calendarData, $supportedComponents); - - $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData); + $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData); $this->objectData['calendardata'] = $calendarData; + $this->objectData['etag'] = $etag; + + return $etag; } /** - * Deletes the calendar object - * + * Deletes the calendar object + * * @return void */ public function delete() { @@ -121,9 +113,9 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV } /** - * Returns the mime content-type - * - * @return string + * Returns the mime content-type + * + * @return string */ public function getContentType() { @@ -134,9 +126,9 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV /** * Returns an ETag for this object. * - * The ETag is an arbritrary string, but MUST be surrounded by double-quotes. - * - * @return string + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * @return string */ public function getETag() { @@ -150,8 +142,8 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV /** * Returns the last modification date as a unix timestamp - * - * @return time + * + * @return time */ public function getLastModified() { @@ -160,21 +152,25 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV } /** - * Returns the size of this object in bytes - * + * Returns the size of this object in bytes + * * @return int */ public function getSize() { - return strlen($this->objectData['calendardata']); + if (array_key_exists('size',$this->objectData)) { + return $this->objectData['size']; + } else { + return strlen($this->get()); + } } /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -187,8 +183,8 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -200,13 +196,13 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { @@ -244,9 +240,9 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -255,6 +251,23 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + + return null; + + } } diff --git a/3rdparty/Sabre/CalDAV/CalendarQueryParser.php b/3rdparty/Sabre/CalDAV/CalendarQueryParser.php new file mode 100644 index 0000000000..bd0d343382 --- /dev/null +++ b/3rdparty/Sabre/CalDAV/CalendarQueryParser.php @@ -0,0 +1,296 @@ +dom = $dom; + + $this->xpath = new DOMXPath($dom); + $this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV); + $this->xpath->registerNameSpace('dav','urn:DAV'); + + } + + /** + * Parses the request. + * + * @return void + */ + public function parse() { + + $filterNode = null; + + $filter = $this->xpath->query('/cal:calendar-query/cal:filter'); + if ($filter->length !== 1) { + throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed'); + } + + $compFilters = $this->parseCompFilters($filter->item(0)); + if (count($compFilters)!==1) { + throw new Sabre_DAV_Exception_BadRequest('There must be exactly 1 top-level comp-filter.'); + } + + $this->filters = $compFilters[0]; + $this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild)); + + $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand'); + if ($expand->length>0) { + $this->expand = $this->parseExpand($expand->item(0)); + } + + + } + + /** + * Parses all the 'comp-filter' elements from a node + * + * @param DOMElement $parentNode + * @return array + */ + protected function parseCompFilters(DOMElement $parentNode) { + + $compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode); + $result = array(); + + for($ii=0; $ii < $compFilterNodes->length; $ii++) { + + $compFilterNode = $compFilterNodes->item($ii); + + $compFilter = array(); + $compFilter['name'] = $compFilterNode->getAttribute('name'); + $compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0; + $compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode); + $compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode); + $compFilter['time-range'] = $this->parseTimeRange($compFilterNode); + + if ($compFilter['time-range'] && !in_array($compFilter['name'],array( + 'VEVENT', + 'VTODO', + 'VJOURNAL', + 'VFREEBUSY', + 'VALARM', + ))) { + throw new Sabre_DAV_Exception_BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component'); + }; + + $result[] = $compFilter; + + } + + return $result; + + } + + /** + * Parses all the prop-filter elements from a node + * + * @param DOMElement $parentNode + * @return array + */ + protected function parsePropFilters(DOMElement $parentNode) { + + $propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode); + $result = array(); + + for ($ii=0; $ii < $propFilterNodes->length; $ii++) { + + $propFilterNode = $propFilterNodes->item($ii); + $propFilter = array(); + $propFilter['name'] = $propFilterNode->getAttribute('name'); + $propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0; + $propFilter['param-filters'] = $this->parseParamFilters($propFilterNode); + $propFilter['text-match'] = $this->parseTextMatch($propFilterNode); + $propFilter['time-range'] = $this->parseTimeRange($propFilterNode); + + $result[] = $propFilter; + + } + + return $result; + + } + + /** + * Parses the param-filter element + * + * @param DOMElement $parentNode + * @return array + */ + protected function parseParamFilters(DOMElement $parentNode) { + + $paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode); + $result = array(); + + for($ii=0;$ii<$paramFilterNodes->length;$ii++) { + + $paramFilterNode = $paramFilterNodes->item($ii); + $paramFilter = array(); + $paramFilter['name'] = $paramFilterNode->getAttribute('name'); + $paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0; + $paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode); + + $result[] = $paramFilter; + + } + + return $result; + + } + + /** + * Parses the text-match element + * + * @param DOMElement $parentNode + * @return array|null + */ + protected function parseTextMatch(DOMElement $parentNode) { + + $textMatchNodes = $this->xpath->query('cal:text-match', $parentNode); + + if ($textMatchNodes->length === 0) + return null; + + $textMatchNode = $textMatchNodes->item(0); + $negateCondition = $textMatchNode->getAttribute('negate-condition'); + $negateCondition = $negateCondition==='yes'; + $collation = $textMatchNode->getAttribute('collation'); + if (!$collation) $collation = 'i;ascii-casemap'; + + return array( + 'negate-condition' => $negateCondition, + 'collation' => $collation, + 'value' => $textMatchNode->nodeValue + ); + + } + + /** + * Parses the time-range element + * + * @param DOMElement $parentNode + * @return array|null + */ + protected function parseTimeRange(DOMElement $parentNode) { + + $timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode); + if ($timeRangeNodes->length === 0) { + return null; + } + + $timeRangeNode = $timeRangeNodes->item(0); + + if ($start = $timeRangeNode->getAttribute('start')) { + $start = Sabre_VObject_DateTimeParser::parseDateTime($start); + } else { + $start = null; + } + if ($end = $timeRangeNode->getAttribute('end')) { + $end = Sabre_VObject_DateTimeParser::parseDateTime($end); + } else { + $end = null; + } + + if (!is_null($start) && !is_null($end) && $end <= $start) { + throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter'); + } + + return array( + 'start' => $start, + 'end' => $end, + ); + + } + + /** + * Parses the CALDAV:expand element + * + * @param DOMElement $parentNode + * @return void + */ + protected function parseExpand(DOMElement $parentNode) { + + $start = $parentNode->getAttribute('start'); + if(!$start) { + throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element'); + } + $start = Sabre_VObject_DateTimeParser::parseDateTime($start); + + $end = $parentNode->getAttribute('end'); + if(!$end) { + throw new Sabre_DAV_Exception_BadRequest('The "end" attribute is required for the CALDAV:expand element'); + } + $end = Sabre_VObject_DateTimeParser::parseDateTime($end); + + if ($end <= $start) { + throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.'); + } + + return array( + 'start' => $start, + 'end' => $end, + ); + + } + +} diff --git a/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php b/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php new file mode 100644 index 0000000000..1bb6b5d53f --- /dev/null +++ b/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php @@ -0,0 +1,347 @@ +name !== $filters['name']) { + return false; + } + + return + $this->validateCompFilters($vObject, $filters['comp-filters']) && + $this->validatePropFilters($vObject, $filters['prop-filters']); + + + } + + /** + * This method checks the validity of comp-filters. + * + * A list of comp-filters needs to be specified. Also the parent of the + * component we're checking should be specified, not the component to check + * itself. + * + * @param Sabre_VObject_Component $parent + * @param array $filters + * @return bool + */ + protected function validateCompFilters(Sabre_VObject_Component $parent, array $filters) { + + foreach($filters as $filter) { + + $isDefined = isset($parent->$filter['name']); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if ($filter['time-range']) { + foreach($parent->$filter['name'] as $subComponent) { + if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { + continue 2; + } + } + return false; + } + + if (!$filter['comp-filters'] && !$filter['prop-filters']) { + continue; + } + + // If there are sub-filters, we need to find at least one component + // for which the subfilters hold true. + foreach($parent->$filter['name'] as $subComponent) { + + if ( + $this->validateCompFilters($subComponent, $filter['comp-filters']) && + $this->validatePropFilters($subComponent, $filter['prop-filters'])) { + // We had a match, so this comp-filter succeeds + continue 2; + } + + } + + // If we got here it means there were sub-comp-filters or + // sub-prop-filters and there was no match. This means this filter + // needs to return false. + return false; + + } + + // If we got here it means we got through all comp-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of prop-filters. + * + * A list of prop-filters needs to be specified. Also the parent of the + * property we're checking should be specified, not the property to check + * itself. + * + * @param Sabre_VObject_Component $parent + * @param array $filters + * @return bool + */ + protected function validatePropFilters(Sabre_VObject_Component $parent, array $filters) { + + foreach($filters as $filter) { + + $isDefined = isset($parent->$filter['name']); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if ($filter['time-range']) { + foreach($parent->$filter['name'] as $subComponent) { + if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { + continue 2; + } + } + return false; + } + + if (!$filter['param-filters'] && !$filter['text-match']) { + continue; + } + + // If there are sub-filters, we need to find at least one property + // for which the subfilters hold true. + foreach($parent->$filter['name'] as $subComponent) { + + if( + $this->validateParamFilters($subComponent, $filter['param-filters']) && + (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match'])) + ) { + // We had a match, so this prop-filter succeeds + continue 2; + } + + } + + // If we got here it means there were sub-param-filters or + // text-match filters and there was no match. This means the + // filter needs to return false. + return false; + + } + + // If we got here it means we got through all prop-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of param-filters. + * + * A list of param-filters needs to be specified. Also the parent of the + * parameter we're checking should be specified, not the parameter to check + * itself. + * + * @param Sabre_VObject_Property $parent + * @param array $filters + * @return bool + */ + protected function validateParamFilters(Sabre_VObject_Property $parent, array $filters) { + + foreach($filters as $filter) { + + $isDefined = isset($parent[$filter['name']]); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if (!$filter['text-match']) { + continue; + } + + // If there are sub-filters, we need to find at least one parameter + // for which the subfilters hold true. + foreach($parent[$filter['name']] as $subParam) { + + if($this->validateTextMatch($subParam,$filter['text-match'])) { + // We had a match, so this param-filter succeeds + continue 2; + } + + } + + // If we got here it means there was a text-match filter and there + // were no matches. This means the filter needs to return false. + return false; + + } + + // If we got here it means we got through all param-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of a text-match. + * + * A single text-match should be specified as well as the specific property + * or parameter we need to validate. + * + * @param Sabre_VObject_Node $parent + * @param array $textMatch + * @return bool + */ + protected function validateTextMatch(Sabre_VObject_Node $parent, array $textMatch) { + + $value = (string)$parent; + + $isMatching = Sabre_DAV_StringUtil::textMatch($value, $textMatch['value'], $textMatch['collation']); + + return ($textMatch['negate-condition'] xor $isMatching); + + } + + /** + * Validates if a component matches the given time range. + * + * This is all based on the rules specified in rfc4791, which are quite + * complex. + * + * @param Sabre_VObject_Node $component + * @param DateTime $start + * @param DateTime $end + * @return bool + */ + protected function validateTimeRange(Sabre_VObject_Node $component, $start, $end) { + + if (is_null($start)) { + $start = new DateTime('1900-01-01'); + } + if (is_null($end)) { + $end = new DateTime('3000-01-01'); + } + + switch($component->name) { + + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + + return $component->isInTimeRange($start, $end); + + case 'VALARM' : + + // If the valarm is wrapped in a recurring event, we need to + // expand the recursions, and validate each. + // + // Our datamodel doesn't easily allow us to do this straight + // in the VALARM component code, so this is a hack, and an + // expensive one too. + if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) { + // Fire up the iterator! + $it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID); + while($it->valid()) { + $expandedEvent = $it->getEventObject(); + + // We need to check from these expanded alarms, which + // one is the first to trigger. Based on this, we can + // determine if we can 'give up' expanding events. + $firstAlarm = null; + foreach($expandedEvent->VALARM as $expandedAlarm) { + $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); + if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { + $firstAlarm = $effectiveTrigger; + } + if ($expandedAlarm->isInTimeRange($start, $end)) { + return true; + } + + } + if ($firstAlarm > $end) { + return false; + } + $it->next(); + } + return false; + } else { + return $component->isInTimeRange($start, $end); + } + + case 'VFREEBUSY' : + throw new Sabre_DAV_Exception_NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components'); + + case 'COMPLETED' : + case 'CREATED' : + case 'DTEND' : + case 'DTSTAMP' : + case 'DTSTART' : + case 'DUE' : + case 'LAST-MODIFIED' : + return ($start <= $component->getDateTime() && $end >= $component->getDateTime()); + + + + default : + throw new Sabre_DAV_Exception_BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component'); + + } + + } + +} diff --git a/3rdparty/Sabre/CalDAV/CalendarRootNode.php b/3rdparty/Sabre/CalDAV/CalendarRootNode.php index 69669a9d7f..3907913cc7 100644 --- a/3rdparty/Sabre/CalDAV/CalendarRootNode.php +++ b/3rdparty/Sabre/CalDAV/CalendarRootNode.php @@ -1,38 +1,38 @@ server->getPlugin('acl')) { + $aclPlugin->checkPrivileges($uri, '{DAV:}read'); + } + $this->server->httpResponse->setHeader('Content-Type','text/calendar'); $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->sendBody($this->generateICS($this->server->tree->getChildren($uri))); + + $nodes = $this->server->getPropertiesForPath($uri, array( + '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data', + ),1); + + $this->server->httpResponse->sendBody($this->generateICS($nodes)); // Returning false to break the event chain return false; @@ -65,16 +75,20 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin { } /** - * Merges all calendar objects, and builds one big ics export - * - * @param array $nodes - * @return void + * Merges all calendar objects, and builds one big ics export + * + * @param array $nodes + * @return string */ public function generateICS(array $nodes) { $calendar = new Sabre_VObject_Component('vcalendar'); $calendar->version = '2.0'; - $calendar->prodid = '-//SabreDAV//SabreDAV ' . Sabre_DAV_Version::VERSION . '//EN'; + if (Sabre_DAV_Server::$exposeVersion) { + $calendar->prodid = '-//SabreDAV//SabreDAV ' . Sabre_DAV_Version::VERSION . '//EN'; + } else { + $calendar->prodid = '-//SabreDAV//SabreDAV//EN'; + } $calendar->calscale = 'GREGORIAN'; $collectedTimezones = array(); @@ -84,7 +98,11 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin { foreach($nodes as $node) { - $nodeData = $node->get(); + if (!isset($node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'])) { + continue; + } + $nodeData = $node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data']; + $nodeComp = Sabre_VObject_Reader::read($nodeData); foreach($nodeComp->children() as $child) { @@ -105,13 +123,10 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin { $collectedTimezones[] = $child->TZID; break; - } - } - } foreach($timezones as $tz) $calendar->add($tz); @@ -119,6 +134,6 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin { return $calendar->serialize(); - } + } } diff --git a/3rdparty/Sabre/CalDAV/ICalendar.php b/3rdparty/Sabre/CalDAV/ICalendar.php index 8193dff3a8..15d51ebcf7 100644 --- a/3rdparty/Sabre/CalDAV/ICalendar.php +++ b/3rdparty/Sabre/CalDAV/ICalendar.php @@ -4,15 +4,15 @@ * Calendar interface * * Implement this interface to allow a node to be recognized as an calendar. - * + * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_CalDAV_ICalendar extends Sabre_DAV_ICollection { - + } diff --git a/3rdparty/Sabre/CalDAV/ICalendarObject.php b/3rdparty/Sabre/CalDAV/ICalendarObject.php index 708300ad7b..280f982a31 100644 --- a/3rdparty/Sabre/CalDAV/ICalendarObject.php +++ b/3rdparty/Sabre/CalDAV/ICalendarObject.php @@ -1,20 +1,20 @@ registerXPathNameSpace('cal','urn:ietf:params:xml:ns:xcal'); - - // Check if there's only 1 component - $components = array('vevent','vtodo','vjournal','vfreebusy'); - $componentsFound = array(); - - foreach($components as $component) { - $test = $xcal->xpath('/cal:iCalendar/cal:vcalendar/cal:' . $component); - if (is_array($test)) $componentsFound = array_merge($componentsFound, $test); - } - if (count($componentsFound)<1) { - throw new Sabre_CalDAV_Exception_InvalidICalendarObject('One VEVENT, VTODO, VJOURNAL or VFREEBUSY must be specified. 0 found.'); - } - $component = $componentsFound[0]; - - if (is_null($allowedComponents)) return true; - - // Check if the component is allowed - $name = $component->getName(); - if (!in_array(strtoupper($name),$allowedComponents)) { - throw new Sabre_CalDAV_Exception_InvalidICalendarObject(strtoupper($name) . ' is not allowed in this calendar.'); - } - - if (count($xcal->xpath('/cal:iCalendar/cal:vcalendar/cal:method'))>0) { - throw new Sabre_CalDAV_Exception_InvalidICalendarObject('The METHOD property is not allowed in calendar objects'); - } - - return true; - - } - - /** - * Converts ICalendar data to XML. - * - * Properties are converted to lowercase xml elements. Parameters are; - * converted to attributes. BEGIN:VEVENT is converted to and - * END:VEVENT as well as other components. - * - * It's a very loose parser. If any line does not conform to the spec, it - * will simply be ignored. It will try to detect if \r\n or \n line endings - * are used. - * - * @todo Currently quoted attributes are not parsed correctly. - * @see http://tools.ietf.org/html/draft-royer-calsch-xcal-03 - * @param string $icalData - * @return string. - */ - static function toXCAL($icalData) { - - // Detecting line endings - $lb="\r\n"; - if (strpos($icalData,"\r\n")!==false) $lb = "\r\n"; - elseif (strpos($icalData,"\n")!==false) $lb = "\n"; - - // Splitting up items per line - $lines = explode($lb,$icalData); - - // Properties can be folded over 2 lines. In this case the second - // line will be preceeded by a space or tab. - $lines2 = array(); - foreach($lines as $line) { - - if (!$line) continue; - if ($line[0]===" " || $line[0]==="\t") { - $lines2[count($lines2)-1].=substr($line,1); - continue; - } - - $lines2[]=$line; - - } - - $xml = '' . "\n"; - $xml.= "\n"; - - $spaces = 2; - foreach($lines2 as $line) { - - $matches = array(); - // This matches PROPERTYNAME;ATTRIBUTES:VALUE - if (!preg_match('/^([^:^;]*)(?:;([^:]*))?:(.*)$/',$line,$matches)) - continue; - - $propertyName = strtolower($matches[1]); - $attributes = $matches[2]; - $value = $matches[3]; - - // If the line was in the format BEGIN:COMPONENT or END:COMPONENT, we need to special case it. - if ($propertyName === 'begin') { - $xml.=str_repeat(" ",$spaces); - $xml.='<' . strtolower($value) . ">\n"; - $spaces+=2; - continue; - } elseif ($propertyName === 'end') { - $spaces-=2; - $xml.=str_repeat(" ",$spaces); - $xml.='\n"; - continue; - } - - $xml.=str_repeat(" ",$spaces); - $xml.='<' . $propertyName; - if ($attributes) { - // There can be multiple attributes - $attributes = explode(';',$attributes); - foreach($attributes as $att) { - - list($attName,$attValue) = explode('=',$att,2); - $attName = strtolower($attName); - if ($attName === 'language') $attName='xml:lang'; - $xml.=' ' . $attName . '="' . htmlspecialchars($attValue) . '"'; - - } - } - - $xml.='>'. htmlspecialchars(trim($value)) . '\n"; - - } - $xml.=""; - return $xml; - - } - -} - diff --git a/3rdparty/Sabre/CalDAV/Plugin.php b/3rdparty/Sabre/CalDAV/Plugin.php index 02747c8395..d7d1d97051 100644 --- a/3rdparty/Sabre/CalDAV/Plugin.php +++ b/3rdparty/Sabre/CalDAV/Plugin.php @@ -8,8 +8,8 @@ * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { @@ -18,7 +18,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { * This is the official CalDAV namespace */ const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav'; - + /** * This is the namespace for the proprietary calendarserver extensions */ @@ -41,21 +41,48 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { const CALENDAR_ROOT = 'calendars'; /** - * Reference to server object - * - * @var Sabre_DAV_Server + * Reference to server object + * + * @var Sabre_DAV_Server */ private $server; + /** + * The email handler for invites and other scheduling messages. + * + * @var Sabre_CalDAV_Schedule_IMip + */ + protected $imipHandler; + + /** + * Sets the iMIP handler. + * + * iMIP = The email transport of iCalendar scheduling messages. Setting + * this is optional, but if you want the server to allow invites to be sent + * out, you must set a handler. + * + * Specifically iCal will plain assume that the server supports this. If + * the server doesn't, iCal will display errors when inviting people to + * events. + * + * @param Sabre_CalDAV_Schedule_IMip $imipHandler + * @return void + */ + public function setIMipHandler(Sabre_CalDAV_Schedule_IMip $imipHandler) { + + $this->imipHandler = $imipHandler; + + } + /** * Use this method to tell the server this plugin defines additional * HTTP methods. * - * This method is passed a uri. It should only return HTTP methods that are + * This method is passed a uri. It should only return HTTP methods that are * available for the specified uri. * * @param string $uri - * @return array + * @return array */ public function getHTTPMethods($uri) { @@ -68,7 +95,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { if ($node instanceof Sabre_DAV_IExtendedCollection) { try { $node->getChild($name); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { return array('MKCALENDAR'); } } @@ -77,9 +104,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Returns a list of features for the DAV: HTTP header. - * - * @return array + * Returns a list of features for the DAV: HTTP header. + * + * @return array */ public function getFeatures() { @@ -89,11 +116,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns a plugin name. - * + * * Using this name other plugins will be able to access other plugins - * using Sabre_DAV_Server::getPlugin - * - * @return string + * using Sabre_DAV_Server::getPlugin + * + * @return string */ public function getPluginName() { @@ -105,38 +132,46 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them - * + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * * @param string $uri - * @return array + * @return array */ public function getSupportedReportSet($uri) { $node = $this->server->tree->getNodeForPath($uri); + + $reports = array(); if ($node instanceof Sabre_CalDAV_ICalendar || $node instanceof Sabre_CalDAV_ICalendarObject) { - return array( - '{' . self::NS_CALDAV . '}calendar-multiget', - '{' . self::NS_CALDAV . '}calendar-query', - ); + $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget'; + $reports[] = '{' . self::NS_CALDAV . '}calendar-query'; } - return array(); + if ($node instanceof Sabre_CalDAV_ICalendar) { + $reports[] = '{' . self::NS_CALDAV . '}free-busy-query'; + } + return $reports; } /** - * Initializes the plugin - * - * @param Sabre_DAV_Server $server + * Initializes the plugin + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { $this->server = $server; + $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); //$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000); $server->subscribeEvent('report',array($this,'report')); $server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties')); + $server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel')); + $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction')); + $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent')); + $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile')); $server->xmlNamespaces[self::NS_CALDAV] = 'cal'; $server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs'; @@ -144,6 +179,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { $server->propertyMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre_CalDAV_Property_SupportedCalendarComponentSet'; $server->resourceTypeMapping['Sabre_CalDAV_ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar'; + $server->resourceTypeMapping['Sabre_CalDAV_Schedule_IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox'; $server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read'; $server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write'; @@ -161,7 +197,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { '{' . self::NS_CALDAV . '}calendar-data', // scheduling extension + '{' . self::NS_CALDAV . '}schedule-inbox-URL', + '{' . self::NS_CALDAV . '}schedule-outbox-URL', '{' . self::NS_CALDAV . '}calendar-user-address-set', + '{' . self::NS_CALDAV . '}calendar-user-type', // CalendarServer extensions '{' . self::NS_CALENDARSERVER . '}getctag', @@ -173,36 +212,55 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * This function handles support for the MKCALENDAR method - * - * @param string $method - * @return bool + * + * @param string $method + * @param string $uri + * @return bool */ public function unknownMethod($method, $uri) { - if ($method!=='MKCALENDAR') return; + switch ($method) { + case 'MKCALENDAR' : + $this->httpMkCalendar($uri); + // false is returned to stop the propagation of the + // unknownMethod event. + return false; + case 'POST' : + // Checking if we're talking to an outbox + try { + $node = $this->server->tree->getNodeForPath($uri); + } catch (Sabre_DAV_Exception_NotFound $e) { + return; + } + if (!$node instanceof Sabre_CalDAV_Schedule_IOutbox) + return; - $this->httpMkCalendar($uri); - // false is returned to stop the unknownMethod event - return false; + $this->outboxRequest($node); + return false; + + } } /** - * This functions handles REPORT requests specific to CalDAV - * - * @param string $reportName - * @param DOMNode $dom - * @return bool + * This functions handles REPORT requests specific to CalDAV + * + * @param string $reportName + * @param DOMNode $dom + * @return bool */ public function report($reportName,$dom) { - switch($reportName) { + switch($reportName) { case '{'.self::NS_CALDAV.'}calendar-multiget' : $this->calendarMultiGetReport($dom); return false; case '{'.self::NS_CALDAV.'}calendar-query' : $this->calendarQueryReport($dom); return false; + case '{'.self::NS_CALDAV.'}free-busy-query' : + $this->freeBusyQueryReport($dom); + return false; } @@ -212,9 +270,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * This function handles the MKCALENDAR HTTP method, which creates * a new calendar. - * + * * @param string $uri - * @return void + * @return void */ public function httpMkCalendar($uri) { @@ -238,7 +296,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { foreach(Sabre_DAV_XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) { $properties[$k] = $prop; } - + } } @@ -254,9 +312,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { * beforeGetProperties * * This method handler is invoked before any after properties for a - * resource are fetched. This allows us to add in any CalDAV specific - * properties. - * + * resource are fetched. This allows us to add in any CalDAV specific + * properties. + * * @param string $path * @param Sabre_DAV_INode $node * @param array $requestedProperties @@ -270,12 +328,21 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { // calendar-home-set property $calHome = '{' . self::NS_CALDAV . '}calendar-home-set'; if (in_array($calHome,$requestedProperties)) { - $principalId = $node->getName(); + $principalId = $node->getName(); $calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/'; unset($requestedProperties[$calHome]); $returnedProperties[200][$calHome] = new Sabre_DAV_Property_Href($calendarHomePath); } + // schedule-outbox-URL property + $scheduleProp = '{' . self::NS_CALDAV . '}schedule-outbox-URL'; + if (in_array($scheduleProp,$requestedProperties)) { + $principalId = $node->getName(); + $outboxPath = self::CALENDAR_ROOT . '/' . $principalId . '/outbox'; + unset($requestedProperties[$scheduleProp]); + $returnedProperties[200][$scheduleProp] = new Sabre_DAV_Property_Href($outboxPath); + } + // calendar-user-address-set property $calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set'; if (in_array($calProp,$requestedProperties)) { @@ -287,7 +354,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { } - // These two properties are shortcuts for ical to easily find + // These two properties are shortcuts for ical to easily find // other principals this principal has access to. $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for'; $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'; @@ -301,8 +368,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { $groupNode = $this->server->tree->getNodeForPath($group); - // If the node is either ap proxy-read or proxy-write - // group, we grab the parent principal and add it to the + // If the node is either ap proxy-read or proxy-write + // group, we grab the parent principal and add it to the // list. if ($groupNode instanceof Sabre_CalDAV_Principal_ProxyRead) { list($readList[]) = Sabre_DAV_URLUtil::splitPath($group); @@ -327,8 +394,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { if ($node instanceof Sabre_CalDAV_ICalendarObject) { - // The calendar-data property is not supposed to be a 'real' - // property, but in large chunks of the spec it does act as such. + // The calendar-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. // Therefore we simply expose it as a property. $calDataProp = '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'; if (in_array($calDataProp, $requestedProperties)) { @@ -350,18 +417,52 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. - * - * @param DOMNode $dom + * + * @param DOMNode $dom * @return void */ public function calendarMultiGetReport($dom) { $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)); - $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); + + $xpath = new DOMXPath($dom); + $xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV); + $xpath->registerNameSpace('dav','urn:DAV'); + + $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); + if ($expand->length>0) { + $expandElem = $expand->item(0); + $start = $expandElem->getAttribute('start'); + $end = $expandElem->getAttribute('end'); + if(!$start || !$end) { + throw new Sabre_DAV_Exception_BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element'); + } + $start = Sabre_VObject_DateTimeParser::parseDateTime($start); + $end = Sabre_VObject_DateTimeParser::parseDateTime($end); + + if ($end <= $start) { + throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.'); + } + + $expand = true; + + } else { + + $expand = false; + + } + foreach($hrefElems as $elem) { $uri = $this->server->calculateUri($elem->nodeValue); list($objProps) = $this->server->getPropertiesForPath($uri,$properties); + + if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { + $vObject = Sabre_VObject_Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); + $vObject->expand($start, $end); + $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + $propertyList[]=$objProps; } @@ -377,48 +478,57 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { * * This report is used by clients to request calendar objects based on * complex conditions. - * - * @param DOMNode $dom + * + * @param DOMNode $dom * @return void */ public function calendarQueryReport($dom) { - $requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)); - - $filterNode = $dom->getElementsByTagNameNS('urn:ietf:params:xml:ns:caldav','filter'); - if ($filterNode->length!==1) { - throw new Sabre_DAV_Exception_BadRequest('The calendar-query report must have a filter element'); - } - $filters = Sabre_CalDAV_XMLUtil::parseCalendarQueryFilters($filterNode->item(0)); + $parser = new Sabre_CalDAV_CalendarQueryParser($dom); + $parser->parse(); $requestedCalendarData = true; + $requestedProperties = $parser->requestedProperties; if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { + // We always retrieve calendar-data, as we need it for filtering. $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; - // If calendar-data wasn't explicitly requested, we need to remove + // If calendar-data wasn't explicitly requested, we need to remove // it after processing. $requestedCalendarData = false; } // These are the list of nodes that potentially match the requirement - $candidateNodes = $this->server->getPropertiesForPath($this->server->getRequestUri(),$requestedProperties,$this->server->getHTTPDepth(0)); + $candidateNodes = $this->server->getPropertiesForPath( + $this->server->getRequestUri(), + $requestedProperties, + $this->server->getHTTPDepth(0) + ); $verifiedNodes = array(); + $validator = new Sabre_CalDAV_CalendarQueryValidator(); + foreach($candidateNodes as $node) { // If the node didn't have a calendar-data property, it must not be a calendar object - if (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) continue; + if (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) + continue; + + $vObject = Sabre_VObject_Reader::read($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); + if ($validator->validate($vObject,$parser->filters)) { - if ($this->validateFilters($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'],$filters)) { - if (!$requestedCalendarData) { unset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } + if ($parser->expand) { + $vObject->expand($parser->expand['start'], $parser->expand['end']); + $node[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } $verifiedNodes[] = $node; - } + } } @@ -428,360 +538,291 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { } - /** - * Verify if a list of filters applies to the calendar data object + * This method is responsible for parsing the request and generating the + * response for the CALDAV:free-busy-query REPORT. * - * The calendarData object must be a valid iCalendar blob. The list of - * filters must be formatted as parsed by Sabre_CalDAV_Plugin::parseCalendarQueryFilters - * - * @param string $calendarData - * @param array $filters - * @return bool - */ - public function validateFilters($calendarData,$filters) { - - // We are converting the calendar object to an XML structure - // This makes it far easier to parse - $xCalendarData = Sabre_CalDAV_ICalendarUtil::toXCal($calendarData); - $xml = simplexml_load_string($xCalendarData); - $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:xcal'); - - foreach($filters as $xpath=>$filter) { - - // if-not-defined comes first - if (isset($filter['is-not-defined'])) { - if (!$xml->xpath($xpath)) - continue; - else - return false; - - } - - $elem = $xml->xpath($xpath); - - if (!$elem) return false; - $elem = $elem[0]; - - if (isset($filter['time-range'])) { - - switch($elem->getName()) { - case 'vevent' : - $result = $this->validateTimeRangeFilterForEvent($xml,$xpath,$filter); - if ($result===false) return false; - break; - case 'vtodo' : - $result = $this->validateTimeRangeFilterForTodo($xml,$xpath,$filter); - if ($result===false) return false; - break; - case 'vjournal' : - case 'vfreebusy' : - case 'valarm' : - // TODO: not implemented - break; - - /* - - case 'vjournal' : - $result = $this->validateTimeRangeFilterForJournal($xml,$xpath,$filter); - if ($result===false) return false; - break; - case 'vfreebusy' : - $result = $this->validateTimeRangeFilterForFreeBusy($xml,$xpath,$filter); - if ($result===false) return false; - break; - case 'valarm' : - $result = $this->validateTimeRangeFilterForAlarm($xml,$xpath,$filter); - if ($result===false) return false; - break; - - */ - - } - - } - - if (isset($filter['text-match'])) { - $currentString = (string)$elem; - - $isMatching = Sabre_DAV_StringUtil::textMatch($currentString, $filter['text-match']['value'], $filter['text-match']['collation']); - if ($filter['text-match']['negate-condition'] && $isMatching) return false; - if (!$filter['text-match']['negate-condition'] && !$isMatching) return false; - - } - - } - return true; - - } - - /** - * Checks whether a time-range filter matches an event. - * - * @param SimpleXMLElement $xml Event as xml object - * @param string $currentXPath XPath to check - * @param array $currentFilter Filter information + * @param DOMNode $dom * @return void */ - private function validateTimeRangeFilterForEvent(SimpleXMLElement $xml,$currentXPath,array $currentFilter) { + protected function freeBusyQueryReport(DOMNode $dom) { - // Grabbing the DTSTART property - $xdtstart = $xml->xpath($currentXPath.'/c:dtstart'); - if (!count($xdtstart)) { - throw new Sabre_DAV_Exception_BadRequest('DTSTART property missing from calendar object'); - } + $start = null; + $end = null; - // The dtstart can be both a date, or datetime property - if ((string)$xdtstart[0]['value']==='DATE' || strlen((string)$xdtstart[0])===8) { - $isDateTime = false; - } else { - $isDateTime = true; - } + foreach($dom->firstChild->childNodes as $childNode) { - // Determining the timezone - if ($tzid = (string)$xdtstart[0]['tzid']) { - $tz = new DateTimeZone($tzid); - } else { - $tz = null; - } - if ($isDateTime) { - $dtstart = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtstart[0],$tz); - } else { - $dtstart = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtstart[0]); - } - - - // Grabbing the DTEND property - $xdtend = $xml->xpath($currentXPath.'/c:dtend'); - $dtend = null; - - if (count($xdtend)) { - // Determining the timezone - if ($tzid = (string)$xdtend[0]['tzid']) { - $tz = new DateTimeZone($tzid); - } else { - $tz = null; + $clark = Sabre_DAV_XMLUtil::toClarkNotation($childNode); + if ($clark == '{' . self::NS_CALDAV . '}time-range') { + $start = $childNode->getAttribute('start'); + $end = $childNode->getAttribute('end'); + break; } - // Since the VALUE prameter of both DTSTART and DTEND must be the same - // we can assume we don't need to check the VALUE paramter of DTEND. - if ($isDateTime) { - $dtend = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtend[0],$tz); - } else { - $dtend = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtend[0],$tz); - } - - } - - if (is_null($dtend)) { - // The DTEND property was not found. We will first see if the event has a duration - // property - - $xduration = $xml->xpath($currentXPath.'/c:duration'); - if (count($xduration)) { - $duration = Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]); - - // Making sure that the duration is bigger than 0 seconds. - $tempDT = clone $dtstart; - $tempDT->modify($duration); - if ($tempDT > $dtstart) { - - // use DTEND = DTSTART + DURATION - $dtend = $tempDT; - } else { - // use DTEND = DTSTART - $dtend = $dtstart; - } - - } + } + if ($start) { + $start = Sabre_VObject_DateTimeParser::parseDateTime($start); + } + if ($end) { + $end = Sabre_VObject_DateTimeParser::parseDateTime($end); } - if (is_null($dtend)) { - if ($isDateTime) { - // DTEND = DTSTART - $dtend = $dtstart; - } else { - // DTEND = DTSTART + 1 DAY - $dtend = clone $dtstart; - $dtend->modify('+1 day'); - } + if (!$start && !$end) { + throw new Sabre_DAV_Exception_BadRequest('The freebusy report must have a time-range filter'); } - // TODO: we need to properly parse RRULE's, but it's very difficult. - // For now, we're always returning events if they have an RRULE at all. - $rrule = $xml->xpath($currentXPath.'/c:rrule'); - $hasRrule = (count($rrule))>0; - - if (!is_null($currentFilter['time-range']['start']) && $currentFilter['time-range']['start'] >= $dtend) return false; - if (!is_null($currentFilter['time-range']['end']) && $currentFilter['time-range']['end'] <= $dtstart && !$hasRrule) return false; - return true; - + $acl = $this->server->getPlugin('acl'); + + if (!$acl) { + throw new Sabre_DAV_Exception('The ACL plugin must be loaded for free-busy queries to work'); + } + $uri = $this->server->getRequestUri(); + $acl->checkPrivileges($uri,'{' . self::NS_CALDAV . '}read-free-busy'); + + $calendar = $this->server->tree->getNodeForPath($uri); + if (!$calendar instanceof Sabre_CalDAV_ICalendar) { + throw new Sabre_DAV_Exception_NotImplemented('The free-busy-query REPORT is only implemented on calendars'); + } + + $objects = array_map(function($child) { + $obj = $child->get(); + if (is_resource($obj)) { + $obj = stream_get_contents($obj); + } + return $obj; + }, $calendar->getChildren()); + + $generator = new Sabre_VObject_FreeBusyGenerator(); + $generator->setObjects($objects); + $generator->setTimeRange($start, $end); + $result = $generator->getResult(); + $result = $result->serialize(); + + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); + $this->server->httpResponse->setHeader('Content-Length', strlen($result)); + $this->server->httpResponse->sendBody($result); + } - private function validateTimeRangeFilterForTodo(SimpleXMLElement $xml,$currentXPath,array $filter) { + /** + * This method is triggered before a file gets updated with new content. + * + * This plugin uses this method to ensure that CalDAV objects receive + * valid calendar data. + * + * @param string $path + * @param Sabre_DAV_IFile $node + * @param resource $data + * @return void + */ + public function beforeWriteContent($path, Sabre_DAV_IFile $node, &$data) { - // Gathering all relevant elements + if (!$node instanceof Sabre_CalDAV_ICalendarObject) + return; - $dtStart = null; - $duration = null; - $due = null; - $completed = null; - $created = null; + $this->validateICalendar($data); - $xdt = $xml->xpath($currentXPath.'/c:dtstart'); - if (count($xdt)) { - // The dtstart can be both a date, or datetime property - if ((string)$xdt[0]['value']==='DATE') { - $isDateTime = false; - } else { - $isDateTime = true; - } + } - // Determining the timezone - if ($tzid = (string)$xdt[0]['tzid']) { - $tz = new DateTimeZone($tzid); - } else { - $tz = null; - } - if ($isDateTime) { - $dtStart = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0],$tz); - } else { - $dtStart = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdt[0]); - } + /** + * This method is triggered before a new file is created. + * + * This plugin uses this method to ensure that newly created calendar + * objects contain valid calendar data. + * + * @param string $path + * @param resource $data + * @param Sabre_DAV_ICollection $parentNode + * @return void + */ + public function beforeCreateFile($path, &$data, Sabre_DAV_ICollection $parentNode) { + + if (!$parentNode instanceof Sabre_CalDAV_Calendar) + return; + + $this->validateICalendar($data); + + } + + /** + * Checks if the submitted iCalendar data is in fact, valid. + * + * An exception is thrown if it's not. + * + * @param resource|string $data + * @return void + */ + protected function validateICalendar(&$data) { + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); } - // Only need to grab duration if dtStart is set - if (!is_null($dtStart)) { + // Converting the data to unicode, if needed. + $data = Sabre_DAV_StringUtil::ensureUTF8($data); - $xduration = $xml->xpath($currentXPath.'/c:duration'); - if (count($xduration)) { - $duration = Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]); - } + try { + + $vobj = Sabre_VObject_Reader::read($data); + + } catch (Sabre_VObject_ParseException $e) { + + throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage()); } - if (!is_null($dtStart) && !is_null($duration)) { + } - // Comparision from RFC 4791: - // (start <= DTSTART+DURATION) AND ((end > DTSTART) OR (end >= DTSTART+DURATION)) + /** + * This method handles POST requests to the schedule-outbox + * + * @param Sabre_CalDAV_Schedule_IOutbox $outboxNode + * @return void + */ + public function outboxRequest(Sabre_CalDAV_Schedule_IOutbox $outboxNode) { - $end = clone $dtStart; - $end->modify($duration); + $originator = $this->server->httpRequest->getHeader('Originator'); + $recipients = $this->server->httpRequest->getHeader('Recipient'); - if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $end) && - (is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $dtStart || $filter['time-range']['end'] >= $end) ) { - return true; - } else { - return false; + if (!$originator) { + throw new Sabre_DAV_Exception_BadRequest('The Originator: header must be specified when making POST requests'); + } + if (!$recipients) { + throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests'); + } + + if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) { + throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address'); + } + $originator = substr($originator,7); + + $recipients = explode(',',$recipients); + foreach($recipients as $k=>$recipient) { + + $recipient = trim($recipient); + if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) { + throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address'); } - + $recipient = substr($recipient, 7); + $recipients[$k] = $recipient; } - // Need to grab the DUE property - $xdt = $xml->xpath($currentXPath.'/c:due'); - if (count($xdt)) { - // The due property can be both a date, or datetime property - if ((string)$xdt[0]['value']==='DATE') { - $isDateTime = false; - } else { - $isDateTime = true; - } - // Determining the timezone - if ($tzid = (string)$xdt[0]['tzid']) { - $tz = new DateTimeZone($tzid); - } else { - $tz = null; - } - if ($isDateTime) { - $due = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0],$tz); - } else { - $due = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdt[0]); - } + // We need to make sure that 'originator' matches one of the email + // addresses of the selected principal. + $principal = $outboxNode->getOwner(); + $props = $this->server->getProperties($principal,array( + '{' . self::NS_CALDAV . '}calendar-user-address-set', + )); + + $addresses = array(); + if (isset($props['{' . self::NS_CALDAV . '}calendar-user-address-set'])) { + $addresses = $props['{' . self::NS_CALDAV . '}calendar-user-address-set']->getHrefs(); } - if (!is_null($dtStart) && !is_null($due)) { - - // Comparision from RFC 4791: - // ((start < DUE) OR (start <= DTSTART)) AND ((end > DTSTART) OR (end >= DUE)) - - if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] < $due || $filter['time-range']['start'] < $dtstart) && - (is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $due) ) { - return true; - } else { - return false; - } - + if (!in_array('mailto:' . $originator, $addresses)) { + throw new Sabre_DAV_Exception_Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header'); } - if (!is_null($dtStart)) { - - // Comparision from RFC 4791 - // (start <= DTSTART) AND (end > DTSTART) - if ( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $dtStart) && - (is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $dtStart) ) { - return true; - } else { - return false; - } - + try { + $vObject = Sabre_VObject_Reader::read($this->server->httpRequest->getBody(true)); + } catch (Sabre_VObject_ParseException $e) { + throw new Sabre_DAV_Exception_BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage()); } - if (!is_null($due)) { - - // Comparison from RFC 4791 - // (start < DUE) AND (end >= DUE) - if ( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] < $due) && - (is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $due) ) { - return true; - } else { - return false; - } - - } - // Need to grab the COMPLETED property - $xdt = $xml->xpath($currentXPath.'/c:completed'); - if (count($xdt)) { - $completed = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0]); - } - // Need to grab the CREATED property - $xdt = $xml->xpath($currentXPath.'/c:created'); - if (count($xdt)) { - $created = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0]); - } - - if (!is_null($completed) && !is_null($created)) { - // Comparison from RFC 4791 - // ((start <= CREATED) OR (start <= COMPLETED)) AND ((end >= CREATED) OR (end >= COMPLETED)) - if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $created || $filter['time-range']['start'] <= $completed) && - (is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $created || $filter['time-range']['end'] >= $completed)) { - return true; - } else { - return false; + // Checking for the object type + $componentType = null; + foreach($vObject->getComponents() as $component) { + if ($component->name !== 'VTIMEZONE') { + $componentType = $component->name; + break; } } - - if (!is_null($completed)) { - // Comparison from RFC 4791 - // (start <= COMPLETED) AND (end >= COMPLETED) - if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $completed) && - (is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $completed)) { - return true; - } else { - return false; - } + if (is_null($componentType)) { + throw new Sabre_DAV_Exception_BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component'); } - if (!is_null($created)) { - // Comparison from RFC 4791 - // (end > CREATED) - if( (is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $created) ) { - return true; - } else { - return false; - } + // Validating the METHOD + $method = strtoupper((string)$vObject->METHOD); + if (!$method) { + throw new Sabre_DAV_Exception_BadRequest('A METHOD property must be specified in iTIP messages'); } - // Everything else is TRUE - return true; + if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') { + $this->iMIPMessage($originator, $recipients, $vObject); + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->sendBody('Messages sent'); + } else { + throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented'); + } + + } + + /** + * Sends an iMIP message by email. + * + * @param string $originator + * @param array $recipients + * @param Sabre_VObject_Component $vObject + * @return void + */ + protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject) { + + if (!$this->imipHandler) { + throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.'); + } + $this->imipHandler->sendMessage($originator, $recipients, $vObject); + + } + + /** + * This method is used to generate HTML output for the + * Sabre_DAV_Browser_Plugin. This allows us to generate an interface users + * can use to create new calendars. + * + * @param Sabre_DAV_INode $node + * @param string $output + * @return bool + */ + public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) { + + if (!$node instanceof Sabre_CalDAV_UserCalendars) + return; + + $output.= '
+

Create new calendar

+ +
+
+ +
+ '; + + return false; + + } + + /** + * This method allows us to intercept the 'mkcalendar' sabreAction. This + * action enables the user to create new calendars from the browser plugin. + * + * @param string $uri + * @param string $action + * @param array $postVars + * @return bool + */ + public function browserPostAction($uri, $action, array $postVars) { + + if ($action!=='mkcalendar') + return; + + $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'); + $properties = array(); + if (isset($postVars['{DAV:}displayname'])) { + $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; + } + $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); + return false; } diff --git a/3rdparty/Sabre/CalDAV/Principal/Collection.php b/3rdparty/Sabre/CalDAV/Principal/Collection.php index 13435b2448..abbefa5567 100644 --- a/3rdparty/Sabre/CalDAV/Principal/Collection.php +++ b/3rdparty/Sabre/CalDAV/Principal/Collection.php @@ -4,23 +4,23 @@ * Principal collection * * This is an alternative collection to the standard ACL principal collection. - * This collection adds support for the calendar-proxy-read and - * calendar-proxy-write sub-principals, as defined by the caldav-proxy + * This collection adds support for the calendar-proxy-read and + * calendar-proxy-write sub-principals, as defined by the caldav-proxy * specification. * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Principal_Collection extends Sabre_DAVACL_AbstractPrincipalCollection { /** - * Returns a child object based on principal information - * - * @param array $principalInfo - * @return Sabre_CalDAV_Principal_User + * Returns a child object based on principal information + * + * @param array $principalInfo + * @return Sabre_CalDAV_Principal_User */ public function getChildForPrincipal(array $principalInfo) { diff --git a/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php b/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php index f531d85d1f..4b3f035634 100644 --- a/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php +++ b/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php @@ -4,38 +4,38 @@ * ProxyRead principal * * This class represents a principal group, hosted under the main principal. - * This is needed to implement 'Calendar delegation' support. This class is + * This is needed to implement 'Calendar delegation' support. This class is * instantiated by Sabre_CalDAV_Principal_User. - * + * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { /** - * Principal information from the parent principal. - * - * @var array + * Principal information from the parent principal. + * + * @var array */ protected $principalInfo; /** - * Principal backend - * - * @var Sabre_DAVACL_IPrincipalBackend + * Principal backend + * + * @var Sabre_DAVACL_IPrincipalBackend */ protected $principalBackend; /** * Creates the object. * - * Note that you MUST supply the parent principal information. - * - * @param Sabre_DAVACL_IPrincipalBackend $principalBackend - * @param array $principalInfo + * Note that you MUST supply the parent principal information. + * + * @param Sabre_DAVACL_IPrincipalBackend $principalBackend + * @param array $principalInfo */ public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalInfo) { @@ -46,8 +46,8 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { /** * Returns this principals name. - * - * @return string + * + * @return string */ public function getName() { @@ -56,13 +56,13 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { } /** - * Returns the last modification time + * Returns the last modification time * - * @return null + * @return null */ public function getLastModified() { - return null; + return null; } @@ -70,7 +70,7 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { * Deletes the current node * * @throws Sabre_DAV_Exception_Forbidden - * @return void + * @return void */ public function delete() { @@ -80,7 +80,7 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { /** * Renames the node - * + * * @throws Sabre_DAV_Exception_Forbidden * @param string $name The new name * @return void @@ -93,11 +93,11 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { /** - * Returns a list of altenative urls for a principal - * + * Returns a list of alternative urls for a principal + * * This can for example be an email address, or ldap url. - * - * @return array + * + * @return array */ public function getAlternateUriSet() { @@ -106,41 +106,41 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { } /** - * Returns the full principal url - * - * @return string + * Returns the full principal url + * + * @return string */ public function getPrincipalUrl() { - return $this->principalInfo['uri'] . '/' . $this->getName(); + return $this->principalInfo['uri'] . '/' . $this->getName(); } /** * Returns the list of group members - * + * * If this principal is a group, this function should return - * all member principal uri's for the group. - * + * all member principal uri's for the group. + * * @return array */ public function getGroupMemberSet() { - return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); + return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); } /** * Returns the list of groups this principal is member of - * + * * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array + * should return a list of principal uri's for it's members. + * + * @return array */ public function getGroupMembership() { - return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); + return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); } @@ -149,11 +149,11 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { * * If this principal is a group, this method sets all the group members. * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $principals - * @return void + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void */ public function setGroupMemberSet(array $principals) { @@ -165,9 +165,9 @@ class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal { * Returns the displayname * * This should be a human readable name for the principal. - * If none is available, return the nodename. - * - * @return string + * If none is available, return the nodename. + * + * @return string */ public function getDisplayName() { diff --git a/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php b/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php index 4d8face206..dd0c2e86ed 100644 --- a/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php +++ b/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php @@ -4,38 +4,38 @@ * ProxyWrite principal * * This class represents a principal group, hosted under the main principal. - * This is needed to implement 'Calendar delegation' support. This class is + * This is needed to implement 'Calendar delegation' support. This class is * instantiated by Sabre_CalDAV_Principal_User. - * + * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { /** - * Parent principal information - * - * @var array + * Parent principal information + * + * @var array */ protected $principalInfo; /** - * Principal Backend - * - * @var Sabre_DAVACL_IPrincipalBackend + * Principal Backend + * + * @var Sabre_DAVACL_IPrincipalBackend */ protected $principalBackend; /** * Creates the object * - * Note that you MUST supply the parent principal information. - * - * @param Sabre_DAVACL_IPrincipalBackend $principalBackend - * @param array $principalInfo + * Note that you MUST supply the parent principal information. + * + * @param Sabre_DAVACL_IPrincipalBackend $principalBackend + * @param array $principalInfo */ public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalInfo) { @@ -46,8 +46,8 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { /** * Returns this principals name. - * - * @return string + * + * @return string */ public function getName() { @@ -56,13 +56,13 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { } /** - * Returns the last modification time + * Returns the last modification time * - * @return null + * @return null */ public function getLastModified() { - return null; + return null; } @@ -70,7 +70,7 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { * Deletes the current node * * @throws Sabre_DAV_Exception_Forbidden - * @return void + * @return void */ public function delete() { @@ -80,7 +80,7 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { /** * Renames the node - * + * * @throws Sabre_DAV_Exception_Forbidden * @param string $name The new name * @return void @@ -93,11 +93,11 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { /** - * Returns a list of altenative urls for a principal - * + * Returns a list of alternative urls for a principal + * * This can for example be an email address, or ldap url. - * - * @return array + * + * @return array */ public function getAlternateUriSet() { @@ -106,41 +106,41 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { } /** - * Returns the full principal url - * - * @return string + * Returns the full principal url + * + * @return string */ public function getPrincipalUrl() { - return $this->principalInfo['uri'] . '/' . $this->getName(); + return $this->principalInfo['uri'] . '/' . $this->getName(); } /** * Returns the list of group members - * + * * If this principal is a group, this function should return - * all member principal uri's for the group. - * + * all member principal uri's for the group. + * * @return array */ public function getGroupMemberSet() { - return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); + return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); } /** * Returns the list of groups this principal is member of - * + * * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array + * should return a list of principal uri's for it's members. + * + * @return array */ public function getGroupMembership() { - return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); + return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); } @@ -149,11 +149,11 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { * * If this principal is a group, this method sets all the group members. * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $principals - * @return void + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void */ public function setGroupMemberSet(array $principals) { @@ -165,9 +165,9 @@ class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal { * Returns the displayname * * This should be a human readable name for the principal. - * If none is available, return the nodename. - * - * @return string + * If none is available, return the nodename. + * + * @return string */ public function getDisplayName() { diff --git a/3rdparty/Sabre/CalDAV/Principal/User.php b/3rdparty/Sabre/CalDAV/Principal/User.php index 034629b89b..8453b877a7 100644 --- a/3rdparty/Sabre/CalDAV/Principal/User.php +++ b/3rdparty/Sabre/CalDAV/Principal/User.php @@ -1,25 +1,25 @@ principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name); + if (!$principal) { + throw new Sabre_DAV_Exception_NotFound('Node with name ' . $name . ' was not found'); + } if ($name === 'calendar-proxy-read') return new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties); if ($name === 'calendar-proxy-write') return new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties); - throw new Sabre_DAV_Exception_FileNotFound('Node with name ' . $name . ' was not found'); + throw new Sabre_DAV_Exception_NotFound('Node with name ' . $name . ' was not found'); } /** - * Returns an array with all the child nodes - * - * @return Sabre_DAV_INode[] + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] */ public function getChildren() { - return array( - new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties), - new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties), - ); + $r = array(); + if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) { + $r[] = new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties); + } + if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) { + $r[] = new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties); + } + + return $r; } /** - * Checks if a child-node with the specified name exists - * - * @return bool + * Returns whether or not the child node exists + * + * @param string $name + * @return bool */ public function childExists($name) { - return $name === 'calendar-proxy-read' || $name === 'calendar-proxy-write'; + try { + $this->getChild($name); + return true; + } catch (Sabre_DAV_Exception_NotFound $e) { + return false; + } } @@ -89,33 +104,28 @@ class Sabre_CalDAV_Principal_User extends Sabre_DAVACL_Principal implements Sabr * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalProperties['uri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write', - 'protected' => true, - ), + $acl = parent::getACL(); + $acl[] = array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read', + 'protected' => true, ); + $acl[] = array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write', + 'protected' => true, + ); + return $acl; } diff --git a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php index 1bbaca6b8a..2ea078d7da 100644 --- a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php +++ b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php @@ -3,40 +3,40 @@ /** * Supported component set property * - * This property is a representation of the supported-calendar_component-set + * This property is a representation of the supported-calendar_component-set * property in the CalDAV namespace. It simply requires an array of components, * such as VEVENT, VTODO * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Property_SupportedCalendarComponentSet extends Sabre_DAV_Property { /** - * List of supported components, such as "VEVENT, VTODO" - * - * @var array + * List of supported components, such as "VEVENT, VTODO" + * + * @var array */ private $components; /** - * Creates the property - * - * @param array $components + * Creates the property + * + * @param array $components */ public function __construct(array $components) { - $this->components = $components; + $this->components = $components; } /** - * Returns the list of supported components - * - * @return array + * Returns the list of supported components + * + * @return array */ public function getValue() { @@ -45,10 +45,10 @@ class Sabre_CalDAV_Property_SupportedCalendarComponentSet extends Sabre_DAV_Prop } /** - * Serializes the property in a DOMDocument - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property in a DOMDocument + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $node) { @@ -58,7 +58,7 @@ class Sabre_CalDAV_Property_SupportedCalendarComponentSet extends Sabre_DAV_Prop $xcomp = $doc->createElement('cal:comp'); $xcomp->setAttribute('name',$component); - $node->appendChild($xcomp); + $node->appendChild($xcomp); } @@ -66,9 +66,9 @@ class Sabre_CalDAV_Property_SupportedCalendarComponentSet extends Sabre_DAV_Prop /** * Unserializes the DOMElement back into a Property class. - * - * @param DOMElement $node - * @return void + * + * @param DOMElement $node + * @return Sabre_CalDAV_Property_SupportedCalendarComponentSet */ static function unserialize(DOMElement $node) { diff --git a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php index 5010ee6d52..1d848dd5cf 100644 --- a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php +++ b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php @@ -9,17 +9,17 @@ * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Property_SupportedCalendarData extends Sabre_DAV_Property { /** - * Serializes the property in a DOMDocument - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property in a DOMDocument + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $node) { @@ -32,7 +32,7 @@ class Sabre_CalDAV_Property_SupportedCalendarData extends Sabre_DAV_Property { $caldata->setAttribute('content-type','text/calendar'); $caldata->setAttribute('version','2.0'); - $node->appendChild($caldata); + $node->appendChild($caldata); } } diff --git a/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php b/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php index e585e9db3d..24e84d4c17 100644 --- a/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php +++ b/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php @@ -4,27 +4,27 @@ * supported-collation-set property * * This property is a representation of the supported-collation-set property - * in the CalDAV namespace. + * in the CalDAV namespace. * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_CalDAV_Property_SupportedCollationSet extends Sabre_DAV_Property { /** - * Serializes the property in a DOM document - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property in a DOM document + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $node) { $doc = $node->ownerDocument; - + $prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:caldav'); if (!$prefix) $prefix = 'cal'; diff --git a/3rdparty/Sabre/CalDAV/Schedule/IMip.php b/3rdparty/Sabre/CalDAV/Schedule/IMip.php new file mode 100644 index 0000000000..37e75fcc4a --- /dev/null +++ b/3rdparty/Sabre/CalDAV/Schedule/IMip.php @@ -0,0 +1,104 @@ +senderEmail = $senderEmail; + + } + + /** + * Sends one or more iTip messages through email. + * + * @param string $originator + * @param array $recipients + * @param Sabre_VObject_Component $vObject + * @return void + */ + public function sendMessage($originator, array $recipients, Sabre_VObject_Component $vObject) { + + foreach($recipients as $recipient) { + + $to = $recipient; + $replyTo = $originator; + $subject = 'SabreDAV iTIP message'; + + switch(strtoupper($vObject->METHOD)) { + case 'REPLY' : + $subject = 'Response for: ' . $vObject->VEVENT->SUMMARY; + break; + case 'REQUEST' : + $subject = 'Invitation for: ' .$vObject->VEVENT->SUMMARY; + break; + case 'CANCEL' : + $subject = 'Cancelled event: ' . $vObject->VEVENT->SUMMARY; + break; + } + + $headers = array(); + $headers[] = 'Reply-To: ' . $replyTo; + $headers[] = 'From: ' . $this->senderEmail; + $headers[] = 'Content-Type: text/calendar; method=' . (string)$vObject->method . '; charset=utf-8'; + if (Sabre_DAV_Server::$exposeVersion) { + $headers[] = 'X-Sabre-Version: ' . Sabre_DAV_Version::VERSION . '-' . Sabre_DAV_Version::STABILITY; + } + + $vcalBody = $vObject->serialize(); + + $this->mail($to, $subject, $vcalBody, $headers); + + } + + } + + /** + * This function is reponsible for sending the actual email. + * + * @param string $to Recipient email address + * @param string $subject Subject of the email + * @param string $body iCalendar body + * @param array $headers List of headers + * @return void + */ + protected function mail($to, $subject, $body, array $headers) { + + mail($to, $subject, $body, implode("\r\n", $headers)); + + } + + +} + +?> diff --git a/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php b/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php new file mode 100644 index 0000000000..46d77514bc --- /dev/null +++ b/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php @@ -0,0 +1,16 @@ +principalUri = $principalUri; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + public function getName() { + + return 'outbox'; + + } + + /** + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] + */ + public function getChildren() { + + return array(); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + public function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + public function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + public function getACL() { + + return array( + array( + 'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + ); + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + public function setACL(array $acl) { + + throw new Sabre_DAV_Exception_MethodNotAllowed('You\'re not allowed to update the ACL'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + + $default = Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet(); + $default['aggregates'][] = array( + 'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy', + ); + + return $default; + + } + +} diff --git a/3rdparty/Sabre/CalDAV/Server.php b/3rdparty/Sabre/CalDAV/Server.php index 969d69c627..325e3d80a7 100644 --- a/3rdparty/Sabre/CalDAV/Server.php +++ b/3rdparty/Sabre/CalDAV/Server.php @@ -3,17 +3,20 @@ /** * CalDAV server * + * Deprecated! Warning: This class is now officially deprecated + * * This script is a convenience script. It quickly sets up a WebDAV server * with caldav and ACL support, and it creates the root 'principals' and * 'calendars' collections. * - * Note that if you plan to do anything moderately complex, you are advised to - * not subclass this server, but use Sabre_DAV_Server directly instead. This + * Note that if you plan to do anything moderately complex, you are advised to + * not subclass this server, but use Sabre_DAV_Server directly instead. This * class is nothing more than an 'easy setup'. - * + * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @deprecated Don't use this class anymore, it will be removed in version 1.7. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -22,17 +25,17 @@ class Sabre_CalDAV_Server extends Sabre_DAV_Server { /** * The authentication realm * - * Note that if this changes, the hashes in the auth backend must also - * be recalculated. - * + * Note that if this changes, the hashes in the auth backend must also + * be recalculated. + * * @var string */ public $authRealm = 'SabreDAV'; /** * Sets up the object. A PDO object must be passed to setup all the backends. - * - * @param PDO $pdo + * + * @param PDO $pdo */ public function __construct(PDO $pdo) { diff --git a/3rdparty/Sabre/CalDAV/UserCalendars.php b/3rdparty/Sabre/CalDAV/UserCalendars.php index f52d65e9a7..b8d3f0573f 100644 --- a/3rdparty/Sabre/CalDAV/UserCalendars.php +++ b/3rdparty/Sabre/CalDAV/UserCalendars.php @@ -1,68 +1,68 @@ principalBackend = $principalBackend; $this->caldavBackend = $caldavBackend; $this->principalInfo = $principalBackend->getPrincipalByPath($userUri); - + } /** - * Returns the name of this object - * + * Returns the name of this object + * * @return string */ public function getName() { - + list(,$name) = Sabre_DAV_URLUtil::splitPath($this->principalInfo['uri']); - return $name; + return $name; } /** - * Updates the name of this object - * - * @param string $name + * Updates the name of this object + * + * @param string $name * @return void */ public function setName($name) { @@ -72,8 +72,8 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre } /** - * Deletes this object - * + * Deletes this object + * * @return void */ public function delete() { @@ -83,13 +83,13 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre } /** - * Returns the last modification date - * - * @return int + * Returns the last modification date + * + * @return int */ public function getLastModified() { - return null; + return null; } @@ -97,9 +97,9 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre * Creates a new file under this object. * * This is currently not allowed - * - * @param string $filename - * @param resource $data + * + * @param string $filename + * @param resource $data * @return void */ public function createFile($filename, $data=null) { @@ -112,8 +112,8 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre * Creates a new directory under this object. * * This is currently not allowed. - * - * @param string $filename + * + * @param string $filename * @return void */ public function createDirectory($filename) { @@ -123,11 +123,11 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre } /** - * Returns a single calendar, by name - * + * Returns a single calendar, by name + * * @param string $name * @todo needs optimizing - * @return Sabre_CalDAV_Calendar + * @return Sabre_CalDAV_Calendar */ public function getChild($name) { @@ -136,22 +136,22 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre return $child; } - throw new Sabre_DAV_Exception_FileNotFound('Calendar with name \'' . $name . '\' could not be found'); + throw new Sabre_DAV_Exception_NotFound('Calendar with name \'' . $name . '\' could not be found'); } /** * Checks if a calendar exists. - * + * * @param string $name * @todo needs optimizing - * @return bool + * @return bool */ public function childExists($name) { foreach($this->getChildren() as $child) { if ($name==$child->getName()) - return true; + return true; } return false; @@ -160,8 +160,8 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre /** * Returns a list of calendars - * - * @return array + * + * @return array */ public function getChildren() { @@ -170,15 +170,17 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre foreach($calendars as $calendar) { $objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar); } + $objs[] = new Sabre_CalDAV_Schedule_Outbox($this->principalInfo['uri']); return $objs; } /** * Creates a new calendar - * - * @param string $name - * @param string $properties + * + * @param string $name + * @param array $resourceType + * @param array $properties * @return void */ public function createExtendedCollection($name, array $resourceType, array $properties) { @@ -193,8 +195,8 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -207,8 +209,8 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -220,13 +222,13 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { @@ -264,9 +266,9 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -275,6 +277,22 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + return null; + + } } diff --git a/3rdparty/Sabre/CalDAV/Version.php b/3rdparty/Sabre/CalDAV/Version.php index df8fe1f6bd..939e903c89 100644 --- a/3rdparty/Sabre/CalDAV/Version.php +++ b/3rdparty/Sabre/CalDAV/Version.php @@ -2,10 +2,10 @@ /** * This class contains the Sabre_CalDAV version constants. - * + * * @package Sabre * @subpackage CalDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -14,7 +14,7 @@ class Sabre_CalDAV_Version { /** * Full version number */ - const VERSION = '1.5.3'; + const VERSION = '1.6.2'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/CalDAV/XMLUtil.php b/3rdparty/Sabre/CalDAV/XMLUtil.php deleted file mode 100644 index bf349a36aa..0000000000 --- a/3rdparty/Sabre/CalDAV/XMLUtil.php +++ /dev/null @@ -1,208 +0,0 @@ -childNodes as $child) { - - switch(Sabre_DAV_XMLUtil::toClarkNotation($child)) { - - case '{urn:ietf:params:xml:ns:caldav}comp-filter' : - case '{urn:ietf:params:xml:ns:caldav}prop-filter' : - - $filterName = $basePath . '/' . 'c:' . strtolower($child->getAttribute('name')); - $filters[$filterName] = array(); - - self::parseCalendarQueryFilters($child, $filterName,$filters); - break; - - case '{urn:ietf:params:xml:ns:caldav}time-range' : - - if ($start = $child->getAttribute('start')) { - $start = self::parseICalendarDateTime($start); - } else { - $start = null; - } - if ($end = $child->getAttribute('end')) { - $end = self::parseICalendarDateTime($end); - } else { - $end = null; - } - - if (!is_null($start) && !is_null($end) && $end <= $start) { - throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter'); - } - - $filters[$basePath]['time-range'] = array( - 'start' => $start, - 'end' => $end - ); - break; - - case '{urn:ietf:params:xml:ns:caldav}is-not-defined' : - $filters[$basePath]['is-not-defined'] = true; - break; - - case '{urn:ietf:params:xml:ns:caldav}param-filter' : - - $filterName = $basePath . '/@' . strtolower($child->getAttribute('name')); - $filters[$filterName] = array(); - self::parseCalendarQueryFilters($child, $filterName, $filters); - break; - - case '{urn:ietf:params:xml:ns:caldav}text-match' : - - $collation = $child->getAttribute('collation'); - if (!$collation) $collation = 'i;ascii-casemap'; - - $filters[$basePath]['text-match'] = array( - 'collation' => ($collation == 'default'?'i;ascii-casemap':$collation), - 'negate-condition' => $child->getAttribute('negate-condition')==='yes', - 'value' => $child->nodeValue, - ); - break; - - } - - } - - return $filters; - - } - - /** - * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object - * - * Specifying a reference timezone is optional. It will only be used - * if the non-UTC format is used. The argument is used as a reference, the - * returned DateTime object will still be in the UTC timezone. - * - * @param string $dt - * @param DateTimeZone $tz - * @return DateTime - */ - static public function parseICalendarDateTime($dt,DateTimeZone $tz = null) { - - // Format is YYYYMMDD + "T" + hhmmss - $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches); - - if (!$result) { - throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar datetime value is incorrect: ' . $dt); - } - - if ($matches[7]==='Z' || is_null($tz)) { - $tz = new DateTimeZone('UTC'); - } - $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz); - - // Still resetting the timezone, to normalize everything to UTC - $date->setTimeZone(new DateTimeZone('UTC')); - return $date; - - } - - /** - * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object - * - * @param string $date - * @param DateTimeZone $tz - * @return DateTime - */ - static public function parseICalendarDate($date) { - - // Format is YYYYMMDD - $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches); - - if (!$result) { - throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar date value is incorrect: ' . $date); - } - - $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new DateTimeZone('UTC')); - return $date; - - } - - /** - * Parses an iCalendar (RFC5545) formatted duration and returns a string suitable - * for strtotime or DateTime::modify. - * - * NOTE: When we require PHP 5.3 this can be replaced by the DateTimeInterval object, which - * supports ISO 8601 Intervals, which is a superset of ICalendar durations. - * - * For now though, we're just gonna live with this messy system - * - * @param string $duration - * @return string - */ - static public function parseICalendarDuration($duration) { - - $result = preg_match('/^(?P\+|-)?P((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$/', $duration, $matches); - if (!$result) { - throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar duration value is incorrect: ' . $duration); - } - - $parts = array( - 'week', - 'day', - 'hour', - 'minute', - 'second', - ); - - $newDur = ''; - foreach($parts as $part) { - if (isset($matches[$part]) && $matches[$part]) { - $newDur.=' '.$matches[$part] . ' ' . $part . 's'; - } - } - - $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur); - return $newDur; - - } - -} diff --git a/3rdparty/Sabre/CalDAV/includes.php b/3rdparty/Sabre/CalDAV/includes.php new file mode 100644 index 0000000000..1ecb870a0e --- /dev/null +++ b/3rdparty/Sabre/CalDAV/includes.php @@ -0,0 +1,43 @@ +carddavBackend = $carddavBackend; $this->addressBookInfo = $addressBookInfo; @@ -42,9 +41,9 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca } /** - * Returns the name of the addressbook - * - * @return string + * Returns the name of the addressbook + * + * @return string */ public function getName() { @@ -55,21 +54,21 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Returns a card * - * @param string $name + * @param string $name * @return Sabre_DAV_Card */ public function getChild($name) { $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name); - if (!$obj) throw new Sabre_DAV_Exception_FileNotFound('Card not found'); + if (!$obj) throw new Sabre_DAV_Exception_NotFound('Card not found'); return new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj); } /** * Returns the full list of cards - * - * @return array + * + * @return array */ public function getChildren() { @@ -85,9 +84,9 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Creates a new directory * - * We actually block this, as subdirectories are not allowed in addressbooks. - * - * @param string $name + * We actually block this, as subdirectories are not allowed in addressbooks. + * + * @param string $name * @return void */ public function createDirectory($name) { @@ -99,11 +98,13 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Creates a new file * - * The contents of the new file must be a valid VCARD - * - * @param string $name - * @param resource $vcardData - * @return void + * The contents of the new file must be a valid VCARD. + * + * This method may return an ETag. + * + * @param string $name + * @param resource $vcardData + * @return void|null */ public function createFile($name,$vcardData = null) { @@ -111,13 +112,13 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca // Converting to UTF-8, if needed $vcardData = Sabre_DAV_StringUtil::ensureUTF8($vcardData); - $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData); + return $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData); } /** - * Deletes the entire addressbook. - * + * Deletes the entire addressbook. + * * @return void */ public function delete() { @@ -128,8 +129,8 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Renames the addressbook - * - * @param string $newName + * + * @param string $newName * @return void */ public function setName($newName) { @@ -140,7 +141,7 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Returns the last modification date as a unix timestamp. - * + * * @return void */ public function getLastModified() { @@ -162,7 +163,7 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca * If the operation was successful, true can be returned. * If the operation failed, false can be returned. * - * Deletion of a non-existant property is always succesful. + * Deletion of a non-existent property is always successful. * * Lastly, it is optional to return detailed information about any * failures. In this case an array should be returned with the following @@ -177,16 +178,16 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca * ) * ) * - * In this example it was forbidden to update {DAV:}displayname. + * In this example it was forbidden to update {DAV:}displayname. * (403 Forbidden), which in turn also caused {DAV:}owner to fail * (424 Failed Dependency) because the request needs to be atomic. * - * @param array $mutations - * @return bool|array + * @param array $mutations + * @return bool|array */ public function updateProperties($mutations) { - return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations); + return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations); } @@ -198,8 +199,8 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca * * If the array is empty, it means 'all properties' were requested. * - * @param array $properties - * @return void + * @param array $properties + * @return array */ public function getProperties($properties) { @@ -221,8 +222,8 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -235,8 +236,8 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -248,13 +249,13 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { @@ -277,9 +278,9 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -288,6 +289,22 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + return null; + + } } diff --git a/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php b/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php index 08adc3b815..85a4963127 100644 --- a/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php +++ b/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php @@ -3,12 +3,12 @@ /** * Parses the addressbook-query report request body. * - * Whoever designed this format, and the CalDAV equavalent even more so, + * Whoever designed this format, and the CalDAV equivalent even more so, * has no feel for design. - * + * * @package Sabre * @subpackage CardDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -19,8 +19,8 @@ class Sabre_CardDAV_AddressBookQueryParser { /** * List of requested properties the client wanted - * - * @var array + * + * @var array */ public $requestedProperties; @@ -28,44 +28,43 @@ class Sabre_CardDAV_AddressBookQueryParser { * The number of results the client wants * * null means it wasn't specified, which in most cases means 'all results'. - * - * @var int|null + * + * @var int|null */ public $limit; /** * List of property filters. * - * @var array + * @var array */ public $filters; /** * Either TEST_ANYOF or TEST_ALLOF - * - * @var string + * + * @var string */ public $test; /** * DOM Document - * - * @var DOMDocument + * + * @var DOMDocument */ protected $dom; /** - * DOM XPath object - * - * @var DOMXPath + * DOM XPath object + * + * @var DOMXPath */ protected $xpath; /** * Creates the parser - * - * @param DOMNode $dom - * @return void + * + * @param DOMDocument $dom */ public function __construct(DOMDocument $dom) { @@ -77,15 +76,14 @@ class Sabre_CardDAV_AddressBookQueryParser { } /** - * Parses the request. - * - * @param DOMNode $dom + * Parses the request. + * * @return void */ public function parse() { $filterNode = null; - + $limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)'); if (is_nan($limit)) $limit = null; @@ -120,9 +118,9 @@ class Sabre_CardDAV_AddressBookQueryParser { /** * Parses the prop-filter xml element - * - * @param DOMElement $propFilterNode - * @return array + * + * @param DOMElement $propFilterNode + * @return array */ protected function parsePropFilterNode(DOMElement $propFilterNode) { @@ -157,13 +155,13 @@ class Sabre_CardDAV_AddressBookQueryParser { } /** - * Parses the param-filter element - * - * @param DOMElement $paramFilterNode - * @return array + * Parses the param-filter element + * + * @param DOMElement $paramFilterNode + * @return array */ public function parseParamFilterNode(DOMElement $paramFilterNode) { - + $paramFilter = array(); $paramFilter['name'] = $paramFilterNode->getAttribute('name'); $paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0; @@ -174,15 +172,15 @@ class Sabre_CardDAV_AddressBookQueryParser { $paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0)); } - return $paramFilter; + return $paramFilter; } /** * Text match - * - * @param DOMElement $textMatchNode - * @return void + * + * @param DOMElement $textMatchNode + * @return array */ public function parseTextMatchNode(DOMElement $textMatchNode) { @@ -204,8 +202,8 @@ class Sabre_CardDAV_AddressBookQueryParser { 'match-type' => $matchType, 'value' => $textMatchNode->nodeValue ); - - } + + } } diff --git a/3rdparty/Sabre/CardDAV/AddressBookRoot.php b/3rdparty/Sabre/CardDAV/AddressBookRoot.php index 1a80efba35..9d37b15f08 100644 --- a/3rdparty/Sabre/CardDAV/AddressBookRoot.php +++ b/3rdparty/Sabre/CardDAV/AddressBookRoot.php @@ -1,45 +1,45 @@ pdo = $pdo; $this->addressBooksTableName = $addressBooksTableName; - $this->cardsTableName = $cardsTableName; + $this->cardsTableName = $cardsTableName; } /** - * Returns the list of addressbooks for a specific user. - * - * @param string $principalUri - * @return array + * Returns the list of addressbooks for a specific user. + * + * @param string $principalUri + * @return array */ public function getAddressBooksForUser($principalUri) { - $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM `'.$this->addressBooksTableName.'` WHERE principaluri = ?'); - $result = $stmt->execute(array($principalUri)); + $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM '.$this->addressBooksTableName.' WHERE principaluri = ?'); + $stmt->execute(array($principalUri)); $addressBooks = array(); @@ -65,7 +67,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { '{DAV:}displayname' => $row['displayname'], '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['ctag'], - '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' => + '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' => new Sabre_CardDAV_Property_SupportedAddressData(), ); @@ -79,8 +81,8 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { /** * Updates an addressbook's properties * - * See Sabre_DAV_IProperties for a description of the mutations array, as - * well as the return value. + * See Sabre_DAV_IProperties for a description of the mutations array, as + * well as the return value. * * @param mixed $addressBookId * @param array $mutations @@ -88,7 +90,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { * @return bool|array */ public function updateAddressBook($addressBookId, array $mutations) { - + $updates = array(); foreach($mutations as $property=>$newValue) { @@ -101,7 +103,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { $updates['description'] = $newValue; break; default : - // If any unsupported values were being updated, we must + // If any unsupported values were being updated, we must // let the entire request fail. return false; } @@ -113,7 +115,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { return false; } - $query = 'UPDATE `' . $this->addressBooksTableName . '` SET ctag = ctag + 1 '; + $query = 'UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 '; foreach($updates as $key=>$value) { $query.=', `' . $key . '` = :' . $key . ' '; } @@ -129,11 +131,11 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { } /** - * Creates a new address book + * Creates a new address book * - * @param string $principalUri - * @param string $url Just the 'basename' of the url. - * @param array $properties + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties * @return void */ public function createAddressBook($principalUri, $url, array $properties) { @@ -160,7 +162,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { } - $query = 'INSERT INTO `' . $this->addressBooksTableName . '` (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)'; + $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)'; $stmt = $this->pdo->prepare($query); $stmt->execute($values); @@ -169,44 +171,61 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { /** * Deletes an entire addressbook and all its contents * - * @param int $addressBookId + * @param int $addressBookId * @return void */ public function deleteAddressBook($addressBookId) { - $stmt = $this->pdo->prepare('DELETE FROM `' . $this->cardsTableName . '` WHERE addressbookid = ?'); + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); $stmt->execute(array($addressBookId)); - $stmt = $this->pdo->prepare('DELETE FROM `' . $this->addressBooksTableName . '` WHERE id = ?'); + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); $stmt->execute(array($addressBookId)); } /** - * Returns all cards for a specific addressbook id. - * - * @param mixed $addressbookId - * @return array + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + * + * It's recommended to also return the following properties: + * * etag - A unique etag. This must change every time the card changes. + * * size - The size of the card in bytes. + * + * If these last two properties are provided, less time will be spent + * calculating them. If they are specified, you can also ommit carddata. + * This may speed up certain requests, especially with large cards. + * + * @param mixed $addressbookId + * @return array */ public function getCards($addressbookId) { - $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM `' . $this->cardsTableName . '` WHERE addressbookid = ?'); + $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); $stmt->execute(array($addressbookId)); return $stmt->fetchAll(PDO::FETCH_ASSOC); - + } + /** - * Returns a specfic card - * - * @param mixed $addressBookId - * @param string $cardUri - * @return array + * Returns a specfic card. + * + * The same set of properties must be returned as with getCards. The only + * exception is that 'carddata' is absolutely required. + * + * @param mixed $addressBookId + * @param string $cardUri + * @return array */ public function getCard($addressBookId, $cardUri) { - $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM `' . $this->cardsTableName . '` WHERE addressbookid = ? AND uri = ? LIMIT 1'); + $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1'); $stmt->execute(array($addressBookId, $cardUri)); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -216,59 +235,93 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { } /** - * Creates a new card - * - * @param mixed $addressBookId - * @param string $cardUri - * @param string $cardData - * @return bool + * Creates a new card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressbooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag is for the + * newly created resource, and must be enclosed with double quotes (that + * is, the string itself must contain the double quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null */ public function createCard($addressBookId, $cardUri, $cardData) { - $stmt = $this->pdo->prepare('INSERT INTO `' . $this->cardsTableName . '` (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)'); + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)'); $result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId)); - $stmt2 = $this->pdo->prepare('UPDATE `' . $this->addressBooksTableName . '` SET ctag = ctag + 1 WHERE id = ?'); + $stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?'); $stmt2->execute(array($addressBookId)); - return $result; + return '"' . md5($cardData) . '"'; } /** - * Updates a card - * - * @param mixed $addressBookId - * @param string $cardUri - * @param string $cardData - * @return bool + * Updates a card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressbooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag should + * match that of the updated resource, and must be enclosed with double + * quotes (that is: the string itself must contain the actual quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null */ public function updateCard($addressBookId, $cardUri, $cardData) { - $stmt = $this->pdo->prepare('UPDATE `' . $this->cardsTableName . '` SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?'); - $result = $stmt->execute(array($cardData, time(), $cardUri, $addressBookId)); + $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?'); + $stmt->execute(array($cardData, time(), $cardUri, $addressBookId)); - $stmt2 = $this->pdo->prepare('UPDATE `' . $this->addressBooksTableName . '` SET ctag = ctag + 1 WHERE id = ?'); + $stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?'); $stmt2->execute(array($addressBookId)); - return $stmt->rowCount()===1; + return '"' . md5($cardData) . '"'; } /** * Deletes a card - * - * @param mixed $addressBookId - * @param string $cardUri - * @return bool + * + * @param mixed $addressBookId + * @param string $cardUri + * @return bool */ public function deleteCard($addressBookId, $cardUri) { - $stmt = $this->pdo->prepare('DELETE FROM `' . $this->cardsTableName . '` WHERE addressbookid = ? AND uri = ?'); + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?'); $stmt->execute(array($addressBookId, $cardUri)); - $stmt2 = $this->pdo->prepare('UPDATE `' . $this->addressBooksTableName . '` SET ctag = ctag + 1 WHERE id = ?'); + $stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?'); $stmt2->execute(array($addressBookId)); return $stmt->rowCount()===1; diff --git a/3rdparty/Sabre/CardDAV/Card.php b/3rdparty/Sabre/CardDAV/Card.php index 2844eaf7ed..d7c6633383 100644 --- a/3rdparty/Sabre/CardDAV/Card.php +++ b/3rdparty/Sabre/CardDAV/Card.php @@ -2,10 +2,10 @@ /** * The Card object represents a single Card from an addressbook - * + * * @package Sabre * @subpackage CardDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -13,29 +13,29 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, /** * CardDAV backend - * - * @var Sabre_CardDAV_Backend_Abstract + * + * @var Sabre_CardDAV_Backend_Abstract */ private $carddavBackend; /** * Array with information about this Card - * - * @var array + * + * @var array */ private $cardData; /** - * Array with information about the containing addressbook - * - * @var array + * Array with information about the containing addressbook + * + * @var array */ private $addressBookInfo; /** - * Constructor - * - * @param Sabre_CardDAV_Backend_Abstract $carddavBackend + * Constructor + * + * @param Sabre_CardDAV_Backend_Abstract $carddavBackend * @param array $addressBookInfo * @param array $cardData */ @@ -48,9 +48,9 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, } /** - * Returns the uri for this object - * - * @return string + * Returns the uri for this object + * + * @return string */ public function getName() { @@ -59,25 +59,26 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, } /** - * Returns the VCard-formatted object - * - * @return string + * Returns the VCard-formatted object + * + * @return string */ public function get() { - $cardData = $this->cardData['carddata']; - $s = fopen('php://temp','r+'); - fwrite($s, $cardData); - rewind($s); - return $s; + // Pre-populating 'carddata' is optional. If we don't yet have it + // already, we fetch it from the backend. + if (!isset($this->cardData['carddata'])) { + $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']); + } + return $this->cardData['carddata']; } /** - * Updates the VCard-formatted object - * - * @param string $cardData - * @return void + * Updates the VCard-formatted object + * + * @param string $cardData + * @return void */ public function put($cardData) { @@ -87,14 +88,17 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, // Converting to UTF-8, if needed $cardData = Sabre_DAV_StringUtil::ensureUTF8($cardData); - $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData); + $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData); $this->cardData['carddata'] = $cardData; + $this->cardData['etag'] = $etag; + + return $etag; } /** * Deletes the card - * + * * @return void */ public function delete() { @@ -104,9 +108,9 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, } /** - * Returns the mime content-type - * - * @return string + * Returns the mime content-type + * + * @return string */ public function getContentType() { @@ -115,20 +119,24 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, } /** - * Returns an ETag for this object - * - * @return string + * Returns an ETag for this object + * + * @return string */ public function getETag() { - return '"' . md5($this->cardData['carddata']) . '"'; + if (isset($this->cardData['etag'])) { + return $this->cardData['etag']; + } else { + return '"' . md5($this->get()) . '"'; + } } /** * Returns the last modification date as a unix timestamp - * - * @return time + * + * @return time */ public function getLastModified() { @@ -137,21 +145,25 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, } /** - * Returns the size of this object in bytes - * + * Returns the size of this object in bytes + * * @return int */ public function getSize() { - return strlen($this->cardData['carddata']); + if (array_key_exists('size', $this->cardData)) { + return $this->cardData['size']; + } else { + return strlen($this->get()); + } } /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -164,8 +176,8 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -177,13 +189,13 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { @@ -205,9 +217,9 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -216,5 +228,23 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + + return null; + + } + } diff --git a/3rdparty/Sabre/CardDAV/IAddressBook.php b/3rdparty/Sabre/CardDAV/IAddressBook.php index a0dffb30ae..2bc275bcf7 100644 --- a/3rdparty/Sabre/CardDAV/IAddressBook.php +++ b/3rdparty/Sabre/CardDAV/IAddressBook.php @@ -4,15 +4,15 @@ * AddressBook interface * * Implement this interface to allow a node to be recognized as an addressbook. - * + * * @package Sabre * @subpackage CardDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_CardDAV_IAddressBook extends Sabre_DAV_ICollection { - + } diff --git a/3rdparty/Sabre/CardDAV/ICard.php b/3rdparty/Sabre/CardDAV/ICard.php index 25bcc551b7..a17299316c 100644 --- a/3rdparty/Sabre/CardDAV/ICard.php +++ b/3rdparty/Sabre/CardDAV/ICard.php @@ -1,18 +1,18 @@ subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); + $server->subscribeEvent('updateProperties', array($this, 'updateProperties')); $server->subscribeEvent('report', array($this,'report')); + $server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel')); + $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction')); /* Namespaces */ $server->xmlNamespaces[self::NS_CARDDAV] = 'card'; @@ -56,11 +59,14 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { /* Mapping Interfaces to {DAV:}resourcetype values */ $server->resourceTypeMapping['Sabre_CardDAV_IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook'; $server->resourceTypeMapping['Sabre_CardDAV_IDirectory'] = '{' . self::NS_CARDDAV . '}directory'; - + /* Adding properties that may never be changed */ $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data'; $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set'; + $server->propertyMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre_DAV_Property_Href'; $this->server = $server; @@ -69,7 +75,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns a list of supported features. * - * This is used in the DAV: header in the OPTIONS and PROPFIND requests. + * This is used in the DAV: header in the OPTIONS and PROPFIND requests. * * @return array */ @@ -83,11 +89,11 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them + * Note that you still need to subscribe to the 'report' event to actually + * implement them * * @param string $uri - * @return array + * @return array */ public function getSupportedReportSet($uri) { @@ -104,22 +110,22 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { /** - * Adds all CardDAV-specific properties + * Adds all CardDAV-specific properties * * @param string $path - * @param Sabre_DAV_INode $node + * @param Sabre_DAV_INode $node * @param array $requestedProperties - * @param array $returnedProperties + * @param array $returnedProperties * @return void */ - public function beforeGetProperties($path, Sabre_DAV_INode $node, array &$requestedProperties, array &$returnedProperties) { + public function beforeGetProperties($path, Sabre_DAV_INode $node, array &$requestedProperties, array &$returnedProperties) { if ($node instanceof Sabre_DAVACL_IPrincipal) { // calendar-home-set property $addHome = '{' . self::NS_CARDDAV . '}addressbook-home-set'; if (in_array($addHome,$requestedProperties)) { - $principalId = $node->getName(); + $principalId = $node->getName(); $addressbookHomePath = self::ADDRESSBOOK_ROOT . '/' . $principalId . '/'; unset($requestedProperties[array_search($addHome, $requestedProperties)]); $returnedProperties[200][$addHome] = new Sabre_DAV_Property_Href($addressbookHomePath); @@ -135,8 +141,8 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { if ($node instanceof Sabre_CardDAV_ICard) { - // The address-data property is not supposed to be a 'real' - // property, but in large chunks of the spec it does act as such. + // The address-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. // Therefore we simply expose it as a property. $addressDataProp = '{' . self::NS_CARDDAV . '}address-data'; if (in_array($addressDataProp, $requestedProperties)) { @@ -151,24 +157,95 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { } } + if ($node instanceof Sabre_CardDAV_UserAddressBooks) { + + $meCardProp = '{http://calendarserver.org/ns/}me-card'; + if (in_array($meCardProp, $requestedProperties)) { + + $props = $this->server->getProperties($node->getOwner(), array('{http://sabredav.org/ns}vcard-url')); + if (isset($props['{http://sabredav.org/ns}vcard-url'])) { + + $returnedProperties[200][$meCardProp] = new Sabre_DAV_Property_Href( + $props['{http://sabredav.org/ns}vcard-url'] + ); + $pos = array_search($meCardProp, $requestedProperties); + unset($requestedProperties[$pos]); + + } + + } + + } + } /** - * This functions handles REPORT requests specific to CardDAV + * This event is triggered when a PROPPATCH method is executed * - * @param string $reportName + * @param array $mutations + * @param array $result + * @param Sabre_DAV_INode $node + * @return void + */ + public function updateProperties(&$mutations, &$result, $node) { + + if (!$node instanceof Sabre_CardDAV_UserAddressBooks) { + return true; + } + + $meCard = '{http://calendarserver.org/ns/}me-card'; + + // The only property we care about + if (!isset($mutations[$meCard])) + return true; + + $value = $mutations[$meCard]; + unset($mutations[$meCard]); + + if ($value instanceof Sabre_DAV_Property_IHref) { + $value = $value->getHref(); + $value = $this->server->calculateUri($value); + } elseif (!is_null($value)) { + $result[400][$meCard] = null; + return false; + } + + $innerResult = $this->server->updateProperties( + $node->getOwner(), + array( + '{http://sabredav.org/ns}vcard-url' => $value, + ) + ); + + $closureResult = false; + foreach($innerResult as $status => $props) { + if (is_array($props) && array_key_exists('{http://sabredav.org/ns}vcard-url', $props)) { + $result[$status][$meCard] = null; + $closureResult = ($status>=200 && $status<300); + } + + } + + return $result; + + } + + /** + * This functions handles REPORT requests specific to CardDAV + * + * @param string $reportName * @param DOMNode $dom - * @return bool + * @return bool */ public function report($reportName,$dom) { - switch($reportName) { + switch($reportName) { case '{'.self::NS_CARDDAV.'}addressbook-multiget' : $this->addressbookMultiGetReport($dom); return false; case '{'.self::NS_CARDDAV.'}addressbook-query' : $this->addressBookQueryReport($dom); - return false; + return false; default : return; @@ -256,7 +333,8 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { $result = array(); foreach($validNodes as $validNode) { - if ($depth==0) { + + if ($depth==0) { $href = $this->server->getRequestUri(); } else { $href = $this->server->getRequestUri() . '/' . $validNode->getName(); @@ -265,7 +343,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0); } - + $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result)); @@ -274,17 +352,15 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * Validates if a vcard makes it throught a list of filters. - * - * @param string $vcardData - * @param array $filters - * @param string $test anyof or allof (which means OR or AND) - * @return bool + * + * @param string $vcardData + * @param array $filters + * @param string $test anyof or allof (which means OR or AND) + * @return bool */ public function validateFilters($vcardData, array $filters, $test) { $vcard = Sabre_VObject_Reader::read($vcardData); - - $success = true; foreach($filters as $filter) { @@ -299,10 +375,10 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { // We only need to check for existence $success = $isDefined; - + } else { - $vProperties = $vcard->select($filter['name']); + $vProperties = $vcard->select($filter['name']); $results = array(); if ($filter['param-filters']) { @@ -328,7 +404,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { } // else - // There are two conditions where we can already determine wether + // There are two conditions where we can already determine whether // or not this filter succeeds. if ($test==='anyof' && $success) { return true; @@ -339,29 +415,28 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { } // foreach - // If we got all the way here, it means we haven't been able to + // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // - // This implies for 'anyof' that the test failed, and for 'allof' that + // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return $test==='allof'; } /** - * Validates if a param-filter can be applied to a specific property. - * - * @todo currently we're only validating the first parameter of the passed + * Validates if a param-filter can be applied to a specific property. + * + * @todo currently we're only validating the first parameter of the passed * property. Any subsequence parameters with the same name are * ignored. - * @param Sabre_VObject_Property $vProperty - * @param array $filters - * @param string $test - * @return bool + * @param array $vProperties + * @param array $filters + * @param string $test + * @return bool */ protected function validateParamFilters(array $vProperties, array $filters, $test) { - $success = false; foreach($filters as $filter) { $isDefined = false; @@ -377,17 +452,16 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { $success = true; } - // If there's no text-match, we can just check for existence + // If there's no text-match, we can just check for existence } elseif (!$filter['text-match'] || !$isDefined) { $success = $isDefined; - + } else { - $texts = array(); $success = false; foreach($vProperties as $vProperty) { - // If we got all the way here, we'll need to validate the + // If we got all the way here, we'll need to validate the // text-match filter. $success = Sabre_DAV_StringUtil::textMatch($vProperty[$filter['name']]->value, $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']); if ($success) break; @@ -398,7 +472,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { } // else - // There are two conditions where we can already determine wether + // There are two conditions where we can already determine whether // or not this filter succeeds. if ($test==='anyof' && $success) { return true; @@ -407,24 +481,24 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { return false; } - } + } - // If we got all the way here, it means we haven't been able to + // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // - // This implies for 'anyof' that the test failed, and for 'allof' that + // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return $test==='allof'; } /** - * Validates if a text-filter can be applied to a specific property. - * + * Validates if a text-filter can be applied to a specific property. + * * @param array $texts - * @param array $filters - * @param string $test - * @return bool + * @param array $filters + * @param string $test + * @return bool */ protected function validateTextMatches(array $texts, array $filters, $test) { @@ -440,7 +514,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { if ($filter['negate-condition']) { $success = !$success; } - + if ($success && $test==='anyof') return true; @@ -450,14 +524,64 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { } - // If we got all the way here, it means we haven't been able to + // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // - // This implies for 'anyof' that the test failed, and for 'allof' that + // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return $test==='allof'; } + /** + * This method is used to generate HTML output for the + * Sabre_DAV_Browser_Plugin. This allows us to generate an interface users + * can use to create new calendars. + * + * @param Sabre_DAV_INode $node + * @param string $output + * @return bool + */ + public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) { + + if (!$node instanceof Sabre_CardDAV_UserAddressBooks) + return; + + $output.= '
+

Create new address book

+ +
+
+ +
+ '; + + return false; + + } + + /** + * This method allows us to intercept the 'mkcalendar' sabreAction. This + * action enables the user to create new calendars from the browser plugin. + * + * @param string $uri + * @param string $action + * @param array $postVars + * @return bool + */ + public function browserPostAction($uri, $action, array $postVars) { + + if ($action!=='mkaddressbook') + return; + + $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:carddav}addressbook'); + $properties = array(); + if (isset($postVars['{DAV:}displayname'])) { + $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; + } + $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); + return false; + + } } diff --git a/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php b/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php index d57d3a6e7b..36d9306e7a 100644 --- a/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php +++ b/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php @@ -4,11 +4,11 @@ * Supported-address-data property * * This property is a representation of the supported-address-data property - * in the CardDAV namespace. + * in the CardDAV namespace. * * @package Sabre * @subpackage CardDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -16,15 +16,15 @@ class Sabre_CardDAV_Property_SupportedAddressData extends Sabre_DAV_Property { /** * supported versions - * - * @var array + * + * @var array */ protected $supportedData = array(); - + /** - * Creates the property - * - * @param array $components + * Creates the property + * + * @param array|null $supportedData */ public function __construct(array $supportedData = null) { @@ -35,22 +35,22 @@ class Sabre_CardDAV_Property_SupportedAddressData extends Sabre_DAV_Property { ); } - $this->supportedData = $supportedData; + $this->supportedData = $supportedData; } - + /** - * Serializes the property in a DOMDocument - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property in a DOMDocument + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $node) { $doc = $node->ownerDocument; - $prefix = + $prefix = isset($server->xmlNamespaces[Sabre_CardDAV_Plugin::NS_CARDDAV]) ? $server->xmlNamespaces[Sabre_CardDAV_Plugin::NS_CARDDAV] : 'card'; diff --git a/3rdparty/Sabre/CardDAV/UserAddressBooks.php b/3rdparty/Sabre/CardDAV/UserAddressBooks.php index e9f2de7f74..3f11fb1123 100644 --- a/3rdparty/Sabre/CardDAV/UserAddressBooks.php +++ b/3rdparty/Sabre/CardDAV/UserAddressBooks.php @@ -7,7 +7,7 @@ * * @package Sabre * @subpackage CardDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -15,47 +15,47 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab /** * Principal uri - * - * @var array + * + * @var array */ protected $principalUri; /** - * carddavBackend - * - * @var Sabre_CardDAV_Backend_Abstract + * carddavBackend + * + * @var Sabre_CardDAV_Backend_Abstract */ protected $carddavBackend; /** - * Constructor - * - * @param Sabre_CardDAV_Backend_Abstract $carddavBackend - * @param string $principalUri + * Constructor + * + * @param Sabre_CardDAV_Backend_Abstract $carddavBackend + * @param string $principalUri */ public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, $principalUri) { $this->carddavBackend = $carddavBackend; $this->principalUri = $principalUri; - + } /** - * Returns the name of this object - * + * Returns the name of this object + * * @return string */ public function getName() { - + list(,$name) = Sabre_DAV_URLUtil::splitPath($this->principalUri); - return $name; + return $name; } /** - * Updates the name of this object - * - * @param string $name + * Updates the name of this object + * + * @param string $name * @return void */ public function setName($name) { @@ -65,8 +65,8 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab } /** - * Deletes this object - * + * Deletes this object + * * @return void */ public function delete() { @@ -76,13 +76,13 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab } /** - * Returns the last modification date - * - * @return int + * Returns the last modification date + * + * @return int */ public function getLastModified() { - return null; + return null; } @@ -90,9 +90,9 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab * Creates a new file under this object. * * This is currently not allowed - * - * @param string $filename - * @param resource $data + * + * @param string $filename + * @param resource $data * @return void */ public function createFile($filename, $data=null) { @@ -105,8 +105,8 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab * Creates a new directory under this object. * * This is currently not allowed. - * - * @param string $filename + * + * @param string $filename * @return void */ public function createDirectory($filename) { @@ -116,8 +116,8 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab } /** - * Returns a single calendar, by name - * + * Returns a single calendar, by name + * * @param string $name * @todo needs optimizing * @return Sabre_CardDAV_AddressBook @@ -129,14 +129,14 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab return $child; } - throw new Sabre_DAV_Exception_FileNotFound('Addressbook with name \'' . $name . '\' could not be found'); + throw new Sabre_DAV_Exception_NotFound('Addressbook with name \'' . $name . '\' could not be found'); } /** - * Returns a list of addressbooks - * - * @return array + * Returns a list of addressbooks + * + * @return array */ public function getChildren() { @@ -150,11 +150,11 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab } /** - * Creates a new addressbook - * + * Creates a new addressbook + * * @param string $name - * @param array $resourceType - * @param array $properties + * @param array $resourceType + * @param array $properties * @return void */ public function createExtendedCollection($name, array $resourceType, array $properties) { @@ -169,8 +169,8 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -183,8 +183,8 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -196,13 +196,13 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { @@ -225,9 +225,9 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -236,5 +236,22 @@ class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sab } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + + return null; + + } } diff --git a/3rdparty/Sabre/CardDAV/Version.php b/3rdparty/Sabre/CardDAV/Version.php index 900fbf5e75..811b929e39 100644 --- a/3rdparty/Sabre/CardDAV/Version.php +++ b/3rdparty/Sabre/CardDAV/Version.php @@ -4,10 +4,10 @@ * Version Class * * This class contains the Sabre_CardDAV version information - * + * * @package Sabre * @subpackage CardDAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -16,7 +16,7 @@ class Sabre_CardDAV_Version { /** * Full version number */ - const VERSION = '1.5.3'; + const VERSION = '1.6.1'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/CardDAV/includes.php b/3rdparty/Sabre/CardDAV/includes.php new file mode 100644 index 0000000000..c3b8c04b07 --- /dev/null +++ b/3rdparty/Sabre/CardDAV/includes.php @@ -0,0 +1,32 @@ +setHTTPRequest($server->httpRequest); @@ -75,5 +79,5 @@ abstract class Sabre_DAV_Auth_Backend_AbstractBasic implements Sabre_DAV_Auth_IB } -} +} diff --git a/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php b/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php index 5bdc72753e..9833928b97 100644 --- a/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php +++ b/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php @@ -4,12 +4,12 @@ * HTTP Digest authentication backend class * * This class can be used by authentication objects wishing to use HTTP Digest - * Most of the digest logic is handled, implementors just need to worry about - * the getDigestHash method + * Most of the digest logic is handled, implementors just need to worry about + * the getDigestHash method * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -17,7 +17,7 @@ abstract class Sabre_DAV_Auth_Backend_AbstractDigest implements Sabre_DAV_Auth_I /** * This variable holds the currently logged in username. - * + * * @var array|null */ protected $currentUser; @@ -25,24 +25,26 @@ abstract class Sabre_DAV_Auth_Backend_AbstractDigest implements Sabre_DAV_Auth_I /** * Returns a users digest hash based on the username and realm. * - * If the user was not known, null must be returned. - * + * If the user was not known, null must be returned. + * * @param string $realm - * @param string $username - * @return string|null + * @param string $username + * @return string|null */ - abstract public function getDigestHash($realm,$username); + abstract public function getDigestHash($realm, $username); /** * Authenticates the user based on the current request. * - * If authentication is succesful, true must be returned. + * If authentication is successful, true must be returned. * If authentication fails, an exception must be thrown. * + * @param Sabre_DAV_Server $server + * @param string $realm * @throws Sabre_DAV_Exception_NotAuthenticated - * @return bool + * @return bool */ - public function authenticate(Sabre_DAV_Server $server,$realm) { + public function authenticate(Sabre_DAV_Server $server, $realm) { $digest = new Sabre_HTTP_DigestAuth(); @@ -83,9 +85,9 @@ abstract class Sabre_DAV_Auth_Backend_AbstractDigest implements Sabre_DAV_Auth_I } /** - * Returns the currently logged in username. - * - * @return string|null + * Returns the currently logged in username. + * + * @return string|null */ public function getCurrentUser() { diff --git a/3rdparty/Sabre/DAV/Auth/Backend/Apache.php b/3rdparty/Sabre/DAV/Auth/Backend/Apache.php index 6bcd76bdcb..d4294ea4d8 100644 --- a/3rdparty/Sabre/DAV/Auth/Backend/Apache.php +++ b/3rdparty/Sabre/DAV/Auth/Backend/Apache.php @@ -4,34 +4,36 @@ * Apache authenticator * * This authentication backend assumes that authentication has been - * conifgured in apache, rather than within SabreDAV. + * configured in apache, rather than within SabreDAV. * * Make sure apache is properly configured for this to work. * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Auth_Backend_Apache implements Sabre_DAV_Auth_IBackend { /** - * Current apache user - * - * @var string + * Current apache user + * + * @var string */ protected $remoteUser; - + /** * Authenticates the user based on the current request. * - * If authentication is succesful, true must be returned. + * If authentication is successful, true must be returned. * If authentication fails, an exception must be thrown. * - * @return bool + * @param Sabre_DAV_Server $server + * @param string $realm + * @return bool */ - public function authenticate(Sabre_DAV_Server $server,$realm) { + public function authenticate(Sabre_DAV_Server $server, $realm) { $remoteUser = $server->httpRequest->getRawServerValue('REMOTE_USER'); if (is_null($remoteUser)) { @@ -47,7 +49,7 @@ class Sabre_DAV_Auth_Backend_Apache implements Sabre_DAV_Auth_IBackend { * Returns information about the currently logged in user. * * If nobody is currently logged in, this method should return null. - * + * * @return array|null */ public function getCurrentUser() { diff --git a/3rdparty/Sabre/DAV/Auth/Backend/File.php b/3rdparty/Sabre/DAV/Auth/Backend/File.php index db1f04c477..de308d64a6 100644 --- a/3rdparty/Sabre/DAV/Auth/Backend/File.php +++ b/3rdparty/Sabre/DAV/Auth/Backend/File.php @@ -4,29 +4,28 @@ * This is an authentication backend that uses a file to manage passwords. * * The backend file must conform to Apache's htdigest format - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Auth_Backend_File extends Sabre_DAV_Auth_Backend_AbstractDigest { /** - * List of users - * + * List of users + * * @var array */ protected $users = array(); /** - * Creates the backend object. + * Creates the backend object. * * If the filename argument is passed in, it will parse out the specified file fist. - * - * @param string $filename - * @return void + * + * @param string|null $filename */ public function __construct($filename=null) { @@ -38,22 +37,22 @@ class Sabre_DAV_Auth_Backend_File extends Sabre_DAV_Auth_Backend_AbstractDigest /** * Loads an htdigest-formatted file. This method can be called multiple times if * more than 1 file is used. - * - * @param string $filename + * + * @param string $filename * @return void */ public function loadFile($filename) { foreach(file($filename,FILE_IGNORE_NEW_LINES) as $line) { - if (substr_count($line, ":") !== 2) + if (substr_count($line, ":") !== 2) throw new Sabre_DAV_Exception('Malformed htdigest file. Every line should contain 2 colons'); - + list($username,$realm,$A1) = explode(':',$line); if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) throw new Sabre_DAV_Exception('Malformed htdigest file. Invalid md5 hash'); - + $this->users[$realm . ':' . $username] = $A1; } @@ -62,10 +61,10 @@ class Sabre_DAV_Auth_Backend_File extends Sabre_DAV_Auth_Backend_AbstractDigest /** * Returns a users' information - * - * @param string $realm - * @param string $username - * @return string + * + * @param string $realm + * @param string $username + * @return string */ public function getDigestHash($realm, $username) { diff --git a/3rdparty/Sabre/DAV/Auth/Backend/PDO.php b/3rdparty/Sabre/DAV/Auth/Backend/PDO.php index 0301503601..eac18a23fb 100644 --- a/3rdparty/Sabre/DAV/Auth/Backend/PDO.php +++ b/3rdparty/Sabre/DAV/Auth/Backend/PDO.php @@ -4,38 +4,37 @@ * This is an authentication backend that uses a file to manage passwords. * * The backend file must conform to Apache's htdigest format - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Auth_Backend_PDO extends Sabre_DAV_Auth_Backend_AbstractDigest { /** - * Reference to PDO connection - * - * @var PDO + * Reference to PDO connection + * + * @var PDO */ protected $pdo; /** - * PDO table name we'll be using - * + * PDO table name we'll be using + * * @var string */ protected $tableName; /** - * Creates the backend object. + * Creates the backend object. * * If the filename argument is passed in, it will parse out the specified file fist. - * - * @param string $filename - * @param string $tableName The PDO table name to use - * @return void + * + * @param PDO $pdo + * @param string $tableName The PDO table name to use */ public function __construct(PDO $pdo, $tableName = 'users') { @@ -45,15 +44,15 @@ class Sabre_DAV_Auth_Backend_PDO extends Sabre_DAV_Auth_Backend_AbstractDigest { } /** - * Returns the digest hash for a user. - * - * @param string $realm - * @param string $username - * @return string|null + * Returns the digest hash for a user. + * + * @param string $realm + * @param string $username + * @return string|null */ public function getDigestHash($realm,$username) { - $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM `'.$this->tableName.'` WHERE username = ?'); + $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM '.$this->tableName.' WHERE username = ?'); $stmt->execute(array($username)); $result = $stmt->fetchAll(); diff --git a/3rdparty/Sabre/DAV/Auth/IBackend.php b/3rdparty/Sabre/DAV/Auth/IBackend.php index 1f67af4c2d..5be5d1bc93 100644 --- a/3rdparty/Sabre/DAV/Auth/IBackend.php +++ b/3rdparty/Sabre/DAV/Auth/IBackend.php @@ -5,7 +5,7 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -14,18 +14,20 @@ interface Sabre_DAV_Auth_IBackend { /** * Authenticates the user based on the current request. * - * If authentication is succesful, true must be returned. + * If authentication is successful, true must be returned. * If authentication fails, an exception must be thrown. * - * @return bool + * @param Sabre_DAV_Server $server + * @param string $realm + * @return bool */ - function authenticate(Sabre_DAV_Server $server,$realm); + function authenticate(Sabre_DAV_Server $server,$realm); /** * Returns information about the currently logged in username. * * If nobody is currently logged in, this method should return null. - * + * * @return string|null */ function getCurrentUser(); diff --git a/3rdparty/Sabre/DAV/Auth/Plugin.php b/3rdparty/Sabre/DAV/Auth/Plugin.php index f3718fcf46..55a4e39167 100644 --- a/3rdparty/Sabre/DAV/Auth/Plugin.php +++ b/3rdparty/Sabre/DAV/Auth/Plugin.php @@ -2,48 +2,47 @@ /** * This plugin provides Authentication for a WebDAV server. - * + * * It relies on a Backend object, which provides user information. * * Additionally, it provides support for: * * {DAV:}current-user-principal property from RFC5397 * * {DAV:}principal-collection-set property from RFC3744 - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Auth_Plugin extends Sabre_DAV_ServerPlugin { /** - * Reference to main server object - * - * @var Sabre_DAV_Server + * Reference to main server object + * + * @var Sabre_DAV_Server */ private $server; /** * Authentication backend - * - * @var Sabre_DAV_Auth_Backend_Abstract + * + * @var Sabre_DAV_Auth_IBackend */ private $authBackend; /** - * The authentication realm. - * - * @var string + * The authentication realm. + * + * @var string */ private $realm; /** - * __construct - * - * @param Sabre_DAV_Auth_Backend_Abstract $authBackend - * @param string $realm - * @return void + * __construct + * + * @param Sabre_DAV_Auth_IBackend $authBackend + * @param string $realm */ public function __construct(Sabre_DAV_Auth_IBackend $authBackend, $realm) { @@ -53,9 +52,9 @@ class Sabre_DAV_Auth_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Initializes the plugin. This function is automatically called by the server - * - * @param Sabre_DAV_Server $server + * Initializes the plugin. This function is automatically called by the server + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { @@ -67,11 +66,11 @@ class Sabre_DAV_Auth_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns a plugin name. - * + * * Using this name other plugins will be able to access other plugins - * using Sabre_DAV_Server::getPlugin - * - * @return string + * using Sabre_DAV_Server::getPlugin + * + * @return string */ public function getPluginName() { @@ -81,10 +80,10 @@ class Sabre_DAV_Auth_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns the current users' principal uri. - * - * If nobody is logged in, this will return null. - * - * @return string|null + * + * If nobody is logged in, this will return null. + * + * @return string|null */ public function getCurrentUser() { @@ -97,10 +96,11 @@ class Sabre_DAV_Auth_Plugin extends Sabre_DAV_ServerPlugin { /** * This method is called before any HTTP method and forces users to be authenticated - * + * * @param string $method + * @param string $uri * @throws Sabre_DAV_Exception_NotAuthenticated - * @return bool + * @return bool */ public function beforeMethod($method, $uri) { diff --git a/3rdparty/Sabre/DAV/Browser/GuessContentType.php b/3rdparty/Sabre/DAV/Browser/GuessContentType.php index ee8c698d78..b6c00d461c 100644 --- a/3rdparty/Sabre/DAV/Browser/GuessContentType.php +++ b/3rdparty/Sabre/DAV/Browser/GuessContentType.php @@ -10,10 +10,10 @@ * There's really no accurate, fast and portable way to determine the contenttype * so this extension does what the rest of the world does, and guesses it based * on the file extension. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -43,9 +43,9 @@ class Sabre_DAV_Browser_GuessContentType extends Sabre_DAV_ServerPlugin { ); /** - * Initializes the plugin - * - * @param Sabre_DAV_Server $server + * Initializes the plugin + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { @@ -57,16 +57,16 @@ class Sabre_DAV_Browser_GuessContentType extends Sabre_DAV_ServerPlugin { } /** - * Handler for teh afterGetProperties event - * - * @param string $path - * @param array $properties + * Handler for teh afterGetProperties event + * + * @param string $path + * @param array $properties * @return void */ public function afterGetProperties($path, &$properties) { if (array_key_exists('{DAV:}getcontenttype', $properties[404])) { - + list(, $fileName) = Sabre_DAV_URLUtil::splitPath($path); $contentType = $this->getContentType($fileName); @@ -81,9 +81,9 @@ class Sabre_DAV_Browser_GuessContentType extends Sabre_DAV_ServerPlugin { /** * Simple method to return the contenttype - * - * @param string $fileName - * @return string + * + * @param string $fileName + * @return string */ protected function getContentType($fileName) { diff --git a/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php b/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php index a66b57a3a9..1588488764 100644 --- a/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php +++ b/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php @@ -1,30 +1,30 @@ server->tree->getNodeForPath($uri); if ($node instanceof Sabre_DAV_IFile) return; $this->server->invokeMethod('PROPFIND',$uri); return false; - + } } diff --git a/3rdparty/Sabre/DAV/Browser/Plugin.php b/3rdparty/Sabre/DAV/Browser/Plugin.php index cd5617babb..09bbdd2ae0 100644 --- a/3rdparty/Sabre/DAV/Browser/Plugin.php +++ b/3rdparty/Sabre/DAV/Browser/Plugin.php @@ -6,77 +6,126 @@ * This plugin provides a html representation, so that a WebDAV server may be accessed * using a browser. * - * The class intercepts GET requests to collection resources and generates a simple - * html index. - * + * The class intercepts GET requests to collection resources and generates a simple + * html index. + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin { /** - * reference to server class - * - * @var Sabre_DAV_Server + * List of default icons for nodes. + * + * This is an array with class / interface names as keys, and asset names + * as values. + * + * The evaluation order is reversed. The last item in the list gets + * precendence. + * + * @var array + */ + public $iconMap = array( + 'Sabre_DAV_IFile' => 'icons/file', + 'Sabre_DAV_ICollection' => 'icons/collection', + 'Sabre_DAVACL_IPrincipal' => 'icons/principal', + 'Sabre_CalDAV_ICalendar' => 'icons/calendar', + 'Sabre_CardDAV_IAddressBook' => 'icons/addressbook', + 'Sabre_CardDAV_ICard' => 'icons/card', + ); + + /** + * The file extension used for all icons + * + * @var string + */ + public $iconExtension = '.png'; + + /** + * reference to server class + * + * @var Sabre_DAV_Server */ protected $server; /** - * enableEditing - * - * @var bool + * enablePost turns on the 'actions' panel, which allows people to create + * folders and upload files straight from a browser. + * + * @var bool */ protected $enablePost = true; + /** + * By default the browser plugin will generate a favicon and other images. + * To turn this off, set this property to false. + * + * @var bool + */ + protected $enableAssets = true; + /** * Creates the object. * * By default it will allow file creation and uploads. * Specify the first argument as false to disable this - * - * @param bool $enablePost - * @return void + * + * @param bool $enablePost + * @param bool $enableAssets */ - public function __construct($enablePost=true) { + public function __construct($enablePost=true, $enableAssets = true) { - $this->enablePost = $enablePost; + $this->enablePost = $enablePost; + $this->enableAssets = $enableAssets; } /** - * Initializes the plugin and subscribes to events - * - * @param Sabre_DAV_Server $server + * Initializes the plugin and subscribes to events + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { $this->server = $server; $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor')); + $this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200); if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler')); } /** - * This method intercepts GET requests to collections and returns the html - * - * @param string $method - * @return bool + * This method intercepts GET requests to collections and returns the html + * + * @param string $method + * @param string $uri + * @return bool */ public function httpGetInterceptor($method, $uri) { - if ($method!='GET') return true; + if ($method !== 'GET') return true; - try { + // We're not using straight-up $_GET, because we want everything to be + // unit testable. + $getVars = array(); + parse_str($this->server->httpRequest->getQueryString(), $getVars); + + if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) { + $this->serveAsset($getVars['assetName']); + return false; + } + + try { $node = $this->server->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { - // We're simply stopping when the file isn't found to not interfere + } catch (Sabre_DAV_Exception_NotFound $e) { + // We're simply stopping when the file isn't found to not interfere // with other plugins. return; } - if ($node instanceof Sabre_DAV_IFile) + if ($node instanceof Sabre_DAV_IFile) return; $this->server->httpResponse->sendStatus(200); @@ -87,57 +136,71 @@ class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin { ); return false; - + } /** - * Handles POST requests for tree operations - * - * This method is not yet used. - * - * @param string $method + * Handles POST requests for tree operations. + * + * @param string $method + * @param string $uri * @return bool */ public function httpPOSTHandler($method, $uri) { - if ($method!='POST') return true; - if (isset($_POST['sabreAction'])) switch($_POST['sabreAction']) { + if ($method!='POST') return; + $contentType = $this->server->httpRequest->getHeader('Content-Type'); + list($contentType) = explode(';', $contentType); + if ($contentType !== 'application/x-www-form-urlencoded' && + $contentType !== 'multipart/form-data') { + return; + } + $postVars = $this->server->httpRequest->getPostVars(); - case 'mkcol' : - if (isset($_POST['name']) && trim($_POST['name'])) { - // Using basename() because we won't allow slashes - list(, $folderName) = Sabre_DAV_URLUtil::splitPath(trim($_POST['name'])); - $this->server->createDirectory($uri . '/' . $folderName); - } - break; - case 'put' : - if ($_FILES) $file = current($_FILES); - else break; - $newName = trim($file['name']); - list(, $newName) = Sabre_DAV_URLUtil::splitPath(trim($file['name'])); - if (isset($_POST['name']) && trim($_POST['name'])) - $newName = trim($_POST['name']); + if (!isset($postVars['sabreAction'])) + return; - // Making sure we only have a 'basename' component - list(, $newName) = Sabre_DAV_URLUtil::splitPath($newName); - - - if (is_uploaded_file($file['tmp_name'])) { - $parent = $this->server->tree->getNodeForPath(trim($uri,'/')); - $parent->createFile($newName,fopen($file['tmp_name'],'r')); - } + if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) { + + switch($postVars['sabreAction']) { + + case 'mkcol' : + if (isset($postVars['name']) && trim($postVars['name'])) { + // Using basename() because we won't allow slashes + list(, $folderName) = Sabre_DAV_URLUtil::splitPath(trim($postVars['name'])); + $this->server->createDirectory($uri . '/' . $folderName); + } + break; + case 'put' : + if ($_FILES) $file = current($_FILES); + else break; + + list(, $newName) = Sabre_DAV_URLUtil::splitPath(trim($file['name'])); + if (isset($postVars['name']) && trim($postVars['name'])) + $newName = trim($postVars['name']); + + // Making sure we only have a 'basename' component + list(, $newName) = Sabre_DAV_URLUtil::splitPath($newName); + + if (is_uploaded_file($file['tmp_name'])) { + $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r')); + } + break; + + } } $this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri()); + $this->server->httpResponse->sendStatus(302); return false; } /** - * Escapes a string for html. - * - * @param string $value - * @return void + * Escapes a string for html. + * + * @param string $value + * @return string */ public function escapeHTML($value) { @@ -146,118 +209,199 @@ class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Generates the html directory index for a given url + * Generates the html directory index for a given url * - * @param string $path - * @return string + * @param string $path + * @return string */ public function generateDirectoryIndex($path) { + $version = ''; + if (Sabre_DAV_Server::$exposeVersion) { + $version = Sabre_DAV_Version::VERSION ."-". Sabre_DAV_Version::STABILITY; + } + $html = " - Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . Sabre_DAV_Version::VERSION . " - - + Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . " + + "; + + if ($this->enableAssets) { + $html.=''; + } + + $html .= "

Index for " . $this->escapeHTML($path) . "/

- - "; - - $files = $this->server->getPropertiesForPath($path,array( - '{DAV:}displayname', - '{DAV:}resourcetype', - '{DAV:}getcontenttype', - '{DAV:}getcontentlength', - '{DAV:}getlastmodified', - ),1); + + "; - $parent = $this->server->tree->getNodeForPath($path); + $files = $this->server->getPropertiesForPath($path,array( + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ),1); + + $parent = $this->server->tree->getNodeForPath($path); - if ($path) { + if ($path) { - list($parentUri) = Sabre_DAV_URLUtil::splitPath($path); - $fullPath = Sabre_DAV_URLUtil::encodePath($this->server->getBaseUri() . $parentUri); + list($parentUri) = Sabre_DAV_URLUtil::splitPath($path); + $fullPath = Sabre_DAV_URLUtil::encodePath($this->server->getBaseUri() . $parentUri); - $html.= " - - - - -"; + $icon = $this->enableAssets?'Parent':''; + $html.= " + + + + + + "; - } + } - foreach($files as $k=>$file) { + foreach($files as $file) { - // This is the current directory, we can skip it - if (rtrim($file['href'],'/')==$path) continue; + // This is the current directory, we can skip it + if (rtrim($file['href'],'/')==$path) continue; - list(, $name) = Sabre_DAV_URLUtil::splitPath($file['href']); + list(, $name) = Sabre_DAV_URLUtil::splitPath($file['href']); - $type = null; + $type = null; - if (isset($file[200]['{DAV:}resourcetype'])) { - $type = $file[200]['{DAV:}resourcetype']->getValue(); + if (isset($file[200]['{DAV:}resourcetype'])) { + $type = $file[200]['{DAV:}resourcetype']->getValue(); - // resourcetype can have multiple values - if (!is_array($type)) $type = array($type); + // resourcetype can have multiple values + if (!is_array($type)) $type = array($type); - foreach($type as $k=>$v) { + foreach($type as $k=>$v) { - // Some name mapping is preferred - switch($v) { - case '{DAV:}collection' : - $type[$k] = 'Collection'; - break; - case '{DAV:}principal' : - $type[$k] = 'Principal'; - break; - case '{urn:ietf:params:xml:ns:carddav}addressbook' : - $type[$k] = 'Addressbook'; - break; - case '{urn:ietf:params:xml:ns:caldav}calendar' : - $type[$k] = 'Calendar'; + // Some name mapping is preferred + switch($v) { + case '{DAV:}collection' : + $type[$k] = 'Collection'; + break; + case '{DAV:}principal' : + $type[$k] = 'Principal'; + break; + case '{urn:ietf:params:xml:ns:carddav}addressbook' : + $type[$k] = 'Addressbook'; + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $type[$k] = 'Calendar'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : + $type[$k] = 'Schedule Inbox'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : + $type[$k] = 'Schedule Outbox'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-read' : + $type[$k] = 'Proxy-Read'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-write' : + $type[$k] = 'Proxy-Write'; + break; + } + + } + $type = implode(', ', $type); + } + + // If no resourcetype was found, we attempt to use + // the contenttype property + if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { + $type = $file[200]['{DAV:}getcontenttype']; + } + if (!$type) $type = 'Unknown'; + + $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; + $lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(DateTime::ATOM):''; + + $fullPath = Sabre_DAV_URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); + + $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; + + $displayName = $this->escapeHTML($displayName); + $type = $this->escapeHTML($type); + + $icon = ''; + + if ($this->enableAssets) { + $node = $parent->getChild($name); + foreach(array_reverse($this->iconMap) as $class=>$iconName) { + + if ($node instanceof $class) { + $icon = ''; break; + } + + } } - $type = implode(', ', $type); + + $html.= " + + + + + + "; + } - // If no resourcetype was found, we attempt to use - // the contenttype property - if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { - $type = $file[200]['{DAV:}getcontenttype']; + $html.= ""; + + $output = ''; + + if ($this->enablePost) { + $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output)); } - if (!$type) $type = 'Unknown'; - $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; - $lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(DateTime::ATOM):''; + $html.=$output; - $fullPath = Sabre_DAV_URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); + $html.= "
NameTypeSizeLast modified

NameTypeSizeLast modified

..[parent]
$icon..[parent]
$icon{$displayName}{$type}{$size}{$lastmodified}

+
Generated by SabreDAV " . $version . " (c)2007-2012 http://code.google.com/p/sabredav/
+ + "; - $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; - - $name = $this->escapeHTML($name); - $displayName = $this->escapeHTML($displayName); - $type = $this->escapeHTML($type); - - $html.= " -{$displayName} -{$type} -{$size} -{$lastmodified} -"; + return $html; } - $html.= "
"; + /** + * This method is used to generate the 'actions panel' output for + * collections. + * + * This specifically generates the interfaces for creating new files, and + * creating new directories. + * + * @param Sabre_DAV_INode $node + * @param mixed $output + * @return void + */ + public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) { - if ($this->enablePost && $parent instanceof Sabre_DAV_ICollection) { - $html.= '
+ if (!$node instanceof Sabre_DAV_ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + if (get_class($node)==='Sabre_DAV_SimpleCollection') + return; + + $output.= '

Create new folder

Name:
@@ -270,15 +414,75 @@ class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin { File:
- '; - } + '; - $html.= " -
Generated by SabreDAV " . Sabre_DAV_Version::VERSION ."-". Sabre_DAV_Version::STABILITY . " (c)2007-2011 http://code.google.com/p/sabredav/
- -"; + } - return $html; + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + + return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName); + + } + + /** + * This method returns a local pathname to an asset. + * + * @param string $assetName + * @return string + */ + protected function getLocalAssetPath($assetName) { + + // Making sure people aren't trying to escape from the base path. + $assetSplit = explode('/', $assetName); + if (in_array('..',$assetSplit)) { + throw new Sabre_DAV_Exception('Incorrect asset path'); + } + $path = __DIR__ . '/assets/' . $assetName; + return $path; + + } + + /** + * This method reads an asset from disk and generates a full http response. + * + * @param string $assetName + * @return void + */ + protected function serveAsset($assetName) { + + $assetPath = $this->getLocalAssetPath($assetName); + if (!file_exists($assetPath)) { + throw new Sabre_DAV_Exception_NotFound('Could not find an asset with this name'); + } + // Rudimentary mime type detection + switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) { + + case 'ico' : + $mime = 'image/vnd.microsoft.icon'; + break; + + case 'png' : + $mime = 'image/png'; + break; + + default: + $mime = 'application/octet-stream'; + break; + + } + + $this->server->httpResponse->setHeader('Content-Type', $mime); + $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath)); + $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600'); + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->sendBody(fopen($assetPath,'r')); } diff --git a/3rdparty/Sabre/DAV/Browser/assets/favicon.ico b/3rdparty/Sabre/DAV/Browser/assets/favicon.ico new file mode 100644 index 0000000000..2b2c10a22c Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/favicon.ico differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png b/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png new file mode 100644 index 0000000000..c9acc84172 Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png b/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png new file mode 100644 index 0000000000..3ecd6a800a Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/card.png b/3rdparty/Sabre/DAV/Browser/assets/icons/card.png new file mode 100644 index 0000000000..2ce954866d Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/card.png differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png b/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png new file mode 100644 index 0000000000..156fa64fd5 Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/file.png b/3rdparty/Sabre/DAV/Browser/assets/icons/file.png new file mode 100644 index 0000000000..3b98551cec Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/file.png differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png b/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png new file mode 100644 index 0000000000..156fa64fd5 Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png differ diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png b/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png new file mode 100644 index 0000000000..f8988f828e Binary files /dev/null and b/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png differ diff --git a/3rdparty/Sabre/DAV/Client.php b/3rdparty/Sabre/DAV/Client.php index fc6a6fff08..a8320dd978 100644 --- a/3rdparty/Sabre/DAV/Client.php +++ b/3rdparty/Sabre/DAV/Client.php @@ -3,14 +3,14 @@ /** * SabreDAV DAV client * - * This client wraps around Curl to provide a convenient API to a WebDAV + * This client wraps around Curl to provide a convenient API to a WebDAV * server. * * NOTE: This class is experimental, it's api will likely change in the future. - * + * * @package Sabre * @subpackage DAVClient - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -26,22 +26,22 @@ class Sabre_DAV_Client { /** * Constructor * - * Settings are provided through the 'settings' argument. The following + * Settings are provided through the 'settings' argument. The following * settings are supported: * * * baseUri * * userName (optional) * * password (optional) * * proxy (optional) - * - * @param array $settings + * + * @param array $settings */ public function __construct(array $settings) { if (!isset($settings['baseUri'])) { throw new InvalidArgumentException('A baseUri must be provided'); } - + $validSettings = array( 'baseUri', 'userName', @@ -62,23 +62,23 @@ class Sabre_DAV_Client { /** * Does a PROPFIND request * - * The list of requested properties must be specified as an array, in clark - * notation. + * The list of requested properties must be specified as an array, in clark + * notation. * - * The returned array will contain a list of filenames as keys, and + * The returned array will contain a list of filenames as keys, and * properties as values. * - * The properties array will contain the list of properties. Only properties - * that are actually returned from the server (without error) will be + * The properties array will contain the list of properties. Only properties + * that are actually returned from the server (without error) will be * returned, anything else is discarded. * - * Depth should be either 0 or 1. A depth of 1 will cause a request to be + * Depth should be either 0 or 1. A depth of 1 will cause a request to be * made to the server to also return all child resources. * - * @param string $url - * @param array $properties - * @param int $depth - * @return array + * @param string $url + * @param array $properties + * @param int $depth + * @return array */ public function propFind($url, array $properties, $depth = 0) { @@ -132,14 +132,14 @@ class Sabre_DAV_Client { /** * Updates a list of properties on the server * - * The list of properties must have clark-notation properties for the keys, - * and the actual (string) value for the value. If the value is null, an - * attempt is made to delete the property. + * The list of properties must have clark-notation properties for the keys, + * and the actual (string) value for the value. If the value is null, an + * attempt is made to delete the property. * - * @todo Must be building the request using the DOM, and does not yet - * support complex properties. - * @param string $url - * @param array $properties + * @todo Must be building the request using the DOM, and does not yet + * support complex properties. + * @param string $url + * @param array $properties * @return void */ public function propPatch($url, array $properties) { @@ -175,7 +175,7 @@ class Sabre_DAV_Client { $body.=" "; } // Shitty.. i know - $body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8'); + $body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8'); if ($namespace === 'DAV:') { $body.='' . "\n"; } else { @@ -189,7 +189,7 @@ class Sabre_DAV_Client { $body.= ''; - $response = $this->request('PROPPATCH', $url, $body, array( + $this->request('PROPPATCH', $url, $body, array( 'Content-Type' => 'application/xml' )); @@ -198,11 +198,11 @@ class Sabre_DAV_Client { /** * Performs an HTTP options request * - * This method returns all the features from the 'DAV:' header as an array. - * If there was no DAV header, or no contents this method will return an - * empty array. - * - * @return array + * This method returns all the features from the 'DAV:' header as an array. + * If there was no DAV header, or no contents this method will return an + * empty array. + * + * @return array */ public function options() { @@ -222,20 +222,20 @@ class Sabre_DAV_Client { /** * Performs an actual HTTP request, and returns the result. * - * If the specified url is relative, it will be expanded based on the base + * If the specified url is relative, it will be expanded based on the base * url. * * The returned array contains 3 keys: * * body - the response body * * httpCode - a HTTP code (200, 404, etc) - * * headers - a list of response http headers. The header names have + * * headers - a list of response http headers. The header names have * been lowercased. * - * @param string $method - * @param string $url - * @param string $body - * @param array $headers - * @return array + * @param string $method + * @param string $url + * @param string $body + * @param array $headers + * @return array */ public function request($method, $url = '', $body = null, $headers = array()) { @@ -243,14 +243,37 @@ class Sabre_DAV_Client { $curlSettings = array( CURLOPT_RETURNTRANSFER => true, - CURLOPT_CUSTOMREQUEST => $method, - CURLOPT_POSTFIELDS => $body, // Return headers as part of the response - CURLOPT_HEADER => true + CURLOPT_HEADER => true, + CURLOPT_POSTFIELDS => $body, + // Automatically follow redirects + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, ); + switch ($method) { + case 'PUT': + $curlSettings[CURLOPT_PUT] = true; + break; + case 'HEAD' : + + // do not read body with HEAD requests (this is neccessary because cURL does not ignore the body with HEAD + // requests when the Content-Length header is given - which in turn is perfectly valid according to HTTP + // specs...) cURL does unfortunately return an error in this case ("transfer closed transfer closed with + // ... bytes remaining to read") this can be circumvented by explicitly telling cURL to ignore the + // response body + $curlSettings[CURLOPT_NOBODY] = true; + $curlSettings[CURLOPT_CUSTOMREQUEST] = 'HEAD'; + break; + + default: + $curlSettings[CURLOPT_CUSTOMREQUEST] = $method; + break; + + } + // Adding HTTP headers - $nHeaders = array(); + $nHeaders = array(); foreach($headers as $key=>$value) { $nHeaders[] = $key . ': ' . $value; @@ -277,17 +300,17 @@ class Sabre_DAV_Client { $headerBlob = substr($response, 0, $curlInfo['header_size']); $response = substr($response, $curlInfo['header_size']); - // In the case of 100 Continue, or redirects we'll have multiple lists - // of headers for each separate HTTP response. We can easily split this + // In the case of 100 Continue, or redirects we'll have multiple lists + // of headers for each separate HTTP response. We can easily split this // because they are separated by \r\n\r\n $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n")); - + // We only care about the last set of headers $headerBlob = $headerBlob[count($headerBlob)-1]; // Splitting headers $headerBlob = explode("\r\n", $headerBlob); - + $headers = array(); foreach($headerBlob as $header) { $parts = explode(':', $header, 2); @@ -304,10 +327,17 @@ class Sabre_DAV_Client { if ($curlErrNo) { throw new Sabre_DAV_Exception('[CURL] Error while making request: ' . $curlError . ' (error code: ' . $curlErrNo . ')'); - } + } if ($response['statusCode']>=400) { - throw new Sabre_DAV_Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')'); + switch ($response['statusCode']) { + case 404: + throw new Sabre_DAV_Exception_NotFound('Resource ' . $url . ' not found.'); + break; + + default: + throw new Sabre_DAV_Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')'); + } } return $response; @@ -317,12 +347,12 @@ class Sabre_DAV_Client { /** * Wrapper for all curl functions. * - * The only reason this was split out in a separate method, is so it - * becomes easier to unittest. + * The only reason this was split out in a separate method, is so it + * becomes easier to unittest. * * @param string $url - * @param array $settings - * @return + * @param array $settings + * @return array */ protected function curlRequest($url, $settings) { @@ -339,20 +369,20 @@ class Sabre_DAV_Client { } /** - * Returns the full url based on the given url (which may be relative). All - * urls are expanded based on the base url as given by the server. - * - * @param string $url - * @return string + * Returns the full url based on the given url (which may be relative). All + * urls are expanded based on the base url as given by the server. + * + * @param string $url + * @return string */ protected function getAbsoluteUrl($url) { - // If the url starts with http:// or https://, the url is already absolute. + // If the url starts with http:// or https://, the url is already absolute. if (preg_match('/^http(s?):\/\//', $url)) { return $url; } - // If the url starts with a slash, we must calculate the url based off + // If the url starts with a slash, we must calculate the url based off // the root of the base url. if (strpos($url,'/') === 0) { $parts = parse_url($this->baseUri); @@ -366,7 +396,7 @@ class Sabre_DAV_Client { /** * Parses a WebDAV multistatus response body - * + * * This method returns an array with the following structure * * array( @@ -387,7 +417,7 @@ class Sabre_DAV_Client { * * * @param string $body xml body - * @return array + * @return array */ public function parseMultiStatus($body) { @@ -397,14 +427,13 @@ class Sabre_DAV_Client { if ($responseXML===false) { throw new InvalidArgumentException('The passed data is not valid XML'); } - - $responseXML->registerXPathNamespace('d','DAV:'); + + $responseXML->registerXPathNamespace('d', 'urn:DAV'); $propResult = array(); foreach($responseXML->xpath('d:response') as $response) { - - $response->registerXPathNamespace('d','DAV:'); + $response->registerXPathNamespace('d', 'urn:DAV'); $href = $response->xpath('d:href'); $href = (string)$href[0]; @@ -412,11 +441,11 @@ class Sabre_DAV_Client { foreach($response->xpath('d:propstat') as $propStat) { - $propStat->registerXPathNamespace('d','DAV:'); + $propStat->registerXPathNamespace('d', 'urn:DAV'); $status = $propStat->xpath('d:status'); list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3); - $properties[$statusCode] = Sabre_DAV_XMLUtil::parseProperties(dom_import_simplexml($propStat), $this->propertyMap); + $properties[$statusCode] = Sabre_DAV_XMLUtil::parseProperties(dom_import_simplexml($propStat), $this->propertyMap); } diff --git a/3rdparty/Sabre/DAV/Collection.php b/3rdparty/Sabre/DAV/Collection.php index 9da04c1279..776c22531b 100644 --- a/3rdparty/Sabre/DAV/Collection.php +++ b/3rdparty/Sabre/DAV/Collection.php @@ -4,12 +4,12 @@ * Collection class * * This is a helper class, that should aid in getting collections classes setup. - * Most of its methods are implemented, and throw permission denied exceptions - * + * Most of its methods are implemented, and throw permission denied exceptions + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ICollection { @@ -17,12 +17,12 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ /** * Returns a child object, by its name. * - * This method makes use of the getChildren method to grab all the child nodes, and compares the name. + * This method makes use of the getChildren method to grab all the child nodes, and compares the name. * Generally its wise to override this, as this can usually be optimized - * + * * @param string $name - * @throws Sabre_DAV_Exception_FileNotFound - * @return Sabre_DAV_INode + * @throws Sabre_DAV_Exception_NotFound + * @return Sabre_DAV_INode */ public function getChild($name) { @@ -31,7 +31,7 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ if ($child->getName()==$name) return $child; } - throw new Sabre_DAV_Exception_FileNotFound('File not found: ' . $name); + throw new Sabre_DAV_Exception_NotFound('File not found: ' . $name); } @@ -39,9 +39,9 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ * Checks is a child-node exists. * * It is generally a good idea to try and override this. Usually it can be optimized. - * - * @param string $name - * @return bool + * + * @param string $name + * @return bool */ public function childExists($name) { @@ -50,7 +50,7 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ $this->getChild($name); return true; - } catch(Sabre_DAV_Exception_FileNotFound $e) { + } catch(Sabre_DAV_Exception_NotFound $e) { return false; @@ -59,12 +59,28 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ } /** - * Creates a new file in the directory - * - * @param string $name Name of the file - * @param resource $data Initial payload, passed as a readable stream resource. - * @throws Sabre_DAV_Exception_Forbidden - * @return void + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After succesful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string */ public function createFile($name, $data = null) { @@ -73,9 +89,9 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ } /** - * Creates a new subdirectory - * - * @param string $name + * Creates a new subdirectory + * + * @param string $name * @throws Sabre_DAV_Exception_Forbidden * @return void */ diff --git a/3rdparty/Sabre/DAV/Directory.php b/3rdparty/Sabre/DAV/Directory.php index 86af4827b3..6db8febc02 100644 --- a/3rdparty/Sabre/DAV/Directory.php +++ b/3rdparty/Sabre/DAV/Directory.php @@ -8,7 +8,7 @@ * @package Sabre * @subpackage DAV * @deprecated Use Sabre_DAV_Collection instead - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ diff --git a/3rdparty/Sabre/DAV/Exception.php b/3rdparty/Sabre/DAV/Exception.php index 61f8b87c0a..a2cd6cf582 100644 --- a/3rdparty/Sabre/DAV/Exception.php +++ b/3rdparty/Sabre/DAV/Exception.php @@ -7,42 +7,42 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ /** - * Main Exception class. + * Main Exception class. * - * This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occured. + * This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occurred. * The default for this is 500. * * This class also allows you to generate custom xml data for your exceptions. This will be displayed * in the 'error' element in the failing response. */ -class Sabre_DAV_Exception extends Exception { +class Sabre_DAV_Exception extends Exception { /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ - public function getHTTPCode() { + public function getHTTPCode() { return 500; } /** - * This method allows the exception to include additonal information into the WebDAV error response + * This method allows the exception to include additional information into the WebDAV error response * * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { - + } @@ -50,14 +50,15 @@ class Sabre_DAV_Exception extends Exception { * This method allows the exception to return any extra HTTP response headers. * * The headers must be returned as an array. - * - * @return array + * + * @param Sabre_DAV_Server $server + * @return array */ public function getHTTPHeaders(Sabre_DAV_Server $server) { return array(); - } + } } diff --git a/3rdparty/Sabre/DAV/Exception/BadRequest.php b/3rdparty/Sabre/DAV/Exception/BadRequest.php index 7025bb1031..b198648a75 100644 --- a/3rdparty/Sabre/DAV/Exception/BadRequest.php +++ b/3rdparty/Sabre/DAV/Exception/BadRequest.php @@ -4,24 +4,24 @@ * BadRequest * * The BadRequest is thrown when the user submitted an invalid HTTP request - * BadRequest - * + * BadRequest + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Exception_BadRequest extends Sabre_DAV_Exception { /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ public function getHTTPCode() { - return 400; + return 400; } diff --git a/3rdparty/Sabre/DAV/Exception/Conflict.php b/3rdparty/Sabre/DAV/Exception/Conflict.php index 7eaa08178a..6b0bd1fad7 100644 --- a/3rdparty/Sabre/DAV/Exception/Conflict.php +++ b/3rdparty/Sabre/DAV/Exception/Conflict.php @@ -8,14 +8,14 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Exception_Conflict extends Sabre_DAV_Exception { /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ diff --git a/3rdparty/Sabre/DAV/Exception/ConflictingLock.php b/3rdparty/Sabre/DAV/Exception/ConflictingLock.php index 279f63dfde..6121868e69 100644 --- a/3rdparty/Sabre/DAV/Exception/ConflictingLock.php +++ b/3rdparty/Sabre/DAV/Exception/ConflictingLock.php @@ -1,28 +1,28 @@ lock) { $error = $errorNode->ownerDocument->createElementNS('DAV:','d:no-conflicting-lock'); $errorNode->appendChild($error); diff --git a/3rdparty/Sabre/DAV/Exception/FileNotFound.php b/3rdparty/Sabre/DAV/Exception/FileNotFound.php index b20e4a2fb3..d76e400c93 100644 --- a/3rdparty/Sabre/DAV/Exception/FileNotFound.php +++ b/3rdparty/Sabre/DAV/Exception/FileNotFound.php @@ -3,26 +3,17 @@ /** * FileNotFound * - * This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404 + * Deprecated: Warning, this class is deprecated and will be removed in a + * future version of SabreDAV. Please use Sabre_DAV_Exception_NotFound instead. * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @deprecated Use Sabre_DAV_Exception_NotFound instead * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ -class Sabre_DAV_Exception_FileNotFound extends Sabre_DAV_Exception { - - /** - * Returns the HTTP statuscode for this exception - * - * @return int - */ - public function getHTTPCode() { - - return 404; - - } +class Sabre_DAV_Exception_FileNotFound extends Sabre_DAV_Exception_NotFound { } diff --git a/3rdparty/Sabre/DAV/Exception/Forbidden.php b/3rdparty/Sabre/DAV/Exception/Forbidden.php index 167f3c2760..20b1056e31 100644 --- a/3rdparty/Sabre/DAV/Exception/Forbidden.php +++ b/3rdparty/Sabre/DAV/Exception/Forbidden.php @@ -4,17 +4,17 @@ * Forbidden * * This exception is thrown whenever a user tries to do an operation he's not allowed to - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Exception_Forbidden extends Sabre_DAV_Exception { /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ diff --git a/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php b/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php index 15007cdd35..1a15089b0a 100644 --- a/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php +++ b/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php @@ -1,20 +1,20 @@ appendChild($error); } - + } diff --git a/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php b/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php index 47032cffc7..80ab7aff65 100644 --- a/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php +++ b/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php @@ -1,13 +1,13 @@ lock) { $error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-submitted'); $errorNode->appendChild($error); diff --git a/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php b/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php index 02c145ffeb..3187575150 100644 --- a/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php +++ b/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php @@ -4,17 +4,17 @@ * MethodNotAllowed * * The 405 is thrown when a client tried to create a directory on an already existing directory - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Exception_MethodNotAllowed extends Sabre_DAV_Exception { /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ @@ -28,8 +28,9 @@ class Sabre_DAV_Exception_MethodNotAllowed extends Sabre_DAV_Exception { * This method allows the exception to return any extra HTTP response headers. * * The headers must be returned as an array. - * - * @return array + * + * @param Sabre_DAV_Server $server + * @return array */ public function getHTTPHeaders(Sabre_DAV_Server $server) { diff --git a/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php b/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php index 1faffddfa0..87ca624429 100644 --- a/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php +++ b/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php @@ -5,22 +5,22 @@ * * This exception is thrown when the client did not provide valid * authentication credentials. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Exception_NotAuthenticated extends Sabre_DAV_Exception { /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ public function getHTTPCode() { - + return 401; } diff --git a/3rdparty/Sabre/DAV/Exception/NotFound.php b/3rdparty/Sabre/DAV/Exception/NotFound.php new file mode 100644 index 0000000000..2b9da560d2 --- /dev/null +++ b/3rdparty/Sabre/DAV/Exception/NotFound.php @@ -0,0 +1,28 @@ +header = $header; - } + } /** - * Returns the HTTP statuscode for this exception + * Returns the HTTP statuscode for this exception * * @return int */ public function getHTTPCode() { - return 412; + return 412; } /** - * This method allows the exception to include additonal information into the WebDAV error response + * This method allows the exception to include additional information into the WebDAV error response * * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { - + if ($this->header) { $prop = $errorNode->ownerDocument->createElement('s:header'); $prop->nodeValue = $this->header; diff --git a/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php b/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php index e4ed601b16..e86800f303 100644 --- a/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php +++ b/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php @@ -7,17 +7,17 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Exception_ReportNotImplemented extends Sabre_DAV_Exception_NotImplemented { /** - * This method allows the exception to include additonal information into the WebDAV error response + * This method allows the exception to include additional information into the WebDAV error response * * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { diff --git a/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php b/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php index 37abbd729d..29ee3654a7 100644 --- a/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php +++ b/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php @@ -1,21 +1,21 @@ path . '/' . $name; - if (!file_exists($path)) throw new Sabre_DAV_Exception_FileNotFound('File with name ' . $path . ' could not be located'); + if (!file_exists($path)) throw new Sabre_DAV_Exception_NotFound('File with name ' . $path . ' could not be located'); if (is_dir($path)) { @@ -66,9 +81,9 @@ class Sabre_DAV_FS_Directory extends Sabre_DAV_FS_Node implements Sabre_DAV_ICol } /** - * Returns an array with all the child nodes - * - * @return Sabre_DAV_INode[] + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] */ public function getChildren() { @@ -79,10 +94,10 @@ class Sabre_DAV_FS_Directory extends Sabre_DAV_FS_Node implements Sabre_DAV_ICol } /** - * Checks if a child exists. - * - * @param string $name - * @return bool + * Checks if a child exists. + * + * @param string $name + * @return bool */ public function childExists($name) { @@ -92,8 +107,8 @@ class Sabre_DAV_FS_Directory extends Sabre_DAV_FS_Node implements Sabre_DAV_ICol } /** - * Deletes all files in this directory, and then itself - * + * Deletes all files in this directory, and then itself + * * @return void */ public function delete() { @@ -104,16 +119,16 @@ class Sabre_DAV_FS_Directory extends Sabre_DAV_FS_Node implements Sabre_DAV_ICol } /** - * Returns available diskspace information - * - * @return array + * Returns available diskspace information + * + * @return array */ public function getQuotaInfo() { return array( disk_total_space($this->path)-disk_free_space($this->path), disk_free_space($this->path) - ); + ); } diff --git a/3rdparty/Sabre/DAV/FS/File.php b/3rdparty/Sabre/DAV/FS/File.php index 262187d7e8..6a8039fe30 100644 --- a/3rdparty/Sabre/DAV/FS/File.php +++ b/3rdparty/Sabre/DAV/FS/File.php @@ -1,21 +1,21 @@ path); } @@ -60,10 +60,10 @@ class Sabre_DAV_FS_File extends Sabre_DAV_FS_Node implements Sabre_DAV_IFile { * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbritrary string, but MUST be surrounded by double-quotes. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined - * + * * @return mixed */ public function getETag() { @@ -77,8 +77,8 @@ class Sabre_DAV_FS_File extends Sabre_DAV_FS_Node implements Sabre_DAV_IFile { * * If null is returned, we'll assume application/octet-stream * - * @return mixed - */ + * @return mixed + */ public function getContentType() { return null; diff --git a/3rdparty/Sabre/DAV/FS/Node.php b/3rdparty/Sabre/DAV/FS/Node.php index b8d7bcfe84..1283e9d0fd 100644 --- a/3rdparty/Sabre/DAV/FS/Node.php +++ b/3rdparty/Sabre/DAV/FS/Node.php @@ -1,30 +1,29 @@ path,$newPath); - + $this->path = $newPath; } @@ -67,9 +66,9 @@ abstract class Sabre_DAV_FS_Node implements Sabre_DAV_INode { /** - * Returns the last modification time, as a unix timestamp - * - * @return int + * Returns the last modification time, as a unix timestamp + * + * @return int */ public function getLastModified() { diff --git a/3rdparty/Sabre/DAV/FSExt/Directory.php b/3rdparty/Sabre/DAV/FSExt/Directory.php index c43d4385ac..540057183b 100644 --- a/3rdparty/Sabre/DAV/FSExt/Directory.php +++ b/3rdparty/Sabre/DAV/FSExt/Directory.php @@ -1,22 +1,39 @@ path . '/' . $name; file_put_contents($newPath,$data); + return '"' . md5_file($newPath) . '"'; + } /** - * Creates a new subdirectory - * - * @param string $name + * Creates a new subdirectory + * + * @param string $name * @return void */ public function createDirectory($name) { @@ -43,18 +62,18 @@ class Sabre_DAV_FSExt_Directory extends Sabre_DAV_FSExt_Node implements Sabre_DA } /** - * Returns a specific child node, referenced by its name - * - * @param string $name - * @throws Sabre_DAV_Exception_FileNotFound - * @return Sabre_DAV_INode + * Returns a specific child node, referenced by its name + * + * @param string $name + * @throws Sabre_DAV_Exception_NotFound + * @return Sabre_DAV_INode */ public function getChild($name) { $path = $this->path . '/' . $name; - if (!file_exists($path)) throw new Sabre_DAV_Exception_FileNotFound('File could not be located'); - if ($name=='.' || $name=='..') throw new Sabre_DAV_Exception_Forbidden('Permission denied to . and ..'); + if (!file_exists($path)) throw new Sabre_DAV_Exception_NotFound('File could not be located'); + if ($name=='.' || $name=='..') throw new Sabre_DAV_Exception_Forbidden('Permission denied to . and ..'); if (is_dir($path)) { @@ -69,10 +88,10 @@ class Sabre_DAV_FSExt_Directory extends Sabre_DAV_FSExt_Node implements Sabre_DA } /** - * Checks if a child exists. - * - * @param string $name - * @return bool + * Checks if a child exists. + * + * @param string $name + * @return bool */ public function childExists($name) { @@ -85,9 +104,9 @@ class Sabre_DAV_FSExt_Directory extends Sabre_DAV_FSExt_Node implements Sabre_DA } /** - * Returns an array with all the child nodes - * - * @return Sabre_DAV_INode[] + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] */ public function getChildren() { @@ -98,9 +117,9 @@ class Sabre_DAV_FSExt_Directory extends Sabre_DAV_FSExt_Node implements Sabre_DA } /** - * Deletes all files in this directory, and then itself - * - * @return void + * Deletes all files in this directory, and then itself + * + * @return bool */ public function delete() { @@ -118,16 +137,16 @@ class Sabre_DAV_FSExt_Directory extends Sabre_DAV_FSExt_Node implements Sabre_DA } /** - * Returns available diskspace information - * - * @return array + * Returns available diskspace information + * + * @return array */ public function getQuotaInfo() { return array( disk_total_space($this->path)-disk_free_space($this->path), disk_free_space($this->path) - ); + ); } diff --git a/3rdparty/Sabre/DAV/FSExt/File.php b/3rdparty/Sabre/DAV/FSExt/File.php index 7a8e7a11f2..b93ce5aee2 100644 --- a/3rdparty/Sabre/DAV/FSExt/File.php +++ b/3rdparty/Sabre/DAV/FSExt/File.php @@ -1,34 +1,35 @@ path,$data); + return '"' . md5_file($this->path) . '"'; } /** * Returns the data * - * @return string + * @return string */ public function get() { @@ -39,7 +40,7 @@ class Sabre_DAV_FSExt_File extends Sabre_DAV_FSExt_Node implements Sabre_DAV_IFi /** * Delete the current file * - * @return void + * @return bool */ public function delete() { @@ -52,9 +53,11 @@ class Sabre_DAV_FSExt_File extends Sabre_DAV_FSExt_Node implements Sabre_DAV_IFi * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbritrary string, but MUST be surrounded by double-quotes. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined + * + * @return string|null */ public function getETag() { @@ -66,7 +69,9 @@ class Sabre_DAV_FSExt_File extends Sabre_DAV_FSExt_Node implements Sabre_DAV_IFi * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream - */ + * + * @return string|null + */ public function getContentType() { return null; @@ -74,9 +79,9 @@ class Sabre_DAV_FSExt_File extends Sabre_DAV_FSExt_Node implements Sabre_DAV_IFi } /** - * Returns the size of the file, in bytes - * - * @return int + * Returns the size of the file, in bytes + * + * @return int */ public function getSize() { diff --git a/3rdparty/Sabre/DAV/FSExt/Node.php b/3rdparty/Sabre/DAV/FSExt/Node.php index 9e36222bfd..68ca06beb7 100644 --- a/3rdparty/Sabre/DAV/FSExt/Node.php +++ b/3rdparty/Sabre/DAV/FSExt/Node.php @@ -1,95 +1,28 @@ getResourceData(); - $locks = $resourceData['locks']; - foreach($locks as $k=>$lock) { - if (time() > $lock->timeout + $lock->created) unset($locks[$k]); - } - return $locks; - - } - - /** - * Locks this node - * - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return void - */ - function lock(Sabre_DAV_Locks_LockInfo $lockInfo) { - - // We're making the lock timeout 30 minutes - $lockInfo->timeout = 1800; - $lockInfo->created = time(); - - $resourceData = $this->getResourceData(); - if (!isset($resourceData['locks'])) $resourceData['locks'] = array(); - $current = null; - foreach($resourceData['locks'] as $k=>$lock) { - if ($lock->token === $lockInfo->token) $current = $k; - } - if (!is_null($current)) $resourceData['locks'][$current] = $lockInfo; - else $resourceData['locks'][] = $lockInfo; - - $this->putResourceData($resourceData); - - } - - /** - * Removes a lock from this node - * - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool - */ - function unlock(Sabre_DAV_Locks_LockInfo $lockInfo) { - - //throw new Sabre_DAV_Exception('bla'); - $resourceData = $this->getResourceData(); - foreach($resourceData['locks'] as $k=>$lock) { - - if ($lock->token === $lockInfo->token) { - - unset($resourceData['locks'][$k]); - $this->putResourceData($resourceData); - return true; - - } - } - return false; - - } +abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_DAV_IProperties { /** * Updates properties on this node, * - * @param array $mutations + * @param array $properties * @see Sabre_DAV_IProperties::updateProperties - * @return bool|array + * @return bool|array */ public function updateProperties($properties) { $resourceData = $this->getResourceData(); - - $result = array(); foreach($properties as $propertyName=>$propertyValue) { @@ -101,11 +34,11 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D } else { $resourceData['properties'][$propertyName] = $propertyValue; } - + } $this->putResourceData($resourceData); - return true; + return true; } /** @@ -114,8 +47,8 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D * The properties list is a list of propertynames the client requested, encoded as xmlnamespace#tagName, for example: http://www.example.org/namespace#author * If the array is empty, all properties should be returned * - * @param array $properties - * @return void + * @param array $properties + * @return array */ function getProperties($properties) { @@ -134,9 +67,9 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D } /** - * Returns the path to the resource file - * - * @return string + * Returns the path to the resource file + * + * @return string */ protected function getResourceInfoPath() { @@ -146,14 +79,14 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D } /** - * Returns all the stored resource information - * - * @return array + * Returns all the stored resource information + * + * @return array */ protected function getResourceData() { $path = $this->getResourceInfoPath(); - if (!file_exists($path)) return array('locks'=>array(), 'properties' => array()); + if (!file_exists($path)) return array('properties' => array()); // opening up the file, and creating a shared lock $handle = fopen($path,'r'); @@ -171,20 +104,19 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D // Unserializing and checking if the resource file contains data for this file $data = unserialize($data); if (!isset($data[$this->getName()])) { - return array('locks'=>array(), 'properties' => array()); + return array('properties' => array()); } $data = $data[$this->getName()]; - if (!isset($data['locks'])) $data['locks'] = array(); if (!isset($data['properties'])) $data['properties'] = array(); return $data; } /** - * Updates the resource information - * - * @param array $newData + * Updates the resource information + * + * @param array $newData * @return void */ protected function putResourceData(array $newData) { @@ -238,6 +170,9 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D } + /** + * @return bool + */ public function deleteResourceData() { // When we're deleting this node, we also need to delete any resource information @@ -264,6 +199,7 @@ abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_D fwrite($handle,serialize($data)); fclose($handle); + return true; } public function delete() { diff --git a/3rdparty/Sabre/DAV/File.php b/3rdparty/Sabre/DAV/File.php index b74bd9525b..3126bd8d36 100644 --- a/3rdparty/Sabre/DAV/File.php +++ b/3rdparty/Sabre/DAV/File.php @@ -4,12 +4,12 @@ * File class * * This is a helper class, that should aid in getting file classes setup. - * Most of its methods are implemented, and throw permission denied exceptions - * + * Most of its methods are implemented, and throw permission denied exceptions + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ abstract class Sabre_DAV_File extends Sabre_DAV_Node implements Sabre_DAV_IFile { @@ -18,33 +18,33 @@ abstract class Sabre_DAV_File extends Sabre_DAV_Node implements Sabre_DAV_IFile * Updates the data * * data is a readable stream resource. - * - * @param resource $data - * @return void + * + * @param resource $data + * @return void */ - public function put($data) { + public function put($data) { throw new Sabre_DAV_Exception_Forbidden('Permission denied to change data'); } /** - * Returns the data + * Returns the data * * This method may either return a string or a readable stream resource * - * @return mixed + * @return mixed */ - public function get() { + public function get() { throw new Sabre_DAV_Exception_Forbidden('Permission denied to read this file'); } - + /** - * Returns the size of the file, in bytes. - * - * @return int + * Returns the size of the file, in bytes. + * + * @return int */ public function getSize() { @@ -56,9 +56,11 @@ abstract class Sabre_DAV_File extends Sabre_DAV_Node implements Sabre_DAV_IFile * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbritrary string, but MUST be surrounded by double-quotes. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined + * + * @return string|null */ public function getETag() { @@ -70,7 +72,9 @@ abstract class Sabre_DAV_File extends Sabre_DAV_Node implements Sabre_DAV_IFile * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream - */ + * + * @return string|null + */ public function getContentType() { return null; diff --git a/3rdparty/Sabre/DAV/ICollection.php b/3rdparty/Sabre/DAV/ICollection.php index 0667d88899..4626038a66 100644 --- a/3rdparty/Sabre/DAV/ICollection.php +++ b/3rdparty/Sabre/DAV/ICollection.php @@ -3,54 +3,70 @@ /** * The ICollection Interface * - * This interface should be implemented by each class that represents a collection - * + * This interface should be implemented by each class that represents a collection + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_DAV_ICollection extends Sabre_DAV_INode { /** - * Creates a new file in the directory - * - * data is a readable stream resource + * Creates a new file in the directory * - * @param string $name Name of the file - * @param resource $data Initial payload - * @return void + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After succesful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string */ function createFile($name, $data = null); /** - * Creates a new subdirectory - * - * @param string $name + * Creates a new subdirectory + * + * @param string $name * @return void */ function createDirectory($name); /** - * Returns a specific child node, referenced by its name - * - * @param string $name - * @return Sabre_DAV_INode + * Returns a specific child node, referenced by its name + * + * @param string $name + * @return Sabre_DAV_INode */ function getChild($name); /** - * Returns an array with all the child nodes - * - * @return Sabre_DAV_INode[] + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] */ function getChildren(); /** - * Checks if a child-node with the specified name exists - * - * @return bool + * Checks if a child-node with the specified name exists + * + * @param string $name + * @return bool */ function childExists($name); diff --git a/3rdparty/Sabre/DAV/IExtendedCollection.php b/3rdparty/Sabre/DAV/IExtendedCollection.php index b8db1ab2f2..6ec345f9a6 100644 --- a/3rdparty/Sabre/DAV/IExtendedCollection.php +++ b/3rdparty/Sabre/DAV/IExtendedCollection.php @@ -5,11 +5,11 @@ * * This interface can be used to create special-type of collection-resources * as defined by RFC 5689. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_DAV_IExtendedCollection extends Sabre_DAV_ICollection { @@ -17,7 +17,7 @@ interface Sabre_DAV_IExtendedCollection extends Sabre_DAV_ICollection { /** * Creates a new collection * - * @param string $name + * @param string $name * @param array $resourceType * @param array $properties * @return void diff --git a/3rdparty/Sabre/DAV/IFile.php b/3rdparty/Sabre/DAV/IFile.php index 446ec86187..478f822ae7 100644 --- a/3rdparty/Sabre/DAV/IFile.php +++ b/3rdparty/Sabre/DAV/IFile.php @@ -1,34 +1,48 @@ getData($currentPath); foreach($uriLocks as $uriLock) { - // Unless we're on the leaf of the uri-tree we should ingore locks with depth 0 + // Unless we're on the leaf of the uri-tree we should ignore locks with depth 0 if($uri==$currentPath || $uriLock->depth!=0) { $uriLock->uri = $currentPath; $lockList[] = $uriLock; @@ -79,18 +81,18 @@ class Sabre_DAV_Locks_Backend_FS extends Sabre_DAV_Locks_Backend_Abstract { // Checking if we can remove any of these locks foreach($lockList as $k=>$lock) { - if (time() > $lock->timeout + $lock->created) unset($lockList[$k]); + if (time() > $lock->timeout + $lock->created) unset($lockList[$k]); } return $lockList; } /** - * Locks a uri - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool + * Locks a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ public function lock($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { @@ -109,11 +111,11 @@ class Sabre_DAV_Locks_Backend_FS extends Sabre_DAV_Locks_Backend_Abstract { } /** - * Removes a lock from a uri - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool + * Removes a lock from a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ public function unlock($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { @@ -136,7 +138,7 @@ class Sabre_DAV_Locks_Backend_FS extends Sabre_DAV_Locks_Backend_Abstract { * Returns the stored data for a uri * * @param string $uri - * @return array + * @return array */ protected function getData($uri) { @@ -167,7 +169,7 @@ class Sabre_DAV_Locks_Backend_FS extends Sabre_DAV_Locks_Backend_Abstract { * Updates the lock information * * @param string $uri - * @param array $newData + * @param array $newData * @return void */ protected function putData($uri,array $newData) { diff --git a/3rdparty/Sabre/DAV/Locks/Backend/File.php b/3rdparty/Sabre/DAV/Locks/Backend/File.php index f65b20c430..c33f963514 100644 --- a/3rdparty/Sabre/DAV/Locks/Backend/File.php +++ b/3rdparty/Sabre/DAV/Locks/Backend/File.php @@ -3,30 +3,30 @@ /** * The Lock manager allows you to handle all file-locks centrally. * - * This Lock Manager stores all its data in a single file. + * This Lock Manager stores all its data in a single file. * * Note that this is not nearly as robust as a database, you are encouraged * to use the PDO backend instead. * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Locks_Backend_File extends Sabre_DAV_Locks_Backend_Abstract { /** * The storage file - * - * @var string + * + * @var string */ private $locksFile; /** * Constructor * - * @param string $locksFile path to file + * @param string $locksFile path to file */ public function __construct($locksFile) { @@ -35,24 +35,24 @@ class Sabre_DAV_Locks_Backend_File extends Sabre_DAV_Locks_Backend_Abstract { } /** - * Returns a list of Sabre_DAV_Locks_LockInfo objects - * + * Returns a list of Sabre_DAV_Locks_LockInfo objects + * * This method should return all the locks for a particular uri, including * locks that might be set on a parent uri. * * If returnChildLocks is set to true, this method should also look for * any locks in the subtree of the uri for locks. * - * @param string $uri + * @param string $uri * @param bool $returnChildLocks - * @return array + * @return array */ public function getLocks($uri, $returnChildLocks) { $newLocks = array(); - $currentPath = ''; $locks = $this->getData(); + foreach($locks as $lock) { if ($lock->uri === $uri || @@ -70,29 +70,35 @@ class Sabre_DAV_Locks_Backend_File extends Sabre_DAV_Locks_Backend_Abstract { // Checking if we can remove any of these locks foreach($newLocks as $k=>$lock) { - if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); + if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); } return $newLocks; } /** - * Locks a uri - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool + * Locks a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ - public function lock($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { + public function lock($uri, Sabre_DAV_Locks_LockInfo $lockInfo) { // We're making the lock timeout 30 minutes $lockInfo->timeout = 1800; $lockInfo->created = time(); $lockInfo->uri = $uri; - $locks = $this->getLocks($uri,false); + $locks = $this->getData(); + foreach($locks as $k=>$lock) { - if ($lock->token == $lockInfo->token) unset($locks[$k]); + if ( + ($lock->token == $lockInfo->token) || + (time() > $lock->timeout + $lock->created) + ) { + unset($locks[$k]); + } } $locks[] = $lockInfo; $this->putData($locks); @@ -101,15 +107,15 @@ class Sabre_DAV_Locks_Backend_File extends Sabre_DAV_Locks_Backend_Abstract { } /** - * Removes a lock from a uri - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool + * Removes a lock from a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ - public function unlock($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { + public function unlock($uri, Sabre_DAV_Locks_LockInfo $lockInfo) { - $locks = $this->getLocks($uri,false); + $locks = $this->getData(); foreach($locks as $k=>$lock) { if ($lock->token == $lockInfo->token) { @@ -127,7 +133,7 @@ class Sabre_DAV_Locks_Backend_File extends Sabre_DAV_Locks_Backend_Abstract { /** * Loads the lockdata from the filesystem. * - * @return array + * @return array */ protected function getData() { @@ -153,7 +159,7 @@ class Sabre_DAV_Locks_Backend_File extends Sabre_DAV_Locks_Backend_Abstract { /** * Saves the lockdata * - * @param array $newData + * @param array $newData * @return void */ protected function putData(array $newData) { diff --git a/3rdparty/Sabre/DAV/Locks/Backend/PDO.php b/3rdparty/Sabre/DAV/Locks/Backend/PDO.php index c3923af19d..acce80638e 100644 --- a/3rdparty/Sabre/DAV/Locks/Backend/PDO.php +++ b/3rdparty/Sabre/DAV/Locks/Backend/PDO.php @@ -5,34 +5,34 @@ * * This Lock Manager stores all its data in a database. You must pass a PDO * connection object in the constructor. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Locks_Backend_PDO extends Sabre_DAV_Locks_Backend_Abstract { /** - * The PDO connection object - * - * @var pdo + * The PDO connection object + * + * @var pdo */ private $pdo; /** - * The PDO tablename this backend uses. - * + * The PDO tablename this backend uses. + * * @var string */ protected $tableName; /** - * Constructor - * + * Constructor + * * @param PDO $pdo - * @param string $tableName + * @param string $tableName */ public function __construct(PDO $pdo, $tableName = 'locks') { @@ -42,24 +42,24 @@ class Sabre_DAV_Locks_Backend_PDO extends Sabre_DAV_Locks_Backend_Abstract { } /** - * Returns a list of Sabre_DAV_Locks_LockInfo objects - * + * Returns a list of Sabre_DAV_Locks_LockInfo objects + * * This method should return all the locks for a particular uri, including * locks that might be set on a parent uri. * * If returnChildLocks is set to true, this method should also look for * any locks in the subtree of the uri for locks. * - * @param string $uri + * @param string $uri * @param bool $returnChildLocks - * @return array + * @return array */ public function getLocks($uri, $returnChildLocks) { - // NOTE: the following 10 lines or so could be easily replaced by - // pure sql. MySQL's non-standard string concatination prevents us + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatenation prevents us // from doing this though. - $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM `'.$this->tableName.'` WHERE ((created + timeout) > CAST(? AS UNSIGNED INTEGER)) AND ((uri = ?)'; + $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE ((created + timeout) > CAST(? AS UNSIGNED INTEGER)) AND ((uri = ?)'; $params = array(time(),$uri); // We need to check locks for every part in the uri. @@ -112,11 +112,11 @@ class Sabre_DAV_Locks_Backend_PDO extends Sabre_DAV_Locks_Backend_Abstract { } /** - * Locks a uri - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool + * Locks a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ public function lock($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { @@ -127,15 +127,15 @@ class Sabre_DAV_Locks_Backend_PDO extends Sabre_DAV_Locks_Backend_Abstract { $locks = $this->getLocks($uri,false); $exists = false; - foreach($locks as $k=>$lock) { + foreach($locks as $lock) { if ($lock->token == $lockInfo->token) $exists = true; } - + if ($exists) { - $stmt = $this->pdo->prepare('UPDATE `'.$this->tableName.'` SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?'); + $stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?'); $stmt->execute(array($lockInfo->owner,$lockInfo->timeout,$lockInfo->scope,$lockInfo->depth,$uri,$lockInfo->created,$lockInfo->token)); } else { - $stmt = $this->pdo->prepare('INSERT INTO `'.$this->tableName.'` (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)'); + $stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)'); $stmt->execute(array($lockInfo->owner,$lockInfo->timeout,$lockInfo->scope,$lockInfo->depth,$uri,$lockInfo->created,$lockInfo->token)); } @@ -146,15 +146,15 @@ class Sabre_DAV_Locks_Backend_PDO extends Sabre_DAV_Locks_Backend_Abstract { /** - * Removes a lock from a uri - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return bool + * Removes a lock from a uri + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ public function unlock($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { - $stmt = $this->pdo->prepare('DELETE FROM `'.$this->tableName.'` WHERE uri = ? AND token = ?'); + $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?'); $stmt->execute(array($uri,$lockInfo->token)); return $stmt->rowCount()===1; diff --git a/3rdparty/Sabre/DAV/Locks/LockInfo.php b/3rdparty/Sabre/DAV/Locks/LockInfo.php index 6a064466f4..9df014a428 100644 --- a/3rdparty/Sabre/DAV/Locks/LockInfo.php +++ b/3rdparty/Sabre/DAV/Locks/LockInfo.php @@ -5,11 +5,11 @@ * * An object of the LockInfo class holds all the information relevant to a * single lock. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Locks_LockInfo { @@ -30,37 +30,37 @@ class Sabre_DAV_Locks_LockInfo { const TIMEOUT_INFINITE = -1; /** - * The owner of the lock - * - * @var string + * The owner of the lock + * + * @var string */ public $owner; /** - * The locktoken - * - * @var string + * The locktoken + * + * @var string */ public $token; /** - * How long till the lock is expiring - * - * @var int + * How long till the lock is expiring + * + * @var int */ public $timeout; /** - * UNIX Timestamp of when this lock was created - * - * @var int + * UNIX Timestamp of when this lock was created + * + * @var int */ public $created; /** - * Exclusive or shared lock - * - * @var int + * Exclusive or shared lock + * + * @var int */ public $scope = self::EXCLUSIVE; @@ -72,7 +72,7 @@ class Sabre_DAV_Locks_LockInfo { /** * The uri this lock locks * - * TODO: This value is not always set + * TODO: This value is not always set * @var mixed */ public $uri; diff --git a/3rdparty/Sabre/DAV/Locks/Plugin.php b/3rdparty/Sabre/DAV/Locks/Plugin.php index 461e2847e0..fd956950b8 100644 --- a/3rdparty/Sabre/DAV/Locks/Plugin.php +++ b/3rdparty/Sabre/DAV/Locks/Plugin.php @@ -9,38 +9,37 @@ * $lockBackend = new Sabre_DAV_Locks_Backend_File('./mylockdb'); * $lockPlugin = new Sabre_DAV_Locks_Plugin($lockBackend); * $server->addPlugin($lockPlugin); - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { /** - * locksBackend - * - * @var Sabre_DAV_Locks_Backend_Abstract + * locksBackend + * + * @var Sabre_DAV_Locks_Backend_Abstract */ private $locksBackend; /** * server - * - * @var Sabre_DAV_Server + * + * @var Sabre_DAV_Server */ private $server; /** - * __construct - * - * @param Sabre_DAV_Locks_Backend_Abstract $locksBackend - * @return void + * __construct + * + * @param Sabre_DAV_Locks_Backend_Abstract $locksBackend */ public function __construct(Sabre_DAV_Locks_Backend_Abstract $locksBackend = null) { - $this->locksBackend = $locksBackend; + $this->locksBackend = $locksBackend; } @@ -48,8 +47,8 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * Initializes the plugin * * This method is automatically called by the Server class after addPlugin. - * - * @param Sabre_DAV_Server $server + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { @@ -63,11 +62,11 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns a plugin name. - * + * * Using this name other plugins will be able to access other plugins - * using Sabre_DAV_Server::getPlugin - * - * @return string + * using Sabre_DAV_Server::getPlugin + * + * @return string */ public function getPluginName() { @@ -76,20 +75,21 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { } /** - * This method is called by the Server if the user used an HTTP method + * This method is called by the Server if the user used an HTTP method * the server didn't recognize. * * This plugin intercepts the LOCK and UNLOCK methods. - * - * @param string $method - * @return bool + * + * @param string $method + * @param string $uri + * @return bool */ public function unknownMethod($method, $uri) { - switch($method) { + switch($method) { - case 'LOCK' : $this->httpLock($uri); return false; - case 'UNLOCK' : $this->httpUnlock($uri); return false; + case 'LOCK' : $this->httpLock($uri); return false; + case 'UNLOCK' : $this->httpUnlock($uri); return false; } @@ -98,26 +98,20 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { /** * This method is called after most properties have been found * it allows us to add in any Lock-related properties - * - * @param string $path - * @param array $properties - * @return bool + * + * @param string $path + * @param array $newProperties + * @return bool */ - public function afterGetProperties($path,&$newProperties) { + public function afterGetProperties($path, &$newProperties) { foreach($newProperties[404] as $propName=>$discard) { - $node = null; - switch($propName) { case '{DAV:}supportedlock' : $val = false; if ($this->locksBackend) $val = true; - else { - if (!$node) $node = $this->server->tree->getNodeForPath($path); - if ($node instanceof Sabre_DAV_ILockable) $val = true; - } $newProperties[200][$propName] = new Sabre_DAV_Property_SupportedLock($val); unset($newProperties[404][$propName]); break; @@ -141,10 +135,10 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * handled. * * This plugin uses that feature to intercept access to locked resources. - * + * * @param string $method * @param string $uri - * @return bool + * @return bool */ public function beforeMethod($method, $uri) { @@ -187,18 +181,17 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * Use this method to tell the server this plugin defines additional * HTTP methods. * - * This method is passed a uri. It should only return HTTP methods that are + * This method is passed a uri. It should only return HTTP methods that are * available for the specified uri. * * @param string $uri - * @return array + * @return array */ public function getHTTPMethods($uri) { - if ($this->locksBackend || - $this->server->tree->getNodeForPath($uri) instanceof Sabre_DAV_ILocks) { + if ($this->locksBackend) return array('LOCK','UNLOCK'); - } + return array(); } @@ -208,8 +201,8 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * * In this case this is only the number 2. The 2 in the Dav: header * indicates the server supports locks. - * - * @return array + * + * @return array */ public function getFeatures() { @@ -218,49 +211,23 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Returns all lock information on a particular uri - * + * Returns all lock information on a particular uri + * * This function should return an array with Sabre_DAV_Locks_LockInfo objects. If there are no locks on a file, return an empty array. * - * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree + * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object * for any possible locks and return those as well. * - * @param string $uri + * @param string $uri * @param bool $returnChildLocks - * @return array + * @return array */ public function getLocks($uri, $returnChildLocks = false) { $lockList = array(); - $currentPath = ''; - foreach(explode('/',$uri) as $uriPart) { - $uriLocks = array(); - if ($currentPath) $currentPath.='/'; - $currentPath.=$uriPart; - - try { - - $node = $this->server->tree->getNodeForPath($currentPath); - if ($node instanceof Sabre_DAV_ILockable) $uriLocks = $node->getLocks(); - - } catch (Sabre_DAV_Exception_FileNotFound $e){ - // In case the node didn't exist, this could be a lock-null request - } - - foreach($uriLocks as $uriLock) { - - // Unless we're on the leaf of the uri-tree we should ignore locks with depth 0 - if($uri==$currentPath || $uriLock->depth!=0) { - $uriLock->uri = $currentPath; - $lockList[] = $uriLock; - } - - } - - } - if ($this->locksBackend) + if ($this->locksBackend) $lockList = array_merge($lockList,$this->locksBackend->getLocks($uri, $returnChildLocks)); return $lockList; @@ -271,13 +238,13 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * Locks an uri * * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock - * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type + * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type * of lock (shared or exclusive) and the owner of the lock * * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock * - * Additionally, a lock can be requested for a non-existant file. In these case we're obligated to create an empty file as per RFC4918:S7.3 - * + * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3 + * * @param string $uri * @return void */ @@ -297,7 +264,7 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { if ($body = $this->server->httpRequest->getBody(true)) { // This is a new lock request $lockInfo = $this->parseLockRequest($body); - $lockInfo->depth = $this->server->getHTTPDepth(); + $lockInfo->depth = $this->server->getHTTPDepth(); $lockInfo->uri = $uri; if($lastLock && $lockInfo->scope != Sabre_DAV_Locks_LockInfo::SHARED) throw new Sabre_DAV_Exception_ConflictingLock($lastLock); @@ -306,11 +273,11 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { // This must have been a lock refresh $lockInfo = $lastLock; - // The resource could have been locked through another uri. + // The resource could have been locked through another uri. if ($uri!=$lockInfo->uri) $uri = $lockInfo->uri; } else { - + // There was neither a lock refresh nor a new lock request throw new Sabre_DAV_Exception_BadRequest('An xml body is required for lock requests'); @@ -322,16 +289,16 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first try { - $node = $this->server->tree->getNodeForPath($uri); - + $this->server->tree->getNodeForPath($uri); + // We need to call the beforeWriteContent event for RFC3744 $this->server->broadcastEvent('beforeWriteContent',array($uri)); - } catch (Sabre_DAV_Exception_FileNotFound $e) { - + } catch (Sabre_DAV_Exception_NotFound $e) { + // It didn't, lets create it - $this->server->createFile($uri,fopen('php://memory','r')); - $newFile = true; + $this->server->createFile($uri,fopen('php://memory','r')); + $newFile = true; } @@ -362,7 +329,7 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { $locks = $this->getLocks($uri); - // Windows sometimes forgets to include < and > in the Lock-Token + // Windows sometimes forgets to include < and > in the Lock-Token // header if ($lockToken[0]!=='<') $lockToken = '<' . $lockToken . '>'; @@ -370,7 +337,6 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { if ('token . '>' == $lockToken) { - $this->server->broadcastEvent('beforeUnlock',array($uri, $lock)); $this->unlockNode($uri,$lock); $this->server->httpResponse->setHeader('Content-Length','0'); $this->server->httpResponse->sendStatus(204); @@ -390,21 +356,15 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return void + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ public function lockNode($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { if (!$this->server->broadcastEvent('beforeLock',array($uri,$lockInfo))) return; - try { - $node = $this->server->tree->getNodeForPath($uri); - if ($node instanceof Sabre_DAV_ILockable) return $node->lock($lockInfo); - } catch (Sabre_DAV_Exception_FileNotFound $e) { - // In case the node didn't exist, this could be a lock-null request - } if ($this->locksBackend) return $this->locksBackend->lock($uri,$lockInfo); throw new Sabre_DAV_Exception_MethodNotAllowed('Locking support is not enabled for this resource. No Locking backend was found so if you didn\'t expect this error, please check your configuration.'); @@ -414,29 +374,22 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * Unlocks a uri * * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified - * - * @param string $uri - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return void + * + * @param string $uri + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return bool */ public function unlockNode($uri,Sabre_DAV_Locks_LockInfo $lockInfo) { if (!$this->server->broadcastEvent('beforeUnlock',array($uri,$lockInfo))) return; - try { - $node = $this->server->tree->getNodeForPath($uri); - if ($node instanceof Sabre_DAV_ILockable) return $node->unlock($lockInfo); - } catch (Sabre_DAV_Exception_FileNotFound $e) { - // In case the node didn't exist, this could be a lock-null request - } - if ($this->locksBackend) return $this->locksBackend->unlock($uri,$lockInfo); } /** - * Returns the contents of the HTTP Timeout header. - * + * Returns the contents of the HTTP Timeout header. + * * The method formats the header into an integer. * * @return int @@ -444,7 +397,7 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { public function getTimeoutHeader() { $header = $this->server->httpRequest->getHeader('Timeout'); - + if ($header) { if (stripos($header,'second-')===0) $header = (int)(substr($header,7)); @@ -462,16 +415,16 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Generates the response for successfull LOCK requests - * - * @param Sabre_DAV_Locks_LockInfo $lockInfo - * @return string + * Generates the response for successful LOCK requests + * + * @param Sabre_DAV_Locks_LockInfo $lockInfo + * @return string */ protected function generateLockResponse(Sabre_DAV_Locks_LockInfo $lockInfo) { $dom = new DOMDocument('1.0','utf-8'); $dom->formatOutput = true; - + $prop = $dom->createElementNS('DAV:','d:prop'); $dom->appendChild($prop); @@ -484,10 +437,10 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { return $dom->saveXML(); } - + /** * validateLock should be called when a write operation is about to happen - * It will check if the requested url is locked, and see if the correct lock tokens are passed + * It will check if the requested url is locked, and see if the correct lock tokens are passed * * @param mixed $urls List of relevant urls. Can be an array, a string or nothing at all for the current request uri * @param mixed $lastLock This variable will be populated with the last checked lock object (Sabre_DAV_Locks_LockInfo) @@ -511,13 +464,13 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { $locks = $this->getLocks($url, $checkChildLocks); - // If there were no conditions, but there were locks, we fail + // If there were no conditions, but there were locks, we fail if (!$conditions && $locks) { reset($locks); $lastLock = current($locks); return false; } - + // If there were no locks or conditions, we go to the next url if (!$locks && !$conditions) continue; @@ -542,7 +495,7 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { // key 2 can contain an etag if ($conditionToken[2]) { - $uri = $conditionUri?$conditionUri:$this->server->getRequestUri(); + $uri = $conditionUri?$conditionUri:$this->server->getRequestUri(); $node = $this->server->tree->getNodeForPath($uri); $etagValid = $node->getETag()==$conditionToken[2]; @@ -609,23 +562,23 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { * This method is created to extract information from the WebDAV HTTP 'If:' header * * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information - * The function will return an array, containg structs with the following keys + * The function will return an array, containing structs with the following keys * - * * uri - the uri the condition applies to. If this is returned as an + * * uri - the uri the condition applies to. If this is returned as an * empty string, this implies it's referring to the request url. - * * tokens - The lock token. another 2 dimensional array containg 2 elements (0 = true/false.. If this is a negative condition its set to false, 1 = the actual token) + * * tokens - The lock token. another 2 dimensional array containing 2 elements (0 = true/false.. If this is a negative condition its set to false, 1 = the actual token) * * etag - an etag, if supplied - * - * @return void + * + * @return array */ public function getIfConditions() { - $header = $this->server->httpRequest->getHeader('If'); + $header = $this->server->httpRequest->getHeader('If'); if (!$header) return array(); $matches = array(); - $regex = '/(?:\<(?P.*?)\>\s)?\((?PNot\s)?(?:\<(?P[^\>]*)\>)?(?:\s?)(?:\[(?P[^\]]*)\])?\)/im'; + $regex = '/(?:\<(?P.*?)\>\s)?\((?PNot\s)?(?:\<(?P[^\>]*)\>)?(?:\s?)(?:\[(?P[^\]]*)\])?\)/im'; preg_match_all($regex,$header,$matches,PREG_SET_ORDER); $conditions = array(); @@ -636,7 +589,7 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { 'uri' => $match['uri'], 'tokens' => array( array($match['not']?0:1,$match['token'],isset($match['etag'])?$match['etag']:'') - ), + ), ); if (!$condition['uri'] && count($conditions)) $conditions[count($conditions)-1]['tokens'][] = array( @@ -655,9 +608,9 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Parses a webdav lock xml body, and returns a new Sabre_DAV_Locks_LockInfo object - * - * @param string $body + * Parses a webdav lock xml body, and returns a new Sabre_DAV_Locks_LockInfo object + * + * @param string $body * @return Sabre_DAV_Locks_LockInfo */ protected function parseLockRequest($body) { diff --git a/3rdparty/Sabre/DAV/Mount/Plugin.php b/3rdparty/Sabre/DAV/Mount/Plugin.php index f93a1aa25a..b37a90ae99 100644 --- a/3rdparty/Sabre/DAV/Mount/Plugin.php +++ b/3rdparty/Sabre/DAV/Mount/Plugin.php @@ -4,25 +4,25 @@ * This plugin provides support for RFC4709: Mounting WebDAV servers * * Simply append ?mount to any collection to generate the davmount response. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) */ class Sabre_DAV_Mount_Plugin extends Sabre_DAV_ServerPlugin { /** - * Reference to Server class - * - * @var Sabre_DAV_Server + * Reference to Server class + * + * @var Sabre_DAV_Server */ private $server; /** - * Initializes the plugin and registers event handles - * - * @param Sabre_DAV_Server $server + * Initializes the plugin and registers event handles + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { @@ -35,9 +35,10 @@ class Sabre_DAV_Mount_Plugin extends Sabre_DAV_ServerPlugin { /** * 'beforeMethod' event handles. This event handles intercepts GET requests ending * with ?mount - * - * @param string $method - * @return void + * + * @param string $method + * @param string $uri + * @return bool */ public function beforeMethod($method, $uri) { @@ -57,13 +58,13 @@ class Sabre_DAV_Mount_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Generates the davmount response - * - * @param string $uri absolute uri + * Generates the davmount response + * + * @param string $uri absolute uri * @return void */ public function davMount($uri) { - + $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->setHeader('Content-Type','application/davmount+xml'); ob_start(); diff --git a/3rdparty/Sabre/DAV/Node.php b/3rdparty/Sabre/DAV/Node.php index 0510df5fdf..070b7176af 100644 --- a/3rdparty/Sabre/DAV/Node.php +++ b/3rdparty/Sabre/DAV/Node.php @@ -3,22 +3,22 @@ /** * Node class * - * This is a helper class, that should aid in getting nodes setup. - * + * This is a helper class, that should aid in getting nodes setup. + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ abstract class Sabre_DAV_Node implements Sabre_DAV_INode { /** - * Returns the last modification time + * Returns the last modification time * * In this case, it will simply return the current time * - * @return int + * @return int */ public function getLastModified() { @@ -30,7 +30,7 @@ abstract class Sabre_DAV_Node implements Sabre_DAV_INode { * Deleted the current node * * @throws Sabre_DAV_Exception_Forbidden - * @return void + * @return void */ public function delete() { @@ -40,7 +40,7 @@ abstract class Sabre_DAV_Node implements Sabre_DAV_INode { /** * Renames the node - * + * * @throws Sabre_DAV_Exception_Forbidden * @param string $name The new name * @return void diff --git a/3rdparty/Sabre/DAV/ObjectTree.php b/3rdparty/Sabre/DAV/ObjectTree.php index f12a368370..bce5146390 100644 --- a/3rdparty/Sabre/DAV/ObjectTree.php +++ b/3rdparty/Sabre/DAV/ObjectTree.php @@ -3,27 +3,27 @@ /** * ObjectTree class * - * This implementation of the Tree class makes use of the INode, IFile and ICollection API's - * + * This implementation of the Tree class makes use of the INode, IFile and ICollection API's + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { /** - * The root node - * + * The root node + * * @var Sabre_DAV_ICollection */ protected $rootNode; /** - * This is the node cache. Accessed nodes are stored here - * - * @var array + * This is the node cache. Accessed nodes are stored here + * + * @var array */ protected $cache = array(); @@ -31,9 +31,8 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { * Creates the object * * This method expects the rootObject to be passed as a parameter - * - * @param Sabre_DAV_ICollection $rootNode - * @return void + * + * @param Sabre_DAV_ICollection $rootNode */ public function __construct(Sabre_DAV_ICollection $rootNode) { @@ -42,10 +41,10 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { } /** - * Returns the INode object for the requested path - * - * @param string $path - * @return Sabre_DAV_INode + * Returns the INode object for the requested path + * + * @param string $path + * @return Sabre_DAV_INode */ public function getNodeForPath($path) { @@ -54,17 +53,17 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { //if (!$path || $path=='.') return $this->rootNode; $currentNode = $this->rootNode; - $i=0; - // We're splitting up the path variable into folder/subfolder components and traverse to the correct node.. + + // We're splitting up the path variable into folder/subfolder components and traverse to the correct node.. foreach(explode('/',$path) as $pathPart) { // If this part of the path is just a dot, it actually means we can skip it if ($pathPart=='.' || $pathPart=='') continue; if (!($currentNode instanceof Sabre_DAV_ICollection)) - throw new Sabre_DAV_Exception_FileNotFound('Could not find node at path: ' . $path); + throw new Sabre_DAV_Exception_NotFound('Could not find node at path: ' . $path); - $currentNode = $currentNode->getChild($pathPart); + $currentNode = $currentNode->getChild($pathPart); } @@ -76,8 +75,8 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { /** * This function allows you to check if a node exists. * - * @param string $path - * @return bool + * @param string $path + * @return bool */ public function nodeExists($path) { @@ -92,7 +91,7 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { if (!$parentNode instanceof Sabre_DAV_ICollection) return false; return $parentNode->childExists($base); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { return false; @@ -101,10 +100,10 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { } /** - * Returns a list of childnodes for a given path. - * - * @param string $path - * @return array + * Returns a list of childnodes for a given path. + * + * @param string $path + * @return array */ public function getChildren($path) { @@ -127,14 +126,14 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { * * node creations * * copy * * move - * * renaming nodes - * + * * renaming nodes + * * If Tree classes implement a form of caching, this will allow * them to make sure caches will be expired. - * + * * If a path is passed, it is assumed that the entire subtree is dirty * - * @param string $path + * @param string $path * @return void */ public function markDirty($path) { @@ -145,7 +144,7 @@ class Sabre_DAV_ObjectTree extends Sabre_DAV_Tree { foreach($this->cache as $nodePath=>$node) { if ($nodePath == $path || strpos($nodePath,$path.'/')===0) unset($this->cache[$nodePath]); - + } } diff --git a/3rdparty/Sabre/DAV/Property.php b/3rdparty/Sabre/DAV/Property.php index 577535b012..1cfada3236 100644 --- a/3rdparty/Sabre/DAV/Property.php +++ b/3rdparty/Sabre/DAV/Property.php @@ -4,16 +4,16 @@ * Abstract property class * * Extend this class to create custom complex properties - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ abstract class Sabre_DAV_Property { - abstract function serialize(Sabre_DAV_Server $server, DOMElement $prop); + abstract function serialize(Sabre_DAV_Server $server, DOMElement $prop); static function unserialize(DOMElement $prop) { diff --git a/3rdparty/Sabre/DAV/Property/GetLastModified.php b/3rdparty/Sabre/DAV/Property/GetLastModified.php index 4a81262997..bd63f57314 100644 --- a/3rdparty/Sabre/DAV/Property/GetLastModified.php +++ b/3rdparty/Sabre/DAV/Property/GetLastModified.php @@ -2,33 +2,32 @@ /** * This property represents the {DAV:}getlastmodified property. - * + * * Although this is normally a simple property, windows requires us to add * some new attributes. * - * This class uses unix timestamps internally, and converts them to RFC 1123 times for + * This class uses unix timestamps internally, and converts them to RFC 1123 times for * serialization * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Property_GetLastModified extends Sabre_DAV_Property { /** - * time - * - * @var int + * time + * + * @var int */ public $time; /** - * __construct - * - * @param int|DateTime $time - * @return void + * __construct + * + * @param int|DateTime $time */ public function __construct($time) { @@ -46,9 +45,10 @@ class Sabre_DAV_Property_GetLastModified extends Sabre_DAV_Property { } /** - * serialize - * - * @param DOMElement $prop + * serialize + * + * @param Sabre_DAV_Server $server + * @param DOMElement $prop * @return void */ public function serialize(Sabre_DAV_Server $server, DOMElement $prop) { @@ -56,14 +56,14 @@ class Sabre_DAV_Property_GetLastModified extends Sabre_DAV_Property { $doc = $prop->ownerDocument; $prop->setAttribute('xmlns:b','urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/'); $prop->setAttribute('b:dt','dateTime.rfc1123'); - $prop->nodeValue = $this->time->format(DateTime::RFC1123); + $prop->nodeValue = Sabre_HTTP_Util::toHTTPDate($this->time); } /** - * getTime - * - * @return DateTime + * getTime + * + * @return DateTime */ public function getTime() { diff --git a/3rdparty/Sabre/DAV/Property/Href.php b/3rdparty/Sabre/DAV/Property/Href.php index 3294ff2ac6..dac564f24d 100644 --- a/3rdparty/Sabre/DAV/Property/Href.php +++ b/3rdparty/Sabre/DAV/Property/Href.php @@ -3,36 +3,36 @@ /** * Href property * - * The href property represpents a url within a {DAV:}href element. + * The href property represents a url within a {DAV:}href element. * This is used by many WebDAV extensions, but not really within the WebDAV core spec - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Property_Href extends Sabre_DAV_Property implements Sabre_DAV_Property_IHref { /** - * href - * - * @var string + * href + * + * @var string */ private $href; /** - * Automatically prefix the url with the server base directory - * - * @var bool + * Automatically prefix the url with the server base directory + * + * @var bool */ private $autoPrefix = true; /** - * __construct - * - * @param string $href - * @return void + * __construct + * + * @param string $href + * @param bool $autoPrefix */ public function __construct($href, $autoPrefix = true) { @@ -42,9 +42,9 @@ class Sabre_DAV_Property_Href extends Sabre_DAV_Property implements Sabre_DAV_Pr } /** - * Returns the uri - * - * @return string + * Returns the uri + * + * @return string */ public function getHref() { @@ -56,12 +56,12 @@ class Sabre_DAV_Property_Href extends Sabre_DAV_Property implements Sabre_DAV_Pr * Serializes this property. * * It will additionally prepend the href property with the server's base uri. - * - * @param Sabre_DAV_Server $server - * @param DOMElement $dom + * + * @param Sabre_DAV_Server $server + * @param DOMElement $dom * @return void */ - public function serialize(Sabre_DAV_Server $server,DOMElement $dom) { + public function serialize(Sabre_DAV_Server $server, DOMElement $dom) { $prefix = $server->xmlNamespaces['DAV:']; @@ -72,13 +72,13 @@ class Sabre_DAV_Property_Href extends Sabre_DAV_Property implements Sabre_DAV_Pr } /** - * Unserializes this property from a DOM Element + * Unserializes this property from a DOM Element * * This method returns an instance of this class. * It will only decode {DAV:}href values. For non-compatible elements null will be returned. * - * @param DOMElement $dom - * @return Sabre_DAV_Property_Href + * @param DOMElement $dom + * @return Sabre_DAV_Property_Href */ static function unserialize(DOMElement $dom) { @@ -86,6 +86,6 @@ class Sabre_DAV_Property_Href extends Sabre_DAV_Property implements Sabre_DAV_Pr return new self($dom->firstChild->textContent,false); } - } + } } diff --git a/3rdparty/Sabre/DAV/Property/HrefList.php b/3rdparty/Sabre/DAV/Property/HrefList.php index 76a5512901..7a52272e88 100644 --- a/3rdparty/Sabre/DAV/Property/HrefList.php +++ b/3rdparty/Sabre/DAV/Property/HrefList.php @@ -3,35 +3,35 @@ /** * HrefList property * - * This property contains multiple {DAV:}href elements, each containing a url. - * + * This property contains multiple {DAV:}href elements, each containing a url. + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Property_HrefList extends Sabre_DAV_Property { /** - * hrefs - * - * @var array + * hrefs + * + * @var array */ private $hrefs; /** - * Automatically prefix the url with the server base directory - * - * @var bool + * Automatically prefix the url with the server base directory + * + * @var bool */ private $autoPrefix = true; /** - * __construct - * + * __construct + * * @param array $hrefs - * @param bool $autoPrefix + * @param bool $autoPrefix */ public function __construct(array $hrefs, $autoPrefix = true) { @@ -41,9 +41,9 @@ class Sabre_DAV_Property_HrefList extends Sabre_DAV_Property { } /** - * Returns the uris - * - * @return array + * Returns the uris + * + * @return array */ public function getHrefs() { @@ -55,9 +55,9 @@ class Sabre_DAV_Property_HrefList extends Sabre_DAV_Property { * Serializes this property. * * It will additionally prepend the href property with the server's base uri. - * - * @param Sabre_DAV_Server $server - * @param DOMElement $dom + * + * @param Sabre_DAV_Server $server + * @param DOMElement $dom * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $dom) { @@ -73,13 +73,13 @@ class Sabre_DAV_Property_HrefList extends Sabre_DAV_Property { } /** - * Unserializes this property from a DOM Element + * Unserializes this property from a DOM Element * * This method returns an instance of this class. * It will only decode {DAV:}href values. * - * @param DOMElement $dom - * @return Sabre_DAV_Property_Href + * @param DOMElement $dom + * @return Sabre_DAV_Property_Href */ static function unserialize(DOMElement $dom) { @@ -91,6 +91,6 @@ class Sabre_DAV_Property_HrefList extends Sabre_DAV_Property { } return new self($hrefs, false); - } + } } diff --git a/3rdparty/Sabre/DAV/Property/IHref.php b/3rdparty/Sabre/DAV/Property/IHref.php index 29d76a44fc..5c0409064c 100644 --- a/3rdparty/Sabre/DAV/Property/IHref.php +++ b/3rdparty/Sabre/DAV/Property/IHref.php @@ -9,16 +9,16 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_DAV_Property_IHref { /** - * getHref - * - * @return string + * getHref + * + * @return string */ function getHref(); diff --git a/3rdparty/Sabre/DAV/Property/LockDiscovery.php b/3rdparty/Sabre/DAV/Property/LockDiscovery.php index 05c7470b4e..2ded5649a4 100644 --- a/3rdparty/Sabre/DAV/Property/LockDiscovery.php +++ b/3rdparty/Sabre/DAV/Property/LockDiscovery.php @@ -4,26 +4,26 @@ * Represents {DAV:}lockdiscovery property * * This property contains all the open locks on a given resource - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Property_LockDiscovery extends Sabre_DAV_Property { /** - * locks - * - * @var array + * locks + * + * @var array */ public $locks; - + /** - * Should we show the locktoken as well? - * - * @var bool + * Should we show the locktoken as well? + * + * @var bool */ public $revealLockToken; @@ -36,13 +36,12 @@ class Sabre_DAV_Property_LockDiscovery extends Sabre_DAV_Property { static public $hideLockRoot = false; /** - * __construct - * - * @param array $locks - * @param bool $revealLockToken - * @return void + * __construct + * + * @param array $locks + * @param bool $revealLockToken */ - public function __construct($locks,$revealLockToken = false) { + public function __construct($locks, $revealLockToken = false) { $this->locks = $locks; $this->revealLockToken = $revealLockToken; @@ -50,12 +49,13 @@ class Sabre_DAV_Property_LockDiscovery extends Sabre_DAV_Property { } /** - * serialize - * - * @param DOMElement $prop + * serialize + * + * @param Sabre_DAV_Server $server + * @param DOMElement $prop * @return void */ - public function serialize(Sabre_DAV_Server $server,DOMElement $prop) { + public function serialize(Sabre_DAV_Server $server, DOMElement $prop) { $doc = $prop->ownerDocument; @@ -74,7 +74,7 @@ class Sabre_DAV_Property_LockDiscovery extends Sabre_DAV_Property { $lockType->appendChild($doc->createElementNS('DAV:','d:write')); - /* {DAV:}lockroot */ + /* {DAV:}lockroot */ if (!self::$hideLockRoot) { $lockRoot = $doc->createElementNS('DAV:','d:lockroot'); $activeLock->appendChild($lockRoot); @@ -91,7 +91,7 @@ class Sabre_DAV_Property_LockDiscovery extends Sabre_DAV_Property { $activeLock->appendChild($lockToken); $lockToken->appendChild($doc->createElementNS('DAV:','d:href','opaquelocktoken:' . $lock->token)); } - + $activeLock->appendChild($doc->createElementNS('DAV:','d:owner',$lock->owner)); } diff --git a/3rdparty/Sabre/DAV/Property/ResourceType.php b/3rdparty/Sabre/DAV/Property/ResourceType.php index 2c606c22d6..f6269611e5 100644 --- a/3rdparty/Sabre/DAV/Property/ResourceType.php +++ b/3rdparty/Sabre/DAV/Property/ResourceType.php @@ -4,28 +4,27 @@ * This class represents the {DAV:}resourcetype property * * Normally for files this is empty, and for collection {DAV:}collection. - * However, other specs define different values for this. - * + * However, other specs define different values for this. + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Property_ResourceType extends Sabre_DAV_Property { /** - * resourceType - * + * resourceType + * * @var array */ public $resourceType = array(); /** - * __construct - * - * @param mixed $resourceType - * @return void + * __construct + * + * @param mixed $resourceType */ public function __construct($resourceType = array()) { @@ -33,7 +32,7 @@ class Sabre_DAV_Property_ResourceType extends Sabre_DAV_Property { $this->resourceType = array(); elseif ($resourceType === Sabre_DAV_Server::NODE_DIRECTORY) $this->resourceType = array('{DAV:}collection'); - elseif (is_array($resourceType)) + elseif (is_array($resourceType)) $this->resourceType = $resourceType; else $this->resourceType = array($resourceType); @@ -41,25 +40,26 @@ class Sabre_DAV_Property_ResourceType extends Sabre_DAV_Property { } /** - * serialize - * - * @param DOMElement $prop + * serialize + * + * @param Sabre_DAV_Server $server + * @param DOMElement $prop * @return void */ - public function serialize(Sabre_DAV_Server $server,DOMElement $prop) { + public function serialize(Sabre_DAV_Server $server, DOMElement $prop) { $propName = null; $rt = $this->resourceType; - + foreach($rt as $resourceType) { - if (preg_match('/^{([^}]*)}(.*)$/',$resourceType,$propName)) { - + if (preg_match('/^{([^}]*)}(.*)$/',$resourceType,$propName)) { + if (isset($server->xmlNamespaces[$propName[1]])) { $prop->appendChild($prop->ownerDocument->createElement($server->xmlNamespaces[$propName[1]] . ':' . $propName[2])); } else { $prop->appendChild($prop->ownerDocument->createElementNS($propName[1],'custom:' . $propName[2])); } - + } } @@ -69,8 +69,8 @@ class Sabre_DAV_Property_ResourceType extends Sabre_DAV_Property { * Returns the values in clark-notation * * For example array('{DAV:}collection') - * - * @return array + * + * @return array */ public function getValue() { @@ -79,10 +79,10 @@ class Sabre_DAV_Property_ResourceType extends Sabre_DAV_Property { } /** - * Checks if the principal contains a certain value - * - * @param string $type - * @return bool + * Checks if the principal contains a certain value + * + * @param string $type + * @return bool */ public function is($type) { @@ -104,10 +104,10 @@ class Sabre_DAV_Property_ResourceType extends Sabre_DAV_Property { } /** - * Unserializes a DOM element into a ResourceType property. - * - * @param DOMElement $dom - * @return void + * Unserializes a DOM element into a ResourceType property. + * + * @param DOMElement $dom + * @return Sabre_DAV_Property_ResourceType */ static public function unserialize(DOMElement $dom) { diff --git a/3rdparty/Sabre/DAV/Property/Response.php b/3rdparty/Sabre/DAV/Property/Response.php index 7d3a2db038..88afbcfb26 100644 --- a/3rdparty/Sabre/DAV/Property/Response.php +++ b/3rdparty/Sabre/DAV/Property/Response.php @@ -1,53 +1,52 @@ href = $href; - $this->responseProperties = $responseProperties; + $this->responseProperties = $responseProperties; } /** - * Returns the url - * - * @return string + * Returns the url + * + * @return string */ public function getHref() { @@ -56,9 +55,9 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA } /** - * Returns the property list - * - * @return array + * Returns the property list + * + * @return array */ public function getResponseProperties() { @@ -67,19 +66,19 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA } /** - * serialize - * - * @param Sabre_DAV_Server $server - * @param DOMElement $dom + * serialize + * + * @param Sabre_DAV_Server $server + * @param DOMElement $dom * @return void */ - public function serialize(Sabre_DAV_Server $server,DOMElement $dom) { + public function serialize(Sabre_DAV_Server $server, DOMElement $dom) { $document = $dom->ownerDocument; $properties = $this->responseProperties; - + $xresponse = $document->createElement('d:response'); - $dom->appendChild($xresponse); + $dom->appendChild($xresponse); $uri = Sabre_DAV_URLUtil::encodePath($this->href); @@ -87,7 +86,7 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA $uri = $server->getBaseUri() . $uri; $xresponse->appendChild($document->createElement('d:href',$uri)); - + // The properties variable is an array containing properties, grouped by // HTTP status foreach($properties as $httpStatus=>$propertyGroup) { @@ -111,7 +110,7 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA $propName = null; preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName); - + // special case for empty namespaces if ($propName[1]=='') { @@ -125,7 +124,7 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA $nsList[$propName[1]] = 'x' . count($nsList); } - // If the namespace was defined in the top-level xml namespaces, it means + // If the namespace was defined in the top-level xml namespaces, it means // there was already a namespace declaration, and we don't have to worry about it. if (isset($server->xmlNamespaces[$propName[1]])) { $currentProperty = $document->createElement($nsList[$propName[1]] . ':' . $propName[2]); diff --git a/3rdparty/Sabre/DAV/Property/ResponseList.php b/3rdparty/Sabre/DAV/Property/ResponseList.php index cd70b12861..cae923afbf 100644 --- a/3rdparty/Sabre/DAV/Property/ResponseList.php +++ b/3rdparty/Sabre/DAV/Property/ResponseList.php @@ -1,33 +1,32 @@ addReport($reports); } @@ -44,8 +43,8 @@ class Sabre_DAV_Property_SupportedReportSet extends Sabre_DAV_Property { * * The report must be a string in clark-notation. * Multiple reports can be specified as an array. - * - * @param mixed $report + * + * @param mixed $report * @return void */ public function addReport($report) { @@ -54,7 +53,7 @@ class Sabre_DAV_Property_SupportedReportSet extends Sabre_DAV_Property { foreach($report as $r) { - if (!preg_match('/^{([^}]*)}(.*)$/',$r)) + if (!preg_match('/^{([^}]*)}(.*)$/',$r)) throw new Sabre_DAV_Exception('Reportname must be in clark-notation'); $this->reports[] = $r; @@ -65,8 +64,8 @@ class Sabre_DAV_Property_SupportedReportSet extends Sabre_DAV_Property { /** * Returns the list of supported reports - * - * @return array + * + * @return array */ public function getValue() { @@ -75,16 +74,16 @@ class Sabre_DAV_Property_SupportedReportSet extends Sabre_DAV_Property { } /** - * Serializes the node + * Serializes the node * * @param Sabre_DAV_Server $server - * @param DOMElement $prop + * @param DOMElement $prop * @return void */ - public function serialize(Sabre_DAV_Server $server,DOMElement $prop) { + public function serialize(Sabre_DAV_Server $server, DOMElement $prop) { foreach($this->reports as $reportName) { - + $supportedReport = $prop->ownerDocument->createElement('d:supported-report'); $prop->appendChild($supportedReport); @@ -92,9 +91,9 @@ class Sabre_DAV_Property_SupportedReportSet extends Sabre_DAV_Property { $supportedReport->appendChild($report); preg_match('/^{([^}]*)}(.*)$/',$reportName,$matches); - + list(, $namespace, $element) = $matches; - + $prefix = isset($server->xmlNamespaces[$namespace])?$server->xmlNamespaces[$namespace]:null; if ($prefix) { diff --git a/3rdparty/Sabre/DAV/Server.php b/3rdparty/Sabre/DAV/Server.php index 3d76d4f191..50b190e8fa 100644 --- a/3rdparty/Sabre/DAV/Server.php +++ b/3rdparty/Sabre/DAV/Server.php @@ -2,11 +2,11 @@ /** * Main DAV server class - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Server { @@ -26,9 +26,6 @@ class Sabre_DAV_Server { */ const NODE_DIRECTORY = 2; - const PROP_SET = 1; - const PROP_REMOVE = 2; - /** * XML namespace for all SabreDAV related elements */ @@ -36,42 +33,42 @@ class Sabre_DAV_Server { /** * The tree object - * - * @var Sabre_DAV_Tree + * + * @var Sabre_DAV_Tree */ public $tree; /** - * The base uri - * - * @var string + * The base uri + * + * @var string */ - protected $baseUri = null; + protected $baseUri = null; /** - * httpResponse - * - * @var Sabre_HTTP_Response + * httpResponse + * + * @var Sabre_HTTP_Response */ public $httpResponse; /** * httpRequest - * - * @var Sabre_HTTP_Request + * + * @var Sabre_HTTP_Request */ public $httpRequest; /** - * The list of plugins - * - * @var array + * The list of plugins + * + * @var array */ protected $plugins = array(); /** - * This array contains a list of callbacks we should call when certain events are triggered - * + * This array contains a list of callbacks we should call when certain events are triggered + * * @var array */ protected $eventSubscriptions = array(); @@ -81,7 +78,7 @@ class Sabre_DAV_Server { * * If you are defining your own custom namespace, add it here to reduce * bandwidth and improve legibility of xml bodies. - * + * * @var array */ public $xmlNamespaces = array( @@ -90,9 +87,9 @@ class Sabre_DAV_Server { ); /** - * The propertymap can be used to map properties from + * The propertymap can be used to map properties from * requests to property classes. - * + * * @var array */ public $propertyMap = array( @@ -125,23 +122,32 @@ class Sabre_DAV_Server { * This is a flag that allow or not showing file, line and code * of the exception in the returned XML * - * @var bool + * @var bool */ public $debugExceptions = false; /** - * This property allows you to automatically add the 'resourcetype' value + * This property allows you to automatically add the 'resourcetype' value * based on a node's classname or interface. * - * The preset ensures that {DAV:}collection is automaticlly added for nodes + * The preset ensures that {DAV:}collection is automaticlly added for nodes * implementing Sabre_DAV_ICollection. - * + * * @var array */ public $resourceTypeMapping = array( 'Sabre_DAV_ICollection' => '{DAV:}collection', ); + /** + * If this setting is turned off, SabreDAV's version number will be hidden + * from various places. + * + * Some people feel this is a good security measure. + * + * @var bool + */ + static public $exposeVersion = true; /** * Sets up the server @@ -150,14 +156,13 @@ class Sabre_DAV_Server { * use it as the directory tree. If a Sabre_DAV_INode is passed, it * will create a Sabre_DAV_ObjectTree and use the node as the root. * - * If nothing is passed, a Sabre_DAV_SimpleCollection is created in + * If nothing is passed, a Sabre_DAV_SimpleCollection is created in * a Sabre_DAV_ObjectTree. * * If an array is passed, we automatically create a root node, and use - * the nodes in the array as top-level children. - * - * @param Sabre_DAV_Tree $tree The tree object - * @return void + * the nodes in the array as top-level children. + * + * @param Sabre_DAV_Tree|Sabre_DAV_INode|null $treeOrNode The tree object */ public function __construct($treeOrNode = null) { @@ -190,7 +195,7 @@ class Sabre_DAV_Server { } /** - * Starts the DAV Server + * Starts the DAV Server * * @return void */ @@ -210,7 +215,7 @@ class Sabre_DAV_Server { $DOM->appendChild($error); $error->appendChild($DOM->createElement('s:exception',get_class($e))); - $error->appendChild($DOM->createElement('s:message',$e->getMessage())); + $error->appendChild($DOM->createElement('s:message',htmlentities($e->getMessage()))); if ($this->debugExceptions) { $error->appendChild($DOM->createElement('s:file',$e->getFile())); $error->appendChild($DOM->createElement('s:line',$e->getLine())); @@ -218,7 +223,9 @@ class Sabre_DAV_Server { $error->appendChild($DOM->createElement('s:stacktrace',$e->getTraceAsString())); } - $error->appendChild($DOM->createElement('s:sabredav-version',Sabre_DAV_Version::VERSION)); + if (self::$exposeVersion) { + $error->appendChild($DOM->createElement('s:sabredav-version',Sabre_DAV_Version::VERSION)); + } if($e instanceof Sabre_DAV_Exception) { @@ -233,7 +240,7 @@ class Sabre_DAV_Server { } $headers['Content-Type'] = 'application/xml; charset=utf-8'; - + $this->httpResponse->sendStatus($httpCode); $this->httpResponse->setHeaders($headers); $this->httpResponse->sendBody($DOM->saveXML()); @@ -244,24 +251,24 @@ class Sabre_DAV_Server { /** * Sets the base server uri - * + * * @param string $uri * @return void */ public function setBaseUri($uri) { // If the baseUri does not end with a slash, we must add it - if ($uri[strlen($uri)-1]!=='/') + if ($uri[strlen($uri)-1]!=='/') $uri.='/'; - $this->baseUri = $uri; + $this->baseUri = $uri; } /** * Returns the base responding uri - * - * @return string + * + * @return string */ public function getBaseUri() { @@ -272,11 +279,11 @@ class Sabre_DAV_Server { /** * This method attempts to detect the base uri. - * Only the PATH_INFO variable is considered. - * - * If this variable is not set, the root (/) is assumed. + * Only the PATH_INFO variable is considered. * - * @return void + * If this variable is not set, the root (/) is assumed. + * + * @return string */ public function guessBaseUri() { @@ -303,21 +310,21 @@ class Sabre_DAV_Server { return rtrim($baseUri,'/') . '/'; } - throw new Sabre_DAV_Exception('The REQUEST_URI ('. $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); + throw new Sabre_DAV_Exception('The REQUEST_URI ('. $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); - } + } - // The last fallback is that we're just going to assume the server root. + // The last fallback is that we're just going to assume the server root. return '/'; } /** * Adds a plugin to the server - * + * * For more information, console the documentation of Sabre_DAV_ServerPlugin * - * @param Sabre_DAV_ServerPlugin $plugin + * @param Sabre_DAV_ServerPlugin $plugin * @return void */ public function addPlugin(Sabre_DAV_ServerPlugin $plugin) { @@ -333,11 +340,11 @@ class Sabre_DAV_Server { * This function returns null if the plugin was not found. * * @param string $name - * @return Sabre_DAV_ServerPlugin + * @return Sabre_DAV_ServerPlugin */ public function getPlugin($name) { - if (isset($this->plugins[$name])) + if (isset($this->plugins[$name])) return $this->plugins[$name]; // This is a fallback and deprecated. @@ -350,9 +357,9 @@ class Sabre_DAV_Server { } /** - * Returns all plugins - * - * @return array + * Returns all plugins + * + * @return array */ public function getPlugins() { @@ -361,7 +368,6 @@ class Sabre_DAV_Server { } - /** * Subscribe to an event. * @@ -371,9 +377,9 @@ class Sabre_DAV_Server { * * This is for example used to make sure that the authentication plugin * is triggered before anything else. If it's not needed to change this - * number, it is recommended to ommit. - * - * @param string $event + * number, it is recommended to ommit. + * + * @param string $event * @param callback $callback * @param int $priority * @return void @@ -398,7 +404,7 @@ class Sabre_DAV_Server { * * @param string $eventName * @param array $arguments - * @return bool + * @return bool */ public function broadcastEvent($eventName,$arguments = array()) { @@ -418,7 +424,7 @@ class Sabre_DAV_Server { } /** - * Handles a http request, and execute a method based on its name + * Handles a http request, and execute a method based on its name * * @param string $method * @param string $uri @@ -426,7 +432,7 @@ class Sabre_DAV_Server { */ public function invokeMethod($method, $uri) { - $method = strtoupper($method); + $method = strtoupper($method); if (!$this->broadcastEvent('beforeMethod',array($method, $uri))) return; @@ -453,7 +459,7 @@ class Sabre_DAV_Server { if ($this->broadcastEvent('unknownMethod',array($method, $uri))) { // Unsupported method - throw new Sabre_DAV_Exception_NotImplemented(); + throw new Sabre_DAV_Exception_NotImplemented('There was no handler found for this "' . $method . '" method'); } } @@ -461,9 +467,9 @@ class Sabre_DAV_Server { } // {{{ HTTP Method implementations - + /** - * HTTP OPTIONS + * HTTP OPTIONS * * @param string $uri * @return void @@ -476,11 +482,13 @@ class Sabre_DAV_Server { $features = array('1','3', 'extended-mkcol'); foreach($this->plugins as $plugin) $features = array_merge($features,$plugin->getFeatures()); - + $this->httpResponse->setHeader('DAV',implode(', ',$features)); $this->httpResponse->setHeader('MS-Author-Via','DAV'); $this->httpResponse->setHeader('Accept-Ranges','bytes'); - $this->httpResponse->setHeader('X-Sabre-Version',Sabre_DAV_Version::VERSION); + if (self::$exposeVersion) { + $this->httpResponse->setHeader('X-Sabre-Version',Sabre_DAV_Version::VERSION); + } $this->httpResponse->setHeader('Content-Length',0); $this->httpResponse->sendStatus(200); @@ -492,13 +500,13 @@ class Sabre_DAV_Server { * This method simply fetches the contents of a uri, like normal * * @param string $uri - * @return void + * @return bool */ protected function httpGet($uri) { $node = $this->tree->getNodeForPath($uri,0); - if (!$this->checkPreconditions(true)) return false; + if (!$this->checkPreconditions(true)) return false; if (!($node instanceof Sabre_DAV_IFile)) throw new Sabre_DAV_Exception_NotImplemented('GET is only implemented on File objects'); $body = $node->get(); @@ -535,7 +543,7 @@ class Sabre_DAV_Server { } else { $nodeSize = null; } - + $this->httpResponse->setHeaders($httpHeaders); $range = $this->getHTTPRange(); @@ -545,12 +553,12 @@ class Sabre_DAV_Server { // If ifRange is set, and range is specified, we first need to check // the precondition. if ($nodeSize && $range && $ifRange) { - + // if IfRange is parsable as a date we'll treat it as a DateTime // otherwise, we must treat it as an etag. try { $ifRangeDate = new DateTime($ifRange); - + // It's a date. We must check if the entity is modified since // the specified date. if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true; @@ -560,8 +568,8 @@ class Sabre_DAV_Server { } } catch (Exception $e) { - - // It's an entity. We can do a simple comparison. + + // It's an entity. We can do a simple comparison. if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true; elseif ($httpHeaders['ETag']!==$ifRange) $ignoreRangeHeader = true; } @@ -575,7 +583,7 @@ class Sabre_DAV_Server { $start = $range[0]; $end = $range[1]?$range[1]:$nodeSize-1; - if($start >= $nodeSize) + if($start >= $nodeSize) throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); if($end < $start) throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); @@ -625,7 +633,7 @@ class Sabre_DAV_Server { $node = $this->tree->getNodeForPath($uri); /* This information is only collection for File objects. - * Ideally we want to throw 405 Method Not Allowed for every + * Ideally we want to throw 405 Method Not Allowed for every * non-file, but MS Office does not like this */ if ($node instanceof Sabre_DAV_IFile) { @@ -640,7 +648,7 @@ class Sabre_DAV_Server { } /** - * HTTP Delete + * HTTP Delete * * The HTTP delete method, deletes a given uri * @@ -651,6 +659,7 @@ class Sabre_DAV_Server { if (!$this->broadcastEvent('beforeUnbind',array($uri))) return; $this->tree->delete($uri); + $this->broadcastEvent('afterUnbind',array($uri)); $this->httpResponse->sendStatus(204); $this->httpResponse->setHeader('Content-Length','0'); @@ -659,13 +668,13 @@ class Sabre_DAV_Server { /** - * WebDAV PROPFIND + * WebDAV PROPFIND * * This WebDAV method requests information about an uri resource, or a list of resources * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) * - * The request body contains an XML data structure that has a list of properties the client understands + * The request body contains an XML data structure that has a list of properties the client understands * The response body is also an xml document, containing information about every uri resource and the requested properties * * It has to return a HTTP 207 Multi-status status code @@ -679,7 +688,7 @@ class Sabre_DAV_Server { $requestedProperties = $this->parsePropfindRequest($this->httpRequest->getBody(true)); $depth = $this->getHTTPDepth(1); - // The only two options for the depth of a propfind is 0 or 1 + // The only two options for the depth of a propfind is 0 or 1 if ($depth!=0) $depth = 1; $newProperties = $this->getPropertiesForPath($uri,$requestedProperties,$depth); @@ -688,8 +697,8 @@ class Sabre_DAV_Server { $this->httpResponse->sendStatus(207); $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - // Normally this header is only needed for OPTIONS responses, however.. - // iCal seems to also depend on these being set for PROPFIND. Since + // Normally this header is only needed for OPTIONS responses, however.. + // iCal seems to also depend on these being set for PROPFIND. Since // this is not harmful, we'll add it. $features = array('1','3', 'extended-mkcol'); foreach($this->plugins as $plugin) $features = array_merge($features,$plugin->getFeatures()); @@ -712,7 +721,7 @@ class Sabre_DAV_Server { protected function httpPropPatch($uri) { $newProperties = $this->parsePropPatchRequest($this->httpRequest->getBody(true)); - + $result = $this->updateProperties($uri, $newProperties); $this->httpResponse->sendStatus(207); @@ -725,14 +734,14 @@ class Sabre_DAV_Server { } /** - * HTTP PUT method - * + * HTTP PUT method + * * This HTTP method updates a file, or creates a new one. * - * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 200 Ok + * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content * * @param string $uri - * @return void + * @return bool */ protected function httpPut($uri) { @@ -768,13 +777,13 @@ class Sabre_DAV_Server { // Intercepting the Finder problem if (($expected = $this->httpRequest->getHeader('X-Expected-Entity-Length')) && $expected > 0) { - + /** - Many webservers will not cooperate well with Finder PUT requests, + Many webservers will not cooperate well with Finder PUT requests, because it uses 'Chunked' transfer encoding for the request body. - The symptom of this problem is that Finder sends files to the - server, but they arrive as 0-lenght files in PHP. + The symptom of this problem is that Finder sends files to the + server, but they arrive as 0-length files in PHP. If we don't do anything, the user might think they are uploading files successfully, but they end up empty on the server. Instead, @@ -808,29 +817,36 @@ class Sabre_DAV_Server { } - if ($this->tree->nodeExists($uri)) { + if ($this->tree->nodeExists($uri)) { $node = $this->tree->getNodeForPath($uri); - + // Checking If-None-Match and related headers. if (!$this->checkPreconditions()) return; - + // If the node is a collection, we'll deny it if (!($node instanceof Sabre_DAV_IFile)) throw new Sabre_DAV_Exception_Conflict('PUT is not allowed on non-files.'); - if (!$this->broadcastEvent('beforeWriteContent',array($this->getRequestUri()))) return false; + if (!$this->broadcastEvent('beforeWriteContent',array($uri, $node, &$body))) return false; + + $etag = $node->put($body); + + $this->broadcastEvent('afterWriteContent',array($uri, $node)); - $node->put($body); $this->httpResponse->setHeader('Content-Length','0'); + if ($etag) $this->httpResponse->setHeader('ETag',$etag); $this->httpResponse->sendStatus(204); } else { + $etag = null; // If we got here, the resource didn't exist yet. - if (!$this->createFile($this->getRequestUri(),$body)) { + if (!$this->createFile($this->getRequestUri(),$body,$etag)) { // For one reason or another the file was not created. return; } + $this->httpResponse->setHeader('Content-Length','0'); + if ($etag) $this->httpResponse->setHeader('ETag', $etag); $this->httpResponse->sendStatus(201); } @@ -855,7 +871,7 @@ class Sabre_DAV_Server { $contentType = $this->httpRequest->getHeader('Content-Type'); if (strpos($contentType,'application/xml')!==0 && strpos($contentType,'text/xml')!==0) { - // We must throw 415 for unsupport mkcol bodies + // We must throw 415 for unsupported mkcol bodies throw new Sabre_DAV_Exception_UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); } @@ -863,7 +879,7 @@ class Sabre_DAV_Server { $dom = Sabre_DAV_XMLUtil::loadDOMDocument($requestBody); if (Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild)!=='{DAV:}mkcol') { - // We must throw 415 for unsupport mkcol bodies + // We must throw 415 for unsupported mkcol bodies throw new Sabre_DAV_Exception_UnsupportedMediaType('The request body for the MKCOL request must be a {DAV:}mkcol request construct.'); } @@ -875,7 +891,7 @@ class Sabre_DAV_Server { $properties = array_merge($properties, Sabre_DAV_XMLUtil::parseProperties($childNode, $this->propertyMap)); } - if (!isset($properties['{DAV:}resourcetype'])) + if (!isset($properties['{DAV:}resourcetype'])) throw new Sabre_DAV_Exception_BadRequest('The mkcol request must include a {DAV:}resourcetype property'); $resourceType = $properties['{DAV:}resourcetype']->getValue(); @@ -918,19 +934,21 @@ class Sabre_DAV_Server { $moveInfo = $this->getCopyAndMoveInfo(); // If the destination is part of the source tree, we must fail - if ($moveInfo['destination']==$uri) + if ($moveInfo['destination']==$uri) throw new Sabre_DAV_Exception_Forbidden('Source and destination uri are identical.'); if ($moveInfo['destinationExists']) { if (!$this->broadcastEvent('beforeUnbind',array($moveInfo['destination']))) return false; $this->tree->delete($moveInfo['destination']); + $this->broadcastEvent('afterUnbind',array($moveInfo['destination'])); } if (!$this->broadcastEvent('beforeUnbind',array($uri))) return false; if (!$this->broadcastEvent('beforeBind',array($moveInfo['destination']))) return false; $this->tree->move($uri,$moveInfo['destination']); + $this->broadcastEvent('afterUnbind',array($uri)); $this->broadcastEvent('afterBind',array($moveInfo['destination'])); // If a resource was overwritten we should send a 204, otherwise a 201 @@ -946,13 +964,13 @@ class Sabre_DAV_Server { * A lot of the actual request processing is done in getCopyMoveInfo * * @param string $uri - * @return void + * @return bool */ protected function httpCopy($uri) { $copyInfo = $this->getCopyAndMoveInfo(); // If the destination is part of the source tree, we must fail - if ($copyInfo['destination']==$uri) + if ($copyInfo['destination']==$uri) throw new Sabre_DAV_Exception_Forbidden('Source and destination uri are identical.'); if ($copyInfo['destinationExists']) { @@ -998,13 +1016,13 @@ class Sabre_DAV_Server { } // }}} - // {{{ HTTP/WebDAV protocol helpers + // {{{ HTTP/WebDAV protocol helpers /** - * Returns an array with all the supported HTTP methods for a specific uri. + * Returns an array with all the supported HTTP methods for a specific uri. * - * @param string $uri - * @return array + * @param string $uri + * @return array */ public function getAllowedMethods($uri) { @@ -1023,13 +1041,13 @@ class Sabre_DAV_Server { // The MKCOL is only allowed on an unmapped uri try { - $node = $this->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + $this->tree->getNodeForPath($uri); + } catch (Sabre_DAV_Exception_NotFound $e) { $methods[] = 'MKCOL'; } // We're also checking if any of the plugins register any new methods - foreach($this->plugins as $plugin) $methods = array_merge($methods,$plugin->getHTTPMethods($uri)); + foreach($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($uri)); array_unique($methods); return $methods; @@ -1037,8 +1055,8 @@ class Sabre_DAV_Server { } /** - * Gets the uri for the request, keeping the base uri into consideration - * + * Gets the uri for the request, keeping the base uri into consideration + * * @return string */ public function getRequestUri() { @@ -1048,9 +1066,9 @@ class Sabre_DAV_Server { } /** - * Calculates the uri for a request, making sure that the base uri is stripped out - * - * @param string $uri + * Calculates the uri for a request, making sure that the base uri is stripped out + * + * @param string $uri * @throws Sabre_DAV_Exception_Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri * @return string */ @@ -1068,9 +1086,9 @@ class Sabre_DAV_Server { return trim(Sabre_DAV_URLUtil::decodePath(substr($uri,strlen($this->getBaseUri()))),'/'); - // A special case, if the baseUri was accessed without a trailing - // slash, we'll accept it as well. - } elseif ($uri.'/' === $this->getBaseUri()) { + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + } elseif ($uri.'/' === $this->getBaseUri()) { return ''; @@ -1086,10 +1104,10 @@ class Sabre_DAV_Server { * Returns the HTTP depth header * * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre_DAV_Server::DEPTH_INFINITY object - * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existant - * - * @param mixed $default - * @return int + * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent + * + * @param mixed $default + * @return int */ public function getHTTPDepth($default = self::DEPTH_INFINITY) { @@ -1100,7 +1118,7 @@ class Sabre_DAV_Server { if ($depth == 'infinity') return self::DEPTH_INFINITY; - + // If its an unknown value. we'll grab the default if (!ctype_digit($depth)) return $default; @@ -1118,14 +1136,14 @@ class Sabre_DAV_Server { * The second number is the offset of the last byte in the range. * * If the second offset is null, it should be treated as the offset of the last byte of the entity - * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity + * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity * - * return $mixed + * @return array|null */ public function getHTTPRange() { $range = $this->httpRequest->getHeader('range'); - if (is_null($range)) return null; + if (is_null($range)) return null; // Matching "Range: bytes=1234-5678: both numbers are optional @@ -1143,15 +1161,15 @@ class Sabre_DAV_Server { /** * Returns information about Copy and Move requests - * - * This function is created to help getting information about the source and the destination for the - * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions - * + * + * This function is created to help getting information about the source and the destination for the + * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions + * * The returned value is an array with the following keys: * * destination - Destination path - * * destinationExists - Wether or not the destination is an existing url (and should therefore be overwritten) + * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) * - * @return array + * @return array */ public function getCopyAndMoveInfo() { @@ -1170,7 +1188,7 @@ class Sabre_DAV_Server { try { $destinationParent = $this->tree->getNodeForPath($destinationDir); if (!($destinationParent instanceof Sabre_DAV_ICollection)) throw new Sabre_DAV_Exception_UnsupportedMediaType('The destination node is not a collection'); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { // If the destination parent node is not found, we throw a 409 throw new Sabre_DAV_Exception_Conflict('The destination node is not found'); @@ -1179,12 +1197,12 @@ class Sabre_DAV_Server { try { $destinationNode = $this->tree->getNodeForPath($destination); - + // If this succeeded, it means the destination already exists // we'll need to throw precondition failed in case overwrite is false if (!$overwrite) throw new Sabre_DAV_Exception_PreconditionFailed('The destination node already exists, and the overwrite header is set to false','Overwrite'); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { // Destination didn't exist, we're all good $destinationNode = false; @@ -1219,23 +1237,51 @@ class Sabre_DAV_Server { } + /** + * A kid-friendly way to fetch properties for a node's children. + * + * The returned array will be indexed by the path of the of child node. + * Only properties that are actually found will be returned. + * + * The parent node will not be returned. + * + * @param string $path + * @param array $propertyNames + * @return array + */ + public function getPropertiesForChildren($path, $propertyNames) { + + $result = array(); + foreach($this->getPropertiesForPath($path,$propertyNames,1) as $k=>$row) { + + // Skipping the parent path + if ($k === 0) continue; + + $result[$row['href']] = $row[200]; + + } + return $result; + + } + /** * Returns a list of HTTP headers for a particular resource * - * The generated http headers are based on properties provided by the + * The generated http headers are based on properties provided by the * resource. The method basically provides a simple mapping between * DAV property and HTTP header. * * The headers are intended to be used for HEAD and GET requests. - * + * * @param string $path + * @return array */ public function getHTTPHeaders($path) { $propertyMap = array( '{DAV:}getcontenttype' => 'Content-Type', '{DAV:}getcontentlength' => 'Content-Length', - '{DAV:}getlastmodified' => 'Last-Modified', + '{DAV:}getlastmodified' => 'Last-Modified', '{DAV:}getetag' => 'ETag', ); @@ -1245,40 +1291,40 @@ class Sabre_DAV_Server { foreach($propertyMap as $property=>$header) { if (!isset($properties[$property])) continue; - if (is_scalar($properties[$property])) { + if (is_scalar($properties[$property])) { $headers[$header] = $properties[$property]; - // GetLastModified gets special cased + // GetLastModified gets special cased } elseif ($properties[$property] instanceof Sabre_DAV_Property_GetLastModified) { - $headers[$header] = $properties[$property]->getTime()->format(DateTime::RFC1123); + $headers[$header] = Sabre_HTTP_Util::toHTTPDate($properties[$property]->getTime()); } } return $headers; - + } /** * Returns a list of properties for a given path - * + * * The path that should be supplied should have the baseUrl stripped out * The list of properties should be supplied in Clark notation. If the list is empty * 'allprops' is assumed. * * If a depth of 1 is requested child elements will also be returned. * - * @param string $path + * @param string $path * @param array $propertyNames - * @param int $depth + * @param int $depth * @return array */ - public function getPropertiesForPath($path,$propertyNames = array(),$depth = 0) { + public function getPropertiesForPath($path, $propertyNames = array(), $depth = 0) { if ($depth!=0) $depth = 1; $returnPropertyList = array(); - + $parentNode = $this->tree->getNodeForPath($path); $nodes = array( $path => $parentNode @@ -1286,11 +1332,11 @@ class Sabre_DAV_Server { if ($depth==1 && $parentNode instanceof Sabre_DAV_ICollection) { foreach($this->tree->getChildren($path) as $childNode) $nodes[$path . '/' . $childNode->getName()] = $childNode; - } - + } + // If the propertyNames array is empty, it means all properties are requested. // We shouldn't actually return everything we know though, and only return a - // sensible list. + // sensible list. $allProperties = count($propertyNames)==0; foreach($nodes as $myPath=>$node) { @@ -1315,8 +1361,8 @@ class Sabre_DAV_Server { ); } - // If the resourceType was not part of the list, we manually add it - // and mark it for removal. We need to know the resourcetype in order + // If the resourceType was not part of the list, we manually add it + // and mark it for removal. We need to know the resourcetype in order // to make certain decisions about the entry. // WebDAV dictates we should add a / and the end of href's for collections $removeRT = false; @@ -1326,32 +1372,39 @@ class Sabre_DAV_Server { } $result = $this->broadcastEvent('beforeGetProperties',array($myPath, $node, &$currentPropertyNames, &$newProperties)); - // If this method explicitly returned false, we must ignore this - // node as it is inacessible. + // If this method explicitly returned false, we must ignore this + // node as it is inaccessible. if ($result===false) continue; if (count($currentPropertyNames) > 0) { - if ($node instanceof Sabre_DAV_IProperties) + if ($node instanceof Sabre_DAV_IProperties) $newProperties['200'] = $newProperties[200] + $node->getProperties($currentPropertyNames); } foreach($currentPropertyNames as $prop) { - + if (isset($newProperties[200][$prop])) continue; switch($prop) { case '{DAV:}getlastmodified' : if ($node->getLastModified()) $newProperties[200][$prop] = new Sabre_DAV_Property_GetLastModified($node->getLastModified()); break; - case '{DAV:}getcontentlength' : if ($node instanceof Sabre_DAV_IFile) $newProperties[200][$prop] = (int)$node->getSize(); break; - case '{DAV:}quota-used-bytes' : + case '{DAV:}getcontentlength' : + if ($node instanceof Sabre_DAV_IFile) { + $size = $node->getSize(); + if (!is_null($size)) { + $newProperties[200][$prop] = (int)$node->getSize(); + } + } + break; + case '{DAV:}quota-used-bytes' : if ($node instanceof Sabre_DAV_IQuota) { $quotaInfo = $node->getQuotaInfo(); $newProperties[200][$prop] = $quotaInfo[0]; } break; - case '{DAV:}quota-available-bytes' : + case '{DAV:}quota-available-bytes' : if ($node instanceof Sabre_DAV_IQuota) { $quotaInfo = $node->getQuotaInfo(); $newProperties[200][$prop] = $quotaInfo[1]; @@ -1364,7 +1417,7 @@ class Sabre_DAV_Server { foreach($this->plugins as $plugin) { $reports = array_merge($reports, $plugin->getSupportedReportSet($myPath)); } - $newProperties[200][$prop] = new Sabre_DAV_Property_SupportedReportSet($reports); + $newProperties[200][$prop] = new Sabre_DAV_Property_SupportedReportSet($reports); break; case '{DAV:}resourcetype' : $newProperties[200]['{DAV:}resourcetype'] = new Sabre_DAV_Property_ResourceType(); @@ -1379,14 +1432,14 @@ class Sabre_DAV_Server { if (!$allProperties && !isset($newProperties[200][$prop])) $newProperties[404][$prop] = null; } - + $this->broadcastEvent('afterGetProperties',array(trim($myPath,'/'),&$newProperties)); - $newProperties['href'] = trim($myPath,'/'); + $newProperties['href'] = trim($myPath,'/'); // Its is a WebDAV recommendation to add a trailing slash to collectionnames. // Apple's iCal also requires a trailing slash for principals (rfc 3744). - // Therefore we add a trailing / for any non-file. This might need adjustments + // Therefore we add a trailing / for any non-file. This might need adjustments // if we find there are other edge cases. if ($myPath!='' && isset($newProperties[200]['{DAV:}resourcetype']) && count($newProperties[200]['{DAV:}resourcetype']->getValue())>0) $newProperties['href'] .='/'; @@ -1397,7 +1450,7 @@ class Sabre_DAV_Server { $returnPropertyList[] = $newProperties; } - + return $returnPropertyList; } @@ -1406,27 +1459,31 @@ class Sabre_DAV_Server { * This method is invoked by sub-systems creating a new file. * * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin). - * It was important to get this done through a centralized function, + * It was important to get this done through a centralized function, * allowing plugins to intercept this using the beforeCreateFile event. * * This method will return true if the file was actually created - * - * @param string $uri - * @param resource $data - * @return bool + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool */ - public function createFile($uri,$data) { + public function createFile($uri,$data, &$etag = null) { list($dir,$name) = Sabre_DAV_URLUtil::splitPath($uri); if (!$this->broadcastEvent('beforeBind',array($uri))) return false; - if (!$this->broadcastEvent('beforeCreateFile',array($uri,$data))) return false; $parent = $this->tree->getNodeForPath($dir); - $parent->createFile($name,$data); + + if (!$this->broadcastEvent('beforeCreateFile',array($uri, &$data, $parent))) return false; + + $etag = $parent->createFile($name,$data); $this->tree->markDirty($dir); $this->broadcastEvent('afterBind',array($uri)); + $this->broadcastEvent('afterCreateFile',array($uri, $parent)); return true; } @@ -1434,7 +1491,7 @@ class Sabre_DAV_Server { /** * This method is invoked by sub-systems creating a new directory. * - * @param string $uri + * @param string $uri * @return void */ public function createDirectory($uri) { @@ -1447,14 +1504,14 @@ class Sabre_DAV_Server { * Use this method to create a new collection * * The {DAV:}resourcetype is specified using the resourceType array. - * At the very least it must contain {DAV:}collection. + * At the very least it must contain {DAV:}collection. * * The properties array can contain a list of additional properties. - * - * @param string $uri The new uri - * @param array $resourceType The resourceType(s) + * + * @param string $uri The new uri + * @param array $resourceType The resourceType(s) * @param array $properties A list of properties - * @return void + * @return array|null */ public function createCollection($uri, array $resourceType, array $properties) { @@ -1471,7 +1528,7 @@ class Sabre_DAV_Server { $parent = $this->tree->getNodeForPath($parentUri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { throw new Sabre_DAV_Exception_Conflict('Parent node does not exist'); @@ -1491,14 +1548,14 @@ class Sabre_DAV_Server { // If we got here.. it means there's already a node on that url, and we need to throw a 405 throw new Sabre_DAV_Exception_MethodNotAllowed('The resource you tried to create already exists'); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { // This is correct } - + if (!$this->broadcastEvent('beforeBind',array($uri))) return; - // There are 2 modes of operation. The standard collection + // There are 2 modes of operation. The standard collection // creates the directory, and then updates properties // the extended collection can create it directly. if ($parent instanceof Sabre_DAV_IExtendedCollection) { @@ -1513,7 +1570,7 @@ class Sabre_DAV_Server { } $parent->createDirectory($newName); - $rollBack = false; + $rollBack = false; $exception = null; $errorResult = null; @@ -1544,7 +1601,7 @@ class Sabre_DAV_Server { return $errorResult; } - + } $this->tree->markDirty($parentUri); $this->broadcastEvent('afterBind',array($uri)); @@ -1557,29 +1614,29 @@ class Sabre_DAV_Server { * The properties array must be a list of properties. Array-keys are * property names in clarknotation, array-values are it's values. * If a property must be deleted, the value should be null. - * - * Note that this request should either completely succeed, or + * + * Note that this request should either completely succeed, or * completely fail. * * The response is an array with statuscodes for keys, which in turn * contain arrays with propertynames. This response can be used * to generate a multistatus body. - * - * @param string $uri - * @param array $properties - * @return array + * + * @param string $uri + * @param array $properties + * @return array */ public function updateProperties($uri, array $properties) { // we'll start by grabbing the node, this will throw the appropriate - // exceptions if it doesn't. + // exceptions if it doesn't. $node = $this->tree->getNodeForPath($uri); - + $result = array( 200 => array(), 403 => array(), 424 => array(), - ); + ); $remainingProperties = $properties; $hasError = false; @@ -1684,14 +1741,15 @@ class Sabre_DAV_Server { * the appropriate HTTP response headers are already set. * * Normally this method will throw 412 Precondition Failed for failures - * related to If-None-Match, If-Match and If-Unmodified Since. It will + * related to If-None-Match, If-Match and If-Unmodified Since. It will * set the status to 304 Not Modified for If-Modified_since. * - * If the $handleAsGET argument is set to true, it will also return 304 + * If the $handleAsGET argument is set to true, it will also return 304 * Not Modified for failure of the If-None-Match precondition. This is the * desired behaviour for HTTP GET and HTTP HEAD requests. * - * @return bool + * @param bool $handleAsGET + * @return bool */ public function checkPreconditions($handleAsGET = false) { @@ -1708,7 +1766,7 @@ class Sabre_DAV_Server { // request succeed if a resource exists at that url. try { $node = $this->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { throw new Sabre_DAV_Exception_PreconditionFailed('An If-Match header was specified and the resource did not exist','If-Match'); } @@ -1722,7 +1780,7 @@ class Sabre_DAV_Server { // Stripping any extra spaces $ifMatchItem = trim($ifMatchItem,' '); - + $etag = $node->getETag(); if ($etag===$ifMatchItem) { $haveMatch = true; @@ -1744,7 +1802,7 @@ class Sabre_DAV_Server { if (!$node) { try { $node = $this->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { $nodeExists = false; } } @@ -1758,10 +1816,10 @@ class Sabre_DAV_Server { $etag = $node->getETag(); foreach($ifNoneMatch as $ifNoneMatchItem) { - + // Stripping any extra spaces $ifNoneMatchItem = trim($ifNoneMatchItem,' '); - + if ($etag===$ifNoneMatchItem) $haveMatch = true; } @@ -1781,7 +1839,7 @@ class Sabre_DAV_Server { } if (!$ifNoneMatch && ($ifModifiedSince = $this->httpRequest->getHeader('If-Modified-Since'))) { - + // The If-Modified-Since header contains a date. We // will only return the entity if it has been changed since // that date. If it hasn't been changed, we return a 304 @@ -1799,23 +1857,24 @@ class Sabre_DAV_Server { $lastMod = new DateTime('@' . $lastMod); if ($lastMod <= $date) { $this->httpResponse->sendStatus(304); + $this->httpResponse->setHeader('Last-Modified', Sabre_HTTP_Util::toHTTPDate($lastMod)); return false; - } + } } } } if ($ifUnmodifiedSince = $this->httpRequest->getHeader('If-Unmodified-Since')) { - + // The If-Unmodified-Since will allow allow the request if the // entity has not changed since the specified date. $date = Sabre_HTTP_Util::parseHTTPDate($ifUnmodifiedSince); - + // We must only check the date if it's valid if ($date) { if (is_null($node)) { $node = $this->tree->getNodeForPath($uri); - } + } $lastMod = $node->getLastModified(); if ($lastMod) { $lastMod = new DateTime('@' . $lastMod); @@ -1830,16 +1889,15 @@ class Sabre_DAV_Server { } - // }}} - // {{{ XML Readers & Writers - - + // }}} + // {{{ XML Readers & Writers + + /** - * Generates a WebDAV propfind response body based on a list of nodes - * + * Generates a WebDAV propfind response body based on a list of nodes + * * @param array $fileProperties The list with nodes - * @param array $requestedProperties The properties that should be returned - * @return string + * @return string */ public function generateMultiStatus(array $fileProperties) { @@ -1859,7 +1917,7 @@ class Sabre_DAV_Server { $href = $entry['href']; unset($entry['href']); - + $response = new Sabre_DAV_Property_Response($href,$entry); $response->serialize($this,$multiStatus); @@ -1878,7 +1936,7 @@ class Sabre_DAV_Server { * The keys in the returned array contain the property name (e.g.: {DAV:}displayname, * and the value contains the property value. If a property is to be removed the value * will be null. - * + * * @param string $body xml body * @return array list of properties in need of updating or deletion */ @@ -1886,17 +1944,17 @@ class Sabre_DAV_Server { //We'll need to change the DAV namespace declaration to something else in order to make it parsable $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); - + $newProperties = array(); foreach($dom->firstChild->childNodes as $child) { - if ($child->nodeType !== XML_ELEMENT_NODE) continue; + if ($child->nodeType !== XML_ELEMENT_NODE) continue; $operation = Sabre_DAV_XMLUtil::toClarkNotation($child); if ($operation!=='{DAV:}set' && $operation!=='{DAV:}remove') continue; - + $innerProperties = Sabre_DAV_XMLUtil::parseProperties($child, $this->propertyMap); foreach($innerProperties as $propertyName=>$propertyValue) { @@ -1920,9 +1978,9 @@ class Sabre_DAV_Server { * * This will either be a list of properties, or an empty array; in which case * an {DAV:}allprop was requested. - * - * @param string $body - * @return array + * + * @param string $body + * @return array */ public function parsePropFindRequest($body) { @@ -1931,7 +1989,7 @@ class Sabre_DAV_Server { $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0); - return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem)); + return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem)); } diff --git a/3rdparty/Sabre/DAV/ServerPlugin.php b/3rdparty/Sabre/DAV/ServerPlugin.php index 6909f600c2..131863d13f 100644 --- a/3rdparty/Sabre/DAV/ServerPlugin.php +++ b/3rdparty/Sabre/DAV/ServerPlugin.php @@ -3,12 +3,12 @@ /** * The baseclass for all server plugins. * - * Plugins can modify or extend the servers behaviour. - * + * Plugins can modify or extend the servers behaviour. + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ abstract class Sabre_DAV_ServerPlugin { @@ -20,18 +20,18 @@ abstract class Sabre_DAV_ServerPlugin { * addPlugin is called. * * This method should set up the requires event subscriptions. - * - * @param Sabre_DAV_Server $server + * + * @param Sabre_DAV_Server $server * @return void */ abstract public function initialize(Sabre_DAV_Server $server); - + /** - * This method should return a list of server-features. + * This method should return a list of server-features. * * This is for example 'versioning' and is added to the DAV: header * in an OPTIONS response. - * + * * @return array */ public function getFeatures() { @@ -44,11 +44,11 @@ abstract class Sabre_DAV_ServerPlugin { * Use this method to tell the server this plugin defines additional * HTTP methods. * - * This method is passed a uri. It should only return HTTP methods that are + * This method is passed a uri. It should only return HTTP methods that are * available for the specified uri. * * @param string $uri - * @return array + * @return array */ public function getHTTPMethods($uri) { @@ -58,11 +58,11 @@ abstract class Sabre_DAV_ServerPlugin { /** * Returns a plugin name. - * + * * Using this name other plugins will be able to access other plugins - * using Sabre_DAV_Server::getPlugin - * - * @return string + * using Sabre_DAV_Server::getPlugin + * + * @return string */ public function getPluginName() { @@ -74,11 +74,11 @@ abstract class Sabre_DAV_ServerPlugin { * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them - * + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * * @param string $uri - * @return array + * @return array */ public function getSupportedReportSet($uri) { diff --git a/3rdparty/Sabre/DAV/SimpleCollection.php b/3rdparty/Sabre/DAV/SimpleCollection.php index 223d05fed5..4acf971caa 100644 --- a/3rdparty/Sabre/DAV/SimpleCollection.php +++ b/3rdparty/Sabre/DAV/SimpleCollection.php @@ -8,23 +8,23 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_SimpleCollection extends Sabre_DAV_Collection { /** - * List of childnodes + * List of childnodes * * @var array */ protected $children = array(); /** - * Name of this resource - * - * @var string + * Name of this resource + * + * @var string */ protected $name; @@ -33,10 +33,9 @@ class Sabre_DAV_SimpleCollection extends Sabre_DAV_Collection { * * The name of the node must be passed, child nodes can also be bassed. * This nodes must be instances of Sabre_DAV_INode - * - * @param string $name - * @param array $children - * @return void + * + * @param string $name + * @param array $children */ public function __construct($name,array $children = array()) { @@ -51,9 +50,9 @@ class Sabre_DAV_SimpleCollection extends Sabre_DAV_Collection { } /** - * Adds a new childnode to this collection - * - * @param Sabre_DAV_INode $child + * Adds a new childnode to this collection + * + * @param Sabre_DAV_INode $child * @return void */ public function addChild(Sabre_DAV_INode $child) { @@ -63,9 +62,9 @@ class Sabre_DAV_SimpleCollection extends Sabre_DAV_Collection { } /** - * Returns the name of the collection - * - * @return string + * Returns the name of the collection + * + * @return string */ public function getName() { @@ -76,24 +75,24 @@ class Sabre_DAV_SimpleCollection extends Sabre_DAV_Collection { /** * Returns a child object, by its name. * - * This method makes use of the getChildren method to grab all the child nodes, and compares the name. + * This method makes use of the getChildren method to grab all the child nodes, and compares the name. * Generally its wise to override this, as this can usually be optimized - * + * * @param string $name - * @throws Sabre_DAV_Exception_FileNotFound - * @return Sabre_DAV_INode + * @throws Sabre_DAV_Exception_NotFound + * @return Sabre_DAV_INode */ public function getChild($name) { if (isset($this->children[$name])) return $this->children[$name]; - throw new Sabre_DAV_Exception_FileNotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\''); + throw new Sabre_DAV_Exception_NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\''); } /** - * Returns a list of children for this collection - * - * @return array + * Returns a list of children for this collection + * + * @return array */ public function getChildren() { diff --git a/3rdparty/Sabre/DAV/SimpleDirectory.php b/3rdparty/Sabre/DAV/SimpleDirectory.php index 516a3aa907..621222ebc5 100644 --- a/3rdparty/Sabre/DAV/SimpleDirectory.php +++ b/3rdparty/Sabre/DAV/SimpleDirectory.php @@ -11,8 +11,8 @@ * @package Sabre * @subpackage DAV * @deprecated Use Sabre_DAV_SimpleCollection instead. - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_SimpleDirectory extends Sabre_DAV_SimpleCollection { diff --git a/3rdparty/Sabre/DAV/SimpleFile.php b/3rdparty/Sabre/DAV/SimpleFile.php index 304dff1c5e..58330d6861 100644 --- a/3rdparty/Sabre/DAV/SimpleFile.php +++ b/3rdparty/Sabre/DAV/SimpleFile.php @@ -3,47 +3,48 @@ /** * SimpleFile * - * The 'SimpleFile' class is used to easily add read-only immutable files to - * the directory structure. One usecase would be to add a 'readme.txt' to a + * The 'SimpleFile' class is used to easily add read-only immutable files to + * the directory structure. One usecase would be to add a 'readme.txt' to a * root of a webserver with some standard content. * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_SimpleFile extends Sabre_DAV_File { /** - * File contents + * File contents * - * @var string + * @var string */ protected $contents = array(); /** - * Name of this resource - * - * @var string + * Name of this resource + * + * @var string */ protected $name; /** * A mimetype, such as 'text/plain' or 'text/html' - * - * @var string + * + * @var string */ protected $mimeType; /** * Creates this node * - * The name of the node must be passed, as well as the contents of the + * The name of the node must be passed, as well as the contents of the * file. - * - * @param string $name - * @param string $contents + * + * @param string $name + * @param string $contents + * @param string|null $mimeType */ public function __construct($name, $contents, $mimeType = null) { @@ -57,8 +58,8 @@ class Sabre_DAV_SimpleFile extends Sabre_DAV_File { * Returns the node name for this file. * * This name is used to construct the url. - * - * @return string + * + * @return string */ public function getName() { @@ -67,7 +68,7 @@ class Sabre_DAV_SimpleFile extends Sabre_DAV_File { } /** - * Returns the data + * Returns the data * * This method may either return a string or a readable stream resource * @@ -75,14 +76,14 @@ class Sabre_DAV_SimpleFile extends Sabre_DAV_File { */ public function get() { - return $this->contents; + return $this->contents; } /** - * Returns the size of the file, in bytes. - * - * @return int + * Returns the size of the file, in bytes. + * + * @return int */ public function getSize() { @@ -94,13 +95,14 @@ class Sabre_DAV_SimpleFile extends Sabre_DAV_File { * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbritrary string, but MUST be surrounded by double-quotes. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined + * @return string */ public function getETag() { - return '"' . md5($this->contents) . '"'; + return '"' . md5($this->contents) . '"'; } @@ -108,13 +110,12 @@ class Sabre_DAV_SimpleFile extends Sabre_DAV_File { * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream - */ + * @return string + */ public function getContentType() { - return $this->mimeType; + return $this->mimeType; } } - -?> diff --git a/3rdparty/Sabre/DAV/StringUtil.php b/3rdparty/Sabre/DAV/StringUtil.php index 440cf6866c..b126a94c82 100644 --- a/3rdparty/Sabre/DAV/StringUtil.php +++ b/3rdparty/Sabre/DAV/StringUtil.php @@ -3,14 +3,14 @@ /** * String utility * - * This class is mainly used to implement the 'text-match' filter, used by both - * the CalDAV calendar-query REPORT, and CardDAV addressbook-query REPORT. + * This class is mainly used to implement the 'text-match' filter, used by both + * the CalDAV calendar-query REPORT, and CardDAV addressbook-query REPORT. * Because they both need it, it was decided to put it in Sabre_DAV instead. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_StringUtil { @@ -18,11 +18,11 @@ class Sabre_DAV_StringUtil { /** * Checks if a needle occurs in a haystack ;) * - * @param string $haystack - * @param string $needle - * @param string $collation - * @param string $matchType - * @return bool + * @param string $haystack + * @param string $needle + * @param string $collation + * @param string $matchType + * @return bool */ static public function textMatch($haystack, $needle, $collation, $matchType = 'contains') { @@ -37,7 +37,7 @@ class Sabre_DAV_StringUtil { case 'i;octet' : // Do nothing - break; + break; case 'i;unicode-casemap' : $haystack = mb_strtoupper($haystack, 'UTF-8'); @@ -63,18 +63,18 @@ class Sabre_DAV_StringUtil { throw new Sabre_DAV_Exception_BadRequest('Match-type: ' . $matchType . ' is not supported'); } - + } /** - * This method takes an input string, checks if it's not valid UTF-8 and + * This method takes an input string, checks if it's not valid UTF-8 and * attempts to convert it to UTF-8 if it's not. * - * Note that currently this can only convert ISO-8559-1 to UTF-8 (latin-1), + * Note that currently this can only convert ISO-8559-1 to UTF-8 (latin-1), * anything else will likely fail. * - * @param string $input - * @return string + * @param string $input + * @return string */ static public function ensureUTF8($input) { @@ -84,7 +84,7 @@ class Sabre_DAV_StringUtil { return utf8_encode($input); } else { return $input; - } + } } diff --git a/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php b/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php index e8276af561..36096e6777 100644 --- a/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php +++ b/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php @@ -22,8 +22,8 @@ * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { @@ -31,7 +31,7 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { /** * This is the list of patterns we intercept. * If new patterns are added, they must be valid patterns for preg_match. - * + * * @var array */ public $temporaryFilePatterns = array( @@ -43,19 +43,19 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { '/^\.dat(.*)$/', // Smultron seems to create these '/^~lock.(.*)#$/', // Windows 7 lockfiles ); - + /** * This is the directory where this plugin * will store it's files. - * - * @var string + * + * @var string */ private $dataDir; /** * A reference to the main Server class - * - * @var Sabre_DAV_Server + * + * @var Sabre_DAV_Server */ private $server; @@ -65,9 +65,8 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { * Make sure you specify a directory for your files. If you don't, we * will use PHP's directory for session-storage instead, and you might * not want that. - * - * @param string $dataDir - * @return void + * + * @param string|null $dataDir */ public function __construct($dataDir = null) { @@ -75,15 +74,15 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { if (!is_dir($dataDir)) mkdir($dataDir); $this->dataDir = $dataDir; - } + } /** * Initialize the plugin * * This is called automatically be the Server class after this plugin is * added with Sabre_DAV_Server::addPlugin() - * - * @param Sabre_DAV_Server $server + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { @@ -97,11 +96,12 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { /** * This method is called before any HTTP method handler * - * This method intercepts any GET, DELETE, PUT and PROPFIND calls to + * This method intercepts any GET, DELETE, PUT and PROPFIND calls to * filenames that are known to match the 'temporary file' regex. - * - * @param string $method - * @return bool + * + * @param string $method + * @param string $uri + * @return bool */ public function beforeMethod($method, $uri) { @@ -125,33 +125,33 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { /** * This method is invoked if some subsystem creates a new file. * - * This is used to deal with HTTP LOCK requests which create a new + * This is used to deal with HTTP LOCK requests which create a new * file. - * - * @param string $uri - * @param resource $data - * @return bool + * + * @param string $uri + * @param resource $data + * @return bool */ public function beforeCreateFile($uri,$data) { if ($tempPath = $this->isTempFile($uri)) { - + $hR = $this->server->httpResponse; $hR->setHeader('X-Sabre-Temp','true'); file_put_contents($tempPath,$data); return false; } - return true; + return true; } /** * This method will check if the url matches the temporary file pattern - * if it does, it will return an path based on $this->dataDir for the + * if it does, it will return an path based on $this->dataDir for the * temporary file storage. - * - * @param string $path - * @return boolean|string + * + * @param string $path + * @return boolean|string */ protected function isTempFile($path) { @@ -161,7 +161,7 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { foreach($this->temporaryFilePatterns as $tempFile) { if (preg_match($tempFile,$tempPath)) { - return $this->dataDir . '/sabredav_' . md5($path) . '.tempfile'; + return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; } } @@ -175,9 +175,9 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { * This method handles the GET method for temporary files. * If the file doesn't exist, it will return false which will kick in * the regular system for the GET method. - * - * @param string $tempLocation - * @return bool + * + * @param string $tempLocation + * @return bool */ public function httpGet($tempLocation) { @@ -195,9 +195,9 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { /** * This method handles the PUT method. - * - * @param string $tempLocation - * @return bool + * + * @param string $tempLocation + * @return bool */ public function httpPut($tempLocation) { @@ -205,7 +205,7 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { $hR->setHeader('X-Sabre-Temp','true'); $newFile = !file_exists($tempLocation); - + if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { throw new Sabre_DAV_Exception_PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); } @@ -219,11 +219,11 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { /** * This method handles the DELETE method. * - * If the file didn't exist, it will return false, which will make the + * If the file didn't exist, it will return false, which will make the * standard HTTP DELETE handler kick in. - * - * @param string $tempLocation - * @return bool + * + * @param string $tempLocation + * @return bool */ public function httpDelete($tempLocation) { @@ -238,25 +238,26 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { } /** - * This method handles the PROPFIND method. + * This method handles the PROPFIND method. * * It's a very lazy method, it won't bother checking the request body * for which properties were requested, and just sends back a default * set of properties. * - * @param string $tempLocation - * @return void + * @param string $tempLocation + * @param string $uri + * @return bool */ public function httpPropfind($tempLocation, $uri) { if (!file_exists($tempLocation)) return true; - + $hR = $this->server->httpResponse; $hR->setHeader('X-Sabre-Temp','true'); $hR->sendStatus(207); $hR->setHeader('Content-Type','application/xml; charset=utf-8'); - $requestedProps = $this->server->parsePropFindRequest($this->server->httpRequest->getBody(true)); + $this->server->parsePropFindRequest($this->server->httpRequest->getBody(true)); $properties = array( 'href' => $uri, @@ -264,7 +265,7 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { '{DAV:}getlastmodified' => new Sabre_DAV_Property_GetLastModified(filemtime($tempLocation)), '{DAV:}getcontentlength' => filesize($tempLocation), '{DAV:}resourcetype' => new Sabre_DAV_Property_ResourceType(null), - '{'.Sabre_DAV_Server::NS_SABREDAV.'}tempFile' => true, + '{'.Sabre_DAV_Server::NS_SABREDAV.'}tempFile' => true, ), ); @@ -276,4 +277,13 @@ class Sabre_DAV_TemporaryFileFilterPlugin extends Sabre_DAV_ServerPlugin { } + /** + * This method returns the directory where the temporary files should be stored. + * + * @return string + */ + protected function getDataDir() + { + return $this->dataDir; + } } diff --git a/3rdparty/Sabre/DAV/Tree.php b/3rdparty/Sabre/DAV/Tree.php index 98e6f62c9e..5021639415 100644 --- a/3rdparty/Sabre/DAV/Tree.php +++ b/3rdparty/Sabre/DAV/Tree.php @@ -1,34 +1,34 @@ getNodeForPath($path); return true; - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { return false; @@ -50,12 +50,12 @@ abstract class Sabre_DAV_Tree { * * @param string $sourcePath The source location * @param string $destinationPath The full destination path - * @return void + * @return void */ public function copy($sourcePath, $destinationPath) { $sourceNode = $this->getNodeForPath($sourcePath); - + // grab the dirname and basename components list($destinationDir, $destinationName) = Sabre_DAV_URLUtil::splitPath($destinationPath); @@ -67,9 +67,9 @@ abstract class Sabre_DAV_Tree { } /** - * Moves a file from one location to another - * - * @param string $sourcePath The path to the file which should be moved + * Moves a file from one location to another + * + * @param string $sourcePath The path to the file which should be moved * @param string $destinationPath The full destination path, so not just the destination parent node * @return int */ @@ -91,26 +91,26 @@ abstract class Sabre_DAV_Tree { } /** - * Deletes a node from the tree - * - * @param string $path + * Deletes a node from the tree + * + * @param string $path * @return void */ public function delete($path) { $node = $this->getNodeForPath($path); $node->delete(); - + list($parent) = Sabre_DAV_URLUtil::splitPath($path); $this->markDirty($parent); } /** - * Returns a list of childnodes for a given path. - * - * @param string $path - * @return array + * Returns a list of childnodes for a given path. + * + * @param string $path + * @return array */ public function getChildren($path) { @@ -127,14 +127,14 @@ abstract class Sabre_DAV_Tree { * * node creations * * copy * * move - * * renaming nodes - * + * * renaming nodes + * * If Tree classes implement a form of caching, this will allow * them to make sure caches will be expired. - * + * * If a path is passed, it is assumed that the entire subtree is dirty * - * @param string $path + * @param string $path * @return void */ public function markDirty($path) { @@ -143,10 +143,11 @@ abstract class Sabre_DAV_Tree { } /** - * copyNode - * - * @param Sabre_DAV_INode $source - * @param Sabre_DAV_ICollection $destination + * copyNode + * + * @param Sabre_DAV_INode $source + * @param Sabre_DAV_ICollection $destinationParent + * @param string $destinationName * @return void */ protected function copyNode(Sabre_DAV_INode $source,Sabre_DAV_ICollection $destinationParent,$destinationName = null) { @@ -163,14 +164,14 @@ abstract class Sabre_DAV_Tree { fwrite($stream,$data); rewind($stream); $data = $stream; - } + } $destinationParent->createFile($destinationName,$data); $destination = $destinationParent->getChild($destinationName); } elseif ($source instanceof Sabre_DAV_ICollection) { $destinationParent->createDirectory($destinationName); - + $destination = $destinationParent->getChild($destinationName); foreach($source->getChildren() as $child) { diff --git a/3rdparty/Sabre/DAV/Tree/Filesystem.php b/3rdparty/Sabre/DAV/Tree/Filesystem.php index 5c611047e0..85a9ee317b 100644 --- a/3rdparty/Sabre/DAV/Tree/Filesystem.php +++ b/3rdparty/Sabre/DAV/Tree/Filesystem.php @@ -1,12 +1,12 @@ getRealPath($path); - if (!file_exists($realPath)) throw new Sabre_DAV_Exception_FileNotFound('File at location ' . $realPath . ' not found'); - if (is_dir($realPath)) { + if (!file_exists($realPath)) throw new Sabre_DAV_Exception_NotFound('File at location ' . $realPath . ' not found'); + if (is_dir($realPath)) { return new Sabre_DAV_FS_Directory($path); } else { return new Sabre_DAV_FS_File($path); @@ -51,10 +50,10 @@ class Sabre_DAV_Tree_Filesystem extends Sabre_DAV_Tree { } /** - * Returns the real filesystem path for a webdav url. - * - * @param string $publicPath - * @return string + * Returns the real filesystem path for a webdav url. + * + * @param string $publicPath + * @return string */ protected function getRealPath($publicPath) { @@ -67,24 +66,24 @@ class Sabre_DAV_Tree_Filesystem extends Sabre_DAV_Tree { * * This method must work recursively and delete the destination * if it exists - * - * @param string $source - * @param string $destination + * + * @param string $source + * @param string $destination * @return void */ public function copy($source,$destination) { $source = $this->getRealPath($source); $destination = $this->getRealPath($destination); - $this->realCopy($source,$destination); + $this->realCopy($source,$destination); } /** - * Used by self::copy - * - * @param string $source - * @param string $destination + * Used by self::copy + * + * @param string $source + * @param string $destination * @return void */ protected function realCopy($source,$destination) { @@ -107,9 +106,9 @@ class Sabre_DAV_Tree_Filesystem extends Sabre_DAV_Tree { * Moves a file or directory recursively. * * If the destination exists, delete it first. - * - * @param string $source - * @param string $destination + * + * @param string $source + * @param string $destination * @return void */ public function move($source,$destination) { diff --git a/3rdparty/Sabre/DAV/URLUtil.php b/3rdparty/Sabre/DAV/URLUtil.php index 8f38749264..794665a44f 100644 --- a/3rdparty/Sabre/DAV/URLUtil.php +++ b/3rdparty/Sabre/DAV/URLUtil.php @@ -11,11 +11,11 @@ * Specifically, it was found that GVFS (gnome's webdav client) does not like encoding of ( and * ). Since these are reserved, but don't have a reserved meaning in url, these characters are * kept as-is. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_URLUtil { @@ -24,46 +24,42 @@ class Sabre_DAV_URLUtil { * Encodes the path of a url. * * slashes (/) are treated as path-separators. - * - * @param string $path - * @return string + * + * @param string $path + * @return string */ static function encodePath($path) { - $valid_chars = '/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.~()'; - $newStr = ''; - for( $i=0; isset($path[$i]); ++$i ) { - if( strpos($valid_chars,($c=$path[$i]))===false ) $newStr .= '%'.sprintf('%02x',ord($c)); - else $newStr .= $c; - } - return $newStr; - + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/])/',function($match) { + + return '%'.sprintf('%02x',ord($match[0])); + + }, $path); + } /** * Encodes a 1 segment of a path * * Slashes are considered part of the name, and are encoded as %2f - * - * @param string $pathSegment - * @return string + * + * @param string $pathSegment + * @return string */ static function encodePathSegment($pathSegment) { - $valid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.~()'; - $newStr = ''; - for( $i=0; isset($pathSegment[$i]); ++$i ) { - if( strpos($valid_chars,($c=$pathSegment[$i]))===false ) $newStr .= '%'.sprintf('%02x',ord($c)); - else $newStr .= $c; - } - return $newStr; + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)])/',function($match) { + + return '%'.sprintf('%02x',ord($match[0])); + + }, $pathSegment); } /** * Decodes a url-encoded path * - * @param string $path - * @return string + * @param string $path + * @return string */ static function decodePath($path) { @@ -74,17 +70,17 @@ class Sabre_DAV_URLUtil { /** * Decodes a url-encoded path segment * - * @param string $path - * @return string + * @param string $path + * @return string */ static function decodePathSegment($path) { - $path = urldecode($path); + $path = rawurldecode($path); $encoding = mb_detect_encoding($path, array('UTF-8','ISO-8859-1')); switch($encoding) { - case 'ISO-8859-1' : + case 'ISO-8859-1' : $path = utf8_encode($path); } @@ -94,7 +90,7 @@ class Sabre_DAV_URLUtil { } /** - * Returns the 'dirname' and 'basename' for a path. + * Returns the 'dirname' and 'basename' for a path. * * The reason there is a custom function for this purpose, is because * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale is used) @@ -108,8 +104,8 @@ class Sabre_DAV_URLUtil { * If there is no dirname, it will return an empty string. Any / appearing at the end of the * string is stripped off. * - * @param string $path - * @return array + * @param string $path + * @return array */ static function splitPath($path) { diff --git a/3rdparty/Sabre/DAV/UUIDUtil.php b/3rdparty/Sabre/DAV/UUIDUtil.php index e42a536ad8..f0eebe598e 100644 --- a/3rdparty/Sabre/DAV/UUIDUtil.php +++ b/3rdparty/Sabre/DAV/UUIDUtil.php @@ -4,13 +4,13 @@ * UUID Utility * * This class has static methods to generate and validate UUID's. - * UUIDs are used a decent amount within various *DAV standards, so it made + * UUIDs are used a decent amount within various *DAV standards, so it made * sense to include it. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_UUIDUtil { @@ -18,10 +18,10 @@ class Sabre_DAV_UUIDUtil { /** * Returns a pseudo-random v4 UUID * - * This function is based on a comment by Andrew Moore on php.net - * + * This function is based on a comment by Andrew Moore on php.net + * * @see http://www.php.net/manual/en/function.uniqid.php#94959 - * @return void + * @return string */ static function getUUID() { @@ -48,9 +48,9 @@ class Sabre_DAV_UUIDUtil { /** * Checks if a string is a valid UUID. - * - * @param string $uuid - * @return bool + * + * @param string $uuid + * @return bool */ static function validateUUID($uuid) { diff --git a/3rdparty/Sabre/DAV/Version.php b/3rdparty/Sabre/DAV/Version.php index 6bece1985e..5e5d15e403 100644 --- a/3rdparty/Sabre/DAV/Version.php +++ b/3rdparty/Sabre/DAV/Version.php @@ -2,10 +2,10 @@ /** * This class contains the SabreDAV version constants. - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -14,7 +14,7 @@ class Sabre_DAV_Version { /** * Full version number */ - const VERSION = '1.5.4'; + const VERSION = '1.6.2'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/DAV/XMLUtil.php b/3rdparty/Sabre/DAV/XMLUtil.php index bd05be4b22..60eff3b159 100644 --- a/3rdparty/Sabre/DAV/XMLUtil.php +++ b/3rdparty/Sabre/DAV/XMLUtil.php @@ -2,32 +2,32 @@ /** * XML utilities for WebDAV - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_XMLUtil { /** * Returns the 'clark notation' for an element. - * + * * For example, and element encoded as: * * will be returned as: * {http://www.example.org}myelem * * This format is used throughout the SabreDAV sourcecode. - * Elements encoded with the urn:DAV namespace will + * Elements encoded with the urn:DAV namespace will * be returned as if they were in the DAV: namespace. This is to avoid * compatibility problems. * * This function will return null if a nodetype other than an Element is passed. * - * @param DOMElement $dom - * @return string + * @param DOMNode $dom + * @return string */ static function toClarkNotation(DOMNode $dom) { @@ -35,21 +35,21 @@ class Sabre_DAV_XMLUtil { // Mapping back to the real namespace, in case it was dav if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI; - + // Mapping to clark notation return '{' . $ns . '}' . $dom->localName; } /** - * Parses a clark-notation string, and returns the namespace and element + * Parses a clark-notation string, and returns the namespace and element * name components. * * If the string was invalid, it will throw an InvalidArgumentException. - * + * * @param string $str - * @throws InvalidArgumentException - * @return array + * @throws InvalidArgumentException + * @return array */ static function parseClarkNotation($str) { @@ -70,6 +70,9 @@ class Sabre_DAV_XMLUtil { * * This is unfortunately needed, because the DAV: namespace violates the xml namespaces * spec, and causes the DOM to throw errors + * + * @param string $xmlDocument + * @return array|string|null */ static function convertDAVNamespace($xmlDocument) { @@ -83,17 +86,17 @@ class Sabre_DAV_XMLUtil { * This method provides a generic way to load a DOMDocument for WebDAV use. * * This method throws a Sabre_DAV_Exception_BadRequest exception for any xml errors. - * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV. - * + * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV. + * * @param string $xml - * @throws Sabre_DAV_Exception_BadRequest - * @return DOMDocument + * @throws Sabre_DAV_Exception_BadRequest + * @return DOMDocument */ static function loadDOMDocument($xml) { if (empty($xml)) throw new Sabre_DAV_Exception_BadRequest('Empty XML document sent'); - + // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower) // does not support this, so we must intercept this and convert to UTF-8. if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") { @@ -111,7 +114,7 @@ class Sabre_DAV_XMLUtil { // Retaining old error setting $oldErrorSetting = libxml_use_internal_errors(true); - // Clearing any previous errors + // Clearing any previous errors libxml_clear_errors(); $dom = new DOMDocument(); @@ -135,7 +138,7 @@ class Sabre_DAV_XMLUtil { /** * Parses all WebDAV properties out of a DOM Element * - * Generally WebDAV properties are encloded in {DAV:}prop elements. This + * Generally WebDAV properties are enclosed in {DAV:}prop elements. This * method helps by going through all these and pulling out the actual * propertynames, making them array keys and making the property values, * well.. the array values. @@ -145,7 +148,7 @@ class Sabre_DAV_XMLUtil { * * Complex values are supported through the propertyMap argument. The * propertyMap should have the clark-notation properties as it's keys, and - * classnames as values. + * classnames as values. * * When any of these properties are found, the unserialize() method will be * (statically) called. The result of this method is used as the value. @@ -168,7 +171,7 @@ class Sabre_DAV_XMLUtil { $propertyName = Sabre_DAV_XMLUtil::toClarkNotation($propNodeData); if (isset($propertyMap[$propertyName])) { - $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData); + $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData); } else { $propList[$propertyName] = $propNodeData->textContent; } diff --git a/3rdparty/Sabre/DAV/includes.php b/3rdparty/Sabre/DAV/includes.php new file mode 100644 index 0000000000..6a4890677e --- /dev/null +++ b/3rdparty/Sabre/DAV/includes.php @@ -0,0 +1,97 @@ +principalPrefix); - return $name; + return $name; } /** - * Return the list of users - * - * @return void + * Return the list of users + * + * @return array */ public function getChildren() { @@ -99,23 +98,57 @@ abstract class Sabre_DAVACL_AbstractPrincipalCollection extends Sabre_DAV_Collec } - return $children; + return $children; } /** * Returns a child object, by its name. - * + * * @param string $name - * @throws Sabre_DAV_Exception_FileNotFound + * @throws Sabre_DAV_Exception_NotFound * @return Sabre_DAV_IPrincipal */ public function getChild($name) { $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/' . $name); - if (!$principalInfo) throw new Sabre_DAV_Exception_FileNotFound('Principal with name ' . $name . ' not found'); + if (!$principalInfo) throw new Sabre_DAV_Exception_NotFound('Principal with name ' . $name . ' not found'); return $this->getChildForPrincipal($principalInfo); } + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. You should at least allow searching on + * http://sabredav.org/ns}email-address. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * If multiple properties are being searched on, the search should be + * AND'ed. + * + * This method should simply return a list of 'child names', which may be + * used to call $this->getChild in the future. + * + * @param array $searchProperties + * @return array + */ + public function searchPrincipals(array $searchProperties) { + + $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties); + $r = array(); + + foreach($result as $row) { + list(, $r[]) = Sabre_DAV_URLUtil::splitPath($row); + } + + return $r; + + } + } diff --git a/3rdparty/Sabre/DAVACL/Exception/AceConflict.php b/3rdparty/Sabre/DAVACL/Exception/AceConflict.php index d10aeb4345..4b9f93b003 100644 --- a/3rdparty/Sabre/DAVACL/Exception/AceConflict.php +++ b/3rdparty/Sabre/DAVACL/Exception/AceConflict.php @@ -1,12 +1,12 @@ ownerDocument; - + $np = $doc->createElementNS('DAV:','d:no-ace-conflict'); $errorNode->appendChild($np); } } - -?> diff --git a/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php b/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php index 024ab6641f..9b055dd970 100644 --- a/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php +++ b/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php @@ -1,60 +1,61 @@ uri = $uri; $this->privileges = $privileges; + parent::__construct('User did not have the required privileges (' . implode(',', $privileges) . ') for path "' . $uri . '"'); + } /** * Adds in extra information in the xml response. * * This method adds the {DAV:}need-privileges element as defined in rfc3744 - * - * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * + * @param Sabre_DAV_Server $server + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { - + $doc = $errorNode->ownerDocument; - + $np = $doc->createElementNS('DAV:','d:need-privileges'); $errorNode->appendChild($np); diff --git a/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php b/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php index 60f49ebff4..f44e3e3228 100644 --- a/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php +++ b/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php @@ -2,11 +2,11 @@ /** * Sabre_DAVACL_Exception_NoAbstract - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Exception_NoAbstract extends Sabre_DAV_Exception_PreconditionFailed { @@ -15,20 +15,18 @@ class Sabre_DAVACL_Exception_NoAbstract extends Sabre_DAV_Exception_Precondition * Adds in extra information in the xml response. * * This method adds the {DAV:}no-abstract element as defined in rfc3744 - * - * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * + * @param Sabre_DAV_Server $server + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { - + $doc = $errorNode->ownerDocument; - + $np = $doc->createElementNS('DAV:','d:no-abstract'); $errorNode->appendChild($np); } } - -?> diff --git a/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php b/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php index e056dc9e4f..8d1e38ca1b 100644 --- a/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php +++ b/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php @@ -2,11 +2,11 @@ /** * Sabre_DAVACL_Exception_NotRecognizedPrincipal - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Exception_NotRecognizedPrincipal extends Sabre_DAV_Exception_PreconditionFailed { @@ -15,20 +15,18 @@ class Sabre_DAVACL_Exception_NotRecognizedPrincipal extends Sabre_DAV_Exception_ * Adds in extra information in the xml response. * * This method adds the {DAV:}recognized-principal element as defined in rfc3744 - * - * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * + * @param Sabre_DAV_Server $server + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { - + $doc = $errorNode->ownerDocument; - + $np = $doc->createElementNS('DAV:','d:recognized-principal'); $errorNode->appendChild($np); } } - -?> diff --git a/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php b/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php index 27db7cdd7d..3b5d012d7f 100644 --- a/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php +++ b/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php @@ -2,11 +2,11 @@ /** * Sabre_DAVACL_Exception_NotSupportedPrivilege - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Exception_NotSupportedPrivilege extends Sabre_DAV_Exception_PreconditionFailed { @@ -15,20 +15,18 @@ class Sabre_DAVACL_Exception_NotSupportedPrivilege extends Sabre_DAV_Exception_P * Adds in extra information in the xml response. * * This method adds the {DAV:}not-supported-privilege element as defined in rfc3744 - * - * @param Sabre_DAV_Server $server - * @param DOMElement $errorNode + * + * @param Sabre_DAV_Server $server + * @param DOMElement $errorNode * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) { - + $doc = $errorNode->ownerDocument; - + $np = $doc->createElementNS('DAV:','d:not-supported-privilege'); $errorNode->appendChild($np); } } - -?> diff --git a/3rdparty/Sabre/DAVACL/IACL.php b/3rdparty/Sabre/DAVACL/IACL.php index 506be4248d..003e699348 100644 --- a/3rdparty/Sabre/DAVACL/IACL.php +++ b/3rdparty/Sabre/DAVACL/IACL.php @@ -4,11 +4,11 @@ * ACL-enabled node * * If you want to add WebDAV ACL to a node, you must implement this class - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_DAVACL_IACL extends Sabre_DAV_INode { @@ -16,8 +16,8 @@ interface Sabre_DAVACL_IACL extends Sabre_DAV_INode { /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ function getOwner(); @@ -26,8 +26,8 @@ interface Sabre_DAVACL_IACL extends Sabre_DAV_INode { * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ function getGroup(); @@ -35,24 +35,39 @@ interface Sabre_DAVACL_IACL extends Sabre_DAV_INode { * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ function getACL(); /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ function setACL(array $acl); + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet(); + + } diff --git a/3rdparty/Sabre/DAVACL/IPrincipal.php b/3rdparty/Sabre/DAVACL/IPrincipal.php index 7868811db7..fc7605bf62 100644 --- a/3rdparty/Sabre/DAVACL/IPrincipal.php +++ b/3rdparty/Sabre/DAVACL/IPrincipal.php @@ -2,50 +2,50 @@ /** * IPrincipal interface - * + * * Implement this interface to define your own principals - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_DAVACL_IPrincipal extends Sabre_DAV_INode { /** - * Returns a list of altenative urls for a principal - * + * Returns a list of alternative urls for a principal + * * This can for example be an email address, or ldap url. - * - * @return array + * + * @return array */ function getAlternateUriSet(); /** - * Returns the full principal url - * - * @return string + * Returns the full principal url + * + * @return string */ function getPrincipalUrl(); /** * Returns the list of group members - * + * * If this principal is a group, this function should return - * all member principal uri's for the group. - * + * all member principal uri's for the group. + * * @return array */ function getGroupMemberSet(); /** * Returns the list of groups this principal is member of - * + * * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array + * should return a list of principal uri's for it's members. + * + * @return array */ function getGroupMembership(); @@ -54,11 +54,11 @@ interface Sabre_DAVACL_IPrincipal extends Sabre_DAV_INode { * * If this principal is a group, this method sets all the group members. * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $principals - * @return void + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void */ function setGroupMemberSet(array $principals); @@ -66,9 +66,9 @@ interface Sabre_DAVACL_IPrincipal extends Sabre_DAV_INode { * Returns the displayname * * This should be a human readable name for the principal. - * If none is available, return the nodename. - * - * @return string + * If none is available, return the nodename. + * + * @return string */ function getDisplayName(); diff --git a/3rdparty/Sabre/DAVACL/IPrincipalBackend.php b/3rdparty/Sabre/DAVACL/IPrincipalBackend.php index 8899f6f80d..e798bf890c 100644 --- a/3rdparty/Sabre/DAVACL/IPrincipalBackend.php +++ b/3rdparty/Sabre/DAVACL/IPrincipalBackend.php @@ -3,14 +3,14 @@ /** * Implement this interface to create your own principal backends. * - * Creating backends for principals is entirely optional. You can also - * implement Sabre_DAVACL_IPrincipal directly. This interface is used solely by + * Creating backends for principals is entirely optional. You can also + * implement Sabre_DAVACL_IPrincipal directly. This interface is used solely by * Sabre_DAVACL_AbstractPrincipalCollection. * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ interface Sabre_DAVACL_IPrincipalBackend { @@ -18,56 +18,136 @@ interface Sabre_DAVACL_IPrincipalBackend { /** * Returns a list of principals based on a prefix. * - * This prefix will often contain something like 'principals'. You are only + * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * - * You are expected to return at least a 'uri' for every user, you can + * You are expected to return at least a 'uri' for every user, you can * return any additional properties if you wish so. Common properties are: - * {DAV:}displayname - * {http://sabredav.org/ns}email-address - This is a custom SabreDAV - * field that's actualy injected in a number of other properties. If + * {DAV:}displayname + * {http://sabredav.org/ns}email-address - This is a custom SabreDAV + * field that's actually injected in a number of other properties. If * you have an email address, use this property. - * - * @param string $prefixPath - * @return array + * + * @param string $prefixPath + * @return array */ function getPrincipalsByPrefix($prefixPath); /** * Returns a specific principal, specified by it's path. - * The returned structure should be the exact same as from - * getPrincipalsByPrefix. - * - * @param string $path - * @return array + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array */ function getPrincipalByPath($path); /** - * Returns the list of members for a group-principal - * - * @param string $principal - * @return array + * Updates one ore more webdav properties on a principal. + * + * The list of mutations is supplied as an array. Each key in the array is + * a propertyname, such as {DAV:}displayname. + * + * Each value is the actual value to be updated. If a value is null, it + * must be deleted. + * + * This method should be atomic. It must either completely succeed, or + * completely fail. Success and failure can simply be returned as 'true' or + * 'false'. + * + * It is also possible to return detailed failure information. In that case + * an array such as this should be returned: + * + * array( + * 200 => array( + * '{DAV:}prop1' => null, + * ), + * 201 => array( + * '{DAV:}prop2' => null, + * ), + * 403 => array( + * '{DAV:}prop3' => null, + * ), + * 424 => array( + * '{DAV:}prop4' => null, + * ), + * ); + * + * In this previous example prop1 was successfully updated or deleted, and + * prop2 was succesfully created. + * + * prop3 failed to update due to '403 Forbidden' and because of this prop4 + * also could not be updated with '424 Failed dependency'. + * + * This last example was actually incorrect. While 200 and 201 could appear + * in 1 response, if there's any error (403) the other properties should + * always fail with 423 (failed dependency). + * + * But anyway, if you don't want to scratch your head over this, just + * return true or false. + * + * @param string $path + * @param array $mutations + * @return array|bool + */ + function updatePrincipal($path, $mutations); + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. You should at least allow searching on + * http://sabredav.org/ns}email-address. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * If multiple properties are being searched on, the search should be + * AND'ed. + * + * This method should simply return an array with full principal uri's. + * + * If somebody attempted to search on a property the backend does not + * support, you should simply return 0 results. + * + * You can also just return 0 results if you choose to not support + * searching at all, but keep in mind that this may stop certain features + * from working. + * + * @param string $prefixPath + * @param array $searchProperties + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties); + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array */ function getGroupMemberSet($principal); /** - * Returns the list of groups a principal is a member of - * - * @param string $principal - * @return array + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array */ function getGroupMembership($principal); /** * Updates the list of group members for a group principal. * - * The principals should be passed as a list of uri's. - * - * @param string $principal - * @param array $members + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members * @return void */ - function setGroupMemberSet($principal, array $members); + function setGroupMemberSet($principal, array $members); } diff --git a/3rdparty/Sabre/DAVACL/Plugin.php b/3rdparty/Sabre/DAVACL/Plugin.php index b964bdb5de..5c828c6d97 100644 --- a/3rdparty/Sabre/DAVACL/Plugin.php +++ b/3rdparty/Sabre/DAVACL/Plugin.php @@ -6,14 +6,14 @@ * This plugin provides funcitonality to enforce ACL permissions. * ACL is defined in RFC3744. * - * In addition it also provides support for the {DAV:}current-user-principal - * property, defined in RFC5397 and the {DAV:}expand-property report, as - * defined in RFC3253. - * + * In addition it also provides support for the {DAV:}current-user-principal + * property, defined in RFC5397 and the {DAV:}expand-property report, as + * defined in RFC3253. + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { @@ -40,16 +40,16 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { const R_RECURSIVEPARENTS = 3; /** - * Reference to server object. - * - * @var Sabre_DAV_Server + * Reference to server object. + * + * @var Sabre_DAV_Server */ protected $server; /** * List of urls containing principal collections. - * Modify this if your principals are located elsewhere. - * + * Modify this if your principals are located elsewhere. + * * @var array */ public $principalCollectionSet = array( @@ -57,14 +57,14 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { ); /** - * By default ACL is only enforced for nodes that have ACL support (the - * ones that implement Sabre_DAVACL_IACL). For any other node, access is + * By default ACL is only enforced for nodes that have ACL support (the + * ones that implement Sabre_DAVACL_IACL). For any other node, access is * always granted. * - * To override this behaviour you can turn this setting off. This is useful + * To override this behaviour you can turn this setting off. This is useful * if you plan to fully support ACL in the entire tree. * - * @var bool + * @var bool */ public $allowAccessToNodesWithoutACL = true; @@ -72,28 +72,50 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * By default nodes that are inaccessible by the user, can still be seen * in directory listings (PROPFIND on parent with Depth: 1) * - * In certain cases it's desirable to hide inaccessible nodes. Setting this + * In certain cases it's desirable to hide inaccessible nodes. Setting this * to true will cause these nodes to be hidden from directory listings. - * - * @var bool + * + * @var bool */ public $hideNodesFromListings = false; /** - * This string is prepended to the username of the currently logged in - * user. This allows the plugin to determine the principal path based on + * This string is prepended to the username of the currently logged in + * user. This allows the plugin to determine the principal path based on * the username. - * + * * @var string */ public $defaultUsernamePath = 'principals'; + /** + * This list of properties are the properties a client can search on using + * the {DAV:}principal-property-search report. + * + * The keys are the property names, values are descriptions. + * + * @var array + */ + public $principalSearchPropertySet = array( + '{DAV:}displayname' => 'Display name', + '{http://sabredav.org/ns}email-address' => 'Email address', + ); + + /** + * Any principal uri's added here, will automatically be added to the list + * of ACL's. They will effectively receive {DAV:}all privileges, as a + * protected privilege. + * + * @var array + */ + public $adminPrincipals = array(); + /** * Returns a list of features added by this plugin. * * This list is used in the response of a HTTP OPTIONS request. - * - * @return array + * + * @return array */ public function getFeatures() { @@ -102,10 +124,10 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Returns a list of available methods for a given url - * - * @param string $uri - * @return array + * Returns a list of available methods for a given url + * + * @param string $uri + * @return array */ public function getMethods($uri) { @@ -115,11 +137,11 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns a plugin name. - * + * * Using this name other plugins will be able to access other plugins - * using Sabre_DAV_Server::getPlugin - * - * @return string + * using Sabre_DAV_Server::getPlugin + * + * @return string */ public function getPluginName() { @@ -131,37 +153,38 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them - * + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * * @param string $uri - * @return array + * @return array */ public function getSupportedReportSet($uri) { return array( '{DAV:}expand-property', '{DAV:}principal-property-search', - '{DAV:}principal-search-property-set', + '{DAV:}principal-search-property-set', ); } /** - * Checks if the current user has the specified privilege(s). - * + * Checks if the current user has the specified privilege(s). + * * You can specify a single privilege, or a list of privileges. * This method will throw an exception if the privilege is not available * and return true otherwise. * * @param string $uri * @param array|string $privileges - * @param bool $throwExceptions if set to false, this method won't through exceptions. + * @param int $recursion + * @param bool $throwExceptions if set to false, this method won't through exceptions. * @throws Sabre_DAVACL_Exception_NeedPrivileges - * @return bool + * @return bool */ - public function checkPrivileges($uri,$privileges,$recursion = self::R_PARENT, $throwExceptions = true) { + public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { if (!is_array($privileges)) $privileges = array($privileges); @@ -171,7 +194,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { if ($this->allowAccessToNodesWithoutACL) { return true; } else { - if ($throwExceptions) + if ($throwExceptions) throw new Sabre_DAVACL_Exception_NeedPrivileges($uri,$privileges); else return false; @@ -189,7 +212,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } if ($failed) { - if ($throwExceptions) + if ($throwExceptions) throw new Sabre_DAVACL_Exception_NeedPrivileges($uri,$failed); else return false; @@ -202,9 +225,9 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * Returns the standard users' principal. * * This is one authorative principal url for the current user. - * This method will return null if the user wasn't logged in. - * - * @return string|null + * This method will return null if the user wasn't logged in. + * + * @return string|null */ public function getCurrentUserPrincipal() { @@ -220,9 +243,9 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * Returns a list of principals that's associated to the current - * user, either directly or through group membership. - * - * @return array + * user, either directly or through group membership. + * + * @return array */ public function getCurrentUserPrincipals() { @@ -236,7 +259,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { while(count($check)) { $principal = array_shift($check); - + $node = $this->server->tree->getNodeForPath($principal); if ($node instanceof Sabre_DAVACL_IPrincipal) { foreach($node->getGroupMembership() as $groupMember) { @@ -262,11 +285,38 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * Returns the supported privilege structure for this ACL plugin. * * See RFC3744 for more details. Currently we default on a simple, - * standard structure. - * - * @return array + * standard structure. + * + * You can either get the list of privileges by a uri (path) or by + * specifying a Node. + * + * @param string|Sabre_DAV_INode $node + * @return array */ - public function getSupportedPrivilegeSet() { + public function getSupportedPrivilegeSet($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + + if ($node instanceof Sabre_DAVACL_IACL) { + $result = $node->getSupportedPrivilegeSet(); + + if ($result) + return $result; + } + + return self::getDefaultSupportedPrivilegeSet(); + + } + + /** + * Returns a fairly standard set of privileges, which may be useful for + * other systems to use as a basis. + * + * @return array + */ + static function getDefaultSupportedPrivilegeSet() { return array( 'privilege' => '{DAV:}all', @@ -314,7 +364,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { ), ), ), // {DAV:}write - ), + ), ); // {DAV:}all } @@ -329,12 +379,13 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * - aggregates * - abstract * - concrete - * - * @return array + * + * @param string|Sabre_DAV_INode $node + * @return array */ - final public function getFlatPrivilegeSet() { + final public function getFlatPrivilegeSet($node) { - $privs = $this->getSupportedPrivilegeSet(); + $privs = $this->getSupportedPrivilegeSet($node); $flat = array(); $this->getFPSTraverse($privs, null, $flat); @@ -346,9 +397,12 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * Traverses the privilege set tree for reordering * - * This function is solely used by getFlatPrivilegeSet, and would have been + * This function is solely used by getFlatPrivilegeSet, and would have been * a closure if it wasn't for the fact I need to support PHP 5.2. - * + * + * @param array $priv + * @param $concrete + * @param array $flat * @return void */ final private function getFPSTraverse($priv, $concrete, &$flat) { @@ -368,7 +422,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { if (isset($priv['aggregates'])) { foreach($priv['aggregates'] as $subPriv) { - + $this->getFPSTraverse($subPriv, $myPriv['concrete'], $flat); } @@ -382,8 +436,8 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * * Either a uri or a Sabre_DAV_INode may be passed. * - * null will be returned if the node doesn't support ACLs. - * + * null will be returned if the node doesn't support ACLs. + * * @param string|Sabre_DAV_INode $node * @return array */ @@ -392,10 +446,18 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { if (is_string($node)) { $node = $this->server->tree->getNodeForPath($node); } - if ($node instanceof Sabre_DAVACL_IACL) { - return $node->getACL(); + if (!$node instanceof Sabre_DAVACL_IACL) { + return null; } - return null; + $acl = $node->getACL(); + foreach($this->adminPrincipals as $adminPrincipal) { + $acl[] = array( + 'principal' => $adminPrincipal, + 'privilege' => '{DAV:}all', + 'protected' => true, + ); + } + return $acl; } @@ -405,10 +467,10 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * * Either a uri or a Sabre_DAV_INode may be passed. * - * null will be returned if the node doesn't support ACLs. - * - * @param string|Sabre_DAV_INode $node - * @return array + * null will be returned if the node doesn't support ACLs. + * + * @param string|Sabre_DAV_INode $node + * @return array */ public function getCurrentUserPrivilegeSet($node) { @@ -417,6 +479,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } $acl = $this->getACL($node); + if (is_null($acl)) return null; $principals = $this->getCurrentUserPrincipals(); @@ -425,27 +488,121 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { foreach($acl as $ace) { - if (in_array($ace['principal'], $principals)) { - $collected[] = $ace; + $principal = $ace['principal']; + + switch($principal) { + + case '{DAV:}owner' : + $owner = $node->getOwner(); + if ($owner && in_array($owner, $principals)) { + $collected[] = $ace; + } + break; + + + // 'all' matches for every user + case '{DAV:}all' : + + // 'authenticated' matched for every user that's logged in. + // Since it's not possible to use ACL while not being logged + // in, this is also always true. + case '{DAV:}authenticated' : + $collected[] = $ace; + break; + + // 'unauthenticated' can never occur either, so we simply + // ignore these. + case '{DAV:}unauthenticated' : + break; + + default : + if (in_array($ace['principal'], $principals)) { + $collected[] = $ace; + } + break; + } + + } // Now we deduct all aggregated privileges. - $flat = $this->getFlatPrivilegeSet(); + $flat = $this->getFlatPrivilegeSet($node); $collected2 = array(); - foreach($collected as $privilege) { + while(count($collected)) { - $collected2[] = $privilege['privilege']; - foreach($flat[$privilege['privilege']]['aggregates'] as $subPriv) { - if (!in_array($subPriv, $collected2)) - $collected2[] = $subPriv; + $current = array_pop($collected); + $collected2[] = $current['privilege']; + + foreach($flat[$current['privilege']]['aggregates'] as $subPriv) { + $collected2[] = $subPriv; + $collected[] = $flat[$subPriv]; } } - return $collected2; + return array_values(array_unique($collected2)); + + } + + /** + * Principal property search + * + * This method can search for principals matching certain values in + * properties. + * + * This method will return a list of properties for the matched properties. + * + * @param array $searchProperties The properties to search on. This is a + * key-value list. The keys are property + * names, and the values the strings to + * match them on. + * @param array $requestedProperties This is the list of properties to + * return for every match. + * @param string $collectionUri The principal collection to search on. + * If this is ommitted, the standard + * principal collection-set will be used. + * @return array This method returns an array structure similar to + * Sabre_DAV_Server::getPropertiesForPath. Returned + * properties are index by a HTTP status code. + * + */ + public function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null) { + + if (!is_null($collectionUri)) { + $uris = array($collectionUri); + } else { + $uris = $this->principalCollectionSet; + } + + $lookupResults = array(); + foreach($uris as $uri) { + + $principalCollection = $this->server->tree->getNodeForPath($uri); + if (!$principalCollection instanceof Sabre_DAVACL_AbstractPrincipalCollection) { + // Not a principal collection, we're simply going to ignore + // this. + continue; + } + + $results = $principalCollection->searchPrincipals($searchProperties); + foreach($results as $result) { + $lookupResults[] = rtrim($uri,'/') . '/' . $result; + } + + } + + $matches = array(); + + foreach($lookupResults as $lookupResult) { + + list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0); + + } + + return $matches; } @@ -453,8 +610,8 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * Sets up the plugin * * This method is automatically called by the server class. - * - * @param Sabre_DAV_Server $server + * + * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { @@ -485,11 +642,11 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { '{DAV:}group' ); - // Automatically mapping nodes implementing IPrincipal to the + // Automatically mapping nodes implementing IPrincipal to the // {DAV:}principal resourcetype. $server->resourceTypeMapping['Sabre_DAVACL_IPrincipal'] = '{DAV:}principal'; - // Mapping the group-member-set property to the HrefList property + // Mapping the group-member-set property to the HrefList property // class. $server->propertyMap['{DAV:}group-member-set'] = 'Sabre_DAV_Property_HrefList'; @@ -499,10 +656,10 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /* {{{ Event handlers */ /** - * Triggered before any method is handled - * - * @param string $method - * @param string $uri + * Triggered before any method is handled + * + * @param string $method + * @param string $uri * @return void */ public function beforeMethod($method, $uri) { @@ -523,14 +680,14 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { case 'PUT' : case 'LOCK' : - case 'UNLOCK' : - // This method requires the write-content priv if the node - // already exists, and bind on the parent if the node is being - // created. - // The bind privilege is handled in the beforeBind event. + case 'UNLOCK' : + // This method requires the write-content priv if the node + // already exists, and bind on the parent if the node is being + // created. + // The bind privilege is handled in the beforeBind event. $this->checkPrivileges($uri,'{DAV:}write-content'); break; - + case 'PROPPATCH' : $this->checkPrivileges($uri,'{DAV:}write-properties'); @@ -543,16 +700,16 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { case 'COPY' : case 'MOVE' : // Copy requires read privileges on the entire source tree. - // If the target exists write-content normally needs to be - // checked, however, we're deleting the node beforehand and - // creating a new one after, so this is handled by the + // If the target exists write-content normally needs to be + // checked, however, we're deleting the node beforehand and + // creating a new one after, so this is handled by the // beforeUnbind event. - // - // The creation of the new node is handled by the beforeBind + // + // The creation of the new node is handled by the beforeBind // event. // - // If MOVE is used beforeUnbind will also be used to check if - // the sourcenode can be deleted. + // If MOVE is used beforeUnbind will also be used to check if + // the sourcenode can be deleted. $this->checkPrivileges($uri,'{DAV:}read',self::R_RECURSIVE); break; @@ -563,11 +720,11 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * Triggered before a new node is created. - * + * * This allows us to check permissions for any operation that creates a * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE. - * - * @param string $uri + * + * @param string $uri * @return void */ public function beforeBind($uri) { @@ -578,12 +735,12 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Triggered before a node is deleted - * - * This allows us to check permissions for any operation that will delete - * an existing node. - * - * @param string $uri + * Triggered before a node is deleted + * + * This allows us to check permissions for any operation that will delete + * an existing node. + * + * @param string $uri * @return void */ public function beforeUnbind($uri) { @@ -594,26 +751,26 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } /** - * Triggered before a node is unlocked. - * - * @param string $uri + * Triggered before a node is unlocked. + * + * @param string $uri * @param Sabre_DAV_Locks_LockInfo $lock - * @TODO: not yet implemented + * @TODO: not yet implemented * @return void */ public function beforeUnlock($uri, Sabre_DAV_Locks_LockInfo $lock) { - + } /** - * Triggered before properties are looked up in specific nodes. - * - * @param string $uri - * @param Sabre_DAV_INode $node - * @param array $requestedProperties + * Triggered before properties are looked up in specific nodes. + * + * @param string $uri + * @param Sabre_DAV_INode $node + * @param array $requestedProperties * @param array $returnedProperties - * @TODO really should be broken into multiple methods, or even a class. + * @TODO really should be broken into multiple methods, or even a class. * @return void */ public function beforeGetProperties($uri, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) { @@ -633,7 +790,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } return; - } + } /* Adding principal properties */ if ($node instanceof Sabre_DAVACL_IPrincipal) { @@ -692,7 +849,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { if (false !== ($index = array_search('{DAV:}supported-privilege-set', $requestedProperties))) { unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}supported-privilege-set'] = new Sabre_DAVACL_Property_SupportedPrivilegeSet($this->getSupportedPrivilegeSet()); + $returnedProperties[200]['{DAV:}supported-privilege-set'] = new Sabre_DAVACL_Property_SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node)); } if (false !== ($index = array_search('{DAV:}current-user-privilege-set', $requestedProperties))) { @@ -730,16 +887,24 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } + /* The acl-restrictions property contains information on how privileges + * must behave. + */ + if (false !== ($index = array_search('{DAV:}acl-restrictions', $requestedProperties))) { + unset($requestedProperties[$index]); + $returnedProperties[200]['{DAV:}acl-restrictions'] = new Sabre_DAVACL_Property_AclRestrictions(); + } + } /** - * This method intercepts PROPPATCH methods and make sure the - * group-member-set is updated correctly. - * - * @param array $propertyDelta - * @param array $result - * @param Sabre_DAV_INode $node - * @return void + * This method intercepts PROPPATCH methods and make sure the + * group-member-set is updated correctly. + * + * @param array $propertyDelta + * @param array $result + * @param Sabre_DAV_INode $node + * @return bool */ public function updateProperties(&$propertyDelta, &$result, Sabre_DAV_INode $node) { @@ -763,18 +928,18 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } $node->setGroupMemberSet($memberSet); - + $result[200]['{DAV:}group-member-set'] = null; unset($propertyDelta['{DAV:}group-member-set']); } /** - * This method handels HTTP REPORT requests - * - * @param string $reportName - * @param DOMNode $dom - * @return void + * This method handels HTTP REPORT requests + * + * @param string $reportName + * @param DOMNode $dom + * @return bool */ public function report($reportName, $dom) { @@ -785,7 +950,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { return false; case '{DAV:}principal-search-property-set' : $this->principalSearchPropertySetReport($dom); - return false; + return false; case '{DAV:}expand-property' : $this->expandPropertyReport($dom); return false; @@ -795,12 +960,12 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } /** - * This event is triggered for any HTTP method that is not known by the - * webserver. + * This event is triggered for any HTTP method that is not known by the + * webserver. * - * @param string $method - * @param string $uri - * @return void + * @param string $method + * @param string $uri + * @return bool */ public function unknownMethod($method, $uri) { @@ -817,12 +982,12 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * @param string $uri * @return void */ - public function httpACL($uri) { + public function httpACL($uri) { $body = $this->server->httpRequest->getBody(true); $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); - $newAcl = + $newAcl = Sabre_DAVACL_Property_Acl::unserialize($dom->firstChild) ->getPrivileges(); @@ -839,13 +1004,13 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { $oldAcl = $this->getACL($node); - $supportedPrivileges = $this->getFlatPrivilegeSet(); + $supportedPrivileges = $this->getFlatPrivilegeSet($node); - /* Checking if protected principals from the existing principal set are + /* Checking if protected principals from the existing principal set are not overwritten. */ - foreach($oldAcl as $k=>$oldAce) { + foreach($oldAcl as $oldAce) { - if (!isset($oldAce['protected']) || !$oldAce['protected']) continue; + if (!isset($oldAce['protected']) || !$oldAce['protected']) continue; $found = false; foreach($newAcl as $newAce) { @@ -853,16 +1018,16 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { $newAce['privilege'] === $oldAce['privilege'] && $newAce['principal'] === $oldAce['principal'] && $newAce['protected'] - ) + ) $found = true; } - if (!$found) + if (!$found) throw new Sabre_DAVACL_Exception_AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request'); } - foreach($newAcl as $k=>$newAce) { + foreach($newAcl as $newAce) { // Do we recognize the privilege if (!isset($supportedPrivileges[$newAce['privilege']])) { @@ -876,12 +1041,12 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { // Looking up the principal try { $principal = $this->server->tree->getNodeForPath($newAce['principal']); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { throw new Sabre_DAVACL_Exception_NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist'); } if (!($principal instanceof Sabre_DAVACL_IPrincipal)) { throw new Sabre_DAVACL_Exception_NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal'); - } + } } $node->setACL($newAcl); @@ -893,7 +1058,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /* Reports {{{ */ /** - * The expand-property report is defined in RFC3253 section 3-8. + * The expand-property report is defined in RFC3253 section 3-8. * * This report is very similar to a standard PROPFIND. The difference is * that it has the additional ability to look at properties containing a @@ -903,7 +1068,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * Other rfc's, such as ACL rely on this report, so it made sense to put * it in this plugin. * - * @param DOMElement $dom + * @param DOMElement $dom * @return void */ protected function expandPropertyReport($dom) { @@ -940,9 +1105,9 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * This method is used by expandPropertyReport to parse * out the entire HTTP request. - * - * @param DOMElement $node - * @return array + * + * @param DOMElement $node + * @return array */ protected function parseExpandPropertyReportRequest($node) { @@ -950,9 +1115,9 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { do { if (Sabre_DAV_XMLUtil::toClarkNotation($node)!=='{DAV:}property') continue; - + if ($node->firstChild) { - + $children = $this->parseExpandPropertyReportRequest($node->firstChild); } else { @@ -965,7 +1130,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { if (!$namespace) $namespace = 'DAV:'; $propName = '{'.$namespace.'}' . $node->getAttribute('name'); - $requestedProperties[$propName] = $children; + $requestedProperties[$propName] = $children; } while ($node = $node->nextSibling); @@ -979,11 +1144,12 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * * @param array $path * @param array $requestedProperties the list of required properties - * @param array $depth + * @param int $depth + * @return array */ - protected function expandProperties($path,array $requestedProperties,$depth) { + protected function expandProperties($path, array $requestedProperties, $depth) { - $foundProperties = $this->server->getPropertiesForPath($path,array_keys($requestedProperties),$depth); + $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth); $result = array(); @@ -993,7 +1159,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { // We're only traversing if sub-properties were requested if(count($childRequestedProperties)===0) continue; - + // We only have to do the expansion if the property was found // and it contains an href element. if (!array_key_exists($propertyName,$node[200])) continue; @@ -1006,7 +1172,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { $childProps = array(); foreach($hrefs as $href) { - $childProps = array_merge($childProps, $this->expandProperties($href,$childRequestedProperties,0)); + $childProps = array_merge($childProps, $this->expandProperties($href, $childRequestedProperties, 0)); } $node[200][$propertyName] = new Sabre_DAV_Property_ResponseList($childProps); @@ -1022,27 +1188,23 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * principalSearchPropertySetReport * - * This method responsible for handing the + * This method responsible for handing the * {DAV:}principal-search-property-set report. This report returns a list * of properties the client may search on, using the * {DAV:}principal-property-search report. - * - * @param DOMDocument $dom + * + * @param DOMDocument $dom * @return void */ protected function principalSearchPropertySetReport(DOMDocument $dom) { - $searchProperties = array( - '{DAV:}displayname' => 'display name' - ); - $httpDepth = $this->server->getHTTPDepth(0); if ($httpDepth!==0) { throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0'); } - - if ($dom->firstChild->hasChildNodes()) - throw new Sabre_DAV_Exception_BadRequest('The principal-search-property-set report element is not allowed to have child elements'); + + if ($dom->firstChild->hasChildNodes()) + throw new Sabre_DAV_Exception_BadRequest('The principal-search-property-set report element is not allowed to have child elements'); $dom = new DOMDocument('1.0','utf-8'); $dom->formatOutput = true; @@ -1055,16 +1217,16 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { } - $nsList = $this->server->xmlNamespaces; + $nsList = $this->server->xmlNamespaces; - foreach($searchProperties as $propertyName=>$description) { + foreach($this->principalSearchPropertySet as $propertyName=>$description) { $psp = $dom->createElement('d:principal-search-property'); $root->appendChild($psp); $prop = $dom->createElement('d:prop'); $psp->appendChild($prop); - + $propName = null; preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName); @@ -1088,78 +1250,25 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { /** * principalPropertySearchReport * - * This method is reponsible for handing the - * {DAV:}principal-property-search report. This report can be used for + * This method is responsible for handing the + * {DAV:}principal-property-search report. This report can be used for * clients to search for groups of principals, based on the value of one * or more properties. - * - * @param DOMDocument $dom + * + * @param DOMDocument $dom * @return void */ protected function principalPropertySearchReport(DOMDocument $dom) { - $searchableProperties = array( - '{DAV:}displayname' => 'display name' - - ); - list($searchProperties, $requestedProperties, $applyToPrincipalCollectionSet) = $this->parsePrincipalPropertySearchReportRequest($dom); - $result = array(); - - if ($applyToPrincipalCollectionSet) { - $uris = array(); - } else { - $uris = array($this->server->getRequestUri()); + $uri = null; + if (!$applyToPrincipalCollectionSet) { + $uri = $this->server->getRequestUri(); } + $result = $this->principalSearch($searchProperties, $requestedProperties, $uri); - $lookupResults = array(); - foreach($uris as $uri) { - - $p = array_keys($searchProperties); - $p[] = '{DAV:}resourcetype'; - $r = $this->server->getPropertiesForPath($uri, $p, 1); - - // The first item in the results is the parent, so we get rid of it. - array_shift($r); - $lookupResults = array_merge($lookupResults, $r); - } - - $matches = array(); - - foreach($lookupResults as $lookupResult) { - - // We're only looking for principals - if (!isset($lookupResult[200]['{DAV:}resourcetype']) || - (!($lookupResult[200]['{DAV:}resourcetype'] instanceof Sabre_DAV_Property_ResourceType)) || - !$lookupResult[200]['{DAV:}resourcetype']->is('{DAV:}principal')) continue; - - foreach($searchProperties as $searchProperty=>$searchValue) { - if (!isset($searchableProperties[$searchProperty])) { - // If a property is not 'searchable', the spec dictates - // this is not a match. - continue; - } - - if (isset($lookupResult[200][$searchProperty]) && - mb_stripos($lookupResult[200][$searchProperty], $searchValue, 0, 'UTF-8')!==false) { - $matches[] = $lookupResult['href']; - } - - } - - } - - $matchProperties = array(); - - foreach($matches as $match) { - - list($result) = $this->server->getPropertiesForPath($match, $requestedProperties, 0); - $matchProperties[] = $result; - - } - - $xml = $this->server->generateMultiStatus($matchProperties); + $xml = $this->server->generateMultiStatus($result); $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->sendBody($xml); @@ -1175,9 +1284,9 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { * This method returns an array with two elements: * 1. an array with properties to search on, and their values * 2. a list of propertyvalues that should be returned for the request. - * - * @param DOMDocument $dom - * @return array + * + * @param DOMDocument $dom + * @return array */ protected function parsePrincipalPropertySearchReportRequest($dom) { @@ -1193,8 +1302,9 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { // Parsing the search request foreach($dom->firstChild->childNodes as $searchNode) { - if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') + if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') { $applyToPrincipalCollectionSet = true; + } if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode)!=='{DAV:}property-search') continue; @@ -1208,7 +1318,7 @@ class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin { case '{DAV:}prop' : $property = Sabre_DAV_XMLUtil::parseProperties($searchNode); - reset($property); + reset($property); $propertyName = key($property); break; diff --git a/3rdparty/Sabre/DAVACL/Principal.php b/3rdparty/Sabre/DAVACL/Principal.php index 790603c900..51c6658afd 100644 --- a/3rdparty/Sabre/DAVACL/Principal.php +++ b/3rdparty/Sabre/DAVACL/Principal.php @@ -4,17 +4,17 @@ * Principal class * * This class is a representation of a simple principal - * - * Many WebDAV specs require a user to show up in the directory - * structure. + * + * Many WebDAV specs require a user to show up in the directory + * structure. * * This principal also has basic ACL settings, only allowing the principal - * access it's own principal. - * + * access it's own principal. + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPrincipal, Sabre_DAV_IProperties, Sabre_DAVACL_IACL { @@ -22,20 +22,21 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri /** * Struct with principal information. * - * @var array + * @var array */ protected $principalProperties; /** - * Principal backend - * - * @var Sabre_DAVACL_IPrincipalBackend + * Principal backend + * + * @var Sabre_DAVACL_IPrincipalBackend */ protected $principalBackend; /** - * Creates the principal object + * Creates the principal object * + * @param Sabre_DAVACL_IPrincipalBackend $principalBackend * @param array $principalProperties */ public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalProperties = array()) { @@ -49,22 +50,22 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri } /** - * Returns the full principal url - * - * @return string + * Returns the full principal url + * + * @return string */ public function getPrincipalUrl() { return $this->principalProperties['uri']; - } + } /** - * Returns a list of altenative urls for a principal - * + * Returns a list of alternative urls for a principal + * * This can for example be an email address, or ldap url. - * - * @return array + * + * @return array */ public function getAlternateUriSet() { @@ -79,16 +80,16 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri $uris[] = 'mailto:' . $this->principalProperties['{http://sabredav.org/ns}email-address']; } - return array_unique($uris); + return array_unique($uris); } /** * Returns the list of group members - * + * * If this principal is a group, this function should return - * all member principal uri's for the group. - * + * all member principal uri's for the group. + * * @return array */ public function getGroupMemberSet() { @@ -99,11 +100,11 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri /** * Returns the list of groups this principal is member of - * + * * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array + * should return a list of principal uri's for it's members. + * + * @return array */ public function getGroupMembership() { @@ -117,11 +118,11 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri * * If this principal is a group, this method sets all the group members. * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $principals - * @return void + * + * This method should throw an exception if the members could not be set. + * + * @param array $groupMembers + * @return void */ public function setGroupMemberSet(array $groupMembers) { @@ -132,22 +133,21 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri /** * Returns this principals name. - * - * @return string + * + * @return string */ public function getName() { $uri = $this->principalProperties['uri']; list(, $name) = Sabre_DAV_URLUtil::splitPath($uri); - return $name; } /** - * Returns the name of the user - * - * @return void + * Returns the name of the user + * + * @return string */ public function getDisplayName() { @@ -160,16 +160,16 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri } /** - * Returns a list of properties - * - * @param array $requestedProperties - * @return void + * Returns a list of properties + * + * @param array $requestedProperties + * @return array */ public function getProperties($requestedProperties) { $newProperties = array(); foreach($requestedProperties as $propName) { - + if (isset($this->principalProperties[$propName])) { $newProperties[$propName] = $this->principalProperties[$propName]; } @@ -177,29 +177,27 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri } return $newProperties; - + } /** * Updates this principals properties. - * - * Currently this is not supported * - * @param array $properties + * @param array $mutations * @see Sabre_DAV_IProperties::updateProperties - * @return bool|array + * @return bool|array */ - public function updateProperties($properties) { + public function updateProperties($mutations) { - return false; + return $this->principalBackend->updatePrincipal($this->principalProperties['uri'], $mutations); } /** * Returns the owner principal * - * This must be a url to a principal, or null if there's no owner - * + * This must be a url to a principal, or null if there's no owner + * * @return string|null */ public function getOwner() { @@ -213,8 +211,8 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri * Returns a group principal * * This must be a url to a principal, or null if there's no owner - * - * @return string|null + * + * @return string|null */ public function getGroup() { @@ -226,20 +224,20 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri * Returns a list of ACE's for this node. * * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array */ public function getACL() { return array( array( 'privilege' => '{DAV:}read', - 'principal' => $this->principalProperties['uri'], + 'principal' => $this->getPrincipalUrl(), 'protected' => true, ), ); @@ -249,9 +247,9 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri /** * Updates the ACL * - * This method will receive a list of new ACE's. - * - * @param array $acl + * This method will receive a list of new ACE's. + * + * @param array $acl * @return void */ public function setACL(array $acl) { @@ -260,4 +258,22 @@ class Sabre_DAVACL_Principal extends Sabre_DAV_Node implements Sabre_DAVACL_IPri } + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + + return null; + + } + } diff --git a/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php b/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php index 55bd1903c9..a76b4a9d72 100644 --- a/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php +++ b/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php @@ -3,46 +3,80 @@ /** * PDO principal backend * - * This is a simple principal backend that maps exactly to the users table, as + * This is a simple principal backend that maps exactly to the users table, as * used by Sabre_DAV_Auth_Backend_PDO. * - * It assumes all principals are in a single collection. The default collection + * It assumes all principals are in a single collection. The default collection * is 'principals/', but this can be overriden. * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBackend { /** - * pdo - * - * @var PDO + * pdo + * + * @var PDO */ protected $pdo; /** - * PDO table name for 'principals' - * - * @var string + * PDO table name for 'principals' + * + * @var string */ protected $tableName; /** - * PDO table name for 'group members' - * - * @var string + * PDO table name for 'group members' + * + * @var string */ protected $groupMembersTableName; + /** + * A list of additional fields to support + * + * @var array + */ + protected $fieldMap = array( + + /** + * This property can be used to display the users' real name. + */ + '{DAV:}displayname' => array( + 'dbField' => 'displayname', + ), + + /** + * This property is actually used by the CardDAV plugin, where it gets + * mapped to {http://calendarserver.orgi/ns/}me-card. + * + * The reason we don't straight-up use that property, is because + * me-card is defined as a property on the users' addressbook + * collection. + */ + '{http://sabredav.org/ns}vcard-url' => array( + 'dbField' => 'vcardurl', + ), + /** + * This is the users' primary email-address. + */ + '{http://sabredav.org/ns}email-address' => array( + 'dbField' => 'email', + ), + ); + /** * Sets up the backend. - * + * * @param PDO $pdo - * @param string $tableName + * @param string $tableName + * @param string $groupMembersTableName */ public function __construct(PDO $pdo, $tableName = 'principals', $groupMembersTableName = 'groupmembers') { @@ -50,27 +84,35 @@ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBacken $this->tableName = $tableName; $this->groupMembersTableName = $groupMembersTableName; - } + } /** * Returns a list of principals based on a prefix. * - * This prefix will often contain something like 'principals'. You are only + * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * - * You are expected to return at least a 'uri' for every user, you can + * You are expected to return at least a 'uri' for every user, you can * return any additional properties if you wish so. Common properties are: - * {DAV:}displayname - * {http://sabredav.org/ns}email-address - This is a custom SabreDAV + * {DAV:}displayname + * {http://sabredav.org/ns}email-address - This is a custom SabreDAV * field that's actualy injected in a number of other properties. If * you have an email address, use this property. - * - * @param string $prefixPath - * @return array + * + * @param string $prefixPath + * @return array */ public function getPrincipalsByPrefix($prefixPath) { - $result = $this->pdo->query('SELECT uri, email, displayname FROM `'. $this->tableName . '`'); + + $fields = array( + 'uri', + ); + + foreach($this->fieldMap as $key=>$value) { + $fields[] = $value['dbField']; + } + $result = $this->pdo->query('SELECT '.implode(',', $fields).' FROM '. $this->tableName); $principals = array(); @@ -80,11 +122,15 @@ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBacken list($rowPrefix) = Sabre_DAV_URLUtil::splitPath($row['uri']); if ($rowPrefix !== $prefixPath) continue; - $principals[] = array( + $principal = array( 'uri' => $row['uri'], - '{DAV:}displayname' => $row['displayname']?$row['displayname']:basename($row['uri']), - '{http://sabredav.org/ns}email-address' => $row['email'], ); + foreach($this->fieldMap as $key=>$value) { + if ($row[$value['dbField']]) { + $principal[$key] = $row[$value['dbField']]; + } + } + $principals[] = $principal; } @@ -94,43 +140,218 @@ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBacken /** * Returns a specific principal, specified by it's path. - * The returned structure should be the exact same as from - * getPrincipalsByPrefix. - * - * @param string $path - * @return array + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array */ public function getPrincipalByPath($path) { - $stmt = $this->pdo->prepare('SELECT id, uri, email, displayname FROM `'.$this->tableName.'` WHERE uri = ?'); - $stmt->execute(array($path)); + $fields = array( + 'id', + 'uri', + ); - $users = array(); + foreach($this->fieldMap as $key=>$value) { + $fields[] = $value['dbField']; + } + $stmt = $this->pdo->prepare('SELECT '.implode(',', $fields).' FROM '. $this->tableName . ' WHERE uri = ?'); + $stmt->execute(array($path)); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!$row) return; - return array( + $principal = array( 'id' => $row['id'], 'uri' => $row['uri'], - '{DAV:}displayname' => $row['displayname']?$row['displayname']:basename($row['uri']), - '{http://sabredav.org/ns}email-address' => $row['email'], ); + foreach($this->fieldMap as $key=>$value) { + if ($row[$value['dbField']]) { + $principal[$key] = $row[$value['dbField']]; + } + } + return $principal; } /** - * Returns the list of members for a group-principal - * - * @param string $principal - * @return array + * Updates one ore more webdav properties on a principal. + * + * The list of mutations is supplied as an array. Each key in the array is + * a propertyname, such as {DAV:}displayname. + * + * Each value is the actual value to be updated. If a value is null, it + * must be deleted. + * + * This method should be atomic. It must either completely succeed, or + * completely fail. Success and failure can simply be returned as 'true' or + * 'false'. + * + * It is also possible to return detailed failure information. In that case + * an array such as this should be returned: + * + * array( + * 200 => array( + * '{DAV:}prop1' => null, + * ), + * 201 => array( + * '{DAV:}prop2' => null, + * ), + * 403 => array( + * '{DAV:}prop3' => null, + * ), + * 424 => array( + * '{DAV:}prop4' => null, + * ), + * ); + * + * In this previous example prop1 was successfully updated or deleted, and + * prop2 was succesfully created. + * + * prop3 failed to update due to '403 Forbidden' and because of this prop4 + * also could not be updated with '424 Failed dependency'. + * + * This last example was actually incorrect. While 200 and 201 could appear + * in 1 response, if there's any error (403) the other properties should + * always fail with 423 (failed dependency). + * + * But anyway, if you don't want to scratch your head over this, just + * return true or false. + * + * @param string $path + * @param array $mutations + * @return array|bool + */ + public function updatePrincipal($path, $mutations) { + + $updateAble = array(); + foreach($mutations as $key=>$value) { + + // We are not aware of this field, we must fail. + if (!isset($this->fieldMap[$key])) { + + $response = array( + 403 => array( + $key => null, + ), + 424 => array(), + ); + + // Adding the rest to the response as a 424 + foreach($mutations as $subKey=>$subValue) { + if ($subKey !== $key) { + $response[424][$subKey] = null; + } + } + return $response; + } + + $updateAble[$this->fieldMap[$key]['dbField']] = $value; + + } + + // No fields to update + $query = "UPDATE " . $this->tableName . " SET "; + + $first = true; + foreach($updateAble as $key => $value) { + if (!$first) { + $query.= ', '; + } + $first = false; + $query.= "$key = :$key "; + } + $query.='WHERE uri = :uri'; + $stmt = $this->pdo->prepare($query); + $updateAble['uri'] = $path; + $stmt->execute($updateAble); + + return true; + + } + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. You should at least allow searching on + * http://sabredav.org/ns}email-address. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * If multiple properties are being searched on, the search should be + * AND'ed. + * + * This method should simply return an array with full principal uri's. + * + * If somebody attempted to search on a property the backend does not + * support, you should simply return 0 results. + * + * You can also just return 0 results if you choose to not support + * searching at all, but keep in mind that this may stop certain features + * from working. + * + * @param string $prefixPath + * @param array $searchProperties + * @return array + */ + public function searchPrincipals($prefixPath, array $searchProperties) { + + $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE 1=1 '; + $values = array(); + foreach($searchProperties as $property => $value) { + + switch($property) { + + case '{DAV:}displayname' : + $query.=' AND displayname LIKE ?'; + $values[] = '%' . $value . '%'; + break; + case '{http://sabredav.org/ns}email-address' : + $query.=' AND email LIKE ?'; + $values[] = '%' . $value . '%'; + break; + default : + // Unsupported property + return array(); + + } + + } + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + $principals = array(); + while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + + // Checking if the principal is in the prefix + list($rowPrefix) = Sabre_DAV_URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $prefixPath) continue; + + $principals[] = $row['uri']; + + } + + return $principals; + + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array */ public function getGroupMemberSet($principal) { $principal = $this->getPrincipalByPath($principal); if (!$principal) throw new Sabre_DAV_Exception('Principal not found'); - $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM `'.$this->groupMembersTableName.'` AS groupmembers LEFT JOIN `'.$this->tableName.'` AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?'); + $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM '.$this->groupMembersTableName.' AS groupmembers LEFT JOIN '.$this->tableName.' AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?'); $stmt->execute(array($principal['id'])); $result = array(); @@ -138,21 +359,21 @@ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBacken $result[] = $row['uri']; } return $result; - + } /** - * Returns the list of groups a principal is a member of - * - * @param string $principal - * @return array + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array */ public function getGroupMembership($principal) { $principal = $this->getPrincipalByPath($principal); if (!$principal) throw new Sabre_DAV_Exception('Principal not found'); - $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM `'.$this->groupMembersTableName.'` AS groupmembers LEFT JOIN `'.$this->tableName.'` AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?'); + $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM '.$this->groupMembersTableName.' AS groupmembers LEFT JOIN '.$this->tableName.' AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?'); $stmt->execute(array($principal['id'])); $result = array(); @@ -166,16 +387,16 @@ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBacken /** * Updates the list of group members for a group principal. * - * The principals should be passed as a list of uri's. - * - * @param string $principal - * @param array $members + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members * @return void */ public function setGroupMemberSet($principal, array $members) { // Grabbing the list of principal id's. - $stmt = $this->pdo->prepare('SELECT id, uri FROM `'.$this->tableName.'` WHERE uri IN (? ' . str_repeat(', ? ', count($members)) . ');'); + $stmt = $this->pdo->prepare('SELECT id, uri FROM '.$this->tableName.' WHERE uri IN (? ' . str_repeat(', ? ', count($members)) . ');'); $stmt->execute(array_merge(array($principal), $members)); $memberIds = array(); @@ -191,12 +412,12 @@ class Sabre_DAVACL_PrincipalBackend_PDO implements Sabre_DAVACL_IPrincipalBacken if (!$principalId) throw new Sabre_DAV_Exception('Principal not found'); // Wiping out old members - $stmt = $this->pdo->prepare('DELETE FROM `'.$this->groupMembersTableName.'` WHERE principal_id = ?;'); + $stmt = $this->pdo->prepare('DELETE FROM '.$this->groupMembersTableName.' WHERE principal_id = ?;'); $stmt->execute(array($principalId)); foreach($memberIds as $memberId) { - $stmt = $this->pdo->prepare('INSERT INTO `'.$this->groupMembersTableName.'` (principal_id, member_id) VALUES (?, ?);'); + $stmt = $this->pdo->prepare('INSERT INTO '.$this->groupMembersTableName.' (principal_id, member_id) VALUES (?, ?);'); $stmt->execute(array($principalId, $memberId)); } diff --git a/3rdparty/Sabre/DAVACL/PrincipalCollection.php b/3rdparty/Sabre/DAVACL/PrincipalCollection.php index 4d22bf8aa7..c3e4cb83f2 100644 --- a/3rdparty/Sabre/DAVACL/PrincipalCollection.php +++ b/3rdparty/Sabre/DAVACL/PrincipalCollection.php @@ -7,11 +7,11 @@ * Sabre_DAV_Auth_Backend to determine which users are available on the list. * * The users are instances of Sabre_DAV_Auth_Principal - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_PrincipalCollection extends Sabre_DAVACL_AbstractPrincipalCollection { @@ -22,9 +22,9 @@ class Sabre_DAVACL_PrincipalCollection extends Sabre_DAVACL_AbstractPrincipalCol * The passed array contains principal information, and is guaranteed to * at least contain a uri item. Other properties may or may not be * supplied by the authentication backend. - * - * @param array $principal - * @return Sabre_DAV_INode + * + * @param array $principal + * @return Sabre_DAV_INode */ public function getChildForPrincipal(array $principal) { diff --git a/3rdparty/Sabre/DAVACL/Property/Acl.php b/3rdparty/Sabre/DAVACL/Property/Acl.php index e41e741131..05e1a690b3 100644 --- a/3rdparty/Sabre/DAVACL/Property/Acl.php +++ b/3rdparty/Sabre/DAVACL/Property/Acl.php @@ -1,47 +1,47 @@ item(0)); - if ($principal->getType()!==Sabre_DAVACL_Property_Principal::HREF) { - throw new Sabre_DAV_Exception_NotImplemented('Currently only uri based principals are support, {DAV:}all, {DAV:}unauthenticated and {DAV:}authenticated are not implemented yet'); + switch($principal->getType()) { + case Sabre_DAVACL_Property_Principal::HREF : + $principal = $principal->getHref(); + break; + case Sabre_DAVACL_Property_Principal::AUTHENTICATED : + $principal = '{DAV:}authenticated'; + break; + case Sabre_DAVACL_Property_Principal::UNAUTHENTICATED : + $principal = '{DAV:}unauthenticated'; + break; + case Sabre_DAVACL_Property_Principal::ALL : + $principal = '{DAV:}all'; + break; + } - $principal = $principal->getHref(); $protected = false; if ($xace->getElementsByTagNameNS('urn:DAV','protected')->length > 0) { @@ -140,7 +151,7 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property { 'privilege' => $privilegeName, ); - } + } } @@ -149,12 +160,12 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property { } /** - * Serializes a single access control entry. - * - * @param DOMDocument $doc - * @param DOMElement $node + * Serializes a single access control entry. + * + * @param DOMDocument $doc + * @param DOMElement $node * @param array $ace - * @param Sabre_DAV_Server $server + * @param Sabre_DAV_Server $server * @return void */ private function serializeAce($doc,$node,$ace, $server) { @@ -164,7 +175,19 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property { $principal = $doc->createElementNS('DAV:','d:principal'); $xace->appendChild($principal); - $principal->appendChild($doc->createElementNS('DAV:','d:href',($this->prefixBaseUrl?$server->getBaseUri():'') . $ace['principal'] . '/')); + switch($ace['principal']) { + case '{DAV:}authenticated' : + $principal->appendChild($doc->createElementNS('DAV:','d:authenticated')); + break; + case '{DAV:}unauthenticated' : + $principal->appendChild($doc->createElementNS('DAV:','d:unauthenticated')); + break; + case '{DAV:}all' : + $principal->appendChild($doc->createElementNS('DAV:','d:all')); + break; + default: + $principal->appendChild($doc->createElementNS('DAV:','d:href',($this->prefixBaseUrl?$server->getBaseUri():'') . $ace['principal'] . '/')); + } $grant = $doc->createElementNS('DAV:','d:grant'); $xace->appendChild($grant); diff --git a/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php b/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php new file mode 100644 index 0000000000..a8b054956d --- /dev/null +++ b/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php @@ -0,0 +1,32 @@ +ownerDocument; + + $elem->appendChild($doc->createElementNS('DAV:','d:grant-only')); + $elem->appendChild($doc->createElementNS('DAV:','d:no-invert')); + + } + +} diff --git a/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php b/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php index 72274597b3..94a2964061 100644 --- a/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php +++ b/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php @@ -2,31 +2,31 @@ /** * CurrentUserPrivilegeSet - * - * This class represents the current-user-privilege-set property. When - * requested, it contain all the privileges a user has on a specific node. - * + * + * This class represents the current-user-privilege-set property. When + * requested, it contain all the privileges a user has on a specific node. + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Property_CurrentUserPrivilegeSet extends Sabre_DAV_Property { /** - * List of privileges - * - * @var array + * List of privileges + * + * @var array */ private $privileges; /** * Creates the object * - * Pass the privileges in clark-notation - * - * @param array $privileges + * Pass the privileges in clark-notation + * + * @param array $privileges */ public function __construct(array $privileges) { @@ -35,10 +35,10 @@ class Sabre_DAVACL_Property_CurrentUserPrivilegeSet extends Sabre_DAV_Property { } /** - * Serializes the property in the DOM - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property in the DOM + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $node) { @@ -53,11 +53,11 @@ class Sabre_DAVACL_Property_CurrentUserPrivilegeSet extends Sabre_DAV_Property { } /** - * Serializes one privilege - * - * @param DOMDocument $doc - * @param DOMElement $node - * @param string $privName + * Serializes one privilege + * + * @param DOMDocument $doc + * @param DOMElement $node + * @param string $privName * @return void */ protected function serializePriv($doc,$node,$privName) { diff --git a/3rdparty/Sabre/DAVACL/Property/Principal.php b/3rdparty/Sabre/DAVACL/Property/Principal.php index dad9a3550f..c36328a58e 100644 --- a/3rdparty/Sabre/DAVACL/Property/Principal.php +++ b/3rdparty/Sabre/DAVACL/Property/Principal.php @@ -4,12 +4,12 @@ * Principal property * * The principal property represents a principal from RFC3744 (ACL). - * The property can be used to specify a principal or pseudo principals. + * The property can be used to specify a principal or pseudo principals. * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabre_DAV_Property_IHref { @@ -25,16 +25,21 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr const AUTHENTICATED = 2; /** - * Specific princpals can be specified with the HREF + * Specific principals can be specified with the HREF */ const HREF = 3; + /** + * Everybody, basically + */ + const ALL = 4; + /** * Principal-type * * Must be one of the UNAUTHENTICATED, AUTHENTICATED or HREF constants. - * - * @var int + * + * @var int */ private $type; @@ -42,8 +47,8 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr * Url to principal * * This value is only used for the HREF principal type. - * - * @var string + * + * @var string */ private $href; @@ -53,10 +58,9 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr * The 'type' argument must be one of the type constants defined in this class. * * 'href' is only required for the HREF type. - * - * @param int $type - * @param string $href - * @return void + * + * @param int $type + * @param string|null $href */ public function __construct($type, $href = null) { @@ -70,9 +74,9 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr } /** - * Returns the principal type - * - * @return int + * Returns the principal type + * + * @return int */ public function getType() { @@ -81,8 +85,8 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr } /** - * Returns the principal uri. - * + * Returns the principal uri. + * * @return string */ public function getHref() { @@ -92,10 +96,10 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr } /** - * Serializes the property into a DOMElement. - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property into a DOMElement. + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server, DOMElement $node) { @@ -124,10 +128,10 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr } /** - * Deserializes a DOM element into a property object. - * - * @param DOMElement $dom - * @return Sabre_DAV_Property_Principal + * Deserializes a DOM element into a property object. + * + * @param DOMElement $dom + * @return Sabre_DAV_Property_Principal */ static public function unserialize(DOMElement $dom) { @@ -144,6 +148,8 @@ class Sabre_DAVACL_Property_Principal extends Sabre_DAV_Property implements Sabr return new self(self::AUTHENTICATED); case '{DAV:}href': return new self(self::HREF, $parent->textContent); + case '{DAV:}all': + return new self(self::ALL); default : throw new Sabre_DAV_Exception_BadRequest('Unexpected element (' . Sabre_DAV_XMLUtil::toClarkNotation($parent) . '). Could not deserialize'); diff --git a/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php b/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php index 93c3895035..276d57ae09 100644 --- a/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php +++ b/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php @@ -3,32 +3,32 @@ /** * SupportedPrivilegeSet property * - * This property encodes the {DAV:}supported-privilege-set property, as defined + * This property encodes the {DAV:}supported-privilege-set property, as defined * in rfc3744. Please consult the rfc for details about it's structure. * - * This class expects a structure like the one given from - * Sabre_DAVACL_Plugin::getSupportedPrivilegeSet as the argument in its + * This class expects a structure like the one given from + * Sabre_DAVACL_Plugin::getSupportedPrivilegeSet as the argument in its * constructor. - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAVACL_Property_SupportedPrivilegeSet extends Sabre_DAV_Property { /** - * privileges - * - * @var array + * privileges + * + * @var array */ private $privileges; /** - * Constructor - * - * @param array $privileges + * Constructor + * + * @param array $privileges */ public function __construct(array $privileges) { @@ -37,10 +37,10 @@ class Sabre_DAVACL_Property_SupportedPrivilegeSet extends Sabre_DAV_Property { } /** - * Serializes the property into a domdocument. - * - * @param Sabre_DAV_Server $server - * @param DOMElement $node + * Serializes the property into a domdocument. + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node * @return void */ public function serialize(Sabre_DAV_Server $server,DOMElement $node) { @@ -53,11 +53,11 @@ class Sabre_DAVACL_Property_SupportedPrivilegeSet extends Sabre_DAV_Property { /** * Serializes a property * - * This is a recursive function. - * - * @param DOMDocument $doc - * @param DOMElement $node - * @param array $privilege + * This is a recursive function. + * + * @param DOMDocument $doc + * @param DOMElement $node + * @param array $privilege * @return void */ private function serializePriv($doc,$node,$privilege) { @@ -81,7 +81,7 @@ class Sabre_DAVACL_Property_SupportedPrivilegeSet extends Sabre_DAV_Property { $xsp->appendChild($doc->createElementNS('DAV:','d:description',$privilege['description'])); } - if (isset($privilege['aggregates'])) { + if (isset($privilege['aggregates'])) { foreach($privilege['aggregates'] as $subPrivilege) { $this->serializePriv($doc,$xsp,$subPrivilege); } diff --git a/3rdparty/Sabre/DAVACL/Version.php b/3rdparty/Sabre/DAVACL/Version.php index 124463e311..9950f74874 100644 --- a/3rdparty/Sabre/DAVACL/Version.php +++ b/3rdparty/Sabre/DAVACL/Version.php @@ -2,10 +2,10 @@ /** * This class contains the SabreDAV version constants. - * + * * @package Sabre * @subpackage DAVACL - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -14,7 +14,7 @@ class Sabre_DAVACL_Version { /** * Full version number */ - const VERSION = '1.5.2'; + const VERSION = '1.6.0'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/DAVACL/includes.php b/3rdparty/Sabre/DAVACL/includes.php new file mode 100644 index 0000000000..28fa3eed22 --- /dev/null +++ b/3rdparty/Sabre/DAVACL/includes.php @@ -0,0 +1,38 @@ +httpRequest->getBody(true); $this->httpRequest->setBody($body,true); - + if ($contentMD5!=base64_encode(md5($body,true))) { // content-md5 header did not match md5 signature of body $this->errorCode = self::ERR_MD5CHECKSUMWRONG; @@ -98,10 +99,10 @@ class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth { } - if (!$requestDate = $this->httpRequest->getHeader('x-amz-date')) + if (!$requestDate = $this->httpRequest->getHeader('x-amz-date')) $requestDate = $this->httpRequest->getHeader('Date'); - if (!$this->validateRFC2616Date($requestDate)) + if (!$this->validateRFC2616Date($requestDate)) return false; $amzHeaders = $this->getAmzHeaders(); @@ -109,10 +110,10 @@ class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth { $signature = base64_encode( $this->hmacsha1($secretKey, $this->httpRequest->getMethod() . "\n" . - $contentMD5 . "\n" . + $contentMD5 . "\n" . $this->httpRequest->getHeader('Content-type') . "\n" . $requestDate . "\n" . - $amzHeaders . + $amzHeaders . $this->httpRequest->getURI() ) ); @@ -146,14 +147,14 @@ class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth { /** * Makes sure the supplied value is a valid RFC2616 date. * - * If we would just use strtotime to get a valid timestamp, we have no way of checking if a + * If we would just use strtotime to get a valid timestamp, we have no way of checking if a * user just supplied the word 'now' for the date header. * - * This function also makes sure the Date header is within 15 minutes of the operating + * This function also makes sure the Date header is within 15 minutes of the operating * system date, to prevent replay attacks. - * - * @param string $dateHeader - * @return bool + * + * @param string $dateHeader + * @return bool */ protected function validateRFC2616Date($dateHeader) { @@ -177,11 +178,11 @@ class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth { return $date; } - + /** - * Returns a list of AMZ headers - * - * @return void + * Returns a list of AMZ headers + * + * @return string */ protected function getAmzHeaders() { @@ -193,7 +194,7 @@ class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth { } } ksort($amzHeaders); - + $headerStr = ''; foreach($amzHeaders as $h=>$v) { $headerStr.=$h.':'.$v; @@ -204,11 +205,11 @@ class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth { } /** - * Generates an HMAC-SHA1 signature - * - * @param string $key - * @param string $message - * @return string + * Generates an HMAC-SHA1 signature + * + * @param string $key + * @param string $message + * @return string */ private function hmacsha1($key, $message) { diff --git a/3rdparty/Sabre/HTTP/AbstractAuth.php b/3rdparty/Sabre/HTTP/AbstractAuth.php index eb528f6fde..3bccabcd1c 100644 --- a/3rdparty/Sabre/HTTP/AbstractAuth.php +++ b/3rdparty/Sabre/HTTP/AbstractAuth.php @@ -4,10 +4,10 @@ * HTTP Authentication baseclass * * This class has the common functionality for BasicAuth and DigestAuth - * + * * @package Sabre - * @subpackage HTTP - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @subpackage HTTP + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -17,29 +17,29 @@ abstract class Sabre_HTTP_AbstractAuth { * The realm will be displayed in the dialog boxes * * This identifier can be changed through setRealm() - * + * * @var string */ protected $realm = 'SabreDAV'; /** - * HTTP response helper - * - * @var Sabre_HTTP_Response + * HTTP response helper + * + * @var Sabre_HTTP_Response */ protected $httpResponse; /** - * HTTP request helper - * - * @var Sabre_HTTP_Request + * HTTP request helper + * + * @var Sabre_HTTP_Request */ protected $httpRequest; /** - * __construct - * + * __construct + * */ public function __construct() { @@ -49,9 +49,9 @@ abstract class Sabre_HTTP_AbstractAuth { } /** - * Sets an alternative HTTP response object - * - * @param Sabre_HTTP_Response $response + * Sets an alternative HTTP response object + * + * @param Sabre_HTTP_Response $response * @return void */ public function setHTTPResponse(Sabre_HTTP_Response $response) { @@ -61,9 +61,9 @@ abstract class Sabre_HTTP_AbstractAuth { } /** - * Sets an alternative HTTP request object - * - * @param Sabre_HTTP_Request $request + * Sets an alternative HTTP request object + * + * @param Sabre_HTTP_Request $request * @return void */ public function setHTTPRequest(Sabre_HTTP_Request $request) { @@ -78,8 +78,8 @@ abstract class Sabre_HTTP_AbstractAuth { * * The realm is often displayed in authentication dialog boxes * Commonly an application name displayed here - * - * @param string $realm + * + * @param string $realm * @return void */ public function setRealm($realm) { @@ -91,7 +91,7 @@ abstract class Sabre_HTTP_AbstractAuth { /** * Returns the realm * - * @return string + * @return string */ public function getRealm() { @@ -106,6 +106,6 @@ abstract class Sabre_HTTP_AbstractAuth { * * @return void */ - abstract public function requireLogin(); + abstract public function requireLogin(); } diff --git a/3rdparty/Sabre/HTTP/BasicAuth.php b/3rdparty/Sabre/HTTP/BasicAuth.php index 35c22d22dc..a747cc6a31 100644 --- a/3rdparty/Sabre/HTTP/BasicAuth.php +++ b/3rdparty/Sabre/HTTP/BasicAuth.php @@ -4,11 +4,11 @@ * HTTP Basic Authentication handler * * Use this class for easy http authentication setup - * + * * @package Sabre - * @subpackage HTTP - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @subpackage HTTP + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_HTTP_BasicAuth extends Sabre_HTTP_AbstractAuth { @@ -22,7 +22,7 @@ class Sabre_HTTP_BasicAuth extends Sabre_HTTP_AbstractAuth { * * If nothing was supplied, 'false' will be returned * - * @return mixed + * @return mixed */ public function getUserPass() { @@ -33,12 +33,18 @@ class Sabre_HTTP_BasicAuth extends Sabre_HTTP_AbstractAuth { } - // Most other webservers + // Most other webservers $auth = $this->httpRequest->getHeader('Authorization'); + // Apache could prefix environment variables with REDIRECT_ when urls + // are passed through mod_rewrite + if (!$auth) { + $auth = $this->httpRequest->getRawServerValue('REDIRECT_HTTP_AUTHORIZATION'); + } + if (!$auth) return false; - if (strpos(strtolower($auth),'basic')!==0) return false; + if (strpos(strtolower($auth),'basic')!==0) return false; return explode(':', base64_decode(substr($auth, 6))); diff --git a/3rdparty/Sabre/HTTP/DigestAuth.php b/3rdparty/Sabre/HTTP/DigestAuth.php index 5e75592957..ee7f05c08e 100644 --- a/3rdparty/Sabre/HTTP/DigestAuth.php +++ b/3rdparty/Sabre/HTTP/DigestAuth.php @@ -10,18 +10,18 @@ * 2. Call the setRealm() method with the realm you plan to use * 3. Call the init method function. * 4. Call the getUserName() function. This function may return false if no - * authentication information was supplied. Based on the username you + * authentication information was supplied. Based on the username you * should check your internal database for either the associated password, * or the so-called A1 hash of the digest. * 5. Call either validatePassword() or validateA1(). This will return true - * or false. + * or false. * 6. To make sure an authentication prompt is displayed, call the * requireLogin() method. - * - * + * + * * @package Sabre - * @subpackage HTTP - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @subpackage HTTP + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -40,7 +40,7 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { protected $qop = self::QOP_AUTH; /** - * Initializes the object + * Initializes the object */ public function __construct() { @@ -54,7 +54,7 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { * Gathers all information from the headers * * This method needs to be called prior to anything else. - * + * * @return void */ public function init() { @@ -73,11 +73,11 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { * * Multiple values can be specified using logical OR. * - * QOP_AUTHINT ensures integrity of the request body, but this is not - * supported by most HTTP clients. QOP_AUTHINT also requires the entire + * QOP_AUTHINT ensures integrity of the request body, but this is not + * supported by most HTTP clients. QOP_AUTHINT also requires the entire * request body to be md5'ed, which can put strains on CPU and memory. * - * @param int $qop + * @param int $qop * @return void */ public function setQOP($qop) { @@ -91,8 +91,8 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { * * The A1 parameter should be md5($username . ':' . $realm . ':' . $password); * - * @param string $A1 - * @return bool + * @param string $A1 + * @return bool */ public function validateA1($A1) { @@ -104,9 +104,9 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { /** * Validates authentication through a password. The actual password must be provided here. * It is strongly recommended not store the password in plain-text and use validateA1 instead. - * - * @param string $password - * @return bool + * + * @param string $password + * @return bool */ public function validatePassword($password) { @@ -116,9 +116,9 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { } /** - * Returns the username for the request - * - * @return string + * Returns the username for the request + * + * @return string */ public function getUsername() { @@ -127,14 +127,14 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { } /** - * Validates the digest challenge - * - * @return bool + * Validates the digest challenge + * + * @return bool */ protected function validate() { $A2 = $this->httpRequest->getMethod() . ':' . $this->digestParts['uri']; - + if ($this->digestParts['qop']=='auth-int') { // Making sure we support this qop value if (!($this->qop & self::QOP_AUTHINT)) return false; @@ -144,16 +144,16 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { $A2 .= ':' . md5($body); } else { - // We need to make sure we support this qop value - if (!($this->qop & self::QOP_AUTH)) return false; + // We need to make sure we support this qop value + if (!($this->qop & self::QOP_AUTH)) return false; } $A2 = md5($A2); - $validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}"); + $validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}"); return $this->digestParts['response']==$validResponse; - + } @@ -186,7 +186,7 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { * * If the header could not be found, null will be returned * - * @return mixed + * @return mixed */ public function getDigest() { @@ -197,6 +197,12 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { // most other servers $digest = $this->httpRequest->getHeader('Authorization'); + // Apache could prefix environment variables with REDIRECT_ when urls + // are passed through mod_rewrite + if (!$digest) { + $digest = $this->httpRequest->getRawServerValue('REDIRECT_HTTP_AUTHORIZATION'); + } + if ($digest && strpos(strtolower($digest),'digest')===0) { return substr($digest,7); } else { @@ -208,11 +214,11 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { /** * Parses the different pieces of the digest string into an array. - * + * * This method returns false if an incomplete digest was supplied * - * @param string $digest - * @return mixed + * @param string $digest + * @return mixed */ protected function parseDigest($digest) { @@ -227,7 +233,7 @@ class Sabre_HTTP_DigestAuth extends Sabre_HTTP_AbstractAuth { unset($needed_parts[$m[1]]); } - return $needed_parts ? false : $data; + return $needed_parts ? false : $data; } diff --git a/3rdparty/Sabre/HTTP/Request.php b/3rdparty/Sabre/HTTP/Request.php index 95a64171aa..4746ef7770 100644 --- a/3rdparty/Sabre/HTTP/Request.php +++ b/3rdparty/Sabre/HTTP/Request.php @@ -6,74 +6,85 @@ * This object can be used to easily access information about an HTTP request. * It can additionally be used to create 'mock' requests. * - * This class mostly operates indepentend, but because of the nature of a single - * request per run it can operate as a singleton. For more information check out + * This class mostly operates independent, but because of the nature of a single + * request per run it can operate as a singleton. For more information check out * the behaviour around 'defaultInputStream'. * * @package Sabre - * @subpackage HTTP - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @subpackage HTTP + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_HTTP_Request { /** * PHP's $_SERVER data - * - * @var string + * + * @var array */ protected $_SERVER; + /** + * PHP's $_POST data + * + * @var array + */ + protected $_POST; + /** * The request body, if any. * * This is stored in the form of a stream resource. * - * @var resource + * @var resource */ protected $body = null; /** * This will be set as the 'default' inputStream for a specific HTTP request - * We sometimes need to retain, or rebuild this if we need multiple runs + * We sometimes need to retain, or rebuild this if we need multiple runs * of parsing the original HTTP request. - * - * @var resource + * + * @var resource */ static $defaultInputStream=null; /** * Sets up the object * - * The serverData array can be used to override usage of PHP's - * global _SERVER variable. - * - * @param array $serverData + * The serverData and postData array can be used to override usage of PHP's + * global _SERVER and _POST variable respectively. + * + * @param array $serverData + * @param array $postData */ - public function __construct($serverData = null) { + public function __construct(array $serverData = null, array $postData = null) { if ($serverData) $this->_SERVER = $serverData; else $this->_SERVER =& $_SERVER; + if ($postData) $this->_POST = $postData; + else $this->_POST =& $_POST; + } /** * Returns the value for a specific http header. * * This method returns null if the header did not exist. - * - * @param string $name - * @return string + * + * @param string $name + * @return string */ public function getHeader($name) { $name = strtoupper(str_replace(array('-'),array('_'),$name)); if (isset($this->_SERVER['HTTP_' . $name])) { return $this->_SERVER['HTTP_' . $name]; - } + } - // There's a few headers that seem to end up in the top-level + // There's a few headers that seem to end up in the top-level // server array. switch($name) { case 'CONTENT_TYPE' : @@ -92,9 +103,9 @@ class Sabre_HTTP_Request { * Returns all (known) HTTP headers. * * All headers are converted to lower-case, and additionally all underscores - * are automatically converted to dashes - * - * @return array + * are automatically converted to dashes + * + * @return array */ public function getHeaders() { @@ -122,9 +133,9 @@ class Sabre_HTTP_Request { /** * Returns the HTTP request method * - * This is for example POST or GET + * This is for example POST or GET * - * @return string + * @return string */ public function getMethod() { @@ -135,18 +146,18 @@ class Sabre_HTTP_Request { /** * Returns the requested uri * - * @return string + * @return string */ public function getUri() { - + return $this->_SERVER['REQUEST_URI']; } /** - * Will return protocol + the hostname + the uri - * - * @return void + * Will return protocol + the hostname + the uri + * + * @return string */ public function getAbsoluteUri() { @@ -157,9 +168,9 @@ class Sabre_HTTP_Request { } /** - * Returns everything after the ? from the current url - * - * @return string + * Returns everything after the ? from the current url + * + * @return string */ public function getQueryString() { @@ -168,13 +179,13 @@ class Sabre_HTTP_Request { } /** - * Returns the HTTP request body body + * Returns the HTTP request body body * * This method returns a readable stream resource. - * If the asString parameter is set to true, a string is sent instead. + * If the asString parameter is set to true, a string is sent instead. * * @param bool asString - * @return resource + * @return resource */ public function getBody($asString = false) { @@ -196,14 +207,14 @@ class Sabre_HTTP_Request { } /** - * Sets the contents of the HTTP request body - * + * Sets the contents of the HTTP request body + * * This method can either accept a string, or a readable stream resource. * - * If the setAsDefaultInputStream is set to true, it means for this run of the + * If the setAsDefaultInputStream is set to true, it means for this run of the * script the supplied body will be used instead of php://input. * - * @param mixed $body + * @param mixed $body * @param bool $setAsDefaultInputStream * @return void */ @@ -226,12 +237,26 @@ class Sabre_HTTP_Request { } /** - * Returns a specific item from the _SERVER array. + * Returns PHP's _POST variable. + * + * The reason this is in a method is so it can be subclassed and + * overridden. + * + * @return array + */ + public function getPostVars() { + + return $this->_POST; + + } + + /** + * Returns a specific item from the _SERVER array. * * Do not rely on this feature, it is for internal use only. * - * @param string $field - * @return string + * @param string $field + * @return string */ public function getRawServerValue($field) { diff --git a/3rdparty/Sabre/HTTP/Response.php b/3rdparty/Sabre/HTTP/Response.php index dce6feac55..ffe9bda208 100644 --- a/3rdparty/Sabre/HTTP/Response.php +++ b/3rdparty/Sabre/HTTP/Response.php @@ -1,20 +1,20 @@ 'Locked', // RFC 4918 424 => 'Failed Dependency', // RFC 4918 426 => 'Upgrade required', + 428 => 'Precondition required', // draft-nottingham-http-new-status + 429 => 'Too Many Requests', // draft-nottingham-http-new-status + 431 => 'Request Header Fields Too Large', // draft-nottingham-http-new-status 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', @@ -71,25 +74,26 @@ class Sabre_HTTP_Response { 504 => 'Gateway Timeout', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', - 507 => 'Unsufficient Storage', // RFC 4918 + 507 => 'Insufficient Storage', // RFC 4918 508 => 'Loop Detected', // RFC 5842 509 => 'Bandwidth Limit Exceeded', // non-standard 510 => 'Not extended', - ); + 511 => 'Network Authentication Required', // draft-nottingham-http-new-status + ); return 'HTTP/1.1 ' . $code . ' ' . $msg[$code]; } /** - * Sends an HTTP status header to the client - * - * @param int $code HTTP status code - * @return void + * Sends an HTTP status header to the client + * + * @param int $code HTTP status code + * @return bool */ public function sendStatus($code) { - if (!headers_sent()) + if (!headers_sent()) return header($this->getStatusMessage($code)); else return false; @@ -97,15 +101,16 @@ class Sabre_HTTP_Response { /** * Sets an HTTP header for the response - * - * @param string $name - * @param string $value - * @return void + * + * @param string $name + * @param string $value + * @param bool $replace + * @return bool */ public function setHeader($name, $value, $replace = true) { $value = str_replace(array("\r","\n"),array('\r','\n'),$value); - if (!headers_sent()) + if (!headers_sent()) return header($name . ': ' . $value, $replace); else return false; @@ -115,8 +120,8 @@ class Sabre_HTTP_Response { * Sets a bunch of HTTP Headers * * headersnames are specified as keys, value in the array value - * - * @param array $headers + * + * @param array $headers * @return void */ public function setHeaders(array $headers) { @@ -130,14 +135,14 @@ class Sabre_HTTP_Response { * Sends the entire response body * * This method can accept either an open filestream, or a string. - * - * @param mixed $body + * + * @param mixed $body * @return void */ public function sendBody($body) { if (is_resource($body)) { - + fpassthru($body); } else { diff --git a/3rdparty/Sabre/HTTP/Util.php b/3rdparty/Sabre/HTTP/Util.php index 8a6bd7df48..67bdd489e1 100644 --- a/3rdparty/Sabre/HTTP/Util.php +++ b/3rdparty/Sabre/HTTP/Util.php @@ -1,11 +1,11 @@ = 0) return new DateTime('@' . $realDate, new DateTimeZone('UTC')); - return false; + } + + /** + * Transforms a DateTime object to HTTP's most common date format. + * + * We're serializing it as the RFC 1123 date, which, for HTTP must be + * specified as GMT. + * + * @param DateTime $dateTime + * @return string + */ + static function toHTTPDate(DateTime $dateTime) { + + // We need to clone it, as we don't want to affect the existing + // DateTime. + $dateTime = clone $dateTime; + $dateTime->setTimeZone(new DateTimeZone('GMT')); + return $dateTime->format('D, d M Y H:i:s \G\M\T'); } diff --git a/3rdparty/Sabre/HTTP/Version.php b/3rdparty/Sabre/HTTP/Version.php index 67be232fc2..23dc7f8a7a 100644 --- a/3rdparty/Sabre/HTTP/Version.php +++ b/3rdparty/Sabre/HTTP/Version.php @@ -2,10 +2,10 @@ /** * This class contains the Sabre_HTTP version constants. - * + * * @package Sabre - * @subpackage HTTP - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @subpackage HTTP + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -14,7 +14,7 @@ class Sabre_HTTP_Version { /** * Full version number */ - const VERSION = '1.5.3'; + const VERSION = '1.6.2'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/HTTP/includes.php b/3rdparty/Sabre/HTTP/includes.php new file mode 100644 index 0000000000..9d34bf3a8b --- /dev/null +++ b/3rdparty/Sabre/HTTP/includes.php @@ -0,0 +1,27 @@ + 'Sabre_VObject_Component_VCalendar', + 'VEVENT' => 'Sabre_VObject_Component_VEvent', + 'VTODO' => 'Sabre_VObject_Component_VTodo', + 'VJOURNAL' => 'Sabre_VObject_Component_VJournal', + 'VALARM' => 'Sabre_VObject_Component_VAlarm', + ); + + /** + * Creates the new component by name, but in addition will also see if + * there's a class mapped to the property name. + * + * @param string $name + * @param string $value + * @return Sabre_VObject_Component + */ + static public function create($name, $value = null) { + + $name = strtoupper($name); + + if (isset(self::$classMap[$name])) { + return new self::$classMap[$name]($name, $value); + } else { + return new self($name, $value); + } + + } /** * Creates a new component. * - * By default this object will iterate over its own children, but this can + * By default this object will iterate over its own children, but this can * be overridden with the iterator argument - * - * @param string $name + * + * @param string $name * @param Sabre_VObject_ElementList $iterator */ public function __construct($name, Sabre_VObject_ElementList $iterator = null) { @@ -47,23 +81,65 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { } /** - * Turns the object back into a serialized blob. - * - * @return string + * Turns the object back into a serialized blob. + * + * @return string */ public function serialize() { $str = "BEGIN:" . $this->name . "\r\n"; + + /** + * Gives a component a 'score' for sorting purposes. + * + * This is solely used by the childrenSort method. + * + * A higher score means the item will be higher in the list + * + * @param Sabre_VObject_Node $n + * @return int + */ + $sortScore = function($n) { + + if ($n instanceof Sabre_VObject_Component) { + // We want to encode VTIMEZONE first, this is a personal + // preference. + if ($n->name === 'VTIMEZONE') { + return 1; + } else { + return 0; + } + } else { + // VCARD version 4.0 wants the VERSION property to appear first + if ($n->name === 'VERSION') { + return 3; + } else { + return 2; + } + } + + }; + + usort($this->children, function($a, $b) use ($sortScore) { + + $sA = $sortScore($a); + $sB = $sortScore($b); + + if ($sA === $sB) return 0; + + return ($sA > $sB) ? -1 : 1; + + }); + foreach($this->children as $child) $str.=$child->serialize(); $str.= "END:" . $this->name . "\r\n"; - + return $str; } - /** - * Adds a new componenten or element + * Adds a new component or element * * You can call this method with the following syntaxes: * @@ -71,10 +147,10 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { * add(string $name, $value) * * The first version adds an Element - * The second adds a property as a string. - * - * @param mixed $item - * @param mixed $itemValue + * The second adds a property as a string. + * + * @param mixed $item + * @param mixed $itemValue * @return void */ public function add($item, $itemValue = null) { @@ -90,12 +166,12 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { if (!is_scalar($itemValue)) { throw new InvalidArgumentException('The second argument must be scalar'); } - $item = new Sabre_VObject_Property($item,$itemValue); + $item = Sabre_VObject_Property::create($item,$itemValue); $item->parent = $this; $this->children[] = $item; } else { - + throw new InvalidArgumentException('The first argument must either be a Sabre_VObject_Element or a string'); } @@ -103,9 +179,9 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { } /** - * Returns an iterable list of children - * - * @return Sabre_VObject_ElementList + * Returns an iterable list of children + * + * @return Sabre_VObject_ElementList */ public function children() { @@ -116,18 +192,18 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { /** * Returns an array with elements that match the specified name. * - * This function is also aware of MIME-Directory groups (as they appear in - * vcards). This means that if a property is grouped as "HOME.EMAIL", it - * will also be returned when searching for just "EMAIL". If you want to - * search for a property in a specific group, you can select on the entire - * string ("HOME.EMAIL"). If you want to search on a specific property that + * This function is also aware of MIME-Directory groups (as they appear in + * vcards). This means that if a property is grouped as "HOME.EMAIL", it + * will also be returned when searching for just "EMAIL". If you want to + * search for a property in a specific group, you can select on the entire + * string ("HOME.EMAIL"). If you want to search on a specific property that * has not been assigned a group, specify ".EMAIL". * - * Keys are retained from the 'children' array, which may be confusing in - * certain cases. + * Keys are retained from the 'children' array, which may be confusing in + * certain cases. * - * @param string $name - * @return array + * @param string $name + * @return array */ public function select($name) { @@ -144,7 +220,7 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { strtoupper($child->name) === $name && (is_null($group) || ( $child instanceof Sabre_VObject_Property && strtoupper($child->group) === $group)) ) { - + $result[$key] = $child; } @@ -155,16 +231,35 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { } + /** + * This method only returns a list of sub-components. Properties are + * ignored. + * + * @return array + */ + public function getComponents() { + + $result = array(); + foreach($this->children as $child) { + if ($child instanceof Sabre_VObject_Component) { + $result[] = $child; + } + } + + return $result; + + } + /* Magic property accessors {{{ */ /** - * Using 'get' you will either get a propery or component, + * Using 'get' you will either get a property or component, * * If there were no child-elements found with the specified name, * null is returned. - * - * @param string $name - * @return void + * + * @param string $name + * @return Sabre_VObject_Property */ public function __get($name) { @@ -173,6 +268,7 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { return null; } else { $firstMatch = current($matches); + /** @var $firstMatch Sabre_VObject_Property */ $firstMatch->setIterator(new Sabre_VObject_ElementList(array_values($matches))); return $firstMatch; } @@ -180,10 +276,10 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { } /** - * This method checks if a sub-element with the specified name exists. - * - * @param string $name - * @return bool + * This method checks if a sub-element with the specified name exists. + * + * @param string $name + * @return bool */ public function __isset($name) { @@ -200,7 +296,7 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { * * If the item already exists, it will be removed. If you want to add * a new item with the same name, always use the add() method. - * + * * @param string $name * @param mixed $value * @return void @@ -218,7 +314,7 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { $this->children[] = $value; } } elseif (is_scalar($value)) { - $property = new Sabre_VObject_Property($name,$value); + $property = Sabre_VObject_Property::create($name,$value); $property->parent = $this; if (!is_null($overWrite)) { $this->children[$overWrite] = $property; @@ -232,9 +328,9 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { } /** - * Removes all properties and components within this component. - * - * @param string $name + * Removes all properties and components within this component. + * + * @param string $name * @return void */ public function __unset($name) { @@ -251,4 +347,19 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { /* }}} */ + /** + * This method is automatically called when the object is cloned. + * Specifically, this will ensure all child elements are also cloned. + * + * @return void + */ + public function __clone() { + + foreach($this->children as $key=>$child) { + $this->children[$key] = clone $child; + $this->children[$key]->parent = $this; + } + + } + } diff --git a/3rdparty/Sabre/VObject/Component/VAlarm.php b/3rdparty/Sabre/VObject/Component/VAlarm.php new file mode 100644 index 0000000000..ebb4a9b18f --- /dev/null +++ b/3rdparty/Sabre/VObject/Component/VAlarm.php @@ -0,0 +1,102 @@ +TRIGGER; + if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') { + $triggerDuration = Sabre_VObject_DateTimeParser::parseDuration($this->TRIGGER); + $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START'; + + $parentComponent = $this->parent; + if ($related === 'START') { + $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); + $effectiveTrigger->add($triggerDuration); + } else { + if ($parentComponent->name === 'VTODO') { + $endProp = 'DUE'; + } elseif ($parentComponent->name === 'VEVENT') { + $endProp = 'DTEND'; + } else { + throw new Sabre_DAV_Exception('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); + } + + if (isset($parentComponent->$endProp)) { + $effectiveTrigger = clone $parentComponent->$endProp->getDateTime(); + $effectiveTrigger->add($triggerDuration); + } elseif (isset($parentComponent->DURATION)) { + $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); + $duration = Sabre_VObject_DateTimeParser::parseDuration($parentComponent->DURATION); + $effectiveTrigger->add($duration); + $effectiveTrigger->add($triggerDuration); + } else { + $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime(); + $effectiveTrigger->add($triggerDuration); + } + } + } else { + $effectiveTrigger = $trigger->getDateTime(); + } + return $effectiveTrigger; + + } + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on the CalDAV specification. + * + * @param DateTime $start + * @param DateTime $end + * @return bool + */ + public function isInTimeRange(DateTime $start, DateTime $end) { + + $effectiveTrigger = $this->getEffectiveTriggerTime(); + + if (isset($this->DURATION)) { + $duration = Sabre_VObject_DateTimeParser::parseDuration($this->DURATION); + $repeat = (string)$this->repeat; + if (!$repeat) { + $repeat = 1; + } + + $period = new DatePeriod($effectiveTrigger, $duration, (int)$repeat); + + foreach($period as $occurrence) { + + if ($start <= $occurrence && $end > $occurrence) { + return true; + } + } + return false; + } else { + return ($start <= $effectiveTrigger && $end > $effectiveTrigger); + } + + } + +} + +?> diff --git a/3rdparty/Sabre/VObject/Component/VCalendar.php b/3rdparty/Sabre/VObject/Component/VCalendar.php new file mode 100644 index 0000000000..f3be29afdb --- /dev/null +++ b/3rdparty/Sabre/VObject/Component/VCalendar.php @@ -0,0 +1,133 @@ +children as $component) { + + if (!$component instanceof Sabre_VObject_Component) + continue; + + if (isset($component->{'RECURRENCE-ID'})) + continue; + + if ($componentName && $component->name !== strtoupper($componentName)) + continue; + + if ($component->name === 'VTIMEZONE') + continue; + + $components[] = $component; + + } + + return $components; + + } + + /** + * If this calendar object, has events with recurrence rules, this method + * can be used to expand the event into multiple sub-events. + * + * Each event will be stripped from it's recurrence information, and only + * the instances of the event in the specified timerange will be left + * alone. + * + * In addition, this method will cause timezone information to be stripped, + * and normalized to UTC. + * + * This method will alter the VCalendar. This cannot be reversed. + * + * This functionality is specifically used by the CalDAV standard. It is + * possible for clients to request expand events, if they are rather simple + * clients and do not have the possibility to calculate recurrences. + * + * @param DateTime $start + * @param DateTime $end + * @return void + */ + public function expand(DateTime $start, DateTime $end) { + + $newEvents = array(); + + foreach($this->select('VEVENT') as $key=>$vevent) { + + if (isset($vevent->{'RECURRENCE-ID'})) { + unset($this->children[$key]); + continue; + } + + + if (!$vevent->rrule) { + unset($this->children[$key]); + if ($vevent->isInTimeRange($start, $end)) { + $newEvents[] = $vevent; + } + continue; + } + + $uid = (string)$vevent->uid; + if (!$uid) { + throw new LogicException('Event did not have a UID!'); + } + + $it = new Sabre_VObject_RecurrenceIterator($this, $vevent->uid); + $it->fastForward($start); + + while($it->valid() && $it->getDTStart() < $end) { + + if ($it->getDTEnd() > $start) { + + $newEvents[] = $it->getEventObject(); + + } + $it->next(); + + } + unset($this->children[$key]); + + } + + foreach($newEvents as $newEvent) { + + foreach($newEvent->children as $child) { + if ($child instanceof Sabre_VObject_Property_DateTime && + $child->getDateType() == Sabre_VObject_Property_DateTime::LOCALTZ) { + $child->setDateTime($child->getDateTime(),Sabre_VObject_Property_DateTime::UTC); + } + } + + $this->add($newEvent); + + } + + // Removing all VTIMEZONE components + unset($this->VTIMEZONE); + + } + +} + diff --git a/3rdparty/Sabre/VObject/Component/VEvent.php b/3rdparty/Sabre/VObject/Component/VEvent.php new file mode 100644 index 0000000000..4cc1e36d7d --- /dev/null +++ b/3rdparty/Sabre/VObject/Component/VEvent.php @@ -0,0 +1,70 @@ +RRULE) { + $it = new Sabre_VObject_RecurrenceIterator($this); + $it->fastForward($start); + + // We fast-forwarded to a spot where the end-time of the + // recurrence instance exceeded the start of the requested + // time-range. + // + // If the starttime of the recurrence did not exceed the + // end of the time range as well, we have a match. + return ($it->getDTStart() < $end && $it->getDTEnd() > $start); + + } + + $effectiveStart = $this->DTSTART->getDateTime(); + if (isset($this->DTEND)) { + $effectiveEnd = $this->DTEND->getDateTime(); + // If this was an all-day event, we should just increase the + // end-date by 1. Otherwise the event will last until the second + // the date changed, by increasing this by 1 day the event lasts + // all of the last day as well. + if ($this->DTSTART->getDateType() == Sabre_VObject_Element_DateTime::DATE) { + $effectiveEnd->modify('+1 day'); + } + } elseif (isset($this->DURATION)) { + $effectiveEnd = clone $effectiveStart; + $effectiveEnd->add( Sabre_VObject_DateTimeParser::parseDuration($this->DURATION) ); + } elseif ($this->DTSTART->getDateType() == Sabre_VObject_Element_DateTime::DATE) { + $effectiveEnd = clone $effectiveStart; + $effectiveEnd->modify('+1 day'); + } else { + $effectiveEnd = clone $effectiveStart; + } + return ( + ($start <= $effectiveEnd) && ($end > $effectiveStart) + ); + + } + +} + +?> diff --git a/3rdparty/Sabre/VObject/Component/VJournal.php b/3rdparty/Sabre/VObject/Component/VJournal.php new file mode 100644 index 0000000000..22b3ec921e --- /dev/null +++ b/3rdparty/Sabre/VObject/Component/VJournal.php @@ -0,0 +1,46 @@ +DTSTART)?$this->DTSTART->getDateTime():null; + if ($dtstart) { + $effectiveEnd = clone $dtstart; + if ($this->DTSTART->getDateType() == Sabre_VObject_Element_DateTime::DATE) { + $effectiveEnd->modify('+1 day'); + } + + return ($start <= $effectiveEnd && $end > $dtstart); + + } + return false; + + + } + +} + +?> diff --git a/3rdparty/Sabre/VObject/Component/VTodo.php b/3rdparty/Sabre/VObject/Component/VTodo.php new file mode 100644 index 0000000000..79d06298d7 --- /dev/null +++ b/3rdparty/Sabre/VObject/Component/VTodo.php @@ -0,0 +1,68 @@ +DTSTART)?$this->DTSTART->getDateTime():null; + $duration = isset($this->DURATION)?Sabre_VObject_DateTimeParser::parseDuration($this->DURATION):null; + $due = isset($this->DUE)?$this->DUE->getDateTime():null; + $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null; + $created = isset($this->CREATED)?$this->CREATED->getDateTime():null; + + if ($dtstart) { + if ($duration) { + $effectiveEnd = clone $dtstart; + $effectiveEnd->add($duration); + return $start <= $effectiveEnd && $end > $dtstart; + } elseif ($due) { + return + ($start < $due || $start <= $dtstart) && + ($end > $dtstart || $end >= $due); + } else { + return $start <= $dtstart && $end > $dtstart; + } + } + if ($due) { + return ($start < $due && $end >= $due); + } + if ($completed && $created) { + return + ($start <= $created || $start <= $completed) && + ($end >= $created || $end >= $completed); + } + if ($completed) { + return ($start <= $completed && $end >= $completed); + } + if ($created) { + return ($end > $created); + } + return true; + + } + +} + +?> diff --git a/3rdparty/Sabre/VObject/DateTimeParser.php b/3rdparty/Sabre/VObject/DateTimeParser.php new file mode 100644 index 0000000000..1e2d54ef3a --- /dev/null +++ b/3rdparty/Sabre/VObject/DateTimeParser.php @@ -0,0 +1,177 @@ +setTimeZone(new DateTimeZone('UTC')); + return $date; + + } + + /** + * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object + * + * @param string $date + * @return DateTime + */ + static public function parseDate($date) { + + // Format is YYYYMMDD + $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches); + + if (!$result) { + throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar date value is incorrect: ' . $date); + } + + $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new DateTimeZone('UTC')); + return $date; + + } + + /** + * Parses an iCalendar (RFC5545) formatted duration value. + * + * This method will either return a DateTimeInterval object, or a string + * suitable for strtotime or DateTime::modify. + * + * @param string $duration + * @param bool $asString + * @return DateInterval|string + */ + static public function parseDuration($duration, $asString = false) { + + $result = preg_match('/^(?P\+|-)?P((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$/', $duration, $matches); + if (!$result) { + throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar duration value is incorrect: ' . $duration); + } + + if (!$asString) { + $invert = false; + if ($matches['plusminus']==='-') { + $invert = true; + } + + + $parts = array( + 'week', + 'day', + 'hour', + 'minute', + 'second', + ); + foreach($parts as $part) { + $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0; + } + + + // We need to re-construct the $duration string, because weeks and + // days are not supported by DateInterval in the same string. + $duration = 'P'; + $days = $matches['day']; + if ($matches['week']) { + $days+=$matches['week']*7; + } + if ($days) + $duration.=$days . 'D'; + + if ($matches['minute'] || $matches['second'] || $matches['hour']) { + $duration.='T'; + + if ($matches['hour']) + $duration.=$matches['hour'].'H'; + + if ($matches['minute']) + $duration.=$matches['minute'].'M'; + + if ($matches['second']) + $duration.=$matches['second'].'S'; + + } + + $iv = new DateInterval($duration); + if ($invert) $iv->invert = true; + + return $iv; + + } + + + + $parts = array( + 'week', + 'day', + 'hour', + 'minute', + 'second', + ); + + $newDur = ''; + foreach($parts as $part) { + if (isset($matches[$part]) && $matches[$part]) { + $newDur.=' '.$matches[$part] . ' ' . $part . 's'; + } + } + + $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur); + return $newDur; + + } + + /** + * Parses either a Date or DateTime, or Duration value. + * + * @param string $date + * @param DateTimeZone|string $referenceTZ + * @return DateTime|DateInterval + */ + static public function parse($date, $referenceTZ = null) { + + if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) { + return self::parseDuration($date); + } elseif (strlen($date)===8) { + return self::parseDate($date); + } else { + return self::parseDateTime($date, $referenceTZ); + } + + } + + +} diff --git a/3rdparty/Sabre/VObject/Element.php b/3rdparty/Sabre/VObject/Element.php index 8d2b0aaacd..e20ff0b353 100644 --- a/3rdparty/Sabre/VObject/Element.php +++ b/3rdparty/Sabre/VObject/Element.php @@ -2,14 +2,15 @@ /** * Base class for all elements - * + * * @package Sabre * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ abstract class Sabre_VObject_Element extends Sabre_VObject_Node { + public $parent = null; } diff --git a/3rdparty/Sabre/VObject/Element/DateTime.php b/3rdparty/Sabre/VObject/Element/DateTime.php index 3350ec02c8..5e5eb7ab6f 100644 --- a/3rdparty/Sabre/VObject/Element/DateTime.php +++ b/3rdparty/Sabre/VObject/Element/DateTime.php @@ -1,25 +1,18 @@ setValue($dt->format('Ymd\\THis')); - $this->offsetUnset('VALUE'); - $this->offsetUnset('TZID'); - $this->offsetSet('VALUE','DATE-TIME'); - break; - case self::UTC : - $dt->setTimeZone(new DateTimeZone('UTC')); - $this->setValue($dt->format('Ymd\\THis\\Z')); - $this->offsetUnset('VALUE'); - $this->offsetUnset('TZID'); - $this->offsetSet('VALUE','DATE-TIME'); - break; - case self::LOCALTZ : - $this->setValue($dt->format('Ymd\\THis')); - $this->offsetUnset('VALUE'); - $this->offsetUnset('TZID'); - $this->offsetSet('VALUE','DATE-TIME'); - $this->offsetSet('TZID', $dt->getTimeZone()->getName()); - break; - case self::DATE : - $this->setValue($dt->format('Ymd')); - $this->offsetUnset('VALUE'); - $this->offsetUnset('TZID'); - $this->offsetSet('VALUE','DATE'); - break; - default : - throw new InvalidArgumentException('You must pass a valid dateType constant'); - - } - $this->dateTime = $dt; - $this->dateType = $dateType; - - } - - /** - * Returns the current DateTime value. - * - * If no value was set, this method returns null. - * - * @return DateTime|null - */ - public function getDateTime() { - - if ($this->dateTime) - return $this->dateTime; - - list( - $this->dateType, - $this->dateTime - ) = self::parseData($this->value, $this); - return $this->dateTime; - - } - - /** - * Returns the type of Date format. - * - * This method returns one of the format constants. If no date was set, - * this method will return null. - * - * @return int|null - */ - public function getDateType() { - - if ($this->dateType) - return $this->dateType; - - list( - $this->dateType, - $this->dateTime, - ) = self::parseData($this->value, $this); - return $this->dateType; - - } - - /** - * Parses the internal data structure to figure out what the current date - * and time is. - * - * The returned array contains two elements: - * 1. A 'DateType' constant (as defined on this class), or null. - * 2. A DateTime object (or null) - * - * @param string|null $propertyValue The string to parse (yymmdd or - * ymmddThhmmss, etc..) - * @param Sabre_VObject_Property|null $property The instance of the - * property we're parsing. - * @return array - */ - static public function parseData($propertyValue, Sabre_VObject_Property $property = null) { - - if (is_null($propertyValue)) { - return array(null, null); - } - - $date = '(?P[1-2][0-9]{3})(?P[0-1][0-9])(?P[0-3][0-9])'; - $time = '(?P[0-2][0-9])(?P[0-5][0-9])(?P[0-5][0-9])'; - $regex = "/^$date(T$time(?PZ)?)?$/"; - - if (!preg_match($regex, $propertyValue, $matches)) { - throw new InvalidArgumentException($propertyValue . ' is not a valid DateTime or Date string'); - } - - if (!isset($matches['hour'])) { - // Date-only - return array( - self::DATE, - new DateTime($matches['year'] . '-' . $matches['month'] . '-' . $matches['date'] . ' 00:00:00'), - ); - } - - $dateStr = - $matches['year'] .'-' . - $matches['month'] . '-' . - $matches['date'] . ' ' . - $matches['hour'] . ':' . - $matches['minute'] . ':' . - $matches['second']; - - if (isset($matches['isutc'])) { - $dt = new DateTime($dateStr,new DateTimeZone('UTC')); - $dt->setTimeZone(new DateTimeZone('UTC')); - return array( - self::UTC, - $dt - ); - } - - // Finding the timezone. - $tzid = $property['TZID']; - if (!$tzid) { - return array( - self::LOCAL, - new DateTime($dateStr) - ); - } - - try { - $tz = new DateTimeZone($tzid->value); - } catch (Exception $e) { - - // The id was invalid, we're going to try to find the information - // through the VTIMEZONE object. - - // First we find the root object - $root = $property; - while($root->parent) { - $root = $root->parent; - } - - if (isset($root->VTIMEZONE)) { - foreach($root->VTIMEZONE as $vtimezone) { - if (((string)$vtimezone->TZID) == $tzid) { - if (isset($vtimezone->{'X-LIC-LOCATION'})) { - $tzid = (string)$vtimezone->{'X-LIC-LOCATION'}; - } - } - } - } - - $tz = new DateTimeZone($tzid); - - } - $dt = new DateTime($dateStr, $tz); - $dt->setTimeZone($tz); - - return array( - self::LOCALTZ, - $dt - ); - - } - } - -?> diff --git a/3rdparty/Sabre/VObject/Element/MultiDateTime.php b/3rdparty/Sabre/VObject/Element/MultiDateTime.php index dc6ca5abb8..8a12ced94a 100644 --- a/3rdparty/Sabre/VObject/Element/MultiDateTime.php +++ b/3rdparty/Sabre/VObject/Element/MultiDateTime.php @@ -1,168 +1,17 @@ offsetUnset('VALUE'); - $this->offsetUnset('TZID'); - switch($dateType) { - - case Sabre_VObject_Element_DateTime::LOCAL : - $val = array(); - foreach($dt as $i) { - $val[] = $i->format('Ymd\\THis'); - } - $this->setValue(implode(',',$val)); - $this->offsetSet('VALUE','DATE-TIME'); - break; - case Sabre_VObject_Element_DateTime::UTC : - $val = array(); - foreach($dt as $i) { - $i->setTimeZone(new DateTimeZone('UTC')); - $val[] = $i->format('Ymd\\THis\\Z'); - } - $this->setValue(implode(',',$val)); - $this->offsetSet('VALUE','DATE-TIME'); - break; - case Sabre_VObject_Element_DateTime::LOCALTZ : - $val = array(); - foreach($dt as $i) { - $val[] = $i->format('Ymd\\THis'); - } - $this->setValue(implode(',',$val)); - $this->offsetSet('VALUE','DATE-TIME'); - $this->offsetSet('TZID', $dt[0]->getTimeZone()->getName()); - break; - case Sabre_VObject_Element_DateTime::DATE : - $val = array(); - foreach($dt as $i) { - $val[] = $i->format('Ymd'); - } - $this->setValue(implode(',',$val)); - $this->offsetSet('VALUE','DATE'); - break; - default : - throw new InvalidArgumentException('You must pass a valid dateType constant'); - - } - $this->dateTimes = $dt; - $this->dateType = $dateType; - - } - - /** - * Returns the current DateTime value. - * - * If no value was set, this method returns null. - * - * @return array|null - */ - public function getDateTimes() { - - if ($this->dateTimes) - return $this->dateTimes; - - $dts = array(); - - if (!$this->value) { - $this->dateTimes = null; - $this->dateType = null; - return null; - } - - foreach(explode(',',$this->value) as $val) { - list( - $type, - $dt - ) = Sabre_VObject_Element_DateTime::parseData($val, $this); - $dts[] = $dt; - $this->dateType = $type; - } - $this->dateTimes = $dts; - return $this->dateTimes; - - } - - /** - * Returns the type of Date format. - * - * This method returns one of the format constants. If no date was set, - * this method will return null. - * - * @return int|null - */ - public function getDateType() { - - if ($this->dateType) - return $this->dateType; - - if (!$this->value) { - $this->dateTimes = null; - $this->dateType = null; - return null; - } - - $dts = array(); - foreach(explode(',',$this->value) as $val) { - list( - $type, - $dt - ) = Sabre_VObject_Element_DateTime::parseData($val, $this); - $dts[] = $dt; - $this->dateType = $type; - } - $this->dateTimes = $dts; - return $this->dateType; - - } +class Sabre_VObject_Element_MultiDateTime extends Sabre_VObject_Property_MultiDateTime { } - -?> diff --git a/3rdparty/Sabre/VObject/ElementList.php b/3rdparty/Sabre/VObject/ElementList.php index 9922cd587b..7e508db20f 100644 --- a/3rdparty/Sabre/VObject/ElementList.php +++ b/3rdparty/Sabre/VObject/ElementList.php @@ -8,15 +8,15 @@ * * @package Sabre * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { /** - * Inner elements - * + * Inner elements + * * @var array */ protected $elements = array(); @@ -24,37 +24,37 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { /** * Creates the element list. * - * @param array $elements + * @param array $elements */ public function __construct(array $elements) { $this->elements = $elements; - } + } /* {{{ Iterator interface */ /** - * Current position - * - * @var int + * Current position + * + * @var int */ private $key = 0; /** - * Returns current item in iteration - * - * @return Sabre_VObject_Element + * Returns current item in iteration + * + * @return Sabre_VObject_Element */ public function current() { return $this->elements[$this->key]; } - + /** - * To the next item in the iterator - * + * To the next item in the iterator + * * @return void */ public function next() { @@ -64,8 +64,8 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { } /** - * Returns the current iterator key - * + * Returns the current iterator key + * * @return int */ public function key() { @@ -75,9 +75,9 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { } /** - * Returns true if the current position in the iterator is a valid one - * - * @return bool + * Returns true if the current position in the iterator is a valid one + * + * @return bool */ public function valid() { @@ -86,9 +86,9 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { } /** - * Rewinds the iterator - * - * @return void + * Rewinds the iterator + * + * @return void */ public function rewind() { @@ -101,9 +101,9 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { /* {{{ Countable interface */ /** - * Returns the number of elements - * - * @return int + * Returns the number of elements + * + * @return int */ public function count() { @@ -115,12 +115,12 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { /* {{{ ArrayAccess Interface */ - + /** * Checks if an item exists through ArrayAccess. * - * @param int $offset - * @return bool + * @param int $offset + * @return bool */ public function offsetExists($offset) { @@ -131,8 +131,8 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { /** * Gets an item through ArrayAccess. * - * @param int $offset - * @return mixed + * @param int $offset + * @return mixed */ public function offsetGet($offset) { @@ -143,8 +143,8 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { /** * Sets an item through ArrayAccess. * - * @param int $offset - * @param mixed $value + * @param int $offset + * @param mixed $value * @return void */ public function offsetSet($offset,$value) { @@ -158,7 +158,7 @@ class Sabre_VObject_ElementList implements Iterator, Countable, ArrayAccess { * * This method just forwards the request to the inner iterator * - * @param int $offset + * @param int $offset * @return void */ public function offsetUnset($offset) { diff --git a/3rdparty/Sabre/VObject/FreeBusyGenerator.php b/3rdparty/Sabre/VObject/FreeBusyGenerator.php new file mode 100644 index 0000000000..1c96a64a00 --- /dev/null +++ b/3rdparty/Sabre/VObject/FreeBusyGenerator.php @@ -0,0 +1,297 @@ +baseObject = $vcalendar; + + } + + /** + * Sets the input objects + * + * Every object must either be a string or a Sabre_VObject_Component. + * + * @param array $objects + * @return void + */ + public function setObjects(array $objects) { + + $this->objects = array(); + foreach($objects as $object) { + + if (is_string($object)) { + $this->objects[] = Sabre_VObject_Reader::read($object); + } elseif ($object instanceof Sabre_VObject_Component) { + $this->objects[] = $object; + } else { + throw new InvalidArgumentException('You can only pass strings or Sabre_VObject_Component arguments to setObjects'); + } + + } + + } + + /** + * Sets the time range + * + * Any freebusy object falling outside of this time range will be ignored. + * + * @param DateTime $start + * @param DateTime $end + * @return void + */ + public function setTimeRange(DateTime $start = null, DateTime $end = null) { + + $this->start = $start; + $this->end = $end; + + } + + /** + * Parses the input data and returns a correct VFREEBUSY object, wrapped in + * a VCALENDAR. + * + * @return Sabre_VObject_Component + */ + public function getResult() { + + $busyTimes = array(); + + foreach($this->objects as $object) { + + foreach($object->getBaseComponents() as $component) { + + switch($component->name) { + + case 'VEVENT' : + + $FBTYPE = 'BUSY'; + if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) { + break; + } + if (isset($component->STATUS)) { + $status = strtoupper($component->STATUS); + if ($status==='CANCELLED') { + break; + } + if ($status==='TENTATIVE') { + $FBTYPE = 'BUSY-TENTATIVE'; + } + } + + $times = array(); + + if ($component->RRULE) { + + $iterator = new Sabre_VObject_RecurrenceIterator($object, (string)$component->uid); + if ($this->start) { + $iterator->fastForward($this->start); + } + + $maxRecurrences = 200; + + while($iterator->valid() && --$maxRecurrences) { + + $startTime = $iterator->getDTStart(); + if ($this->end && $startTime > $this->end) { + break; + } + $times[] = array( + $iterator->getDTStart(), + $iterator->getDTEnd(), + ); + + $iterator->next(); + + } + + } else { + + $startTime = $component->DTSTART->getDateTime(); + if ($this->end && $startTime > $this->end) { + break; + } + $endTime = null; + if (isset($component->DTEND)) { + $endTime = $component->DTEND->getDateTime(); + } elseif (isset($component->DURATION)) { + $duration = Sabre_VObject_DateTimeParser::parseDuration((string)$component->DURATION); + $endTime = clone $startTime; + $endTime->add($duration); + } elseif ($component->DTSTART->getDateType() === Sabre_VObject_Property_DateTime::DATE) { + $endTime = clone $startTime; + $endTime->modify('+1 day'); + } else { + // The event had no duration (0 seconds) + break; + } + + $times[] = array($startTime, $endTime); + + } + + foreach($times as $time) { + + if ($this->end && $time[0] > $this->end) break; + if ($this->start && $time[1] < $this->start) break; + + $busyTimes[] = array( + $time[0], + $time[1], + $FBTYPE, + ); + } + break; + + case 'VFREEBUSY' : + foreach($component->FREEBUSY as $freebusy) { + + $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY'; + + // Skipping intervals marked as 'free' + if ($fbType==='FREE') + continue; + + $values = explode(',', $freebusy); + foreach($values as $value) { + list($startTime, $endTime) = explode('/', $value); + $startTime = Sabre_VObject_DateTimeParser::parseDateTime($startTime); + + if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') { + $duration = Sabre_VObject_DateTimeParser::parseDuration($endTime); + $endTime = clone $startTime; + $endTime->add($duration); + } else { + $endTime = Sabre_VObject_DateTimeParser::parseDateTime($endTime); + } + + if($this->start && $this->start > $endTime) continue; + if($this->end && $this->end < $startTime) continue; + $busyTimes[] = array( + $startTime, + $endTime, + $fbType + ); + + } + + + } + break; + + + + } + + + } + + } + + if ($this->baseObject) { + $calendar = $this->baseObject; + } else { + $calendar = new Sabre_VObject_Component('VCALENDAR'); + $calendar->version = '2.0'; + if (Sabre_DAV_Server::$exposeVersion) { + $calendar->prodid = '-//SabreDAV//Sabre VObject ' . Sabre_VObject_Version::VERSION . '//EN'; + } else { + $calendar->prodid = '-//SabreDAV//Sabre VObject//EN'; + } + $calendar->calscale = 'GREGORIAN'; + } + + $vfreebusy = new Sabre_VObject_Component('VFREEBUSY'); + $calendar->add($vfreebusy); + + if ($this->start) { + $dtstart = new Sabre_VObject_Property_DateTime('DTSTART'); + $dtstart->setDateTime($this->start,Sabre_VObject_Property_DateTime::UTC); + $vfreebusy->add($dtstart); + } + if ($this->end) { + $dtend = new Sabre_VObject_Property_DateTime('DTEND'); + $dtend->setDateTime($this->start,Sabre_VObject_Property_DateTime::UTC); + $vfreebusy->add($dtend); + } + $dtstamp = new Sabre_VObject_Property_DateTime('DTSTAMP'); + $dtstamp->setDateTime(new DateTime('now'), Sabre_VObject_Property_DateTime::UTC); + $vfreebusy->add($dtstamp); + + foreach($busyTimes as $busyTime) { + + $busyTime[0]->setTimeZone(new DateTimeZone('UTC')); + $busyTime[1]->setTimeZone(new DateTimeZone('UTC')); + + $prop = new Sabre_VObject_Property( + 'FREEBUSY', + $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z') + ); + $prop['FBTYPE'] = $busyTime[2]; + $vfreebusy->add($prop); + + } + + return $calendar; + + } + +} + diff --git a/3rdparty/Sabre/VObject/Node.php b/3rdparty/Sabre/VObject/Node.php index 7100b62f1c..d89e01b56c 100644 --- a/3rdparty/Sabre/VObject/Node.php +++ b/3rdparty/Sabre/VObject/Node.php @@ -1,47 +1,47 @@ iterator)) + if (!is_null($this->iterator)) return $this->iterator; return new Sabre_VObject_ElementList(array($this)); @@ -52,8 +52,8 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou * Sets the overridden iterator * * Note that this is not actually part of the iterator interface - * - * @param Sabre_VObject_ElementList $iterator + * + * @param Sabre_VObject_ElementList $iterator * @return void */ public function setIterator(Sabre_VObject_ElementList $iterator) { @@ -67,9 +67,9 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou /* {{{ Countable interface */ /** - * Returns the number of elements - * - * @return int + * Returns the number of elements + * + * @return int */ public function count() { @@ -82,14 +82,14 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou /* {{{ ArrayAccess Interface */ - + /** * Checks if an item exists through ArrayAccess. * * This method just forwards the request to the inner iterator - * - * @param int $offset - * @return bool + * + * @param int $offset + * @return bool */ public function offsetExists($offset) { @@ -103,8 +103,8 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou * * This method just forwards the request to the inner iterator * - * @param int $offset - * @return mixed + * @param int $offset + * @return mixed */ public function offsetGet($offset) { @@ -118,8 +118,8 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou * * This method just forwards the request to the inner iterator * - * @param int $offset - * @param mixed $value + * @param int $offset + * @param mixed $value * @return void */ public function offsetSet($offset,$value) { @@ -134,7 +134,7 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou * * This method just forwards the request to the inner iterator * - * @param int $offset + * @param int $offset * @return void */ public function offsetUnset($offset) { diff --git a/3rdparty/Sabre/VObject/Parameter.php b/3rdparty/Sabre/VObject/Parameter.php index 9ebab6ec69..2e39af5f78 100644 --- a/3rdparty/Sabre/VObject/Parameter.php +++ b/3rdparty/Sabre/VObject/Parameter.php @@ -5,51 +5,54 @@ * * This class represents a parameter. A parameter is always tied to a property. * In the case of: - * DTSTART;VALUE=DATE:20101108 + * DTSTART;VALUE=DATE:20101108 * VALUE=DATE would be the parameter name and value. - * + * * @package Sabre * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_VObject_Parameter extends Sabre_VObject_Node { /** - * Parameter name - * - * @var string + * Parameter name + * + * @var string */ public $name; /** - * Parameter value - * - * @var string + * Parameter value + * + * @var string */ public $value; /** - * Sets up the object - * - * @param string $name - * @param string $value + * Sets up the object + * + * @param string $name + * @param string $value */ public function __construct($name, $value = null) { $this->name = strtoupper($name); $this->value = $value; - } + } /** - * Turns the object back into a serialized blob. - * - * @return string + * Turns the object back into a serialized blob. + * + * @return string */ public function serialize() { + if (is_null($this->value)) { + return $this->name; + } $src = array( '\\', "\n", @@ -68,9 +71,9 @@ class Sabre_VObject_Parameter extends Sabre_VObject_Node { } /** - * Called when this object is being cast to a string - * - * @return string + * Called when this object is being cast to a string + * + * @return string */ public function __toString() { diff --git a/3rdparty/Sabre/VObject/ParseException.php b/3rdparty/Sabre/VObject/ParseException.php index ed4ef2e859..1b5e95bf16 100644 --- a/3rdparty/Sabre/VObject/ParseException.php +++ b/3rdparty/Sabre/VObject/ParseException.php @@ -2,11 +2,11 @@ /** * Exception thrown by Sabre_VObject_Reader if an invalid object was attempted to be parsed. - * + * * @package Sabre * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_VObject_ParseException extends Exception { } diff --git a/3rdparty/Sabre/VObject/Property.php b/3rdparty/Sabre/VObject/Property.php index 0605822904..ce74fe3865 100644 --- a/3rdparty/Sabre/VObject/Property.php +++ b/3rdparty/Sabre/VObject/Property.php @@ -4,58 +4,103 @@ * VObject Property * * A property in VObject is usually in the form PARAMNAME:paramValue. - * An example is : SUMMARY:Weekly meeting + * An example is : SUMMARY:Weekly meeting * * Properties can also have parameters: * SUMMARY;LANG=en:Weekly meeting. * - * Parameters can be accessed using the ArrayAccess interface. + * Parameters can be accessed using the ArrayAccess interface. * * @package Sabre * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_VObject_Property extends Sabre_VObject_Element { /** - * Propertyname - * - * @var string + * Propertyname + * + * @var string */ public $name; /** * Group name - * + * * This may be something like 'HOME' for vcards. * - * @var string + * @var string */ public $group; /** - * Property parameters - * - * @var array + * Property parameters + * + * @var array */ public $parameters = array(); /** - * Property value - * - * @var string + * Property value + * + * @var string */ public $value; + /** + * If properties are added to this map, they will be automatically mapped + * to their respective classes, if parsed by the reader or constructed with + * the 'create' method. + * + * @var array + */ + static public $classMap = array( + 'COMPLETED' => 'Sabre_VObject_Property_DateTime', + 'CREATED' => 'Sabre_VObject_Property_DateTime', + 'DTEND' => 'Sabre_VObject_Property_DateTime', + 'DTSTAMP' => 'Sabre_VObject_Property_DateTime', + 'DTSTART' => 'Sabre_VObject_Property_DateTime', + 'DUE' => 'Sabre_VObject_Property_DateTime', + 'EXDATE' => 'Sabre_VObject_Property_MultiDateTime', + 'LAST-MODIFIED' => 'Sabre_VObject_Property_DateTime', + 'RECURRENCE-ID' => 'Sabre_VObject_Property_DateTime', + 'TRIGGER' => 'Sabre_VObject_Property_DateTime', + ); + + /** + * Creates the new property by name, but in addition will also see if + * there's a class mapped to the property name. + * + * @param string $name + * @param string $value + * @return void + */ + static public function create($name, $value = null) { + + $name = strtoupper($name); + $shortName = $name; + $group = null; + if (strpos($shortName,'.')!==false) { + list($group, $shortName) = explode('.', $shortName); + } + + if (isset(self::$classMap[$shortName])) { + return new self::$classMap[$shortName]($name, $value); + } else { + return new self($name, $value); + } + + } + /** * Creates a new property object - * - * By default this object will iterate over its own children, but this can + * + * By default this object will iterate over its own children, but this can * be overridden with the iterator argument - * - * @param string $name + * + * @param string $name * @param string $value * @param Sabre_VObject_ElementList $iterator */ @@ -73,10 +118,12 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { } + + /** - * Updates the internal value - * - * @param string $value + * Updates the internal value + * + * @param string $value * @return void */ public function setValue($value) { @@ -86,9 +133,9 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { } /** - * Turns the object back into a serialized blob. - * - * @return string + * Turns the object back into a serialized blob. + * + * @return string */ public function serialize() { @@ -97,7 +144,7 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { if (count($this->parameters)) { foreach($this->parameters as $param) { - + $str.=';' . $param->serialize(); } @@ -115,8 +162,8 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { $out = ''; while(strlen($str)>0) { if (strlen($str)>75) { - $out.= substr($str,0,75) . "\r\n"; - $str = ' ' . substr($str,75); + $out.= mb_strcut($str,0,75,'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8'); } else { $out.=$str . "\r\n"; $str=''; @@ -136,11 +183,11 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { * add(Sabre_VObject_Parameter $element) * add(string $name, $value) * - * The first version adds an Parameter - * The second adds a property as a string. - * - * @param mixed $item - * @param mixed $itemValue + * The first version adds an Parameter + * The second adds a property as a string. + * + * @param mixed $item + * @param mixed $itemValue * @return void */ public function add($item, $itemValue = null) { @@ -153,7 +200,7 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { $this->parameters[] = $item; } elseif(is_string($item)) { - if (!is_scalar($itemValue)) { + if (!is_scalar($itemValue) && !is_null($itemValue)) { throw new InvalidArgumentException('The second argument must be scalar'); } $parameter = new Sabre_VObject_Parameter($item,$itemValue); @@ -161,21 +208,20 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { $this->parameters[] = $parameter; } else { - + throw new InvalidArgumentException('The first argument must either be a Sabre_VObject_Element or a string'); } } - /* ArrayAccess interface {{{ */ /** * Checks if an array element exists - * - * @param mixed $name - * @return bool + * + * @param mixed $name + * @return bool */ public function offsetExists($name) { @@ -191,16 +237,16 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { } /** - * Returns a parameter, or parameter list. - * - * @param string $name - * @return Sabre_VObject_Element + * Returns a parameter, or parameter list. + * + * @param string $name + * @return Sabre_VObject_Element */ public function offsetGet($name) { if (is_int($name)) return parent::offsetGet($name); $name = strtoupper($name); - + $result = array(); foreach($this->parameters as $parameter) { if ($parameter->name == $name) @@ -219,8 +265,8 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { } /** - * Creates a new parameter - * + * Creates a new parameter + * * @param string $name * @param mixed $value * @return void @@ -230,7 +276,7 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { if (is_int($name)) return parent::offsetSet($name, $value); if (is_scalar($value)) { - if (!is_string($name)) + if (!is_string($name)) throw new InvalidArgumentException('A parameter name must be specified. This means you cannot use the $array[]="string" to add parameters.'); $this->offsetUnset($name); @@ -242,7 +288,7 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { if (!is_null($name)) throw new InvalidArgumentException('Don\'t specify a parameter name if you\'re passing a Sabre_VObject_Parameter. Add using $array[]=$parameterObject.'); - $value->parent = $this; + $value->parent = $this; $this->parameters[] = $value; } else { throw new InvalidArgumentException('You can only add parameters to the property object'); @@ -251,17 +297,16 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { } /** - * Removes one or more parameters with the specified name - * - * @param string $name - * @return void + * Removes one or more parameters with the specified name + * + * @param string $name + * @return void */ public function offsetUnset($name) { - if (is_int($name)) return parent::offsetUnset($name, $value); + if (is_int($name)) return parent::offsetUnset($name); $name = strtoupper($name); - - $result = array(); + foreach($this->parameters as $key=>$parameter) { if ($parameter->name == $name) { $parameter->parent = null; @@ -275,15 +320,29 @@ class Sabre_VObject_Property extends Sabre_VObject_Element { /* }}} */ /** - * Called when this object is being cast to a string - * - * @return string + * Called when this object is being cast to a string + * + * @return string */ public function __toString() { - return $this->value; + return (string)$this->value; } + /** + * This method is automatically called when the object is cloned. + * Specifically, this will ensure all child elements are also cloned. + * + * @return void + */ + public function __clone() { + + foreach($this->parameters as $key=>$child) { + $this->parameters[$key] = clone $child; + $this->parameters[$key]->parent = $this; + } + + } } diff --git a/3rdparty/Sabre/VObject/Property/DateTime.php b/3rdparty/Sabre/VObject/Property/DateTime.php new file mode 100644 index 0000000000..fe2372caa8 --- /dev/null +++ b/3rdparty/Sabre/VObject/Property/DateTime.php @@ -0,0 +1,260 @@ +setValue($dt->format('Ymd\\THis')); + $this->offsetUnset('VALUE'); + $this->offsetUnset('TZID'); + $this->offsetSet('VALUE','DATE-TIME'); + break; + case self::UTC : + $dt->setTimeZone(new DateTimeZone('UTC')); + $this->setValue($dt->format('Ymd\\THis\\Z')); + $this->offsetUnset('VALUE'); + $this->offsetUnset('TZID'); + $this->offsetSet('VALUE','DATE-TIME'); + break; + case self::LOCALTZ : + $this->setValue($dt->format('Ymd\\THis')); + $this->offsetUnset('VALUE'); + $this->offsetUnset('TZID'); + $this->offsetSet('VALUE','DATE-TIME'); + $this->offsetSet('TZID', $dt->getTimeZone()->getName()); + break; + case self::DATE : + $this->setValue($dt->format('Ymd')); + $this->offsetUnset('VALUE'); + $this->offsetUnset('TZID'); + $this->offsetSet('VALUE','DATE'); + break; + default : + throw new InvalidArgumentException('You must pass a valid dateType constant'); + + } + $this->dateTime = $dt; + $this->dateType = $dateType; + + } + + /** + * Returns the current DateTime value. + * + * If no value was set, this method returns null. + * + * @return DateTime|null + */ + public function getDateTime() { + + if ($this->dateTime) + return $this->dateTime; + + list( + $this->dateType, + $this->dateTime + ) = self::parseData($this->value, $this); + return $this->dateTime; + + } + + /** + * Returns the type of Date format. + * + * This method returns one of the format constants. If no date was set, + * this method will return null. + * + * @return int|null + */ + public function getDateType() { + + if ($this->dateType) + return $this->dateType; + + list( + $this->dateType, + $this->dateTime, + ) = self::parseData($this->value, $this); + return $this->dateType; + + } + + /** + * Parses the internal data structure to figure out what the current date + * and time is. + * + * The returned array contains two elements: + * 1. A 'DateType' constant (as defined on this class), or null. + * 2. A DateTime object (or null) + * + * @param string|null $propertyValue The string to parse (yymmdd or + * ymmddThhmmss, etc..) + * @param Sabre_VObject_Property|null $property The instance of the + * property we're parsing. + * @return array + */ + static public function parseData($propertyValue, Sabre_VObject_Property $property = null) { + + if (is_null($propertyValue)) { + return array(null, null); + } + + $date = '(?P[1-2][0-9]{3})(?P[0-1][0-9])(?P[0-3][0-9])'; + $time = '(?P[0-2][0-9])(?P[0-5][0-9])(?P[0-5][0-9])'; + $regex = "/^$date(T$time(?PZ)?)?$/"; + + if (!preg_match($regex, $propertyValue, $matches)) { + throw new InvalidArgumentException($propertyValue . ' is not a valid DateTime or Date string'); + } + + if (!isset($matches['hour'])) { + // Date-only + return array( + self::DATE, + new DateTime($matches['year'] . '-' . $matches['month'] . '-' . $matches['date'] . ' 00:00:00'), + ); + } + + $dateStr = + $matches['year'] .'-' . + $matches['month'] . '-' . + $matches['date'] . ' ' . + $matches['hour'] . ':' . + $matches['minute'] . ':' . + $matches['second']; + + if (isset($matches['isutc'])) { + $dt = new DateTime($dateStr,new DateTimeZone('UTC')); + $dt->setTimeZone(new DateTimeZone('UTC')); + return array( + self::UTC, + $dt + ); + } + + // Finding the timezone. + $tzid = $property['TZID']; + if (!$tzid) { + return array( + self::LOCAL, + new DateTime($dateStr) + ); + } + + try { + // tzid an Olson identifier? + $tz = new DateTimeZone($tzid->value); + } catch (Exception $e) { + + // Not an Olson id, we're going to try to find the information + // through the time zone name map. + $newtzid = Sabre_VObject_WindowsTimezoneMap::lookup($tzid->value); + if (is_null($newtzid)) { + + // Not a well known time zone name either, we're going to try + // to find the information through the VTIMEZONE object. + + // First we find the root object + $root = $property; + while($root->parent) { + $root = $root->parent; + } + + if (isset($root->VTIMEZONE)) { + foreach($root->VTIMEZONE as $vtimezone) { + if (((string)$vtimezone->TZID) == $tzid) { + if (isset($vtimezone->{'X-LIC-LOCATION'})) { + $newtzid = (string)$vtimezone->{'X-LIC-LOCATION'}; + } else { + // No libical location specified. As a last resort we could + // try matching $vtimezone's DST rules against all known + // time zones returned by DateTimeZone::list* + + // TODO + } + } + } + } + } + + try { + $tz = new DateTimeZone($newtzid); + } catch (Exception $e) { + // If all else fails, we use the default PHP timezone + $tz = new DateTimeZone(date_default_timezone_get()); + } + } + $dt = new DateTime($dateStr, $tz); + $dt->setTimeZone($tz); + + return array( + self::LOCALTZ, + $dt + ); + + } + +} diff --git a/3rdparty/Sabre/VObject/Property/MultiDateTime.php b/3rdparty/Sabre/VObject/Property/MultiDateTime.php new file mode 100644 index 0000000000..ae53ab6a61 --- /dev/null +++ b/3rdparty/Sabre/VObject/Property/MultiDateTime.php @@ -0,0 +1,166 @@ +offsetUnset('VALUE'); + $this->offsetUnset('TZID'); + switch($dateType) { + + case Sabre_VObject_Property_DateTime::LOCAL : + $val = array(); + foreach($dt as $i) { + $val[] = $i->format('Ymd\\THis'); + } + $this->setValue(implode(',',$val)); + $this->offsetSet('VALUE','DATE-TIME'); + break; + case Sabre_VObject_Property_DateTime::UTC : + $val = array(); + foreach($dt as $i) { + $i->setTimeZone(new DateTimeZone('UTC')); + $val[] = $i->format('Ymd\\THis\\Z'); + } + $this->setValue(implode(',',$val)); + $this->offsetSet('VALUE','DATE-TIME'); + break; + case Sabre_VObject_Property_DateTime::LOCALTZ : + $val = array(); + foreach($dt as $i) { + $val[] = $i->format('Ymd\\THis'); + } + $this->setValue(implode(',',$val)); + $this->offsetSet('VALUE','DATE-TIME'); + $this->offsetSet('TZID', $dt[0]->getTimeZone()->getName()); + break; + case Sabre_VObject_Property_DateTime::DATE : + $val = array(); + foreach($dt as $i) { + $val[] = $i->format('Ymd'); + } + $this->setValue(implode(',',$val)); + $this->offsetSet('VALUE','DATE'); + break; + default : + throw new InvalidArgumentException('You must pass a valid dateType constant'); + + } + $this->dateTimes = $dt; + $this->dateType = $dateType; + + } + + /** + * Returns the current DateTime value. + * + * If no value was set, this method returns null. + * + * @return array|null + */ + public function getDateTimes() { + + if ($this->dateTimes) + return $this->dateTimes; + + $dts = array(); + + if (!$this->value) { + $this->dateTimes = null; + $this->dateType = null; + return null; + } + + foreach(explode(',',$this->value) as $val) { + list( + $type, + $dt + ) = Sabre_VObject_Property_DateTime::parseData($val, $this); + $dts[] = $dt; + $this->dateType = $type; + } + $this->dateTimes = $dts; + return $this->dateTimes; + + } + + /** + * Returns the type of Date format. + * + * This method returns one of the format constants. If no date was set, + * this method will return null. + * + * @return int|null + */ + public function getDateType() { + + if ($this->dateType) + return $this->dateType; + + if (!$this->value) { + $this->dateTimes = null; + $this->dateType = null; + return null; + } + + $dts = array(); + foreach(explode(',',$this->value) as $val) { + list( + $type, + $dt + ) = Sabre_VObject_Property_DateTime::parseData($val, $this); + $dts[] = $dt; + $this->dateType = $type; + } + $this->dateTimes = $dts; + return $this->dateType; + + } + +} diff --git a/3rdparty/Sabre/VObject/Reader.php b/3rdparty/Sabre/VObject/Reader.php index 7d1c282838..eea73fa3dc 100644 --- a/3rdparty/Sabre/VObject/Reader.php +++ b/3rdparty/Sabre/VObject/Reader.php @@ -5,40 +5,22 @@ * * This class reads the vobject file, and returns a full element tree. * + * TODO: this class currently completely works 'statically'. This is pointless, + * and defeats OOP principals. Needs refactoring in a future version. * - * TODO: this class currently completely works 'statically'. This is pointless, - * and defeats OOP principals. Needs refaxtoring in a future version. - * * @package Sabre * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_VObject_Reader { /** - * This array contains a list of Property names that are automatically - * mapped to specific class names. + * Parses the file and returns the top component * - * Adding to this list allows you to specify custom property classes, - * adding extra functionality. - * - * @var array - */ - static public $elementMap = array( - 'DTSTART' => 'Sabre_VObject_Element_DateTime', - 'DTEND' => 'Sabre_VObject_Element_DateTime', - 'COMPLETED' => 'Sabre_VObject_Element_DateTime', - 'DUE' => 'Sabre_VObject_Element_DateTime', - 'EXDATE' => 'Sabre_VObject_Element_MultiDateTime', - ); - - /** - * Parses the file and returns the top component - * - * @param string $data - * @return Sabre_VObject_Element + * @param string $data + * @return Sabre_VObject_Element */ static function read($data) { @@ -63,11 +45,11 @@ class Sabre_VObject_Reader { } unset($lines); - + reset($lines2); return self::readLine($lines2); - + } /** @@ -75,9 +57,9 @@ class Sabre_VObject_Reader { * * This method receives the full array of lines. The array pointer is used * to traverse. - * - * @param array $lines - * @return Sabre_VObject_Element + * + * @param array $lines + * @return Sabre_VObject_Element */ static private function readLine(&$lines) { @@ -88,22 +70,23 @@ class Sabre_VObject_Reader { // Components if (stripos($line,"BEGIN:")===0) { - // This is a component - $obj = new Sabre_VObject_Component(strtoupper(substr($line,6))); + $componentName = strtoupper(substr($line,6)); + $obj = Sabre_VObject_Component::create($componentName); $nextLine = current($lines); while(stripos($nextLine,"END:")!==0) { $obj->add(self::readLine($lines)); + $nextLine = current($lines); - if ($nextLine===false) + if ($nextLine===false) throw new Sabre_VObject_ParseException('Invalid VObject. Document ended prematurely.'); } - // Checking component name of the 'END:' line. + // Checking component name of the 'END:' line. if (substr($nextLine,4)!==$obj->name) { throw new Sabre_VObject_ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"'); } @@ -117,7 +100,7 @@ class Sabre_VObject_Reader { //$result = preg_match('/(?P[A-Z0-9-]+)(?:;(?P^(?([^:^\"]|\"([^\"]*)\")*))?"; $regex = "/^(?P$token)$parameters:(?P.*)$/i"; @@ -128,22 +111,23 @@ class Sabre_VObject_Reader { } $propertyName = strtoupper($matches['name']); - $propertyValue = stripcslashes($matches['value']); + $propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) { + if ($matches[2]==='n' || $matches[2]==='N') { + return "\n"; + } else { + return $matches[2]; + } + }, $matches['value']); - if (isset(self::$elementMap[$propertyName])) { - $className = self::$elementMap[$propertyName]; - } else { - $className = 'Sabre_VObject_Property'; - } - - $obj = new $className($propertyName, $propertyValue); + $obj = Sabre_VObject_Property::create($propertyName, $propertyValue); if ($matches['parameters']) { foreach(self::readParameters($matches['parameters']) as $param) { $obj->add($param); } - } + + } return $obj; @@ -151,12 +135,12 @@ class Sabre_VObject_Reader { } /** - * Reads a parameter list from a property + * Reads a parameter list from a property * * This method returns an array of Sabre_VObject_Parameter * - * @param string $parameters - * @return array + * @param string $parameters + * @return array */ static private function readParameters($parameters) { @@ -179,7 +163,15 @@ class Sabre_VObject_Reader { $value = ''; } - $params[] = new Sabre_VObject_Parameter($match['paramName'], stripcslashes($value)); + $value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) { + if ($matches[2]==='n' || $matches[2]==='N') { + return "\n"; + } else { + return $matches[2]; + } + }, $value); + + $params[] = new Sabre_VObject_Parameter($match['paramName'], $value); } diff --git a/3rdparty/Sabre/VObject/RecurrenceIterator.php b/3rdparty/Sabre/VObject/RecurrenceIterator.php new file mode 100644 index 0000000000..833aa091ab --- /dev/null +++ b/3rdparty/Sabre/VObject/RecurrenceIterator.php @@ -0,0 +1,1009 @@ + 0, + 'MO' => 1, + 'TU' => 2, + 'WE' => 3, + 'TH' => 4, + 'FR' => 5, + 'SA' => 6, + ); + + /** + * Mappings between the day number and english day name. + * + * @var array + */ + private $dayNames = array( + 0 => 'Sunday', + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + ); + + /** + * If the current iteration of the event is an overriden event, this + * property will hold the VObject + * + * @var Sabre_Component_VObject + */ + private $currentOverriddenEvent; + + /** + * This property may contain the date of the next not-overridden event. + * This date is calculated sometimes a bit early, before overridden events + * are evaluated. + * + * @var DateTime + */ + private $nextDate; + + /** + * Creates the iterator + * + * You should pass a VCALENDAR component, as well as the UID of the event + * we're going to traverse. + * + * @param Sabre_VObject_Component $vcal + * @param string|null $uid + */ + public function __construct(Sabre_VObject_Component $vcal, $uid=null) { + + if (is_null($uid)) { + if ($vcal->name === 'VCALENDAR') { + throw new InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well'); + } + $components = array($vcal); + $uid = (string)$vcal->uid; + } else { + $components = $vcal->select('VEVENT'); + } + foreach($components as $component) { + if ((string)$component->uid == $uid) { + if (isset($component->{'RECURRENCE-ID'})) { + $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component; + $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime(); + } else { + $this->baseEvent = $component; + } + } + } + if (!$this->baseEvent) { + throw new InvalidArgumentException('Could not find a base event with uid: ' . $uid); + } + + $this->startDate = clone $this->baseEvent->DTSTART->getDateTime(); + + $this->endDate = null; + if (isset($this->baseEvent->DTEND)) { + $this->endDate = clone $this->baseEvent->DTEND->getDateTime(); + } else { + $this->endDate = clone $this->startDate; + if (isset($this->baseEvent->DURATION)) { + $this->endDate->add(Sabre_VObject_DateTimeParser::parse($this->baseEvent->DURATION->value)); + } + } + $this->currentDate = clone $this->startDate; + + $rrule = (string)$this->baseEvent->RRULE; + + $parts = explode(';', $rrule); + + foreach($parts as $part) { + + list($key, $value) = explode('=', $part, 2); + + switch(strtoupper($key)) { + + case 'FREQ' : + if (!in_array( + strtolower($value), + array('secondly','minutely','hourly','daily','weekly','monthly','yearly') + )) { + throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value)); + + } + $this->frequency = strtolower($value); + break; + + case 'UNTIL' : + $this->until = Sabre_VObject_DateTimeParser::parse($value); + break; + + case 'COUNT' : + $this->count = (int)$value; + break; + + case 'INTERVAL' : + $this->interval = (int)$value; + break; + + case 'BYSECOND' : + $this->bySecond = explode(',', $value); + break; + + case 'BYMINUTE' : + $this->byMinute = explode(',', $value); + break; + + case 'BYHOUR' : + $this->byHour = explode(',', $value); + break; + + case 'BYDAY' : + $this->byDay = explode(',', strtoupper($value)); + break; + + case 'BYMONTHDAY' : + $this->byMonthDay = explode(',', $value); + break; + + case 'BYYEARDAY' : + $this->byYearDay = explode(',', $value); + break; + + case 'BYWEEKNO' : + $this->byWeekNo = explode(',', $value); + break; + + case 'BYMONTH' : + $this->byMonth = explode(',', $value); + break; + + case 'BYSETPOS' : + $this->bySetPos = explode(',', $value); + break; + + case 'WKST' : + $this->weekStart = strtoupper($value); + break; + + } + + } + + // Parsing exception dates + if (isset($this->baseEvent->EXDATE)) { + foreach($this->baseEvent->EXDATE as $exDate) { + + foreach(explode(',', (string)$exDate) as $exceptionDate) { + + $this->exceptionDates[] = + Sabre_VObject_DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone()); + + } + + } + + } + + } + + /** + * Returns the current item in the list + * + * @return DateTime + */ + public function current() { + + if (!$this->valid()) return null; + return clone $this->currentDate; + + } + + /** + * This method returns the startdate for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtStart() { + + if (!$this->valid()) return null; + return clone $this->currentDate; + + } + + /** + * This method returns the enddate for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtEnd() { + + if (!$this->valid()) return null; + $dtEnd = clone $this->currentDate; + $dtEnd->add( $this->startDate->diff( $this->endDate ) ); + return clone $dtEnd; + + } + + /** + * Returns a VEVENT object with the updated start and end date. + * + * Any recurrence information is removed, and this function may return an + * 'overridden' event instead. + * + * This method always returns a cloned instance. + * + * @return void + */ + public function getEventObject() { + + if ($this->currentOverriddenEvent) { + return clone $this->currentOverriddenEvent; + } + $event = clone $this->baseEvent; + unset($event->RRULE); + unset($event->EXDATE); + unset($event->RDATE); + unset($event->EXRULE); + + $event->DTSTART->setDateTime($this->getDTStart(), $event->DTSTART->getDateType()); + if (isset($event->DTEND)) { + $event->DTEND->setDateTime($this->getDtEnd(), $event->DTSTART->getDateType()); + } + if ($this->counter > 0) { + $event->{'RECURRENCE-ID'} = (string)$event->DTSTART; + } + + return $event; + + } + + /** + * Returns the current item number + * + * @return int + */ + public function key() { + + return $this->counter; + + } + + /** + * Whether or not there is a 'next item' + * + * @return bool + */ + public function valid() { + + if (!is_null($this->count)) { + return $this->counter < $this->count; + } + if (!is_null($this->until)) { + return $this->currentDate <= $this->until; + } + return true; + + } + + /** + * Resets the iterator + * + * @return void + */ + public function rewind() { + + $this->currentDate = clone $this->startDate; + $this->counter = 0; + + } + + /** + * This method allows you to quickly go to the next occurrence after the + * specified date. + * + * Note that this checks the current 'endDate', not the 'stardDate'. This + * means that if you forward to January 1st, the iterator will stop at the + * first event that ends *after* January 1st. + * + * @param DateTime $dt + * @return void + */ + public function fastForward(DateTime $dt) { + + while($this->valid() && $this->getDTEnd() < $dt) { + $this->next(); + } + + } + + /** + * Goes on to the next iteration + * + * @return void + */ + public function next() { + + /* + if (!is_null($this->count) && $this->counter >= $this->count) { + $this->currentDate = null; + }*/ + + + $previousStamp = $this->currentDate->getTimeStamp(); + + while(true) { + + $this->currentOverriddenEvent = null; + + // If we have a next date 'stored', we use that + if ($this->nextDate) { + $this->currentDate = $this->nextDate; + $currentStamp = $this->currentDate->getTimeStamp(); + $this->nextDate = null; + } else { + + // Otherwise, we calculate it + switch($this->frequency) { + + case 'daily' : + $this->nextDaily(); + break; + + case 'weekly' : + $this->nextWeekly(); + break; + + case 'monthly' : + $this->nextMonthly(); + break; + + case 'yearly' : + $this->nextYearly(); + break; + + } + $currentStamp = $this->currentDate->getTimeStamp(); + + // Checking exception dates + foreach($this->exceptionDates as $exceptionDate) { + if ($this->currentDate == $exceptionDate) { + $this->counter++; + continue 2; + } + } + foreach($this->overriddenDates as $overriddenDate) { + if ($this->currentDate == $overriddenDate) { + continue 2; + } + } + + } + + // Checking overriden events + foreach($this->overriddenEvents as $index=>$event) { + if ($index > $previousStamp && $index <= $currentStamp) { + + // We're moving the 'next date' aside, for later use. + $this->nextDate = clone $this->currentDate; + + $this->currentDate = $event->DTSTART->getDateTime(); + $this->currentOverriddenEvent = $event; + + break; + } + } + + break; + + } + + /* + if (!is_null($this->until)) { + if($this->currentDate > $this->until) { + $this->currentDate = null; + } + }*/ + + $this->counter++; + + } + + /** + * Does the processing for advancing the iterator for daily frequency. + * + * @return void + */ + protected function nextDaily() { + + if (!$this->byDay) { + $this->currentDate->modify('+' . $this->interval . ' days'); + return; + } + + $recurrenceDays = array(); + foreach($this->byDay as $byDay) { + + // The day may be preceeded with a positive (+n) or + // negative (-n) integer. However, this does not make + // sense in 'weekly' so we ignore it here. + $recurrenceDays[] = $this->dayMap[substr($byDay,-2)]; + + } + + do { + + $this->currentDate->modify('+' . $this->interval . ' days'); + + // Current day of the week + $currentDay = $this->currentDate->format('w'); + + } while (!in_array($currentDay, $recurrenceDays)); + + } + + /** + * Does the processing for advancing the iterator for weekly frequency. + * + * @return void + */ + protected function nextWeekly() { + + if (!$this->byDay) { + $this->currentDate->modify('+' . $this->interval . ' weeks'); + return; + } + + $recurrenceDays = array(); + foreach($this->byDay as $byDay) { + + // The day may be preceeded with a positive (+n) or + // negative (-n) integer. However, this does not make + // sense in 'weekly' so we ignore it here. + $recurrenceDays[] = $this->dayMap[substr($byDay,-2)]; + + } + + // Current day of the week + $currentDay = $this->currentDate->format('w'); + + // First day of the week: + $firstDay = $this->dayMap[$this->weekStart]; + + $time = array( + $this->currentDate->format('H'), + $this->currentDate->format('i'), + $this->currentDate->format('s') + ); + + // Increasing the 'current day' until we find our next + // occurrence. + while(true) { + + $currentDay++; + + if ($currentDay>6) { + $currentDay = 0; + } + + // We need to roll over to the next week + if ($currentDay === $firstDay) { + $this->currentDate->modify('+' . $this->interval . ' weeks'); + + // We need to go to the first day of this week, but only if we + // are not already on this first day of this week. + if($this->currentDate->format('w') != $firstDay) { + $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]); + $this->currentDate->setTime($time[0],$time[1],$time[2]); + } + } + + // We have a match + if (in_array($currentDay ,$recurrenceDays)) { + $this->currentDate->modify($this->dayNames[$currentDay]); + $this->currentDate->setTime($time[0],$time[1],$time[2]); + break; + } + + } + + } + + /** + * Does the processing for advancing the iterator for monthly frequency. + * + * @return void + */ + protected function nextMonthly() { + + $currentDayOfMonth = $this->currentDate->format('j'); + if (!$this->byMonthDay && !$this->byDay) { + + // If the current day is higher than the 28th, rollover can + // occur to the next month. We Must skip these invalid + // entries. + if ($currentDayOfMonth < 29) { + $this->currentDate->modify('+' . $this->interval . ' months'); + } else { + $increase = 0; + do { + $increase++; + $tempDate = clone $this->currentDate; + $tempDate->modify('+ ' . ($this->interval*$increase) . ' months'); + } while ($tempDate->format('j') != $currentDayOfMonth); + $this->currentDate = $tempDate; + } + return; + } + + while(true) { + + $occurrences = $this->getMonthlyOccurrences(); + + foreach($occurrences as $occurrence) { + + // The first occurrence thats higher than the current + // day of the month wins. + if ($occurrence > $currentDayOfMonth) { + break 2; + } + + } + + // If we made it all the way here, it means there were no + // valid occurrences, and we need to advance to the next + // month. + $this->currentDate->modify('first day of this month'); + $this->currentDate->modify('+ ' . $this->interval . ' months'); + + // This goes to 0 because we need to start counting at hte + // beginning. + $currentDayOfMonth = 0; + + } + + $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence); + + } + + /** + * Does the processing for advancing the iterator for yearly frequency. + * + * @return void + */ + protected function nextYearly() { + + if (!$this->byMonth) { + $this->currentDate->modify('+' . $this->interval . ' years'); + return; + } + + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + $advancedToNewMonth = false; + + // If we got a byDay or getMonthDay filter, we must first expand + // further. + if ($this->byDay || $this->byMonthDay) { + + while(true) { + + $occurrences = $this->getMonthlyOccurrences(); + + foreach($occurrences as $occurrence) { + + // The first occurrence that's higher than the current + // day of the month wins. + // If we advanced to the next month or year, the first + // occurence is always correct. + if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { + break 2; + } + + } + + // If we made it here, it means we need to advance to + // the next month or year. + $currentDayOfMonth = 1; + $advancedToNewMonth = true; + do { + + $currentMonth++; + if ($currentMonth>12) { + $currentYear+=$this->interval; + $currentMonth = 1; + } + } while (!in_array($currentMonth, $this->byMonth)); + + $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); + + } + + // If we made it here, it means we got a valid occurrence + $this->currentDate->setDate($currentYear, $currentMonth, $occurrence); + return; + + } else { + + // no byDay or byMonthDay, so we can just loop through the + // months. + do { + + $currentMonth++; + if ($currentMonth>12) { + $currentYear+=$this->interval; + $currentMonth = 1; + } + } while (!in_array($currentMonth, $this->byMonth)); + $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); + return; + + } + + } + + /** + * Returns all the occurrences for a monthly frequency with a 'byDay' or + * 'byMonthDay' expansion for the current month. + * + * The returned list is an array of integers with the day of month (1-31). + * + * @return array + */ + protected function getMonthlyOccurrences() { + + $startDate = clone $this->currentDate; + + $byDayResults = array(); + + // Our strategy is to simply go through the byDays, advance the date to + // that point and add it to the results. + if ($this->byDay) foreach($this->byDay as $day) { + + $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]]; + + // Dayname will be something like 'wednesday'. Now we need to find + // all wednesdays in this month. + $dayHits = array(); + + $checkDate = clone $startDate; + $checkDate->modify('first day of this month'); + $checkDate->modify($dayName); + + do { + $dayHits[] = $checkDate->format('j'); + $checkDate->modify('next ' . $dayName); + } while ($checkDate->format('n') === $startDate->format('n')); + + // So now we have 'all wednesdays' for month. It is however + // possible that the user only really wanted the 1st, 2nd or last + // wednesday. + if (strlen($day)>2) { + $offset = (int)substr($day,0,-2); + + if ($offset>0) { + // It is possible that the day does not exist, such as a + // 5th or 6th wednesday of the month. + if (isset($dayHits[$offset-1])) { + $byDayResults[] = $dayHits[$offset-1]; + } + } else { + + // if it was negative we count from the end of the array + $byDayResults[] = $dayHits[count($dayHits) + $offset]; + } + } else { + // There was no counter (first, second, last wednesdays), so we + // just need to add the all to the list). + $byDayResults = array_merge($byDayResults, $dayHits); + + } + + } + + $byMonthDayResults = array(); + if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) { + + // Removing values that are out of range for this month + if ($monthDay > $startDate->format('t') || + $monthDay < 0-$startDate->format('t')) { + continue; + } + if ($monthDay>0) { + $byMonthDayResults[] = $monthDay; + } else { + // Negative values + $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay; + } + } + + // If there was just byDay or just byMonthDay, they just specify our + // (almost) final list. If both were provided, then byDay limits the + // list. + if ($this->byMonthDay && $this->byDay) { + $result = array_intersect($byMonthDayResults, $byDayResults); + } elseif ($this->byMonthDay) { + $result = $byMonthDayResults; + } else { + $result = $byDayResults; + } + $result = array_unique($result); + sort($result, SORT_NUMERIC); + + // The last thing that needs checking is the BYSETPOS. If it's set, it + // means only certain items in the set survive the filter. + if (!$this->bySetPos) { + return $result; + } + + $filteredResult = array(); + foreach($this->bySetPos as $setPos) { + + if ($setPos<0) { + $setPos = count($result)-($setPos+1); + } + if (isset($result[$setPos-1])) { + $filteredResult[] = $result[$setPos-1]; + } + } + + sort($filteredResult, SORT_NUMERIC); + return $filteredResult; + + } + + +} + diff --git a/3rdparty/Sabre/VObject/Version.php b/3rdparty/Sabre/VObject/Version.php index 937c367e22..00110febc0 100644 --- a/3rdparty/Sabre/VObject/Version.php +++ b/3rdparty/Sabre/VObject/Version.php @@ -2,10 +2,10 @@ /** * This class contains the version number for the VObject package - * + * * @package Sabre - * @subpackage VObject - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @subpackage VObject + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ @@ -14,7 +14,7 @@ class Sabre_VObject_Version { /** * Full version number */ - const VERSION = '1.2.4'; + const VERSION = '1.3.2'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/VObject/WindowsTimezoneMap.php b/3rdparty/Sabre/VObject/WindowsTimezoneMap.php new file mode 100644 index 0000000000..5e1cc5d479 --- /dev/null +++ b/3rdparty/Sabre/VObject/WindowsTimezoneMap.php @@ -0,0 +1,128 @@ +'Australia/Darwin', + 'AUS Eastern Standard Time'=>'Australia/Sydney', + 'Afghanistan Standard Time'=>'Asia/Kabul', + 'Alaskan Standard Time'=>'America/Anchorage', + 'Arab Standard Time'=>'Asia/Riyadh', + 'Arabian Standard Time'=>'Asia/Dubai', + 'Arabic Standard Time'=>'Asia/Baghdad', + 'Argentina Standard Time'=>'America/Buenos_Aires', + 'Armenian Standard Time'=>'Asia/Yerevan', + 'Atlantic Standard Time'=>'America/Halifax', + 'Azerbaijan Standard Time'=>'Asia/Baku', + 'Azores Standard Time'=>'Atlantic/Azores', + 'Bangladesh Standard Time'=>'Asia/Dhaka', + 'Canada Central Standard Time'=>'America/Regina', + 'Cape Verde Standard Time'=>'Atlantic/Cape_Verde', + 'Caucasus Standard Time'=>'Asia/Yerevan', + 'Cen. Australia Standard Time'=>'Australia/Adelaide', + 'Central America Standard Time'=>'America/Guatemala', + 'Central Asia Standard Time'=>'Asia/Almaty', + 'Central Brazilian Standard Time'=>'America/Cuiaba', + 'Central Europe Standard Time'=>'Europe/Budapest', + 'Central European Standard Time'=>'Europe/Warsaw', + 'Central Pacific Standard Time'=>'Pacific/Guadalcanal', + 'Central Standard Time'=>'America/Chicago', + 'Central Standard Time (Mexico)'=>'America/Mexico_City', + 'China Standard Time'=>'Asia/Shanghai', + 'Dateline Standard Time'=>'Etc/GMT+12', + 'E. Africa Standard Time'=>'Africa/Nairobi', + 'E. Australia Standard Time'=>'Australia/Brisbane', + 'E. Europe Standard Time'=>'Europe/Minsk', + 'E. South America Standard Time'=>'America/Sao_Paulo', + 'Eastern Standard Time'=>'America/New_York', + 'Egypt Standard Time'=>'Africa/Cairo', + 'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg', + 'FLE Standard Time'=>'Europe/Kiev', + 'Fiji Standard Time'=>'Pacific/Fiji', + 'GMT Standard Time'=>'Europe/London', + 'GTB Standard Time'=>'Europe/Istanbul', + 'Georgian Standard Time'=>'Asia/Tbilisi', + 'Greenland Standard Time'=>'America/Godthab', + 'Greenwich Standard Time'=>'Atlantic/Reykjavik', + 'Hawaiian Standard Time'=>'Pacific/Honolulu', + 'India Standard Time'=>'Asia/Calcutta', + 'Iran Standard Time'=>'Asia/Tehran', + 'Israel Standard Time'=>'Asia/Jerusalem', + 'Jordan Standard Time'=>'Asia/Amman', + 'Kamchatka Standard Time'=>'Asia/Kamchatka', + 'Korea Standard Time'=>'Asia/Seoul', + 'Magadan Standard Time'=>'Asia/Magadan', + 'Mauritius Standard Time'=>'Indian/Mauritius', + 'Mexico Standard Time'=>'America/Mexico_City', + 'Mexico Standard Time 2'=>'America/Chihuahua', + 'Mid-Atlantic Standard Time'=>'Etc/GMT+2', + 'Middle East Standard Time'=>'Asia/Beirut', + 'Montevideo Standard Time'=>'America/Montevideo', + 'Morocco Standard Time'=>'Africa/Casablanca', + 'Mountain Standard Time'=>'America/Denver', + 'Mountain Standard Time (Mexico)'=>'America/Chihuahua', + 'Myanmar Standard Time'=>'Asia/Rangoon', + 'N. Central Asia Standard Time'=>'Asia/Novosibirsk', + 'Namibia Standard Time'=>'Africa/Windhoek', + 'Nepal Standard Time'=>'Asia/Katmandu', + 'New Zealand Standard Time'=>'Pacific/Auckland', + 'Newfoundland Standard Time'=>'America/St_Johns', + 'North Asia East Standard Time'=>'Asia/Irkutsk', + 'North Asia Standard Time'=>'Asia/Krasnoyarsk', + 'Pacific SA Standard Time'=>'America/Santiago', + 'Pacific Standard Time'=>'America/Los_Angeles', + 'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel', + 'Pakistan Standard Time'=>'Asia/Karachi', + 'Paraguay Standard Time'=>'America/Asuncion', + 'Romance Standard Time'=>'Europe/Paris', + 'Russian Standard Time'=>'Europe/Moscow', + 'SA Eastern Standard Time'=>'America/Cayenne', + 'SA Pacific Standard Time'=>'America/Bogota', + 'SA Western Standard Time'=>'America/La_Paz', + 'SE Asia Standard Time'=>'Asia/Bangkok', + 'Samoa Standard Time'=>'Pacific/Apia', + 'Singapore Standard Time'=>'Asia/Singapore', + 'South Africa Standard Time'=>'Africa/Johannesburg', + 'Sri Lanka Standard Time'=>'Asia/Colombo', + 'Syria Standard Time'=>'Asia/Damascus', + 'Taipei Standard Time'=>'Asia/Taipei', + 'Tasmania Standard Time'=>'Australia/Hobart', + 'Tokyo Standard Time'=>'Asia/Tokyo', + 'Tonga Standard Time'=>'Pacific/Tongatapu', + 'US Eastern Standard Time'=>'America/Indianapolis', + 'US Mountain Standard Time'=>'America/Phoenix', + 'UTC'=>'Etc/GMT', + 'UTC+12'=>'Etc/GMT-12', + 'UTC-02'=>'Etc/GMT+2', + 'UTC-11'=>'Etc/GMT+11', + 'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar', + 'Venezuela Standard Time'=>'America/Caracas', + 'Vladivostok Standard Time'=>'Asia/Vladivostok', + 'W. Australia Standard Time'=>'Australia/Perth', + 'W. Central Africa Standard Time'=>'Africa/Lagos', + 'W. Europe Standard Time'=>'Europe/Berlin', + 'West Asia Standard Time'=>'Asia/Tashkent', + 'West Pacific Standard Time'=>'Pacific/Port_Moresby', + 'Yakutsk Standard Time'=>'Asia/Yakutsk', + ); + + static public function lookup($tzid) { + return isset(self::$map[$tzid]) ? self::$map[$tzid] : null; + } +} diff --git a/3rdparty/Sabre/VObject/includes.php b/3rdparty/Sabre/VObject/includes.php index f21010fb27..0177a8f1ba 100644 --- a/3rdparty/Sabre/VObject/includes.php +++ b/3rdparty/Sabre/VObject/includes.php @@ -1,29 +1,41 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: System.php,v 1.36 2004/06/15 16:33:46 pajoye Exp $ -// +/** + * File/Directory manipulation + * + * PHP versions 4 and 5 + * + * @category pear + * @package System + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: System.php 313024 2011-07-06 19:51:24Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ -require_once( 'PEAR.php'); -require_once( 'Console/Getopt.php'); +/** + * base class + */ +require_once 'PEAR.php'; +require_once 'Console/Getopt.php'; $GLOBALS['_System_temp_files'] = array(); @@ -31,7 +29,9 @@ $GLOBALS['_System_temp_files'] = array(); * Unix and Windows. The names and usage has been taken from its respectively * GNU commands. The functions will return (bool) false on error and will * trigger the error with the PHP trigger_error() function (you can silence -* the error by prefixing a '@' sign after the function call). +* the error by prefixing a '@' sign after the function call, but this +* is not recommended practice. Instead use an error handler with +* {@link set_error_handler()}). * * Documentation on this class you can find in: * http://pear.php.net/manual/ @@ -46,39 +46,53 @@ $GLOBALS['_System_temp_files'] = array(); * * System::rm(array('-r', $file1, $dir1)); * -* @package System -* @author Tomas V.V.Cox -* @version $Revision: 1.36 $ -* @access public -* @see http://pear.php.net/manual/ +* @category pear +* @package System +* @author Tomas V.V. Cox +* @copyright 1997-2006 The PHP Group +* @license http://opensource.org/licenses/bsd-license.php New BSD License +* @version Release: 1.9.4 +* @link http://pear.php.net/package/PEAR +* @since Class available since Release 0.1 +* @static */ class System { /** - * returns the commandline arguments of a function - * - * @param string $argv the commandline - * @param string $short_options the allowed option short-tags - * @param string $long_options the allowed option long-tags - * @return array the given options and there values - * @access private - */ + * returns the commandline arguments of a function + * + * @param string $argv the commandline + * @param string $short_options the allowed option short-tags + * @param string $long_options the allowed option long-tags + * @return array the given options and there values + * @static + * @access private + */ function _parseArgs($argv, $short_options, $long_options = null) { if (!is_array($argv) && $argv !== null) { - $argv = preg_split('/\s+/', $argv); + // Find all items, quoted or otherwise + preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av); + $argv = $av[1]; + foreach ($av[2] as $k => $a) { + if (empty($a)) { + continue; + } + $argv[$k] = trim($a) ; + } } - return Console_Getopt::getopt2($argv, $short_options); + return Console_Getopt::getopt2($argv, $short_options, $long_options); } /** - * Output errors with PHP trigger_error(). You can silence the errors - * with prefixing a "@" sign to the function call: @System::mkdir(..); - * - * @param mixed $error a PEAR error or a string with the error message - * @return bool false - * @access private - */ + * Output errors with PHP trigger_error(). You can silence the errors + * with prefixing a "@" sign to the function call: @System::mkdir(..); + * + * @param mixed $error a PEAR error or a string with the error message + * @return bool false + * @static + * @access private + */ function raiseError($error) { if (PEAR::isError($error)) { @@ -89,96 +103,106 @@ class System } /** - * Creates a nested array representing the structure of a directory - * - * System::_dirToStruct('dir1', 0) => - * Array - * ( - * [dirs] => Array - * ( - * [0] => dir1 - * ) - * - * [files] => Array - * ( - * [0] => dir1/file2 - * [1] => dir1/file3 - * ) - * ) - * @param string $sPath Name of the directory - * @param integer $maxinst max. deep of the lookup - * @param integer $aktinst starting deep of the lookup - * @return array the structure of the dir - * @access private - */ - - function _dirToStruct($sPath, $maxinst, $aktinst = 0) + * Creates a nested array representing the structure of a directory + * + * System::_dirToStruct('dir1', 0) => + * Array + * ( + * [dirs] => Array + * ( + * [0] => dir1 + * ) + * + * [files] => Array + * ( + * [0] => dir1/file2 + * [1] => dir1/file3 + * ) + * ) + * @param string $sPath Name of the directory + * @param integer $maxinst max. deep of the lookup + * @param integer $aktinst starting deep of the lookup + * @param bool $silent if true, do not emit errors. + * @return array the structure of the dir + * @static + * @access private + */ + function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) { $struct = array('dirs' => array(), 'files' => array()); if (($dir = @opendir($sPath)) === false) { - System::raiseError("Could not open dir $sPath"); + if (!$silent) { + System::raiseError("Could not open dir $sPath"); + } return $struct; // XXX could not open error } - $struct['dirs'][] = $sPath; // XXX don't add if '.' or '..' ? + + $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ? $list = array(); - while ($file = readdir($dir)) { + while (false !== ($file = readdir($dir))) { if ($file != '.' && $file != '..') { $list[] = $file; } } + closedir($dir); - sort($list); + natsort($list); if ($aktinst < $maxinst || $maxinst == 0) { - foreach($list as $val) { + foreach ($list as $val) { $path = $sPath . DIRECTORY_SEPARATOR . $val; - if (is_dir($path)) { - $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1); - $struct = array_merge_recursive($tmp, $struct); + if (is_dir($path) && !is_link($path)) { + $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent); + $struct = array_merge_recursive($struct, $tmp); } else { $struct['files'][] = $path; } } } + + return $struct; + } + + /** + * Creates a nested array representing the structure of a directory and files + * + * @param array $files Array listing files and dirs + * @return array + * @static + * @see System::_dirToStruct() + */ + function _multipleToStruct($files) + { + $struct = array('dirs' => array(), 'files' => array()); + settype($files, 'array'); + foreach ($files as $file) { + if (is_dir($file) && !is_link($file)) { + $tmp = System::_dirToStruct($file, 0); + $struct = array_merge_recursive($tmp, $struct); + } else { + if (!in_array($file, $struct['files'])) { + $struct['files'][] = $file; + } + } + } return $struct; } /** - * Creates a nested array representing the structure of a directory and files - * - * @param array $files Array listing files and dirs - * @return array - * @see System::_dirToStruct() - */ - function _multipleToStruct($files) - { - $struct = array('dirs' => array(), 'files' => array()); - settype($files, 'array'); - foreach ($files as $file) { - if (is_dir($file)) { - $tmp = System::_dirToStruct($file, 0); - $struct = array_merge_recursive($tmp, $struct); - } else { - $struct['files'][] = $file; - } - } - return $struct; - } - - /** - * The rm command for removing files. - * Supports multiple files and dirs and also recursive deletes - * - * @param string $args the arguments for rm - * @return mixed PEAR_Error or true for success - * @access public - */ + * The rm command for removing files. + * Supports multiple files and dirs and also recursive deletes + * + * @param string $args the arguments for rm + * @return mixed PEAR_Error or true for success + * @static + * @access public + */ function rm($args) { - $opts = System::_parseArgs($args, 'rf'); // "f" do nothing but like it :-) + $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) if (PEAR::isError($opts)) { return System::raiseError($opts); } - foreach($opts[0] as $opt) { + foreach ($opts[0] as $opt) { if ($opt[0] == 'r') { $do_recursive = true; } @@ -186,12 +210,14 @@ class System $ret = true; if (isset($do_recursive)) { $struct = System::_multipleToStruct($opts[1]); - foreach($struct['files'] as $file) { + foreach ($struct['files'] as $file) { if (!@unlink($file)) { $ret = false; } } - foreach($struct['dirs'] as $dir) { + + rsort($struct['dirs']); + foreach ($struct['dirs'] as $dir) { if (!@rmdir($dir)) { $ret = false; } @@ -208,24 +234,26 @@ class System } /** - * Make directories. Note that we use call_user_func('mkdir') to avoid - * a problem with ZE2 calling System::mkDir instead of the native PHP func. - * - * @param string $args the name of the director(y|ies) to create - * @return bool True for success - * @access public - */ + * Make directories. + * + * The -p option will create parent directories + * @param string $args the name of the director(y|ies) to create + * @return bool True for success + * @static + * @access public + */ function mkDir($args) { $opts = System::_parseArgs($args, 'pm:'); if (PEAR::isError($opts)) { return System::raiseError($opts); } + $mode = 0777; // default mode - foreach($opts[0] as $opt) { + foreach ($opts[0] as $opt) { if ($opt[0] == 'p') { $create_parents = true; - } elseif($opt[0] == 'm') { + } elseif ($opt[0] == 'm') { // if the mode is clearly an octal number (starts with 0) // convert it to decimal if (strlen($opt[1]) && $opt[1]{0} == '0') { @@ -237,52 +265,64 @@ class System $mode = $opt[1]; } } + $ret = true; if (isset($create_parents)) { - foreach($opts[1] as $dir) { + foreach ($opts[1] as $dir) { $dirstack = array(); - while (!@is_dir($dir) && $dir != DIRECTORY_SEPARATOR) { + while ((!file_exists($dir) || !is_dir($dir)) && + $dir != DIRECTORY_SEPARATOR) { array_unshift($dirstack, $dir); $dir = dirname($dir); } + while ($newdir = array_shift($dirstack)) { - if (!call_user_func('mkdir', $newdir, $mode)) { + if (!is_writeable(dirname($newdir))) { + $ret = false; + break; + } + + if (!mkdir($newdir, $mode)) { $ret = false; } } } } else { foreach($opts[1] as $dir) { - if (!@is_dir($dir) && !call_user_func('mkdir', $dir, $mode)) { + if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) { $ret = false; } } } + return $ret; } /** - * Concatenate files - * - * Usage: - * 1) $var = System::cat('sample.txt test.txt'); - * 2) System::cat('sample.txt test.txt > final.txt'); - * 3) System::cat('sample.txt test.txt >> final.txt'); - * - * Note: as the class use fopen, urls should work also (test that) - * - * @param string $args the arguments - * @return boolean true on success - * @access public - */ + * Concatenate files + * + * Usage: + * 1) $var = System::cat('sample.txt test.txt'); + * 2) System::cat('sample.txt test.txt > final.txt'); + * 3) System::cat('sample.txt test.txt >> final.txt'); + * + * Note: as the class use fopen, urls should work also (test that) + * + * @param string $args the arguments + * @return boolean true on success + * @static + * @access public + */ function &cat($args) { $ret = null; $files = array(); if (!is_array($args)) { - $args = preg_split('/\s+/', $args); + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); } - for($i=0; $i < count($args); $i++) { + + $count_args = count($args); + for ($i = 0; $i < $count_args; $i++) { if ($args[$i] == '>') { $mode = 'wb'; $outputfile = $args[$i+1]; @@ -295,6 +335,7 @@ class System $files[] = $args[$i]; } } + $outputfd = false; if (isset($mode)) { if (!$outputfd = fopen($outputfile, $mode)) { $err = System::raiseError("Could not open $outputfile"); @@ -308,7 +349,7 @@ class System continue; } while ($cont = fread($fd, 2048)) { - if (isset($outputfd)) { + if (is_resource($outputfd)) { fwrite($outputfd, $cont); } else { $ret .= $cont; @@ -316,35 +357,36 @@ class System } fclose($fd); } - if (@is_resource($outputfd)) { + if (is_resource($outputfd)) { fclose($outputfd); } return $ret; } /** - * Creates temporary files or directories. This function will remove - * the created files when the scripts finish its execution. - * - * Usage: - * 1) $tempfile = System::mktemp("prefix"); - * 2) $tempdir = System::mktemp("-d prefix"); - * 3) $tempfile = System::mktemp(); - * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); - * - * prefix -> The string that will be prepended to the temp name - * (defaults to "tmp"). - * -d -> A temporary dir will be created instead of a file. - * -t -> The target dir where the temporary (file|dir) will be created. If - * this param is missing by default the env vars TMP on Windows or - * TMPDIR in Unix will be used. If these vars are also missing - * c:\windows\temp or /tmp will be used. - * - * @param string $args The arguments - * @return mixed the full path of the created (file|dir) or false - * @see System::tmpdir() - * @access public - */ + * Creates temporary files or directories. This function will remove + * the created files when the scripts finish its execution. + * + * Usage: + * 1) $tempfile = System::mktemp("prefix"); + * 2) $tempdir = System::mktemp("-d prefix"); + * 3) $tempfile = System::mktemp(); + * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); + * + * prefix -> The string that will be prepended to the temp name + * (defaults to "tmp"). + * -d -> A temporary dir will be created instead of a file. + * -t -> The target dir where the temporary (file|dir) will be created. If + * this param is missing by default the env vars TMP on Windows or + * TMPDIR in Unix will be used. If these vars are also missing + * c:\windows\temp or /tmp will be used. + * + * @param string $args The arguments + * @return mixed the full path of the created (file|dir) or false + * @see System::tmpdir() + * @static + * @access public + */ function mktemp($args = null) { static $first_time = true; @@ -352,65 +394,81 @@ class System if (PEAR::isError($opts)) { return System::raiseError($opts); } - foreach($opts[0] as $opt) { - if($opt[0] == 'd') { + + foreach ($opts[0] as $opt) { + if ($opt[0] == 'd') { $tmp_is_dir = true; - } elseif($opt[0] == 't') { + } elseif ($opt[0] == 't') { $tmpdir = $opt[1]; } } + $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp'; if (!isset($tmpdir)) { $tmpdir = System::tmpdir(); } - if (!System::mkDir("-p $tmpdir")) { + + if (!System::mkDir(array('-p', $tmpdir))) { return false; } + $tmp = tempnam($tmpdir, $prefix); if (isset($tmp_is_dir)) { unlink($tmp); // be careful possible race condition here - if (!call_user_func('mkdir', $tmp, 0700)) { + if (!mkdir($tmp, 0700)) { return System::raiseError("Unable to create temporary directory $tmpdir"); } } + $GLOBALS['_System_temp_files'][] = $tmp; + if (isset($tmp_is_dir)) { + //$GLOBALS['_System_temp_files'][] = dirname($tmp); + } + if ($first_time) { PEAR::registerShutdownFunc(array('System', '_removeTmpFiles')); $first_time = false; } + return $tmp; } /** - * Remove temporary files created my mkTemp. This function is executed - * at script shutdown time - * - * @access private - */ + * Remove temporary files created my mkTemp. This function is executed + * at script shutdown time + * + * @static + * @access private + */ function _removeTmpFiles() { if (count($GLOBALS['_System_temp_files'])) { $delete = $GLOBALS['_System_temp_files']; array_unshift($delete, '-r'); System::rm($delete); + $GLOBALS['_System_temp_files'] = array(); } } /** - * Get the path of the temporal directory set in the system - * by looking in its environments variables. - * Note: php.ini-recommended removes the "E" from the variables_order setting, - * making unavaible the $_ENV array, that s why we do tests with _ENV - * - * @return string The temporal directory on the system - */ + * Get the path of the temporal directory set in the system + * by looking in its environments variables. + * Note: php.ini-recommended removes the "E" from the variables_order setting, + * making unavaible the $_ENV array, that s why we do tests with _ENV + * + * @static + * @return string The temporary directory on the system + */ function tmpdir() { if (OS_WINDOWS) { + if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { + return $var; + } if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) { return $var; } - if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { + if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) { return $var; } if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) { @@ -421,38 +479,60 @@ class System if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) { return $var; } - return '/tmp'; + return realpath('/tmp'); } /** - * The "which" command (show the full path of a command) - * - * @param string $program The command to search for - * @return mixed A string with the full path or false if not found - * @author Stig Bakken - */ + * The "which" command (show the full path of a command) + * + * @param string $program The command to search for + * @param mixed $fallback Value to return if $program is not found + * + * @return mixed A string with the full path or false if not found + * @static + * @author Stig Bakken + */ function which($program, $fallback = false) { - // is_executable() is not available on windows - if (OS_WINDOWS) { - $pear_is_executable = 'is_file'; - } else { - $pear_is_executable = 'is_executable'; + // enforce API + if (!is_string($program) || '' == $program) { + return $fallback; } // full path given if (basename($program) != $program) { - return (@$pear_is_executable($program)) ? $program : $fallback; + $path_elements[] = dirname($program); + $program = basename($program); + } else { + // Honor safe mode + if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) { + $path = getenv('PATH'); + if (!$path) { + $path = getenv('Path'); // some OSes are just stupid enough to do this + } + } + $path_elements = explode(PATH_SEPARATOR, $path); + } + + if (OS_WINDOWS) { + $exe_suffixes = getenv('PATHEXT') + ? explode(PATH_SEPARATOR, getenv('PATHEXT')) + : array('.exe','.bat','.cmd','.com'); + // allow passing a command.exe param + if (strpos($program, '.') !== false) { + array_unshift($exe_suffixes, ''); + } + // is_executable() is not available on windows for PHP4 + $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file'; + } else { + $exe_suffixes = array(''); + $pear_is_executable = 'is_executable'; } - // XXX FIXME honor safe mode - $path_delim = OS_WINDOWS ? ';' : ':'; - $exe_suffixes = OS_WINDOWS ? array('.exe','.bat','.cmd','.com') : array(''); - $path_elements = explode($path_delim, getenv('PATH')); foreach ($exe_suffixes as $suff) { foreach ($path_elements as $dir) { $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; - if (@is_file($file) && @$pear_is_executable($file)) { + if (@$pear_is_executable($file)) { return $file; } } @@ -461,43 +541,48 @@ class System } /** - * The "find" command - * - * Usage: - * - * System::find($dir); - * System::find("$dir -type d"); - * System::find("$dir -type f"); - * System::find("$dir -name *.php"); - * System::find("$dir -name *.php -name *.htm*"); - * System::find("$dir -maxdepth 1"); - * - * Params implmented: - * $dir -> Start the search at this directory - * -type d -> return only directories - * -type f -> return only files - * -maxdepth -> max depth of recursion - * -name -> search pattern (bash style). Multiple -name param allowed - * - * @param mixed Either array or string with the command line - * @return array Array of found files - * - */ + * The "find" command + * + * Usage: + * + * System::find($dir); + * System::find("$dir -type d"); + * System::find("$dir -type f"); + * System::find("$dir -name *.php"); + * System::find("$dir -name *.php -name *.htm*"); + * System::find("$dir -maxdepth 1"); + * + * Params implmented: + * $dir -> Start the search at this directory + * -type d -> return only directories + * -type f -> return only files + * -maxdepth -> max depth of recursion + * -name -> search pattern (bash style). Multiple -name param allowed + * + * @param mixed Either array or string with the command line + * @return array Array of found files + * @static + * + */ function find($args) { if (!is_array($args)) { $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); } - $dir = array_shift($args); + $dir = realpath(array_shift($args)); + if (!$dir) { + return array(); + } $patterns = array(); $depth = 0; $do_files = $do_dirs = true; - for ($i = 0; $i < count($args); $i++) { + $args_count = count($args); + for ($i = 0; $i < $args_count; $i++) { switch ($args[$i]) { case '-type': if (in_array($args[$i+1], array('d', 'f'))) { if ($args[$i+1] == 'd') { - $do_files = false; + $do_files = false; } else { $do_dirs = false; } @@ -505,10 +590,11 @@ class System $i++; break; case '-name': - $patterns[] = "(" . preg_replace(array('/\./', '/\*/'), - array('\.', '.*'), - $args[$i+1]) - . ")"; + $name = preg_quote($args[$i+1], '#'); + // our magic characters ? and * have just been escaped, + // so now we change the escaped versions to PCRE operators + $name = strtr($name, array('\?' => '.', '\*' => '.*')); + $patterns[] = '('.$name.')'; $i++; break; case '-maxdepth': @@ -516,7 +602,7 @@ class System break; } } - $path = System::_dirToStruct($dir, $depth); + $path = System::_dirToStruct($dir, $depth, 0, true); if ($do_files && $do_dirs) { $files = array_merge($path['files'], $path['dirs']); } elseif ($do_dirs) { @@ -525,10 +611,14 @@ class System $files = $path['files']; } if (count($patterns)) { - $patterns = implode('|', $patterns); + $dsq = preg_quote(DIRECTORY_SEPARATOR, '#'); + $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#'; $ret = array(); - for ($i = 0; $i < count($files); $i++) { - if (preg_match("#^$patterns\$#", $files[$i])) { + $files_count = count($files); + for ($i = 0; $i < $files_count; $i++) { + // only search in the part of the file below the current directory + $filepart = basename($files[$i]); + if (preg_match($pattern, $filepart)) { $ret[] = $files[$i]; } } @@ -536,5 +626,4 @@ class System } return $files; } -} -?> +} \ No newline at end of file diff --git a/3rdparty/XML/Parser.php b/3rdparty/XML/Parser.php index 38b4f8fe8a..a38a161aef 100644 --- a/3rdparty/XML/Parser.php +++ b/3rdparty/XML/Parser.php @@ -1,42 +1,58 @@ | -// | Tomas V.V.Cox | -// | Stephan Schmidt | -// +----------------------------------------------------------------------+ -// -// $Id: Parser.php,v 1.25 2005/03/25 17:13:10 schst Exp $ + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /** - * XML Parser class. + * XML_Parser * - * This is an XML parser based on PHP's "xml" extension, - * based on the bundled expat library. + * XML Parser package * - * @category XML - * @package XML_Parser - * @author Stig Bakken - * @author Tomas V.V.Cox - * @author Stephan Schmidt + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2008 The PHP Group + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Parser.php 302733 2010-08-24 01:09:09Z clockwerx $ + * @link http://pear.php.net/package/XML_Parser */ /** * uses PEAR's error handling */ -require_once('PEAR.php'); +require_once 'PEAR.php'; /** * resource could not be created @@ -79,25 +95,29 @@ define('XML_PARSER_ERROR_REMOTE', 205); * - From revision 1.17, the function names used by the 'func' mode * are in the format "xmltag_$elem", for example: use "xmltag_name" * to handle the tags of your xml file. - * - * @category XML - * @package XML_Parser - * @author Stig Bakken - * @author Tomas V.V.Cox - * @author Stephan Schmidt - * @todo create XML_Parser_Namespace to parse documents with namespaces - * @todo create XML_Parser_Pull - * @todo Tests that need to be made: - * - mixing character encodings - * - a test using all expat handlers - * - options (folding, output charset) * - different parsing modes + * + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + * @todo create XML_Parser_Namespace to parse documents with namespaces + * @todo create XML_Parser_Pull + * @todo Tests that need to be made: + * - mixing character encodings + * - a test using all expat handlers + * - options (folding, output charset) */ class XML_Parser extends PEAR { // {{{ properties - /** + /** * XML parser handle * * @var resource @@ -164,7 +184,35 @@ class XML_Parser extends PEAR */ var $_handlerObj; + /** + * valid encodings + * + * @var array + */ + var $_validEncodings = array('ISO-8859-1', 'UTF-8', 'US-ASCII'); + // }}} + // {{{ php4 constructor + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgtenc a valid target encoding + */ + function XML_Parser($srcenc = null, $mode = 'event', $tgtenc = null) + { + XML_Parser::__construct($srcenc, $mode, $tgtenc); + } + // }}} + // {{{ php5 constructor /** * PHP5 constructor @@ -174,7 +222,7 @@ class XML_Parser extends PEAR * @param string $mode how this parser object should work, "event" for * startelement/endelement-type events, "func" * to have it call functions named after elements - * @param string $tgenc a valid target encoding + * @param string $tgtenc a valid target encoding */ function __construct($srcenc = null, $mode = 'event', $tgtenc = null) { @@ -199,14 +247,16 @@ class XML_Parser extends PEAR * This method is only needed, when switching to a new * mode at a later point. * - * @access public - * @param string mode, either 'func' or 'event' - * @return boolean|object true on success, PEAR_Error otherwise + * @param string $mode mode, either 'func' or 'event' + * + * @return boolean|object true on success, PEAR_Error otherwise + * @access public */ function setMode($mode) { if ($mode != 'func' && $mode != 'event') { - $this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE); + $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); } $this->mode = $mode; @@ -223,10 +273,11 @@ class XML_Parser extends PEAR * If no object will be set, XML_Parser assumes that you * extend this class and handle the events in $this. * - * @access public - * @param object object to handle the events - * @return boolean will always return true - * @since v1.2.0beta3 + * @param object &$obj object to handle the events + * + * @return boolean will always return true + * @access public + * @since v1.2.0beta3 */ function setHandlerObj(&$obj) { @@ -237,7 +288,8 @@ class XML_Parser extends PEAR /** * Init the element handlers * - * @access private + * @return mixed + * @access private */ function _initHandlers() { @@ -250,21 +302,22 @@ class XML_Parser extends PEAR } switch ($this->mode) { - case 'func': - xml_set_object($this->parser, $this->_handlerObj); - xml_set_element_handler($this->parser, array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler')); - break; + case 'func': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, + array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler')); + break; - case 'event': - xml_set_object($this->parser, $this->_handlerObj); - xml_set_element_handler($this->parser, 'startHandler', 'endHandler'); - break; - default: - return $this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE); - break; + case 'event': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, 'startHandler', 'endHandler'); + break; + default: + return $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + break; } - /** * set additional handlers for character data, entities, etc. */ @@ -273,7 +326,7 @@ class XML_Parser extends PEAR $xml_func = 'xml_set_' . $xml_func; $xml_func($this->parser, $method); } - } + } } // {{{ _create() @@ -287,9 +340,10 @@ class XML_Parser extends PEAR * Furthermore it allows us returning an error * if something fails. * - * @access private - * @return boolean|object true on success, PEAR_Error otherwise + * NOTE: uses '@' error suppresion in this method * + * @return bool|PEAR_Error true on success, PEAR_Error otherwise + * @access private * @see xml_parser_create */ function _create() @@ -301,21 +355,27 @@ class XML_Parser extends PEAR } if (is_resource($xp)) { if ($this->tgtenc !== null) { - if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, - $this->tgtenc)) { - return $this->raiseError('invalid target encoding', XML_PARSER_ERROR_INVALID_ENCODING); + if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, + $this->tgtenc) + ) { + return $this->raiseError('invalid target encoding', + XML_PARSER_ERROR_INVALID_ENCODING); } } $this->parser = $xp; - $result = $this->_initHandlers($this->mode); + $result = $this->_initHandlers($this->mode); if ($this->isError($result)) { return $result; } xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding); - return true; } - return $this->raiseError('Unable to create XML parser resource.', XML_PARSER_ERROR_NO_RESOURCE); + if (!in_array(strtoupper($this->srcenc), $this->_validEncodings)) { + return $this->raiseError('invalid source encoding', + XML_PARSER_ERROR_INVALID_ENCODING); + } + return $this->raiseError('Unable to create XML parser resource.', + XML_PARSER_ERROR_NO_RESOURCE); } // }}} @@ -333,7 +393,7 @@ class XML_Parser extends PEAR function reset() { $result = $this->_create(); - if ($this->isError( $result )) { + if ($this->isError($result)) { return $result; } return true; @@ -345,47 +405,53 @@ class XML_Parser extends PEAR /** * Sets the input xml file to be parsed * - * @param string Filename (full path) - * @return resource fopen handle of the given file - * @throws XML_Parser_Error - * @see setInput(), setInputString(), parse() - * @access public + * @param string $file Filename (full path) + * + * @return resource fopen handle of the given file + * @access public + * @throws XML_Parser_Error + * @see setInput(), setInputString(), parse() */ function setInputFile($file) { /** * check, if file is a remote file */ - if (preg_match('[^(http|ftp)://]', substr($file, 0, 10))) { + if (preg_match('/^(http|ftp):\/\//i', substr($file, 0, 10))) { if (!ini_get('allow_url_fopen')) { - return $this->raiseError('Remote files cannot be parsed, as safe mode is enabled.', XML_PARSER_ERROR_REMOTE); + return $this-> + raiseError('Remote files cannot be parsed, as safe mode is enabled.', + XML_PARSER_ERROR_REMOTE); } } - $fp = fopen($file, 'rb'); + + $fp = @fopen($file, 'rb'); if (is_resource($fp)) { $this->fp = $fp; return $fp; } - return $this->raiseError('File could not be opened.', XML_PARSER_ERROR_FILE_NOT_READABLE); + return $this->raiseError('File could not be opened.', + XML_PARSER_ERROR_FILE_NOT_READABLE); } // }}} // {{{ setInputString() - + /** * XML_Parser::setInputString() - * + * * Sets the xml input from a string - * + * * @param string $data a string containing the XML document + * * @return null - **/ + */ function setInputString($data) { $this->fp = $data; return null; } - + // }}} // {{{ setInput() @@ -393,35 +459,35 @@ class XML_Parser extends PEAR * Sets the file handle to use with parse(). * * You should use setInputFile() or setInputString() if you - * pass a string + * pass a string * - * @param mixed $fp Can be either a resource returned from fopen(), - * a URL, a local filename or a string. - * @access public - * @see parse() - * @uses setInputString(), setInputFile() + * @param mixed $fp Can be either a resource returned from fopen(), + * a URL, a local filename or a string. + * + * @return mixed + * @access public + * @see parse() + * @uses setInputString(), setInputFile() */ function setInput($fp) { if (is_resource($fp)) { $this->fp = $fp; return true; - } - // see if it's an absolute URL (has a scheme at the beginning) - elseif (eregi('^[a-z]+://', substr($fp, 0, 10))) { + } elseif (preg_match('/^[a-z]+:\/\//i', substr($fp, 0, 10))) { + // see if it's an absolute URL (has a scheme at the beginning) return $this->setInputFile($fp); - } - // see if it's a local file - elseif (file_exists($fp)) { + } elseif (file_exists($fp)) { + // see if it's a local file return $this->setInputFile($fp); - } - // it must be a string - else { + } else { + // it must be a string $this->fp = $fp; return true; } - return $this->raiseError('Illegal input format', XML_PARSER_ERROR_INVALID_INPUT); + return $this->raiseError('Illegal input format', + XML_PARSER_ERROR_INVALID_INPUT); } // }}} @@ -430,8 +496,8 @@ class XML_Parser extends PEAR /** * Central parsing function. * - * @return true|object PEAR error returns true on success, or a PEAR_Error otherwise - * @access public + * @return bool|PEAR_Error returns true on success, or a PEAR_Error otherwise + * @access public */ function parse() { @@ -444,7 +510,7 @@ class XML_Parser extends PEAR } // if $this->fp was fopened previously if (is_resource($this->fp)) { - + while ($data = fread($this->fp, 4096)) { if (!$this->_parseString($data, feof($this->fp))) { $error = &$this->raiseError(); @@ -452,8 +518,8 @@ class XML_Parser extends PEAR return $error; } } - // otherwise, $this->fp must be a string } else { + // otherwise, $this->fp must be a string if (!$this->_parseString($this->fp, true)) { $error = &$this->raiseError(); $this->free(); @@ -467,9 +533,10 @@ class XML_Parser extends PEAR /** * XML_Parser::_parseString() - * - * @param string $data - * @param boolean $eof + * + * @param string $data data + * @param bool $eof end-of-file flag + * * @return bool * @access private * @see parseString() @@ -478,31 +545,33 @@ class XML_Parser extends PEAR { return xml_parse($this->parser, $data, $eof); } - + // }}} // {{{ parseString() /** * XML_Parser::parseString() - * + * * Parses a string. * - * @param string $data XML data - * @param boolean $eof If set and TRUE, data is the last piece of data sent in this parser - * @throws XML_Parser_Error - * @return Pear Error|true true on success or a PEAR Error - * @see _parseString() + * @param string $data XML data + * @param boolean $eof If set and TRUE, data is the last piece + * of data sent in this parser + * + * @return bool|PEAR_Error true on success or a PEAR Error + * @throws XML_Parser_Error + * @see _parseString() */ function parseString($data, $eof = false) { if (!isset($this->parser) || !is_resource($this->parser)) { $this->reset(); } - + if (!$this->_parseString($data, $eof)) { - $error = &$this->raiseError(); - $this->free(); - return $error; + $error = &$this->raiseError(); + $this->free(); + return $error; } if ($eof === true) { @@ -510,12 +579,12 @@ class XML_Parser extends PEAR } return true; } - + /** * XML_Parser::free() - * + * * Free the internal resources associated with the parser - * + * * @return null **/ function free() @@ -530,52 +599,63 @@ class XML_Parser extends PEAR unset($this->fp); return null; } - + /** * XML_Parser::raiseError() - * + * * Throws a XML_Parser_Error - * + * * @param string $msg the error message * @param integer $ecode the error message code - * @return XML_Parser_Error + * + * @return XML_Parser_Error reference to the error object **/ - function raiseError($msg = null, $ecode = 0,$mode = null, - $options = null, - $userinfo = null, - $error_class = null, - $skipmsg = false) + function &raiseError($msg = null, $ecode = 0) { $msg = !is_null($msg) ? $msg : $this->parser; - $err = new XML_Parser_Error($msg, $ecode); + $err = &new XML_Parser_Error($msg, $ecode); return parent::raiseError($err); } - + // }}} // {{{ funcStartHandler() + /** + * derives and calls the Start Handler function + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * @param mixed $attribs ?? + * + * @return void + */ function funcStartHandler($xp, $elem, $attribs) { $func = 'xmltag_' . $elem; - if (strchr($func, '.')) { - $func = str_replace('.', '_', $func); - } + $func = str_replace(array('.', '-', ':'), '_', $func); if (method_exists($this->_handlerObj, $func)) { call_user_func(array(&$this->_handlerObj, $func), $xp, $elem, $attribs); } elseif (method_exists($this->_handlerObj, 'xmltag')) { - call_user_func(array(&$this->_handlerObj, 'xmltag'), $xp, $elem, $attribs); + call_user_func(array(&$this->_handlerObj, 'xmltag'), + $xp, $elem, $attribs); } } // }}} // {{{ funcEndHandler() + /** + * derives and calls the End Handler function + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * + * @return void + */ function funcEndHandler($xp, $elem) { $func = 'xmltag_' . $elem . '_'; - if (strchr($func, '.')) { - $func = str_replace('.', '_', $func); - } + $func = str_replace(array('.', '-', ':'), '_', $func); if (method_exists($this->_handlerObj, $func)) { call_user_func(array(&$this->_handlerObj, $func), $xp, $elem); } elseif (method_exists($this->_handlerObj, 'xmltag_')) { @@ -587,24 +667,35 @@ class XML_Parser extends PEAR // {{{ startHandler() /** + * abstract method signature for Start Handler * + * @param mixed $xp ?? + * @param mixed $elem ?? + * @param mixed &$attribs ?? + * + * @return null * @abstract */ - function startHandler($xp, $elem, $attribs) + function startHandler($xp, $elem, &$attribs) { - return NULL; + return null; } // }}} // {{{ endHandler() /** + * abstract method signature for End Handler * + * @param mixed $xp ?? + * @param mixed $elem ?? + * + * @return null * @abstract */ function endHandler($xp, $elem) { - return NULL; + return null; } @@ -622,46 +713,56 @@ class XML_Parser extends PEAR * - check for XML_Parser error, using is_a( $error, 'XML_Parser_Error' ) * - messages can be generated from the xml_parser resource * - * @package XML_Parser - * @access public - * @see PEAR_Error + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + * @see PEAR_Error */ class XML_Parser_Error extends PEAR_Error { // {{{ properties - /** + /** * prefix for all messages * * @var string - */ + */ var $error_message_prefix = 'XML_Parser: '; // }}} // {{{ constructor() - /** + /** * construct a new error instance * * You may either pass a message or an xml_parser resource as first * parameter. If a resource has been passed, the last error that * happened will be retrieved and returned. * + * @param string|resource $msgorparser message or parser resource + * @param integer $code error code + * @param integer $mode error handling + * @param integer $level error level + * * @access public - * @param string|resource message or parser resource - * @param integer error code - * @param integer error handling - * @param integer error level - */ + * @todo PEAR CS - can't meet 85char line limit without arg refactoring + */ function XML_Parser_Error($msgorparser = 'unknown error', $code = 0, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE) { if (is_resource($msgorparser)) { - $code = xml_get_error_code($msgorparser); - $msgorparser = sprintf('%s at XML input line %d', - xml_error_string($code), - xml_get_current_line_number($msgorparser)); + $code = xml_get_error_code($msgorparser); + $msgorparser = sprintf('%s at XML input line %d:%d', + xml_error_string($code), + xml_get_current_line_number($msgorparser), + xml_get_current_column_number($msgorparser)); } $this->PEAR_Error($msgorparser, $code, $mode, $level); } // }}} } -?> \ No newline at end of file +?> diff --git a/3rdparty/XML/Parser/Simple.php b/3rdparty/XML/Parser/Simple.php new file mode 100644 index 0000000000..9ed0abfc6c --- /dev/null +++ b/3rdparty/XML/Parser/Simple.php @@ -0,0 +1,326 @@ + + * @copyright 2004-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Simple.php 265444 2008-08-24 21:48:21Z ashnazg $ + * @link http://pear.php.net/package/XML_Parser + */ + +/** + * built on XML_Parser + */ +require_once 'XML/Parser.php'; + +/** + * Simple XML parser class. + * + * This class is a simplified version of XML_Parser. + * In most XML applications the real action is executed, + * when a closing tag is found. + * + * XML_Parser_Simple allows you to just implement one callback + * for each tag that will receive the tag with its attributes + * and CData. + * + * + * require_once '../Parser/Simple.php'; + * + * class myParser extends XML_Parser_Simple + * { + * function myParser() + * { + * $this->XML_Parser_Simple(); + * } + * + * function handleElement($name, $attribs, $data) + * { + * printf('handle %s
', $name); + * } + * } + * + * $p = &new myParser(); + * + * $result = $p->setInputFile('myDoc.xml'); + * $result = $p->parse(); + *
+ * + * @category XML + * @package XML_Parser + * @author Stephan Schmidt + * @copyright 2004-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + */ +class XML_Parser_Simple extends XML_Parser +{ + /** + * element stack + * + * @access private + * @var array + */ + var $_elStack = array(); + + /** + * all character data + * + * @access private + * @var array + */ + var $_data = array(); + + /** + * element depth + * + * @access private + * @var integer + */ + var $_depth = 0; + + /** + * Mapping from expat handler function to class method. + * + * @var array + */ + var $handler = array( + 'default_handler' => 'defaultHandler', + 'processing_instruction_handler' => 'piHandler', + 'unparsed_entity_decl_handler' => 'unparsedHandler', + 'notation_decl_handler' => 'notationHandler', + 'external_entity_ref_handler' => 'entityrefHandler' + ); + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * handleElement(), "func" to have it call functions + * named after elements (handleElement_$name()) + * @param string $tgtenc a valid target encoding + */ + function XML_Parser_Simple($srcenc = null, $mode = 'event', $tgtenc = null) + { + $this->XML_Parser($srcenc, $mode, $tgtenc); + } + + /** + * inits the handlers + * + * @return mixed + * @access private + */ + function _initHandlers() + { + if (!is_object($this->_handlerObj)) { + $this->_handlerObj = &$this; + } + + if ($this->mode != 'func' && $this->mode != 'event') { + return $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + } + xml_set_object($this->parser, $this->_handlerObj); + + xml_set_element_handler($this->parser, array(&$this, 'startHandler'), + array(&$this, 'endHandler')); + xml_set_character_data_handler($this->parser, array(&$this, 'cdataHandler')); + + /** + * set additional handlers for character data, entities, etc. + */ + foreach ($this->handler as $xml_func => $method) { + if (method_exists($this->_handlerObj, $method)) { + $xml_func = 'xml_set_' . $xml_func; + $xml_func($this->parser, $method); + } + } + } + + /** + * Reset the parser. + * + * This allows you to use one parser instance + * to parse multiple XML documents. + * + * @access public + * @return boolean|object true on success, PEAR_Error otherwise + */ + function reset() + { + $this->_elStack = array(); + $this->_data = array(); + $this->_depth = 0; + + $result = $this->_create(); + if ($this->isError($result)) { + return $result; + } + return true; + } + + /** + * start handler + * + * Pushes attributes and tagname onto a stack + * + * @param resource $xp xml parser resource + * @param string $elem element name + * @param array &$attribs attributes + * + * @return mixed + * @access private + * @final + */ + function startHandler($xp, $elem, &$attribs) + { + array_push($this->_elStack, array( + 'name' => $elem, + 'attribs' => $attribs + )); + $this->_depth++; + $this->_data[$this->_depth] = ''; + } + + /** + * end handler + * + * Pulls attributes and tagname from a stack + * + * @param resource $xp xml parser resource + * @param string $elem element name + * + * @return mixed + * @access private + * @final + */ + function endHandler($xp, $elem) + { + $el = array_pop($this->_elStack); + $data = $this->_data[$this->_depth]; + $this->_depth--; + + switch ($this->mode) { + case 'event': + $this->_handlerObj->handleElement($el['name'], $el['attribs'], $data); + break; + case 'func': + $func = 'handleElement_' . $elem; + if (strchr($func, '.')) { + $func = str_replace('.', '_', $func); + } + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), + $el['name'], $el['attribs'], $data); + } + break; + } + } + + /** + * handle character data + * + * @param resource $xp xml parser resource + * @param string $data data + * + * @return void + * @access private + * @final + */ + function cdataHandler($xp, $data) + { + $this->_data[$this->_depth] .= $data; + } + + /** + * handle a tag + * + * Implement this in your parser + * + * @param string $name element name + * @param array $attribs attributes + * @param string $data character data + * + * @return void + * @access public + * @abstract + */ + function handleElement($name, $attribs, $data) + { + } + + /** + * get the current tag depth + * + * The root tag is in depth 0. + * + * @access public + * @return integer + */ + function getCurrentDepth() + { + return $this->_depth; + } + + /** + * add some string to the current ddata. + * + * This is commonly needed, when a document is parsed recursively. + * + * @param string $data data to add + * + * @return void + * @access public + */ + function addToData($data) + { + $this->_data[$this->_depth] .= $data; + } +} +?> diff --git a/3rdparty/XML/RPC.php b/3rdparty/XML/RPC.php deleted file mode 100644 index 096b22a0ab..0000000000 --- a/3rdparty/XML/RPC.php +++ /dev/null @@ -1,1951 +0,0 @@ - - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version CVS: $Id: RPC.php,v 1.83 2005/08/14 20:25:35 danielc Exp $ - * @link http://pear.php.net/package/XML_RPC - */ - - -if (!function_exists('xml_parser_create')) { - PEAR::loadExtension('xml'); -} - -/**#@+ - * Error constants - */ -/** - * Parameter values don't match parameter types - */ -define('XML_RPC_ERROR_INVALID_TYPE', 101); -/** - * Parameter declared to be numeric but the values are not - */ -define('XML_RPC_ERROR_NON_NUMERIC_FOUND', 102); -/** - * Communication error - */ -define('XML_RPC_ERROR_CONNECTION_FAILED', 103); -/** - * The array or struct has already been started - */ -define('XML_RPC_ERROR_ALREADY_INITIALIZED', 104); -/** - * Incorrect parameters submitted - */ -define('XML_RPC_ERROR_INCORRECT_PARAMS', 105); -/** - * Programming error by developer - */ -define('XML_RPC_ERROR_PROGRAMMING', 106); -/**#@-*/ - - -/** - * Data types - * @global string $GLOBALS['XML_RPC_I4'] - */ -$GLOBALS['XML_RPC_I4'] = 'i4'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_Int'] - */ -$GLOBALS['XML_RPC_Int'] = 'int'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_Boolean'] - */ -$GLOBALS['XML_RPC_Boolean'] = 'boolean'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_Double'] - */ -$GLOBALS['XML_RPC_Double'] = 'double'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_String'] - */ -$GLOBALS['XML_RPC_String'] = 'string'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_DateTime'] - */ -$GLOBALS['XML_RPC_DateTime'] = 'dateTime.iso8601'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_Base64'] - */ -$GLOBALS['XML_RPC_Base64'] = 'base64'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_Array'] - */ -$GLOBALS['XML_RPC_Array'] = 'array'; - -/** - * Data types - * @global string $GLOBALS['XML_RPC_Struct'] - */ -$GLOBALS['XML_RPC_Struct'] = 'struct'; - - -/** - * Data type meta-types - * @global array $GLOBALS['XML_RPC_Types'] - */ -$GLOBALS['XML_RPC_Types'] = array( - $GLOBALS['XML_RPC_I4'] => 1, - $GLOBALS['XML_RPC_Int'] => 1, - $GLOBALS['XML_RPC_Boolean'] => 1, - $GLOBALS['XML_RPC_String'] => 1, - $GLOBALS['XML_RPC_Double'] => 1, - $GLOBALS['XML_RPC_DateTime'] => 1, - $GLOBALS['XML_RPC_Base64'] => 1, - $GLOBALS['XML_RPC_Array'] => 2, - $GLOBALS['XML_RPC_Struct'] => 3, -); - - -/** - * Error message numbers - * @global array $GLOBALS['XML_RPC_err'] - */ -$GLOBALS['XML_RPC_err'] = array( - 'unknown_method' => 1, - 'invalid_return' => 2, - 'incorrect_params' => 3, - 'introspect_unknown' => 4, - 'http_error' => 5, - 'not_response_object' => 6, - 'invalid_request' => 7, -); - -/** - * Error message strings - * @global array $GLOBALS['XML_RPC_str'] - */ -$GLOBALS['XML_RPC_str'] = array( - 'unknown_method' => 'Unknown method', - 'invalid_return' => 'Invalid return payload: enable debugging to examine incoming payload', - 'incorrect_params' => 'Incorrect parameters passed to method', - 'introspect_unknown' => 'Can\'t introspect: method unknown', - 'http_error' => 'Didn\'t receive 200 OK from remote server.', - 'not_response_object' => 'The requested method didn\'t return an XML_RPC_Response object.', - 'invalid_request' => 'Invalid request payload', -); - - -/** - * Default XML encoding (ISO-8859-1, UTF-8 or US-ASCII) - * @global string $GLOBALS['XML_RPC_defencoding'] - */ -$GLOBALS['XML_RPC_defencoding'] = 'UTF-8'; - -/** - * User error codes start at 800 - * @global int $GLOBALS['XML_RPC_erruser'] - */ -$GLOBALS['XML_RPC_erruser'] = 800; - -/** - * XML parse error codes start at 100 - * @global int $GLOBALS['XML_RPC_errxml'] - */ -$GLOBALS['XML_RPC_errxml'] = 100; - - -/** - * Compose backslashes for escaping regexp - * @global string $GLOBALS['XML_RPC_backslash'] - */ -$GLOBALS['XML_RPC_backslash'] = chr(92) . chr(92); - - -/** - * Valid parents of XML elements - * @global array $GLOBALS['XML_RPC_valid_parents'] - */ -$GLOBALS['XML_RPC_valid_parents'] = array( - 'BOOLEAN' => array('VALUE'), - 'I4' => array('VALUE'), - 'INT' => array('VALUE'), - 'STRING' => array('VALUE'), - 'DOUBLE' => array('VALUE'), - 'DATETIME.ISO8601' => array('VALUE'), - 'BASE64' => array('VALUE'), - 'ARRAY' => array('VALUE'), - 'STRUCT' => array('VALUE'), - 'PARAM' => array('PARAMS'), - 'METHODNAME' => array('METHODCALL'), - 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), - 'MEMBER' => array('STRUCT'), - 'NAME' => array('MEMBER'), - 'DATA' => array('ARRAY'), - 'FAULT' => array('METHODRESPONSE'), - 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), -); - - -/** - * Stores state during parsing - * - * quick explanation of components: - * + ac = accumulates values - * + qt = decides if quotes are needed for evaluation - * + cm = denotes struct or array (comma needed) - * + isf = indicates a fault - * + lv = indicates "looking for a value": implements the logic - * to allow values with no types to be strings - * + params = stores parameters in method calls - * + method = stores method name - * - * @global array $GLOBALS['XML_RPC_xh'] - */ -$GLOBALS['XML_RPC_xh'] = array(); - - -/** - * Start element handler for the XML parser - * - * @return void - */ -function XML_RPC_se($parser_resource, $name, $attrs) -{ - global $XML_RPC_xh, $XML_RPC_DateTime, $XML_RPC_String, $XML_RPC_valid_parents; - $parser = (int) $parser_resource; - - // if invalid xmlrpc already detected, skip all processing - if ($XML_RPC_xh[$parser]['isf'] >= 2) { - return; - } - - // check for correct element nesting - // top level element can only be of 2 types - if (count($XML_RPC_xh[$parser]['stack']) == 0) { - if ($name != 'METHODRESPONSE' && $name != 'METHODCALL') { - $XML_RPC_xh[$parser]['isf'] = 2; - $XML_RPC_xh[$parser]['isf_reason'] = 'missing top level xmlrpc element'; - return; - } - } else { - // not top level element: see if parent is OK - if (!in_array($XML_RPC_xh[$parser]['stack'][0], $XML_RPC_valid_parents[$name])) { - $name = preg_replace('[^a-zA-Z0-9._-]', '', $name); - $XML_RPC_xh[$parser]['isf'] = 2; - $XML_RPC_xh[$parser]['isf_reason'] = "xmlrpc element $name cannot be child of {$XML_RPC_xh[$parser]['stack'][0]}"; - return; - } - } - - switch ($name) { - case 'STRUCT': - $XML_RPC_xh[$parser]['cm']++; - - // turn quoting off - $XML_RPC_xh[$parser]['qt'] = 0; - - $cur_val = array(); - $cur_val['value'] = array(); - $cur_val['members'] = 1; - array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); - break; - - case 'ARRAY': - $XML_RPC_xh[$parser]['cm']++; - - // turn quoting off - $XML_RPC_xh[$parser]['qt'] = 0; - - $cur_val = array(); - $cur_val['value'] = array(); - $cur_val['members'] = 0; - array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); - break; - - case 'NAME': - $XML_RPC_xh[$parser]['ac'] = ''; - break; - - case 'FAULT': - $XML_RPC_xh[$parser]['isf'] = 1; - break; - - case 'PARAM': - $XML_RPC_xh[$parser]['valuestack'] = array(); - break; - - case 'VALUE': - $XML_RPC_xh[$parser]['lv'] = 1; - $XML_RPC_xh[$parser]['vt'] = $XML_RPC_String; - $XML_RPC_xh[$parser]['ac'] = ''; - $XML_RPC_xh[$parser]['qt'] = 0; - // look for a value: if this is still 1 by the - // time we reach the first data segment then the type is string - // by implication and we need to add in a quote - break; - - case 'I4': - case 'INT': - case 'STRING': - case 'BOOLEAN': - case 'DOUBLE': - case 'DATETIME.ISO8601': - case 'BASE64': - $XML_RPC_xh[$parser]['ac'] = ''; // reset the accumulator - - if ($name == 'DATETIME.ISO8601' || $name == 'STRING') { - $XML_RPC_xh[$parser]['qt'] = 1; - - if ($name == 'DATETIME.ISO8601') { - $XML_RPC_xh[$parser]['vt'] = $XML_RPC_DateTime; - } - - } elseif ($name == 'BASE64') { - $XML_RPC_xh[$parser]['qt'] = 2; - } else { - // No quoting is required here -- but - // at the end of the element we must check - // for data format errors. - $XML_RPC_xh[$parser]['qt'] = 0; - } - break; - - case 'MEMBER': - $XML_RPC_xh[$parser]['ac'] = ''; - break; - - case 'DATA': - case 'METHODCALL': - case 'METHODNAME': - case 'METHODRESPONSE': - case 'PARAMS': - // valid elements that add little to processing - break; - } - - - // Save current element to stack - array_unshift($XML_RPC_xh[$parser]['stack'], $name); - - if ($name != 'VALUE') { - $XML_RPC_xh[$parser]['lv'] = 0; - } -} - -/** - * End element handler for the XML parser - * - * @return void - */ -function XML_RPC_ee($parser_resource, $name) -{ - global $XML_RPC_xh, $XML_RPC_Types, $XML_RPC_String; - $parser = (int) $parser_resource; - - if ($XML_RPC_xh[$parser]['isf'] >= 2) { - return; - } - - // push this element from stack - // NB: if XML validates, correct opening/closing is guaranteed and - // we do not have to check for $name == $curr_elem. - // we also checked for proper nesting at start of elements... - $curr_elem = array_shift($XML_RPC_xh[$parser]['stack']); - - switch ($name) { - case 'STRUCT': - case 'ARRAY': - $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']); - $XML_RPC_xh[$parser]['value'] = $cur_val['value']; - $XML_RPC_xh[$parser]['vt'] = strtolower($name); - $XML_RPC_xh[$parser]['cm']--; - break; - - case 'NAME': - $XML_RPC_xh[$parser]['valuestack'][0]['name'] = $XML_RPC_xh[$parser]['ac']; - break; - - case 'BOOLEAN': - // special case here: we translate boolean 1 or 0 into PHP - // constants true or false - if ($XML_RPC_xh[$parser]['ac'] == '1') { - $XML_RPC_xh[$parser]['ac'] = 'true'; - } else { - $XML_RPC_xh[$parser]['ac'] = 'false'; - } - - $XML_RPC_xh[$parser]['vt'] = strtolower($name); - // Drop through intentionally. - - case 'I4': - case 'INT': - case 'STRING': - case 'DOUBLE': - case 'DATETIME.ISO8601': - case 'BASE64': - if ($XML_RPC_xh[$parser]['qt'] == 1) { - // we use double quotes rather than single so backslashification works OK - $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; - } elseif ($XML_RPC_xh[$parser]['qt'] == 2) { - $XML_RPC_xh[$parser]['value'] = base64_decode($XML_RPC_xh[$parser]['ac']); - } elseif ($name == 'BOOLEAN') { - $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; - } else { - // we have an I4, INT or a DOUBLE - // we must check that only 0123456789-. are characters here - if (!ereg("^[+-]?[0123456789 \t\.]+$", $XML_RPC_xh[$parser]['ac'])) { - XML_RPC_Base::raiseError('Non-numeric value received in INT or DOUBLE', - XML_RPC_ERROR_NON_NUMERIC_FOUND); - $XML_RPC_xh[$parser]['value'] = XML_RPC_ERROR_NON_NUMERIC_FOUND; - } else { - // it's ok, add it on - $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; - } - } - - $XML_RPC_xh[$parser]['ac'] = ''; - $XML_RPC_xh[$parser]['qt'] = 0; - $XML_RPC_xh[$parser]['lv'] = 3; // indicate we've found a value - break; - - case 'VALUE': - // deal with a string value - if (strlen($XML_RPC_xh[$parser]['ac']) > 0 && - $XML_RPC_xh[$parser]['vt'] == $XML_RPC_String) { - $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; - } - - $temp = new XML_RPC_Value($XML_RPC_xh[$parser]['value'], $XML_RPC_xh[$parser]['vt']); - - $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']); - if (is_array($cur_val)) { - if ($cur_val['members']==0) { - $cur_val['value'][] = $temp; - } else { - $XML_RPC_xh[$parser]['value'] = $temp; - } - array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); - } else { - $XML_RPC_xh[$parser]['value'] = $temp; - } - break; - - case 'MEMBER': - $XML_RPC_xh[$parser]['ac'] = ''; - $XML_RPC_xh[$parser]['qt'] = 0; - - $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']); - if (is_array($cur_val)) { - if ($cur_val['members']==1) { - $cur_val['value'][$cur_val['name']] = $XML_RPC_xh[$parser]['value']; - } - array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); - } - break; - - case 'DATA': - $XML_RPC_xh[$parser]['ac'] = ''; - $XML_RPC_xh[$parser]['qt'] = 0; - break; - - case 'PARAM': - $XML_RPC_xh[$parser]['params'][] = $XML_RPC_xh[$parser]['value']; - break; - - case 'METHODNAME': - case 'RPCMETHODNAME': - $XML_RPC_xh[$parser]['method'] = ereg_replace("^[\n\r\t ]+", '', - $XML_RPC_xh[$parser]['ac']); - break; - } - - // if it's a valid type name, set the type - if (isset($XML_RPC_Types[strtolower($name)])) { - $XML_RPC_xh[$parser]['vt'] = strtolower($name); - } -} - -/** - * Character data handler for the XML parser - * - * @return void - */ -function XML_RPC_cd($parser_resource, $data) -{ - global $XML_RPC_xh, $XML_RPC_backslash; - $parser = (int) $parser_resource; - - if ($XML_RPC_xh[$parser]['lv'] != 3) { - // "lookforvalue==3" means that we've found an entire value - // and should discard any further character data - - if ($XML_RPC_xh[$parser]['lv'] == 1) { - // if we've found text and we're just in a then - // turn quoting on, as this will be a string - $XML_RPC_xh[$parser]['qt'] = 1; - // and say we've found a value - $XML_RPC_xh[$parser]['lv'] = 2; - } - - // replace characters that eval would - // do special things with - if (!isset($XML_RPC_xh[$parser]['ac'])) { - $XML_RPC_xh[$parser]['ac'] = ''; - } - $XML_RPC_xh[$parser]['ac'] .= $data; - } -} - -/** - * The common methods and properties for all of the XML_RPC classes - * - * @category Web Services - * @package XML_RPC - * @author Edd Dumbill - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version Release: 1.4.0 - * @link http://pear.php.net/package/XML_RPC - */ -class XML_RPC_Base { - - /** - * PEAR Error handling - * - * @return object PEAR_Error object - */ - function raiseError($msg, $code) - { - include_once 'PEAR.php'; - if (is_object(@$this)) { - return PEAR::raiseError(get_class($this) . ': ' . $msg, $code); - } else { - return PEAR::raiseError('XML_RPC: ' . $msg, $code); - } - } - - /** - * Tell whether something is a PEAR_Error object - * - * @param mixed $value the item to check - * - * @return bool whether $value is a PEAR_Error object or not - * - * @access public - */ - function isError($value) - { - return is_a($value, 'PEAR_Error'); - } -} - -/** - * The methods and properties for submitting XML RPC requests - * - * @category Web Services - * @package XML_RPC - * @author Edd Dumbill - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version Release: 1.4.0 - * @link http://pear.php.net/package/XML_RPC - */ -class XML_RPC_Client extends XML_RPC_Base { - - /** - * The path and name of the RPC server script you want the request to go to - * @var string - */ - var $path = ''; - - /** - * The name of the remote server to connect to - * @var string - */ - var $server = ''; - - /** - * The protocol to use in contacting the remote server - * @var string - */ - var $protocol = 'http://'; - - /** - * The port for connecting to the remote server - * - * The default is 80 for http:// connections - * and 443 for https:// and ssl:// connections. - * - * @var integer - */ - var $port = 80; - - /** - * A user name for accessing the RPC server - * @var string - * @see XML_RPC_Client::setCredentials() - */ - var $username = ''; - - /** - * A password for accessing the RPC server - * @var string - * @see XML_RPC_Client::setCredentials() - */ - var $password = ''; - - /** - * The name of the proxy server to use, if any - * @var string - */ - var $proxy = ''; - - /** - * The protocol to use in contacting the proxy server, if any - * @var string - */ - var $proxy_protocol = 'http://'; - - /** - * The port for connecting to the proxy server - * - * The default is 8080 for http:// connections - * and 443 for https:// and ssl:// connections. - * - * @var integer - */ - var $proxy_port = 8080; - - /** - * A user name for accessing the proxy server - * @var string - */ - var $proxy_user = ''; - - /** - * A password for accessing the proxy server - * @var string - */ - var $proxy_pass = ''; - - /** - * The error number, if any - * @var integer - */ - var $errno = 0; - - /** - * The error message, if any - * @var string - */ - var $errstr = ''; - - /** - * The current debug mode (1 = on, 0 = off) - * @var integer - */ - var $debug = 0; - - /** - * The HTTP headers for the current request. - * @var string - */ - var $headers = ''; - - - /** - * Sets the object's properties - * - * @param string $path the path and name of the RPC server script - * you want the request to go to - * @param string $server the URL of the remote server to connect to. - * If this parameter doesn't specify a - * protocol and $port is 443, ssl:// is - * assumed. - * @param integer $port a port for connecting to the remote server. - * Defaults to 80 for http:// connections and - * 443 for https:// and ssl:// connections. - * @param string $proxy the URL of the proxy server to use, if any. - * If this parameter doesn't specify a - * protocol and $port is 443, ssl:// is - * assumed. - * @param integer $proxy_port a port for connecting to the remote server. - * Defaults to 8080 for http:// connections and - * 443 for https:// and ssl:// connections. - * @param string $proxy_user a user name for accessing the proxy server - * @param string $proxy_pass a password for accessing the proxy server - * - * @return void - */ - function XML_RPC_Client($path, $server, $port = 0, - $proxy = '', $proxy_port = 0, - $proxy_user = '', $proxy_pass = '') - { - $this->path = $path; - $this->proxy_user = $proxy_user; - $this->proxy_pass = $proxy_pass; - - preg_match('@^(http://|https://|ssl://)?(.*)$@', $server, $match); - if ($match[1] == '') { - if ($port == 443) { - $this->server = $match[2]; - $this->protocol = 'ssl://'; - $this->port = 443; - } else { - $this->server = $match[2]; - if ($port) { - $this->port = $port; - } - } - } elseif ($match[1] == 'http://') { - $this->server = $match[2]; - if ($port) { - $this->port = $port; - } - } else { - $this->server = $match[2]; - $this->protocol = 'ssl://'; - if ($port) { - $this->port = $port; - } else { - $this->port = 443; - } - } - - if ($proxy) { - preg_match('@^(http://|https://|ssl://)?(.*)$@', $proxy, $match); - if ($match[1] == '') { - if ($proxy_port == 443) { - $this->proxy = $match[2]; - $this->proxy_protocol = 'ssl://'; - $this->proxy_port = 443; - } else { - $this->proxy = $match[2]; - if ($proxy_port) { - $this->proxy_port = $proxy_port; - } - } - } elseif ($match[1] == 'http://') { - $this->proxy = $match[2]; - if ($proxy_port) { - $this->proxy_port = $proxy_port; - } - } else { - $this->proxy = $match[2]; - $this->proxy_protocol = 'ssl://'; - if ($proxy_port) { - $this->proxy_port = $proxy_port; - } else { - $this->proxy_port = 443; - } - } - } - } - - /** - * Change the current debug mode - * - * @param int $in where 1 = on, 0 = off - * - * @return void - */ - function setDebug($in) - { - if ($in) { - $this->debug = 1; - } else { - $this->debug = 0; - } - } - - /** - * Set username and password properties for connecting to the RPC server - * - * @param string $u the user name - * @param string $p the password - * - * @return void - * - * @see XML_RPC_Client::$username, XML_RPC_Client::$password - */ - function setCredentials($u, $p) - { - $this->username = $u; - $this->password = $p; - } - - /** - * Transmit the RPC request via HTTP 1.0 protocol - * - * @param object $msg the XML_RPC_Message object - * @param int $timeout how many seconds to wait for the request - * - * @return object an XML_RPC_Response object. 0 is returned if any - * problems happen. - * - * @see XML_RPC_Message, XML_RPC_Client::XML_RPC_Client(), - * XML_RPC_Client::setCredentials() - */ - function send($msg, $timeout = 0) - { - if (strtolower(get_class($msg)) != 'xml_rpc_message') { - $this->errstr = 'send()\'s $msg parameter must be an' - . ' XML_RPC_Message object.'; - $this->raiseError($this->errstr, XML_RPC_ERROR_PROGRAMMING); - return 0; - } - $msg->debug = $this->debug; - return $this->sendPayloadHTTP10($msg, $this->server, $this->port, - $timeout, $this->username, - $this->password); - } - - /** - * Transmit the RPC request via HTTP 1.0 protocol - * - * Requests should be sent using XML_RPC_Client send() rather than - * calling this method directly. - * - * @param object $msg the XML_RPC_Message object - * @param string $server the server to send the request to - * @param int $port the server port send the request to - * @param int $timeout how many seconds to wait for the request - * before giving up - * @param string $username a user name for accessing the RPC server - * @param string $password a password for accessing the RPC server - * - * @return object an XML_RPC_Response object. 0 is returned if any - * problems happen. - * - * @access protected - * @see XML_RPC_Client::send() - */ - function sendPayloadHTTP10($msg, $server, $port, $timeout = 0, - $username = '', $password = '') - { - /* - * If we're using a proxy open a socket to the proxy server - * instead to the xml-rpc server - */ - if ($this->proxy) { - if ($this->proxy_protocol == 'http://') { - $protocol = ''; - } else { - $protocol = $this->proxy_protocol; - } - if ($timeout > 0) { - $fp = @fsockopen($protocol . $this->proxy, $this->proxy_port, - $this->errno, $this->errstr, $timeout); - } else { - $fp = @fsockopen($protocol . $this->proxy, $this->proxy_port, - $this->errno, $this->errstr); - } - } else { - if ($this->protocol == 'http://') { - $protocol = ''; - } else { - $protocol = $this->protocol; - } - if ($timeout > 0) { - $fp = @fsockopen($protocol . $server, $port, - $this->errno, $this->errstr, $timeout); - } else { - $fp = @fsockopen($protocol . $server, $port, - $this->errno, $this->errstr); - } - } - - /* - * Just raising the error without returning it is strange, - * but keep it here for backwards compatibility. - */ - if (!$fp && $this->proxy) { - $this->raiseError('Connection to proxy server ' - . $this->proxy . ':' . $this->proxy_port - . ' failed. ' . $this->errstr, - XML_RPC_ERROR_CONNECTION_FAILED); - return 0; - } elseif (!$fp) { - $this->raiseError('Connection to RPC server ' - . $server . ':' . $port - . ' failed. ' . $this->errstr, - XML_RPC_ERROR_CONNECTION_FAILED); - return 0; - } - - if ($timeout) { - /* - * Using socket_set_timeout() because stream_set_timeout() - * was introduced in 4.3.0, but we need to support 4.2.0. - */ - socket_set_timeout($fp, $timeout); - } - - // Pre-emptive BC hacks for fools calling sendPayloadHTTP10() directly - if ($username != $this->username) { - $this->setCredentials($username, $password); - } - - // Only create the payload if it was not created previously - if (empty($msg->payload)) { - $msg->createPayload(); - } - $this->createHeaders($msg); - - $op = $this->headers . "\r\n\r\n"; - $op .= $msg->payload; - - if (!fputs($fp, $op, strlen($op))) { - $this->errstr = 'Write error'; - return 0; - } - $resp = $msg->parseResponseFile($fp); - - $meta = socket_get_status($fp); - if ($meta['timed_out']) { - fclose($fp); - $this->errstr = 'RPC server did not send response before timeout.'; - $this->raiseError($this->errstr, XML_RPC_ERROR_CONNECTION_FAILED); - return 0; - } - - fclose($fp); - return $resp; - } - - /** - * Determines the HTTP headers and puts it in the $headers property - * - * @param object $msg the XML_RPC_Message object - * - * @return boolean TRUE if okay, FALSE if the message payload isn't set. - * - * @access protected - */ - function createHeaders($msg) - { - if (empty($msg->payload)) { - return false; - } - if ($this->proxy) { - $this->headers = 'POST ' . $this->protocol . $this->server; - if ($this->proxy_port) { - $this->headers .= ':' . $this->port; - } - } else { - $this->headers = 'POST '; - } - $this->headers .= $this->path. " HTTP/1.0\r\n"; - - $this->headers .= "User-Agent: PEAR XML_RPC\r\n"; - $this->headers .= 'Host: ' . $this->server . "\r\n"; - - if ($this->proxy && $this->proxy_user) { - $this->headers .= 'Proxy-Authorization: Basic ' - . base64_encode("$this->proxy_user:$this->proxy_pass") - . "\r\n"; - } - - // thanks to Grant Rauscher for this - if ($this->username) { - $this->headers .= 'Authorization: Basic ' - . base64_encode("$this->username:$this->password") - . "\r\n"; - } - - $this->headers .= "Content-Type: text/xml\r\n"; - $this->headers .= 'Content-Length: ' . strlen($msg->payload); - return true; - } -} - -/** - * The methods and properties for interpreting responses to XML RPC requests - * - * @category Web Services - * @package XML_RPC - * @author Edd Dumbill - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version Release: 1.4.0 - * @link http://pear.php.net/package/XML_RPC - */ -class XML_RPC_Response extends XML_RPC_Base -{ - var $xv; - var $fn; - var $fs; - var $hdrs; - - /** - * @return void - */ - function XML_RPC_Response($val, $fcode = 0, $fstr = '') - { - if ($fcode != 0) { - $this->fn = $fcode; - $this->fs = htmlspecialchars($fstr); - } else { - $this->xv = $val; - } - } - - /** - * @return int the error code - */ - function faultCode() - { - if (isset($this->fn)) { - return $this->fn; - } else { - return 0; - } - } - - /** - * @return string the error string - */ - function faultString() - { - return $this->fs; - } - - /** - * @return mixed the value - */ - function value() - { - return $this->xv; - } - - /** - * @return string the error message in XML format - */ - function serialize() - { - $rs = "\n"; - if ($this->fn) { - $rs .= " - - - - faultCode - " . $this->fn . " - - - faultString - " . $this->fs . " - - - -"; - } else { - $rs .= "\n\n" . $this->xv->serialize() . - "\n"; - } - $rs .= "\n"; - return $rs; - } -} - -/** - * The methods and properties for composing XML RPC messages - * - * @category Web Services - * @package XML_RPC - * @author Edd Dumbill - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version Release: 1.4.0 - * @link http://pear.php.net/package/XML_RPC - */ -class XML_RPC_Message extends XML_RPC_Base -{ - /** - * The current debug mode (1 = on, 0 = off) - * @var integer - */ - var $debug = 0; - - /** - * The encoding to be used for outgoing messages - * - * Defaults to the value of $GLOBALS['XML_RPC_defencoding'] - * - * @var string - * @see XML_RPC_Message::setSendEncoding(), - * $GLOBALS['XML_RPC_defencoding'], XML_RPC_Message::xml_header() - */ - var $send_encoding = ''; - - /** - * The method presently being evaluated - * @var string - */ - var $methodname = ''; - - /** - * @var array - */ - var $params = array(); - - /** - * The XML message being generated - * @var string - */ - var $payload = ''; - - /** - * @return void - */ - function XML_RPC_Message($meth, $pars = 0) - { - $this->methodname = $meth; - if (is_array($pars) && sizeof($pars) > 0) { - for ($i = 0; $i < sizeof($pars); $i++) { - $this->addParam($pars[$i]); - } - } - } - - /** - * Produces the XML declaration including the encoding attribute - * - * The encoding is determined by this class' $send_encoding - * property. If the $send_encoding property is not set, use - * $GLOBALS['XML_RPC_defencoding']. - * - * @return string the XML declaration and element - * - * @see XML_RPC_Message::setSendEncoding(), - * XML_RPC_Message::$send_encoding, $GLOBALS['XML_RPC_defencoding'] - */ - function xml_header() - { - global $XML_RPC_defencoding; - if (!$this->send_encoding) { - $this->send_encoding = $XML_RPC_defencoding; - } - return 'send_encoding . '"?>' - . "\n\n"; - } - - /** - * @return string the closing tag - */ - function xml_footer() - { - return "\n"; - } - - /** - * @return void - * - * @uses XML_RPC_Message::xml_header(), XML_RPC_Message::xml_footer() - */ - function createPayload() - { - $this->payload = $this->xml_header(); - $this->payload .= '' . $this->methodname . "\n"; - $this->payload .= "\n"; - for ($i = 0; $i < sizeof($this->params); $i++) { - $p = $this->params[$i]; - $this->payload .= "\n" . $p->serialize() . "\n"; - } - $this->payload .= "\n"; - $this->payload .= $this->xml_footer(); - $this->payload = ereg_replace("[\r\n]+", "\r\n", $this->payload); - } - - /** - * @return string the name of the method - */ - function method($meth = '') - { - if ($meth != '') { - $this->methodname = $meth; - } - return $this->methodname; - } - - /** - * @return string the payload - */ - function serialize() - { - $this->createPayload(); - return $this->payload; - } - - /** - * @return void - */ - function addParam($par) - { - $this->params[] = $par; - } - - /** - * Obtains an XML_RPC_Value object for the given parameter - * - * @param int $i the index number of the parameter to obtain - * - * @return object the XML_RPC_Value object. - * If the parameter doesn't exist, an XML_RPC_Response object. - * - * @since Returns XML_RPC_Response object on error since Release 1.3.0 - */ - function getParam($i) - { - global $XML_RPC_err, $XML_RPC_str; - - if (isset($this->params[$i])) { - return $this->params[$i]; - } else { - $this->raiseError('The submitted request did not contain this parameter', - XML_RPC_ERROR_INCORRECT_PARAMS); - return new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'], - $XML_RPC_str['incorrect_params']); - } - } - - /** - * @return int the number of parameters - */ - function getNumParams() - { - return sizeof($this->params); - } - - /** - * Sets the XML declaration's encoding attribute - * - * @param string $type the encoding type (ISO-8859-1, UTF-8 or US-ASCII) - * - * @return void - * - * @see XML_RPC_Message::$send_encoding, XML_RPC_Message::xml_header() - * @since Method available since Release 1.2.0 - */ - function setSendEncoding($type) - { - $this->send_encoding = $type; - } - - /** - * Determine the XML's encoding via the encoding attribute - * in the XML declaration - * - * If the encoding parameter is not set or is not ISO-8859-1, UTF-8 - * or US-ASCII, $XML_RPC_defencoding will be returned. - * - * @param string $data the XML that will be parsed - * - * @return string the encoding to be used - * - * @link http://php.net/xml_parser_create - * @since Method available since Release 1.2.0 - */ - function getEncoding($data) - { - global $XML_RPC_defencoding; - - if (preg_match('/<\?xml[^>]*\s*encoding\s*=\s*[\'"]([^"\']*)[\'"]/i', - $data, $match)) - { - $match[1] = trim(strtoupper($match[1])); - switch ($match[1]) { - case 'ISO-8859-1': - case 'UTF-8': - case 'US-ASCII': - return $match[1]; - break; - - default: - return $XML_RPC_defencoding; - } - } else { - return $XML_RPC_defencoding; - } - } - - /** - * @return object a new XML_RPC_Response object - */ - function parseResponseFile($fp) - { - $ipd = ''; - while ($data = @fread($fp, 8192)) { - $ipd .= $data; - } - return $this->parseResponse($ipd); - } - - /** - * @return object a new XML_RPC_Response object - */ - function parseResponse($data = '') - { - global $XML_RPC_xh, $XML_RPC_err, $XML_RPC_str, $XML_RPC_defencoding; - - $encoding = $this->getEncoding($data); - $parser_resource = xml_parser_create($encoding); - $parser = (int) $parser_resource; - - $XML_RPC_xh = array(); - $XML_RPC_xh[$parser] = array(); - - $XML_RPC_xh[$parser]['cm'] = 0; - $XML_RPC_xh[$parser]['isf'] = 0; - $XML_RPC_xh[$parser]['ac'] = ''; - $XML_RPC_xh[$parser]['qt'] = ''; - $XML_RPC_xh[$parser]['stack'] = array(); - $XML_RPC_xh[$parser]['valuestack'] = array(); - - xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true); - xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee'); - xml_set_character_data_handler($parser_resource, 'XML_RPC_cd'); - - $hdrfnd = 0; - if ($this->debug) { - print "\n
---GOT---\n";
-            print isset($_SERVER['SERVER_PROTOCOL']) ? htmlspecialchars($data) : $data;
-            print "\n---END---
\n"; - } - - // See if response is a 200 or a 100 then a 200, else raise error. - // But only do this if we're using the HTTP protocol. - if (ereg('^HTTP', $data) && - !ereg('^HTTP/[0-9\.]+ 200 ', $data) && - !preg_match('@^HTTP/[0-9\.]+ 10[0-9]([A-Za-z ]+)?[\r\n]+HTTP/[0-9\.]+ 200@', $data)) - { - $errstr = substr($data, 0, strpos($data, "\n") - 1); - if(defined("DEBUG") && DEBUG) {error_log('HTTP error, got response: ' . $errstr);} - $r = new XML_RPC_Response(0, $XML_RPC_err['http_error'], - $XML_RPC_str['http_error'] . ' (' . - $errstr . ')'); - xml_parser_free($parser_resource); - return $r; - } - - // gotta get rid of headers here - if (!$hdrfnd && ($brpos = strpos($data,"\r\n\r\n"))) { - $XML_RPC_xh[$parser]['ha'] = substr($data, 0, $brpos); - $data = substr($data, $brpos + 4); - $hdrfnd = 1; - } - - /* - * be tolerant of junk after methodResponse - * (e.g. javascript automatically inserted by free hosts) - * thanks to Luca Mariano - */ - $data = substr($data, 0, strpos($data, "") + 17); - - if (!xml_parse($parser_resource, $data, sizeof($data))) { - // thanks to Peter Kocks - if (xml_get_current_line_number($parser_resource) == 1) { - $errstr = 'XML error at line 1, check URL'; - } else { - $errstr = sprintf('XML error: %s at line %d', - xml_error_string(xml_get_error_code($parser_resource)), - xml_get_current_line_number($parser_resource)); - } - if(defined("DEBUG") && DEBUG) {error_log($errstr);} - $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'], - $XML_RPC_str['invalid_return']); - xml_parser_free($parser_resource); - return $r; - } - - xml_parser_free($parser_resource); - - if ($this->debug) { - print "\n
---PARSED---\n";
-            var_dump($XML_RPC_xh[$parser]['value']);
-            print "---END---
\n"; - } - - if ($XML_RPC_xh[$parser]['isf'] > 1) { - $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'], - $XML_RPC_str['invalid_return'].' '.$XML_RPC_xh[$parser]['isf_reason']); - } elseif (!is_object($XML_RPC_xh[$parser]['value'])) { - // then something odd has happened - // and it's time to generate a client side error - // indicating something odd went on - $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'], - $XML_RPC_str['invalid_return']); - } else { - $v = $XML_RPC_xh[$parser]['value']; - $allOK=1; - if ($XML_RPC_xh[$parser]['isf']) { - $f = $v->structmem('faultCode'); - $fs = $v->structmem('faultString'); - $r = new XML_RPC_Response($v, $f->scalarval(), - $fs->scalarval()); - } else { - $r = new XML_RPC_Response($v); - } - } - $r->hdrs = split("\r?\n", $XML_RPC_xh[$parser]['ha'][1]); - return $r; - } -} - -/** - * The methods and properties that represent data in XML RPC format - * - * @category Web Services - * @package XML_RPC - * @author Edd Dumbill - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version Release: 1.4.0 - * @link http://pear.php.net/package/XML_RPC - */ -class XML_RPC_Value extends XML_RPC_Base -{ - var $me = array(); - var $mytype = 0; - - /** - * @return void - */ - function XML_RPC_Value($val = -1, $type = '') - { - global $XML_RPC_Types; - $this->me = array(); - $this->mytype = 0; - if ($val != -1 || $type != '') { - if ($type == '') { - $type = 'string'; - } - if (!array_key_exists($type, $XML_RPC_Types)) { - // XXX - // need some way to report this error - } elseif ($XML_RPC_Types[$type] == 1) { - $this->addScalar($val, $type); - } elseif ($XML_RPC_Types[$type] == 2) { - $this->addArray($val); - } elseif ($XML_RPC_Types[$type] == 3) { - $this->addStruct($val); - } - } - } - - /** - * @return int returns 1 if successful or 0 if there are problems - */ - function addScalar($val, $type = 'string') - { - global $XML_RPC_Types, $XML_RPC_Boolean; - - if ($this->mytype == 1) { - $this->raiseError('Scalar can have only one value', - XML_RPC_ERROR_INVALID_TYPE); - return 0; - } - $typeof = $XML_RPC_Types[$type]; - if ($typeof != 1) { - $this->raiseError("Not a scalar type (${typeof})", - XML_RPC_ERROR_INVALID_TYPE); - return 0; - } - - if ($type == $XML_RPC_Boolean) { - if (strcasecmp($val, 'true') == 0 - || $val == 1 - || ($val == true && strcasecmp($val, 'false'))) - { - $val = 1; - } else { - $val = 0; - } - } - - if ($this->mytype == 2) { - // we're adding to an array here - $ar = $this->me['array']; - $ar[] = new XML_RPC_Value($val, $type); - $this->me['array'] = $ar; - } else { - // a scalar, so set the value and remember we're scalar - $this->me[$type] = $val; - $this->mytype = $typeof; - } - return 1; - } - - /** - * @return int returns 1 if successful or 0 if there are problems - */ - function addArray($vals) - { - global $XML_RPC_Types; - if ($this->mytype != 0) { - $this->raiseError( - 'Already initialized as a [' . $this->kindOf() . ']', - XML_RPC_ERROR_ALREADY_INITIALIZED); - return 0; - } - $this->mytype = $XML_RPC_Types['array']; - $this->me['array'] = $vals; - return 1; - } - - /** - * @return int returns 1 if successful or 0 if there are problems - */ - function addStruct($vals) - { - global $XML_RPC_Types; - if ($this->mytype != 0) { - $this->raiseError( - 'Already initialized as a [' . $this->kindOf() . ']', - XML_RPC_ERROR_ALREADY_INITIALIZED); - return 0; - } - $this->mytype = $XML_RPC_Types['struct']; - $this->me['struct'] = $vals; - return 1; - } - - /** - * @return void - */ - function dump($ar) - { - reset($ar); - foreach ($ar as $key => $val) { - echo "$key => $val
"; - if ($key == 'array') { - foreach ($val as $key2 => $val2) { - echo "-- $key2 => $val2
"; - } - } - } - } - - /** - * @return string the data type of the current value - */ - function kindOf() - { - switch ($this->mytype) { - case 3: - return 'struct'; - - case 2: - return 'array'; - - case 1: - return 'scalar'; - - default: - return 'undef'; - } - } - - /** - * @return string the data in XML format - */ - function serializedata($typ, $val) - { - $rs = ''; - global $XML_RPC_Types, $XML_RPC_Base64, $XML_RPC_String, $XML_RPC_Boolean; - if (!array_key_exists($typ, $XML_RPC_Types)) { - // XXX - // need some way to report this error - return; - } - switch ($XML_RPC_Types[$typ]) { - case 3: - // struct - $rs .= "\n"; - reset($val); - foreach ($val as $key2 => $val2) { - $rs .= "${key2}\n"; - $rs .= $this->serializeval($val2); - $rs .= "\n"; - } - $rs .= ''; - break; - - case 2: - // array - $rs .= "\n\n"; - for ($i = 0; $i < sizeof($val); $i++) { - $rs .= $this->serializeval($val[$i]); - } - $rs .= "\n"; - break; - - case 1: - switch ($typ) { - case $XML_RPC_Base64: - $rs .= "<${typ}>" . base64_encode($val) . ""; - break; - case $XML_RPC_Boolean: - $rs .= "<${typ}>" . ($val ? '1' : '0') . ""; - break; - case $XML_RPC_String: - $rs .= "<${typ}>" . htmlspecialchars($val). ""; - break; - default: - $rs .= "<${typ}>${val}"; - } - } - return $rs; - } - - /** - * @return string the data in XML format - */ - function serialize() - { - return $this->serializeval($this); - } - - /** - * @return string the data in XML format - */ - function serializeval($o) - { - if (!is_object($o) || empty($o->me) || !is_array($o->me)) { - return ''; - } - $ar = $o->me; - reset($ar); - list($typ, $val) = each($ar); - return '' . $this->serializedata($typ, $val) . "\n"; - } - - /** - * @return mixed the contents of the element requested - */ - function structmem($m) - { - return $this->me['struct'][$m]; - } - - /** - * @return void - */ - function structreset() - { - reset($this->me['struct']); - } - - /** - * @return the key/value pair of the struct's current element - */ - function structeach() - { - return each($this->me['struct']); - } - - /** - * @return mixed the current value - */ - function getval() - { - // UNSTABLE - global $XML_RPC_BOOLEAN, $XML_RPC_Base64; - - reset($this->me); - $b = current($this->me); - - // contributed by I Sofer, 2001-03-24 - // add support for nested arrays to scalarval - // i've created a new method here, so as to - // preserve back compatibility - - if (is_array($b)) { - foreach ($b as $id => $cont) { - $b[$id] = $cont->scalarval(); - } - } - - // add support for structures directly encoding php objects - if (is_object($b)) { - $t = get_object_vars($b); - foreach ($t as $id => $cont) { - $t[$id] = $cont->scalarval(); - } - foreach ($t as $id => $cont) { - $b->$id = $cont; - } - } - - // end contrib - return $b; - } - - /** - * @return mixed - */ - function scalarval() - { - global $XML_RPC_Boolean, $XML_RPC_Base64; - reset($this->me); - return current($this->me); - } - - /** - * @return string - */ - function scalartyp() - { - global $XML_RPC_I4, $XML_RPC_Int; - reset($this->me); - $a = key($this->me); - if ($a == $XML_RPC_I4) { - $a = $XML_RPC_Int; - } - return $a; - } - - /** - * @return mixed the struct's current element - */ - function arraymem($m) - { - return $this->me['array'][$m]; - } - - /** - * @return int the number of elements in the array - */ - function arraysize() - { - reset($this->me); - list($a, $b) = each($this->me); - return sizeof($b); - } - - /** - * Determines if the item submitted is an XML_RPC_Value object - * - * @param mixed $val the variable to be evaluated - * - * @return bool TRUE if the item is an XML_RPC_Value object - * - * @static - * @since Method available since Release 1.3.0 - */ - function isValue($val) - { - return (strtolower(get_class($val)) == 'xml_rpc_value'); - } -} - -/** - * Return an ISO8601 encoded string - * - * While timezones ought to be supported, the XML-RPC spec says: - * - * "Don't assume a timezone. It should be specified by the server in its - * documentation what assumptions it makes about timezones." - * - * This routine always assumes localtime unless $utc is set to 1, in which - * case UTC is assumed and an adjustment for locale is made when encoding. - * - * @return string the formatted date - */ -function XML_RPC_iso8601_encode($timet, $utc = 0) -{ - if (!$utc) { - $t = strftime('%Y%m%dT%H:%M:%S', $timet); - } else { - if (function_exists('gmstrftime')) { - // gmstrftime doesn't exist in some versions - // of PHP - $t = gmstrftime('%Y%m%dT%H:%M:%S', $timet); - } else { - $t = strftime('%Y%m%dT%H:%M:%S', $timet - date('Z')); - } - } - return $t; -} - -/** - * Convert a datetime string into a Unix timestamp - * - * While timezones ought to be supported, the XML-RPC spec says: - * - * "Don't assume a timezone. It should be specified by the server in its - * documentation what assumptions it makes about timezones." - * - * This routine always assumes localtime unless $utc is set to 1, in which - * case UTC is assumed and an adjustment for locale is made when encoding. - * - * @return int the unix timestamp of the date submitted - */ -function XML_RPC_iso8601_decode($idate, $utc = 0) -{ - $t = 0; - if (ereg('([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})', $idate, $regs)) { - if ($utc) { - $t = gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); - } else { - $t = mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); - } - } - return $t; -} - -/** - * Converts an XML_RPC_Value object into native PHP types - * - * @param object $XML_RPC_val the XML_RPC_Value object to decode - * - * @return mixed the PHP values - */ -function XML_RPC_decode($XML_RPC_val) -{ - $kind = $XML_RPC_val->kindOf(); - - if ($kind == 'scalar') { - return $XML_RPC_val->scalarval(); - - } elseif ($kind == 'array') { - $size = $XML_RPC_val->arraysize(); - $arr = array(); - for ($i = 0; $i < $size; $i++) { - $arr[] = XML_RPC_decode($XML_RPC_val->arraymem($i)); - } - return $arr; - - } elseif ($kind == 'struct') { - $XML_RPC_val->structreset(); - $arr = array(); - while (list($key, $value) = $XML_RPC_val->structeach()) { - $arr[$key] = XML_RPC_decode($value); - } - return $arr; - } -} - -/** - * Converts native PHP types into an XML_RPC_Value object - * - * @param mixed $php_val the PHP value or variable you want encoded - * - * @return object the XML_RPC_Value object - */ -function XML_RPC_encode($php_val) -{ - global $XML_RPC_Boolean, $XML_RPC_Int, $XML_RPC_Double, $XML_RPC_String, - $XML_RPC_Array, $XML_RPC_Struct; - - $type = gettype($php_val); - $XML_RPC_val = new XML_RPC_Value; - - switch ($type) { - case 'array': - if (empty($php_val)) { - $XML_RPC_val->addArray($php_val); - break; - } - $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1)); - if (empty($tmp)) { - $arr = array(); - foreach ($php_val as $k => $v) { - $arr[$k] = XML_RPC_encode($v); - } - $XML_RPC_val->addArray($arr); - break; - } - // fall though if it's not an enumerated array - - case 'object': - $arr = array(); - foreach ($php_val as $k => $v) { - $arr[$k] = XML_RPC_encode($v); - } - $XML_RPC_val->addStruct($arr); - break; - - case 'integer': - $XML_RPC_val->addScalar($php_val, $XML_RPC_Int); - break; - - case 'double': - $XML_RPC_val->addScalar($php_val, $XML_RPC_Double); - break; - - case 'string': - case 'NULL': - $XML_RPC_val->addScalar($php_val, $XML_RPC_String); - break; - - case 'boolean': - // Add support for encoding/decoding of booleans, since they - // are supported in PHP - // by - $XML_RPC_val->addScalar($php_val, $XML_RPC_Boolean); - break; - - case 'unknown type': - default: - $XML_RPC_val = false; - } - return $XML_RPC_val; -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * c-hanging-comment-ender-p: nil - * End: - */ - -?> diff --git a/3rdparty/XML/RPC/Server.php b/3rdparty/XML/RPC/Server.php deleted file mode 100644 index 5c5c04b1f7..0000000000 --- a/3rdparty/XML/RPC/Server.php +++ /dev/null @@ -1,624 +0,0 @@ - - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version CVS: $Id: Server.php,v 1.29 2005/08/14 20:25:35 danielc Exp $ - * @link http://pear.php.net/package/XML_RPC - */ - - -/** - * Pull in the XML_RPC class - */ -require_once 'XML/RPC.php'; - - -/** - * signature for system.listMethods: return = array, - * parameters = a string or nothing - * @global array $GLOBALS['XML_RPC_Server_listMethods_sig'] - */ -$GLOBALS['XML_RPC_Server_listMethods_sig'] = array( - array($GLOBALS['XML_RPC_Array'], - $GLOBALS['XML_RPC_String'] - ), - array($GLOBALS['XML_RPC_Array']) -); - -/** - * docstring for system.listMethods - * @global string $GLOBALS['XML_RPC_Server_listMethods_doc'] - */ -$GLOBALS['XML_RPC_Server_listMethods_doc'] = 'This method lists all the' - . ' methods that the XML-RPC server knows how to dispatch'; - -/** - * signature for system.methodSignature: return = array, - * parameters = string - * @global array $GLOBALS['XML_RPC_Server_methodSignature_sig'] - */ -$GLOBALS['XML_RPC_Server_methodSignature_sig'] = array( - array($GLOBALS['XML_RPC_Array'], - $GLOBALS['XML_RPC_String'] - ) -); - -/** - * docstring for system.methodSignature - * @global string $GLOBALS['XML_RPC_Server_methodSignature_doc'] - */ -$GLOBALS['XML_RPC_Server_methodSignature_doc'] = 'Returns an array of known' - . ' signatures (an array of arrays) for the method name passed. If' - . ' no signatures are known, returns a none-array (test for type !=' - . ' array to detect missing signature)'; - -/** - * signature for system.methodHelp: return = string, - * parameters = string - * @global array $GLOBALS['XML_RPC_Server_methodHelp_sig'] - */ -$GLOBALS['XML_RPC_Server_methodHelp_sig'] = array( - array($GLOBALS['XML_RPC_String'], - $GLOBALS['XML_RPC_String'] - ) -); - -/** - * docstring for methodHelp - * @global string $GLOBALS['XML_RPC_Server_methodHelp_doc'] - */ -$GLOBALS['XML_RPC_Server_methodHelp_doc'] = 'Returns help text if defined' - . ' for the method passed, otherwise returns an empty string'; - -/** - * dispatch map for the automatically declared XML-RPC methods. - * @global array $GLOBALS['XML_RPC_Server_dmap'] - */ -$GLOBALS['XML_RPC_Server_dmap'] = array( - 'system.listMethods' => array( - 'function' => 'XML_RPC_Server_listMethods', - 'signature' => $GLOBALS['XML_RPC_Server_listMethods_sig'], - 'docstring' => $GLOBALS['XML_RPC_Server_listMethods_doc'] - ), - 'system.methodHelp' => array( - 'function' => 'XML_RPC_Server_methodHelp', - 'signature' => $GLOBALS['XML_RPC_Server_methodHelp_sig'], - 'docstring' => $GLOBALS['XML_RPC_Server_methodHelp_doc'] - ), - 'system.methodSignature' => array( - 'function' => 'XML_RPC_Server_methodSignature', - 'signature' => $GLOBALS['XML_RPC_Server_methodSignature_sig'], - 'docstring' => $GLOBALS['XML_RPC_Server_methodSignature_doc'] - ) -); - -/** - * @global string $GLOBALS['XML_RPC_Server_debuginfo'] - */ -$GLOBALS['XML_RPC_Server_debuginfo'] = ''; - - -/** - * Lists all the methods that the XML-RPC server knows how to dispatch - * - * @return object a new XML_RPC_Response object - */ -function XML_RPC_Server_listMethods($server, $m) -{ - global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap; - - $v = new XML_RPC_Value(); - $outAr = array(); - foreach ($server->dmap as $key => $val) { - $outAr[] = new XML_RPC_Value($key, 'string'); - } - foreach ($XML_RPC_Server_dmap as $key => $val) { - $outAr[] = new XML_RPC_Value($key, 'string'); - } - $v->addArray($outAr); - return new XML_RPC_Response($v); -} - -/** - * Returns an array of known signatures (an array of arrays) - * for the given method - * - * If no signatures are known, returns a none-array - * (test for type != array to detect missing signature) - * - * @return object a new XML_RPC_Response object - */ -function XML_RPC_Server_methodSignature($server, $m) -{ - global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap; - - $methName = $m->getParam(0); - $methName = $methName->scalarval(); - if (strpos($methName, 'system.') === 0) { - $dmap = $XML_RPC_Server_dmap; - $sysCall = 1; - } else { - $dmap = $server->dmap; - $sysCall = 0; - } - // print "\n"; - if (isset($dmap[$methName])) { - if ($dmap[$methName]['signature']) { - $sigs = array(); - $thesigs = $dmap[$methName]['signature']; - for ($i = 0; $i < sizeof($thesigs); $i++) { - $cursig = array(); - $inSig = $thesigs[$i]; - for ($j = 0; $j < sizeof($inSig); $j++) { - $cursig[] = new XML_RPC_Value($inSig[$j], 'string'); - } - $sigs[] = new XML_RPC_Value($cursig, 'array'); - } - $r = new XML_RPC_Response(new XML_RPC_Value($sigs, 'array')); - } else { - $r = new XML_RPC_Response(new XML_RPC_Value('undef', 'string')); - } - } else { - $r = new XML_RPC_Response(0, $XML_RPC_err['introspect_unknown'], - $XML_RPC_str['introspect_unknown']); - } - return $r; -} - -/** - * Returns help text if defined for the method passed, otherwise returns - * an empty string - * - * @return object a new XML_RPC_Response object - */ -function XML_RPC_Server_methodHelp($server, $m) -{ - global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap; - - $methName = $m->getParam(0); - $methName = $methName->scalarval(); - if (strpos($methName, 'system.') === 0) { - $dmap = $XML_RPC_Server_dmap; - $sysCall = 1; - } else { - $dmap = $server->dmap; - $sysCall = 0; - } - - if (isset($dmap[$methName])) { - if ($dmap[$methName]['docstring']) { - $r = new XML_RPC_Response(new XML_RPC_Value($dmap[$methName]['docstring']), - 'string'); - } else { - $r = new XML_RPC_Response(new XML_RPC_Value('', 'string')); - } - } else { - $r = new XML_RPC_Response(0, $XML_RPC_err['introspect_unknown'], - $XML_RPC_str['introspect_unknown']); - } - return $r; -} - -/** - * @return void - */ -function XML_RPC_Server_debugmsg($m) -{ - global $XML_RPC_Server_debuginfo; - $XML_RPC_Server_debuginfo = $XML_RPC_Server_debuginfo . $m . "\n"; -} - - -/** - * A server for receiving and replying to XML RPC requests - * - * - * $server = new XML_RPC_Server( - * array( - * 'isan8' => - * array( - * 'function' => 'is_8', - * 'signature' => - * array( - * array('boolean', 'int'), - * array('boolean', 'int', 'boolean'), - * array('boolean', 'string'), - * array('boolean', 'string', 'boolean'), - * ), - * 'docstring' => 'Is the value an 8?' - * ), - * ), - * 1, - * 0 - * ); - * - * - * @category Web Services - * @package XML_RPC - * @author Edd Dumbill - * @author Stig Bakken - * @author Martin Jansen - * @author Daniel Convissor - * @copyright 1999-2001 Edd Dumbill, 2001-2005 The PHP Group - * @version Release: 1.4.0 - * @link http://pear.php.net/package/XML_RPC - */ -class XML_RPC_Server -{ - /** - * The dispatch map, listing the methods this server provides. - * @var array - */ - var $dmap = array(); - - /** - * The present response's encoding - * @var string - * @see XML_RPC_Message::getEncoding() - */ - var $encoding = ''; - - /** - * Debug mode (0 = off, 1 = on) - * @var integer - */ - var $debug = 0; - - /** - * The response's HTTP headers - * @var string - */ - var $server_headers = ''; - - /** - * The response's XML payload - * @var string - */ - var $server_payload = ''; - - - /** - * Constructor for the XML_RPC_Server class - * - * @param array $dispMap the dispatch map. An associative array - * explaining each function. The keys of the main - * array are the procedure names used by the - * clients. The value is another associative array - * that contains up to three elements: - * + The 'function' element's value is the name - * of the function or method that gets called. - * To define a class' method: 'class::method'. - * + The 'signature' element (optional) is an - * array describing the return values and - * parameters - * + The 'docstring' element (optional) is a - * string describing what the method does - * @param int $serviceNow should the HTTP response be sent now? - * (1 = yes, 0 = no) - * @param int $debug should debug output be displayed? - * (1 = yes, 0 = no) - * - * @return void - */ - function XML_RPC_Server($dispMap, $serviceNow = 1, $debug = 0) - { - global $HTTP_RAW_POST_DATA; - - if ($debug) { - $this->debug = 1; - } else { - $this->debug = 0; - } - - $this->dmap = $dispMap; - - if ($serviceNow) { - $this->service(); - } else { - $this->createServerPayload(); - $this->createServerHeaders(); - } - } - - /** - * @return string the debug information if debug debug mode is on - */ - function serializeDebug() - { - global $XML_RPC_Server_debuginfo, $HTTP_RAW_POST_DATA; - - if ($this->debug) { - XML_RPC_Server_debugmsg('vvv POST DATA RECEIVED BY SERVER vvv' . "\n" - . $HTTP_RAW_POST_DATA - . "\n" . '^^^ END POST DATA ^^^'); - } - - if ($XML_RPC_Server_debuginfo != '') { - return "\n"; - } else { - return ''; - } - } - - /** - * Sends the response - * - * The encoding and content-type are determined by - * XML_RPC_Message::getEncoding() - * - * @return void - * - * @uses XML_RPC_Server::createServerPayload(), - * XML_RPC_Server::createServerHeaders() - */ - function service() - { - if (!$this->server_payload) { - $this->createServerPayload(); - } - if (!$this->server_headers) { - $this->createServerHeaders(); - } - header($this->server_headers); - print $this->server_payload; - } - - /** - * Generates the payload and puts it in the $server_payload property - * - * @return void - * - * @uses XML_RPC_Server::parseRequest(), XML_RPC_Server::$encoding, - * XML_RPC_Response::serialize(), XML_RPC_Server::serializeDebug() - */ - function createServerPayload() - { - $r = $this->parseRequest(); - $this->server_payload = 'encoding . '"?>' . "\n" - . $this->serializeDebug() - . $r->serialize(); - } - - /** - * Determines the HTTP headers and puts them in the $server_headers - * property - * - * @return boolean TRUE if okay, FALSE if $server_payload isn't set. - * - * @uses XML_RPC_Server::createServerPayload(), - * XML_RPC_Server::$server_headers - */ - function createServerHeaders() - { - if (!$this->server_payload) { - return false; - } - $this->server_headers = 'Content-Length: ' - . strlen($this->server_payload) . "\r\n" - . 'Content-Type: text/xml;' - . ' charset=' . $this->encoding; - return true; - } - - /** - * @return array - */ - function verifySignature($in, $sig) - { - for ($i = 0; $i < sizeof($sig); $i++) { - // check each possible signature in turn - $cursig = $sig[$i]; - if (sizeof($cursig) == $in->getNumParams() + 1) { - $itsOK = 1; - for ($n = 0; $n < $in->getNumParams(); $n++) { - $p = $in->getParam($n); - // print "\n"; - if ($p->kindOf() == 'scalar') { - $pt = $p->scalartyp(); - } else { - $pt = $p->kindOf(); - } - // $n+1 as first type of sig is return type - if ($pt != $cursig[$n+1]) { - $itsOK = 0; - $pno = $n+1; - $wanted = $cursig[$n+1]; - $got = $pt; - break; - } - } - if ($itsOK) { - return array(1); - } - } - } - if (isset($wanted)) { - return array(0, "Wanted ${wanted}, got ${got} at param ${pno}"); - } else { - $allowed = array(); - foreach ($sig as $val) { - end($val); - $allowed[] = key($val); - } - $allowed = array_unique($allowed); - $last = count($allowed) - 1; - if ($last > 0) { - $allowed[$last] = 'or ' . $allowed[$last]; - } - return array(0, - 'Signature permits ' . implode(', ', $allowed) - . ' parameters but the request had ' - . $in->getNumParams()); - } - } - - /** - * @return object a new XML_RPC_Response object - * - * @uses XML_RPC_Message::getEncoding(), XML_RPC_Server::$encoding - */ - function parseRequest($data = '') - { - global $XML_RPC_xh, $HTTP_RAW_POST_DATA, - $XML_RPC_err, $XML_RPC_str, $XML_RPC_errxml, - $XML_RPC_defencoding, $XML_RPC_Server_dmap; - - if ($data == '') { - $data = $HTTP_RAW_POST_DATA; - } - - $this->encoding = XML_RPC_Message::getEncoding($data); - $parser_resource = xml_parser_create($this->encoding); - $parser = (int) $parser_resource; - - $XML_RPC_xh[$parser] = array(); - $XML_RPC_xh[$parser]['cm'] = 0; - $XML_RPC_xh[$parser]['isf'] = 0; - $XML_RPC_xh[$parser]['params'] = array(); - $XML_RPC_xh[$parser]['method'] = ''; - $XML_RPC_xh[$parser]['stack'] = array(); - $XML_RPC_xh[$parser]['valuestack'] = array(); - - $plist = ''; - - // decompose incoming XML into request structure - - xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true); - xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee'); - xml_set_character_data_handler($parser_resource, 'XML_RPC_cd'); - if (!xml_parse($parser_resource, $data, 1)) { - // return XML error as a faultCode - $r = new XML_RPC_Response(0, - $XML_RPC_errxml+xml_get_error_code($parser_resource), - sprintf('XML error: %s at line %d', - xml_error_string(xml_get_error_code($parser_resource)), - xml_get_current_line_number($parser_resource))); - xml_parser_free($parser_resource); - } elseif ($XML_RPC_xh[$parser]['isf']>1) { - $r = new XML_RPC_Response(0, - $XML_RPC_err['invalid_request'], - $XML_RPC_str['invalid_request'] - . ': ' - . $XML_RPC_xh[$parser]['isf_reason']); - xml_parser_free($parser_resource); - } else { - xml_parser_free($parser_resource); - $m = new XML_RPC_Message($XML_RPC_xh[$parser]['method']); - // now add parameters in - for ($i = 0; $i < sizeof($XML_RPC_xh[$parser]['params']); $i++) { - // print '\n"; - $plist .= "$i - " . var_export($XML_RPC_xh[$parser]['params'][$i], true) . " \n"; - $m->addParam($XML_RPC_xh[$parser]['params'][$i]); - } - XML_RPC_Server_debugmsg($plist); - - // now to deal with the method - $methName = $XML_RPC_xh[$parser]['method']; - if (strpos($methName, 'system.') === 0) { - $dmap = $XML_RPC_Server_dmap; - $sysCall = 1; - } else { - $dmap = $this->dmap; - $sysCall = 0; - } - - if (isset($dmap[$methName]['function']) - && is_string($dmap[$methName]['function']) - && strpos($dmap[$methName]['function'], '::') !== false) - { - $dmap[$methName]['function'] = - explode('::', $dmap[$methName]['function']); - } - - if (isset($dmap[$methName]['function']) - && is_callable($dmap[$methName]['function'])) - { - // dispatch if exists - if (isset($dmap[$methName]['signature'])) { - $sr = $this->verifySignature($m, - $dmap[$methName]['signature'] ); - } - if (!isset($dmap[$methName]['signature']) || $sr[0]) { - // if no signature or correct signature - if ($sysCall) { - $r = call_user_func($dmap[$methName]['function'], $this, $m); - } else { - $r = call_user_func($dmap[$methName]['function'], $m); - } - if (!is_a($r, 'XML_RPC_Response')) { - $r = new XML_RPC_Response(0, $XML_RPC_err['not_response_object'], - $XML_RPC_str['not_response_object']); - } - } else { - $r = new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'], - $XML_RPC_str['incorrect_params'] - . ': ' . $sr[1]); - } - } else { - // else prepare error response - $r = new XML_RPC_Response(0, $XML_RPC_err['unknown_method'], - $XML_RPC_str['unknown_method']); - } - } - return $r; - } - - /** - * Echos back the input packet as a string value - * - * @return void - * - * Useful for debugging. - */ - function echoInput() - { - global $HTTP_RAW_POST_DATA; - - $r = new XML_RPC_Response(0); - $r->xv = new XML_RPC_Value("'Aha said I: '" . $HTTP_RAW_POST_DATA, 'string'); - print $r->serialize(); - } -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * c-hanging-comment-ender-p: nil - * End: - */ - -?> diff --git a/3rdparty/fullcalendar/changelog.txt b/3rdparty/fullcalendar/changelog.txt deleted file mode 100644 index 50d0880fd7..0000000000 --- a/3rdparty/fullcalendar/changelog.txt +++ /dev/null @@ -1,313 +0,0 @@ - -version 1.5.2 (8/21/11) - - correctly process UTC "Z" ISO8601 date strings (issue 750) - -version 1.5.1 (4/9/11) - - more flexible ISO8601 date parsing (issue 814) - - more flexible parsing of UNIX timestamps (issue 826) - - FullCalendar now buildable from source on a Mac (issue 795) - - FullCalendar QA'd in FF4 (issue 883) - - upgraded to jQuery 1.5.2 (which supports IE9) and jQuery UI 1.8.11 - -version 1.5 (3/19/11) - - slicker default styling for buttons - - reworked a lot of the calendar's HTML and accompanying CSS - (solves issues 327 and 395) - - more printer-friendly (fullcalendar-print.css) - - fullcalendar now inherits styles from jquery-ui themes differently. - styles for buttons are distinct from styles for calendar cells. - (solves issue 299) - - can now color events through FullCalendar options and Event-Object properties (issue 117) - THIS IS NOW THE PREFERRED METHOD OF COLORING EVENTS (as opposed to using className and CSS) - - FullCalendar options: - - eventColor (changes both background and border) - - eventBackgroundColor - - eventBorderColor - - eventTextColor - - Event-Object options: - - color (changes both background and border) - - backgroundColor - - borderColor - - textColor - - can now specify an event source as an *object* with a `url` property (json feed) or - an `events` property (function or array) with additional properties that will - be applied to the entire event source: - - color (changes both background and border) - - backgroudColor - - borderColor - - textColor - - className - - editable - - allDayDefault - - ignoreTimezone - - startParam (for a feed) - - endParam (for a feed) - - ANY OF THE JQUERY $.ajax OPTIONS - allows for easily changing from GET to POST and sending additional parameters (issue 386) - allows for easily attaching ajax handlers such as `error` (issue 754) - allows for turning caching on (issue 355) - - Google Calendar feeds are now specified differently: - - specify a simple string of your feed's URL - - specify an *object* with a `url` property of your feed's URL. - you can include any of the new Event-Source options in this object. - - the old `$.fullCalendar.gcalFeed` method still works - - no more IE7 SSL popup (issue 504) - - remove `cacheParam` - use json event source `cache` option instead - - latest jquery/jquery-ui - -version 1.4.11 (2/22/11) - - fixed rerenderEvents bug (issue 790) - - fixed bug with faulty dragging of events from all-day slot in agenda views - - bundled with jquery 1.5 and jquery-ui 1.8.9 - -version 1.4.10 (1/2/11) - - fixed bug with resizing event to different week in 5-day month view (issue 740) - - fixed bug with events not sticking after a removeEvents call (issue 757) - - fixed bug with underlying parseTime method, and other uses of parseInt (issue 688) - -version 1.4.9 (11/16/10) - - new algorithm for vertically stacking events (issue 111) - - resizing an event to a different week (issue 306) - - bug: some events not rendered with consecutive calls to addEventSource (issue 679) - -version 1.4.8 (10/16/10) - - ignoreTimezone option (set to `false` to process UTC offsets in ISO8601 dates) - - bugfixes - - event refetching not being called under certain conditions (issues 417, 554) - - event refetching being called multiple times under certain conditions (issues 586, 616) - - selection cannot be triggered by right mouse button (issue 558) - - agenda view left axis sized incorrectly (issue 465) - - IE js error when calendar is too narrow (issue 517) - - agenda view looks strange when no scrollbars (issue 235) - - improved parsing of ISO8601 dates with UTC offsets - - $.fullCalendar.version - - an internal refactor of the code, for easier future development and modularity - -version 1.4.7 (7/5/10) - - "dropping" external objects onto the calendar - - droppable (boolean, to turn on/off) - - dropAccept (to filter which events the calendar will accept) - - drop (trigger) - - selectable options can now be specified with a View Option Hash - - bugfixes - - dragged & reverted events having wrong time text (issue 406) - - bug rendering events that have an endtime with seconds, but no hours/minutes (issue 477) - - gotoDate date overflow bug (issue 429) - - wrong date reported when clicking on edge of last column in agenda views (412) - - support newlines in event titles - - select/unselect callbacks now passes native js event - -version 1.4.6 (5/31/10) - - "selecting" days or timeslots - - options: selectable, selectHelper, unselectAuto, unselectCancel - - callbacks: select, unselect - - methods: select, unselect - - when dragging an event, the highlighting reflects the duration of the event - - code compressing by Google Closure Compiler - - bundled with jQuery 1.4.2 and jQuery UI 1.8.1 - -version 1.4.5 (2/21/10) - - lazyFetching option, which can force the calendar to fetch events on every view/date change - - scroll state of agenda views are preserved when switching back to view - - bugfixes - - calling methods on an uninitialized fullcalendar throws error - - IE6/7 bug where an entire view becomes invisible (issue 320) - - error when rendering a hidden calendar (in jquery ui tabs for example) in IE (issue 340) - - interconnected bugs related to calendar resizing and scrollbars - - when switching views or clicking prev/next, calendar would "blink" (issue 333) - - liquid-width calendar's events shifted (depending on initial height of browser) (issue 341) - - more robust underlying algorithm for calendar resizing - -version 1.4.4 (2/3/10) - - optimized event rendering in all views (events render in 1/10 the time) - - gotoDate() does not force the calendar to unnecessarily rerender - - render() method now correctly readjusts height - -version 1.4.3 (12/22/09) - - added destroy method - - Google Calendar event pages respect currentTimezone - - caching now handled by jQuery's ajax - - protection from setting aspectRatio to zero - - bugfixes - - parseISO8601 and DST caused certain events to display day before - - button positioning problem in IE6 - - ajax event source removed after recently being added, events still displayed - - event not displayed when end is an empty string - - dynamically setting calendar height when no events have been fetched, throws error - -version 1.4.2 (12/02/09) - - eventAfterRender trigger - - getDate & getView methods - - height & contentHeight options (explicitly sets the pixel height) - - minTime & maxTime options (restricts shown hours in agenda view) - - getters [for all options] and setters [for height, contentHeight, and aspectRatio ONLY! stay tuned..] - - render method now readjusts calendar's size - - bugfixes - - lightbox scripts that use iframes (like fancybox) - - day-of-week classNames were off when firstDay=1 - - guaranteed space on right side of agenda events (even when stacked) - - accepts ISO8601 dates with a space (instead of 'T') - -version 1.4.1 (10/31/09) - - can exclude weekends with new 'weekends' option - - gcal feed 'currentTimezone' option - - bugfixes - - year/month/date option sometimes wouldn't set correctly (depending on current date) - - daylight savings issue caused agenda views to start at 1am (for BST users) - - cleanup of gcal.js code - -version 1.4 (10/19/09) - - agendaWeek and agendaDay views - - added some options for agenda views: - - allDaySlot - - allDayText - - firstHour - - slotMinutes - - defaultEventMinutes - - axisFormat - - modified some existing options/triggers to work with agenda views: - - dragOpacity and timeFormat can now accept a "View Hash" (a new concept) - - dayClick now has an allDay parameter - - eventDrop now has an an allDay parameter - (this will affect those who use revertFunc, adjust parameter list) - - added 'prevYear' and 'nextYear' for buttons in header - - minor change for theme users, ui-state-hover not applied to active/inactive buttons - - added event-color-changing example in docs - - better defaults for right-to-left themed button icons - -version 1.3.2 (10/13/09) - - Bugfixes (please upgrade from 1.3.1!) - - squashed potential infinite loop when addMonths and addDays - is called with an invalid date - - $.fullCalendar.parseDate() now correctly parses IETF format - - when switching views, the 'today' button sticks inactive, fixed - - gotoDate now can accept a single Date argument - - documentation for changes in 1.3.1 and 1.3.2 now on website - -version 1.3.1 (9/30/09) - - Important Bugfixes (please upgrade from 1.3!) - - When current date was late in the month, for long months, and prev/next buttons - were clicked in month-view, some months would be skipped/repeated - - In certain time zones, daylight savings time would cause certain days - to be misnumbered in month-view - - Subtle change in way week interval is chosen when switching from month to basicWeek/basicDay view - - Added 'allDayDefault' option - - Added 'changeView' and 'render' methods - -version 1.3 (9/21/09) - - different 'views': month/basicWeek/basicDay - - more flexible 'header' system for buttons - - themable by jQuery UI themes - - resizable events (require jQuery UI resizable plugin) - - rescoped & rewritten CSS, enhanced default look - - cleaner css & rendering techniques for right-to-left - - reworked options & API to support multiple views / be consistent with jQuery UI - - refactoring of entire codebase - - broken into different JS & CSS files, assembled w/ build scripts - - new test suite for new features, uses firebug-lite - - refactored docs - - Options - + date - + defaultView - + aspectRatio - + disableResizing - + monthNames (use instead of $.fullCalendar.monthNames) - + monthNamesShort (use instead of $.fullCalendar.monthAbbrevs) - + dayNames (use instead of $.fullCalendar.dayNames) - + dayNamesShort (use instead of $.fullCalendar.dayAbbrevs) - + theme - + buttonText - + buttonIcons - x draggable -> editable/disableDragging - x fixedWeeks -> weekMode - x abbrevDayHeadings -> columnFormat - x buttons/title -> header - x eventDragOpacity -> dragOpacity - x eventRevertDuration -> dragRevertDuration - x weekStart -> firstDay - x rightToLeft -> isRTL - x showTime (use 'allDay' CalEvent property instead) - - Triggered Actions - + eventResizeStart - + eventResizeStop - + eventResize - x monthDisplay -> viewDisplay - x resize -> windowResize - 'eventDrop' params changed, can revert if ajax cuts out - - CalEvent Properties - x showTime -> allDay - x draggable -> editable - 'end' is now INCLUSIVE when allDay=true - 'url' now produces a real tag, more native clicking/tab behavior - - Methods: - + renderEvent - x prevMonth -> prev - x nextMonth -> next - x prevYear/nextYear -> moveDate - x refresh -> rerenderEvents/refetchEvents - x removeEvent -> removeEvents - x getEventsByID -> clientEvents - - Utilities: - 'formatDate' format string completely changed (inspired by jQuery UI datepicker + datejs) - 'formatDates' added to support date-ranges - - Google Calendar Options: - x draggable -> editable - - Bugfixes - - gcal extension fetched 25 results max, now fetches all - -version 1.2.1 (6/29/09) - - bugfixes - - allows and corrects invalid end dates for events - - doesn't throw an error in IE while rendering when display:none - - fixed 'loading' callback when used w/ multiple addEventSource calls - - gcal className can now be an array - -version 1.2 (5/31/09) - - expanded API - - 'className' CalEvent attribute - - 'source' CalEvent attribute - - dynamically get/add/remove/update events of current month - - locale improvements: change month/day name text - - better date formatting ($.fullCalendar.formatDate) - - multiple 'event sources' allowed - - dynamically add/remove event sources - - options for prevYear and nextYear buttons - - docs have been reworked (include addition of Google Calendar docs) - - changed behavior of parseDate for number strings - (now interpets as unix timestamp, not MS times) - - bugfixes - - rightToLeft month start bug - - off-by-one errors with month formatting commands - - events from previous months sticking when clicking prev/next quickly - - Google Calendar API changed to work w/ multiple event sources - - can also provide 'className' and 'draggable' options - - date utilties moved from $ to $.fullCalendar - - more documentation in source code - - minified version of fullcalendar.js - - test suit (available from svn) - - top buttons now use " ) - .attr( "tabIndex", -1 ) - .attr( "title", "Show All Items" ) - .insertAfter( input ) - .addClass('svg') - .addClass('action') - .addClass('combo-button') - .click(function() { - // close if already visible - if ( input.autocomplete( "widget" ).is( ":visible" ) ) { - input.autocomplete( "close" ); - return; - } - - // work around a bug (likely same cause as #5265) - $( this ).blur(); - - // pass empty string as value to search for, displaying all results - input.autocomplete( "search", "" ); - input.focus(); - });*/ $.each(this.options, function(key, value) { self._setOption(key, value); }); + + if(this.options['showButton']) { + this.button = $( "" ) + .attr( "tabIndex", -1 ) + .attr( "title", "Show All Items" ) + .insertAfter( input ) + .addClass('svg') + .addClass('action') + .addClass('combo-button') + .click(function() { + // close if already visible + if ( input.autocomplete( "widget" ).is( ":visible" ) ) { + input.autocomplete( "close" ); + return; + } + + // work around a bug (likely same cause as #5265) + $( this ).blur(); + + // pass empty string as value to search for, displaying all results + input.autocomplete( "search", "" ); + input.focus(); + }); + } }, destroy: function() { this.input.remove(); - this.button.remove(); + //this.button.remove(); this.element.show(); $.Widget.prototype.destroy.call( this ); }, value: function(val) { - console.log('combobox.value: ' + val); if(val != undefined) { this.input.val(val); } else { @@ -113,35 +119,35 @@ }, _setOption: function( key, value ) { switch( key ) { - case "id": + case 'id': this.options['id'] = value; this.input.attr('id', value); break; - case "name": + case 'name': this.options['name'] = value; this.input.attr('name', value); break; - case "attributes": + case 'attributes': var input = this.input; $.each(this.options['attributes'], function(key, value) { input.attr(key, value); }); break; - case "classes": + case 'classes': var input = this.input; $.each(this.options['classes'], function(key, value) { input.addClass(value); }); break; + case 'editable': + case 'showButton': + this.options[key] = value; + break; } // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget $.Widget.prototype._setOption.apply( this, arguments ); // In jQuery UI 1.9 and above, you use the _super method instead //this._super( "_setOption", key, value ); - }, - options: { - id: null, - name: null - }, + } }); })( jQuery ); diff --git a/apps/contacts/l10n/ar.php b/apps/contacts/l10n/ar.php index 88671f600e..6bebda3b6a 100644 --- a/apps/contacts/l10n/ar.php +++ b/apps/contacts/l10n/ar.php @@ -6,13 +6,13 @@ "Error adding contact property." => "خطء خلال اضافة صفة المعرفه.", "Error adding addressbook." => "خطء خلال اضافة كتاب عناوين.", "Error activating addressbook." => "خطء خلال تفعيل كتاب العناوين.", +"Information about vCard is incorrect. Please reload the page." => "المعلومات الموجودة في ال vCard غير صحيحة. الرجاء إعادة تحديث الصفحة.", "Error deleting contact property." => "خطء خلال محي الصفه.", "Error updating contact property." => "خطء خلال تعديل الصفه.", "Error updating addressbook." => "خطء خلال تعديل كتاب العناوين", "Contacts" => "المعارف", "This is not your addressbook." => "هذا ليس دفتر عناوينك.", "Contact could not be found." => "لم يتم العثور على الشخص.", -"Information about vCard is incorrect. Please reload the page." => "المعلومات الموجودة في ال vCard غير صحيحة. الرجاء إعادة تحديث الصفحة.", "Address" => "عنوان", "Telephone" => "الهاتف", "Email" => "البريد الالكتروني", @@ -26,11 +26,18 @@ "Video" => "الفيديو", "Pager" => "الرنان", "Contact" => "معرفه", -"This is not your contact." => ".هذا ليس من معارفي", "Add Contact" => "أضف شخص ", "Addressbooks" => "كتب العناوين", -"Addressbook" => "كتاب العناوين", -"Name" => "الاسم", +"New Address Book" => "كتاب عناوين جديد", +"CardDav Link" => "وصلة CardDav", +"Download" => "انزال", +"Edit" => "تعديل", +"Delete" => "حذف", +"Download contact" => "انزال المعرفه", +"Delete contact" => "امحي المعرفه", +"Birthday" => "تاريخ الميلاد", +"Preferred" => "مفضل", +"Phone" => "الهاتف", "Type" => "نوع", "PO Box" => "العنوان البريدي", "Extended" => "إضافة", @@ -39,26 +46,13 @@ "Region" => "المنطقة", "Zipcode" => "رقم المنطقة", "Country" => "البلد", -"Create Contact" => "أضف شخص ", -"Choose active Address Books" => "أختر كتاب العناوين الفعال", -"New Address Book" => "كتاب عناوين جديد", -"CardDav Link" => "وصلة CardDav", -"Download" => "انزال", -"Edit" => "تعديل", -"Delete" => "حذف", -"Download contact" => "انزال المعرفه", -"Delete contact" => "امحي المعرفه", "Add" => "أدخل", +"Addressbook" => "كتاب العناوين", "New Addressbook" => "كتاب عناوين جديد", "Edit Addressbook" => "عدل كتاب العناوين", "Displayname" => "الاسم المعروض", "Active" => "فعال", "Save" => "حفظ", "Submit" => "ارسال", -"Cancel" => "الغاء", -"Birthday" => "تاريخ الميلاد", -"Preferred" => "مفضل", -"Phone" => "الهاتف", -"Update" => "تعديل", -"CardDAV syncing address:" => "CardDAV مزامنه العناوين:" +"Cancel" => "الغاء" ); diff --git a/apps/contacts/l10n/ca.php b/apps/contacts/l10n/ca.php index 72338cf40d..950823bc65 100644 --- a/apps/contacts/l10n/ca.php +++ b/apps/contacts/l10n/ca.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Error en afegir la propietat del contacte.", "Error adding addressbook." => "Error en afegir la llibreta d'adreces.", "Error activating addressbook." => "Error en activar la llibreta d'adreces.", +"Information about vCard is incorrect. Please reload the page." => "La informació de la vCard és incorrecta. Carregueu la pàgina de nou.", "Error deleting contact property." => "Error en eliminar la propietat del contacte.", "Error updating contact property." => "Error en actualitzar la propietat del contacte.", "Error updating addressbook." => "Error en actualitzar la llibreta d'adreces.", "Contacts" => "Contactes", "This is not your addressbook." => "Aquesta no és la vostra llibreta d'adreces", "Contact could not be found." => "No s'ha trobat el contacte.", -"Information about vCard is incorrect. Please reload the page." => "La informació de la vCard és incorrecta. Carregueu la pàgina de nou.", "Address" => "Adreça", "Telephone" => "Telèfon", "Email" => "Correu electrònic", @@ -26,39 +26,33 @@ "Video" => "Vídeo", "Pager" => "Paginador", "Contact" => "Contacte", -"This is not your contact." => "Aquest contacte no és vostre.", -"Add Contact" => "Afegir un contacte", +"Add Contact" => "Afegeix un contacte", "Addressbooks" => "Llibretes d'adreces", -"Addressbook" => "Llibreta d'adreces", -"Name" => "Nom", +"New Address Book" => "Nova llibreta d'adreces", +"CardDav Link" => "Enllaç CardDav", +"Download" => "Baixa", +"Edit" => "Edita", +"Delete" => "Suprimeix", +"Download contact" => "Baixa el contacte", +"Delete contact" => "Suprimeix el contacte", +"Birthday" => "Aniversari", +"Preferred" => "Preferit", +"Phone" => "Telèfon", "Type" => "Tipus", -"PO Box" => "Adreça Postal", +"PO Box" => "Adreça postal", "Extended" => "Addicional", "Street" => "Carrer", "City" => "Ciutat", "Region" => "Comarca", "Zipcode" => "Codi postal", "Country" => "País", -"Create Contact" => "Crea un contacte", -"Choose active Address Books" => "Escolliu una llibreda d'adreces activa", -"New Address Book" => "Nova llibreta d'adreces", -"CardDav Link" => "Enllaç CardDav", -"Download" => "Baixa", -"Edit" => "Edita", -"Delete" => "Elimina", -"Download contact" => "Baixa el contacte", -"Delete contact" => "Elimina el contacte", "Add" => "Afegeix", +"Addressbook" => "Llibreta d'adreces", "New Addressbook" => "Nova llibreta d'adreces", "Edit Addressbook" => "Edita la llibreta d'adreces", "Displayname" => "Nom a mostrar", "Active" => "Actiu", "Save" => "Desa", "Submit" => "Envia", -"Cancel" => "Cancel·la", -"Birthday" => "Aniversari", -"Preferred" => "Preferit", -"Phone" => "Telèfon", -"Update" => "Actualitza", -"CardDAV syncing address:" => "Adreça de sincronització CardDAV:" +"Cancel" => "Cancel·la" ); diff --git a/apps/contacts/l10n/cs_CZ.php b/apps/contacts/l10n/cs_CZ.php index 0831a91abc..c3bcd5cea3 100644 --- a/apps/contacts/l10n/cs_CZ.php +++ b/apps/contacts/l10n/cs_CZ.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Chyba během přdávání údaje kontaktu.", "Error adding addressbook." => "Chyba při přidávání adresáře.", "Error activating addressbook." => "Chyba při aktivaci adresáře.", +"Information about vCard is incorrect. Please reload the page." => "Informace o vCard je nesprávná. Obnovte stránku, prosím.", "Error deleting contact property." => "Chyba při odstraňování údaje kontaktu.", "Error updating contact property." => "Chyba při aktualizaci údaje kontaktu.", "Error updating addressbook." => "Chyba při aktualizaci adresáře.", "Contacts" => "Kontakty", "This is not your addressbook." => "Toto není Váš adresář.", "Contact could not be found." => "Kontakt nebyl nalezen.", -"Information about vCard is incorrect. Please reload the page." => "Informace o vCard je nesprávná. Obnovte stránku, prosím.", "Address" => "Adresa", "Telephone" => "Telefon", "Email" => "Email", @@ -26,11 +26,18 @@ "Video" => "Video", "Pager" => "Pager", "Contact" => "Kontakt", -"This is not your contact." => "Toto není Váš kontakt.", "Add Contact" => "Přidat kontakt", "Addressbooks" => "Adresáře", -"Addressbook" => "Adresář", -"Name" => "Jméno", +"New Address Book" => "Nový adresář", +"CardDav Link" => "CardDav odkaz", +"Download" => "Stažení", +"Edit" => "Editovat", +"Delete" => "Odstranit", +"Download contact" => "Stáhnout kontakt", +"Delete contact" => "Odstranit kontakt", +"Birthday" => "Narozeniny", +"Preferred" => "Preferovaný", +"Phone" => "Telefon", "Type" => "Typ", "PO Box" => "PO box", "Extended" => "Rozšířené", @@ -39,26 +46,13 @@ "Region" => "Kraj", "Zipcode" => "PSČ", "Country" => "Země", -"Create Contact" => "Vytvořit kontakt", -"Choose active Address Books" => "Zvolte aktivní adresář", -"New Address Book" => "Nový adresář", -"CardDav Link" => "Odkaz CardDav", -"Download" => "Stažení", -"Edit" => "Editovat", -"Delete" => "Odstranit", -"Download contact" => "Stáhnout kontakt", -"Delete contact" => "Odstranit kontakt", "Add" => "Přidat", +"Addressbook" => "Adresář", "New Addressbook" => "Nový adresář", "Edit Addressbook" => "Editace adresáře", "Displayname" => "Zobrazené jméno", "Active" => "Aktivní", "Save" => "Uložit", "Submit" => "Potvrdit", -"Cancel" => "Storno", -"Birthday" => "Narozeniny", -"Preferred" => "Preferovaný", -"Phone" => "Telefon", -"Update" => "Aktualizovat", -"CardDAV syncing address:" => "CardDAV synchronizuje adresu:" +"Cancel" => "Storno" ); diff --git a/apps/contacts/l10n/da.php b/apps/contacts/l10n/da.php index a0a3760d01..9e82d1967a 100644 --- a/apps/contacts/l10n/da.php +++ b/apps/contacts/l10n/da.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Fejl ved tilføjelse af egenskab.", "Error adding addressbook." => "Fejl ved tilføjelse af adressebog.", "Error activating addressbook." => "Fejl ved aktivering af adressebog.", +"Information about vCard is incorrect. Please reload the page." => "Informationen om vCard er forkert. Genindlæs siden.", "Error deleting contact property." => "Fejl ved sletning af egenskab for kontaktperson.", "Error updating contact property." => "Fejl ved opdatering af egenskab for kontaktperson.", "Error updating addressbook." => "Fejl ved opdatering af adressebog", "Contacts" => "Kontaktpersoner", "This is not your addressbook." => "Dette er ikke din adressebog.", "Contact could not be found." => "Kontaktperson kunne ikke findes.", -"Information about vCard is incorrect. Please reload the page." => "Informationen om vCard er forkert. Genindlæs siden.", "Address" => "Adresse", "Telephone" => "Telefon", "Email" => "Email", @@ -26,11 +26,18 @@ "Video" => "Video", "Pager" => "Personsøger", "Contact" => "Kontaktperson", -"This is not your contact." => "Dette er ikke din kontaktperson.", "Add Contact" => "Tilføj kontaktperson", "Addressbooks" => "Adressebøger", -"Addressbook" => "Adressebog", -"Name" => "Navn", +"New Address Book" => "Ny adressebog", +"CardDav Link" => "CardDav-link", +"Download" => "Download", +"Edit" => "Rediger", +"Delete" => "Slet", +"Download contact" => "Download kontaktperson", +"Delete contact" => "Slet kontaktperson", +"Birthday" => "Fødselsdag", +"Preferred" => "Foretrukken", +"Phone" => "Telefon", "Type" => "Type", "PO Box" => "Postboks", "Extended" => "Udvidet", @@ -39,26 +46,13 @@ "Region" => "Region", "Zipcode" => "Postnummer", "Country" => "Land", -"Create Contact" => "Ny kontaktperson", -"Choose active Address Books" => "Vælg aktive adressebøger", -"New Address Book" => "Ny adressebog", -"CardDav Link" => "CardDav-link", -"Download" => "Download", -"Edit" => "Rediger", -"Delete" => "Slet", -"Download contact" => "Download kontaktperson", -"Delete contact" => "Slet kontaktperson", "Add" => "Tilføj", +"Addressbook" => "Adressebog", "New Addressbook" => "Ny adressebog", "Edit Addressbook" => "Rediger adressebog", "Displayname" => "Vist navn", "Active" => "Aktiv", "Save" => "Gem", "Submit" => "Gem", -"Cancel" => "Fortryd", -"Birthday" => "Fødselsdag", -"Preferred" => "Foretrukken", -"Phone" => "Telefon", -"Update" => "Opdater", -"CardDAV syncing address:" => "Synkroniseringsadresse for CardDAV:" +"Cancel" => "Fortryd" ); diff --git a/apps/contacts/l10n/de.php b/apps/contacts/l10n/de.php index 278009092c..616fe062dc 100644 --- a/apps/contacts/l10n/de.php +++ b/apps/contacts/l10n/de.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Kontakt ändern fehlgeschlagen", "Error adding addressbook." => "Adressbuch hinzufügen fehlgeschlagen", "Error activating addressbook." => "Adressbuchaktivierung fehlgeschlagen", +"Information about vCard is incorrect. Please reload the page." => "Die Information der vCard ist fehlerhaft. Bitte aktualisiere die Seite.", "Error deleting contact property." => "Kontakteigenschaft löschen fehlgeschlagen", "Error updating contact property." => "Kontakteigenschaft aktualisieren fehlgeschlagen", "Error updating addressbook." => "Adressbuch aktualisieren fehlgeschlagen", "Contacts" => "Kontakte", "This is not your addressbook." => "Dies ist nicht dein Adressbuch.", "Contact could not be found." => "Kontakt konnte nicht gefunden werden.", -"Information about vCard is incorrect. Please reload the page." => "Die Information der vCard ist fehlerhaft. Bitte aktualisiere die Seite.", "Address" => "Adresse", "Telephone" => "Telefon", "Email" => "Email", @@ -26,11 +26,18 @@ "Video" => "Video", "Pager" => "Pager", "Contact" => "Kontakt", -"This is not your contact." => "Dies ist nicht dein Kontakt.", "Add Contact" => "Kontakt hinzufügen", "Addressbooks" => "Adressbücher", -"Addressbook" => "Adressbuch", -"Name" => "Name", +"New Address Book" => "Neues Adressbuch", +"CardDav Link" => "CardDav Link", +"Download" => "Herunterladen", +"Edit" => "Bearbeiten", +"Delete" => "Löschen", +"Download contact" => "Kontakt herunterladen", +"Delete contact" => "Kontakt löschen", +"Birthday" => "Geburtstag", +"Preferred" => "Bevorzugt", +"Phone" => "Telefon", "Type" => "Typ", "PO Box" => "Postfach", "Extended" => "Erweitert", @@ -39,26 +46,13 @@ "Region" => "Region", "Zipcode" => "Postleitzahl", "Country" => "Land", -"Create Contact" => "Kontakt erstellen", -"Choose active Address Books" => "Aktive Adressbücher auswählen", -"New Address Book" => "Neues Adressbuch", -"CardDav Link" => "CardDav Link", -"Download" => "Herunterladen", -"Edit" => "Bearbeiten", -"Delete" => "Löschen", -"Download contact" => "Kontakt herunterladen", -"Delete contact" => "Kontakt löschen", "Add" => "Hinzufügen", +"Addressbook" => "Adressbuch", "New Addressbook" => "Neues Adressbuch", "Edit Addressbook" => "Adressbuch editieren", "Displayname" => "Anzeigename", "Active" => "Aktiv", "Save" => "Speichern", "Submit" => "Eintragen", -"Cancel" => "Abbrechen", -"Birthday" => "Geburtstag", -"Preferred" => "Bevorzugt", -"Phone" => "Telefon", -"Update" => "Aktualisieren", -"CardDAV syncing address:" => "CardDAV Sync-Adresse:" +"Cancel" => "Abbrechen" ); diff --git a/apps/contacts/l10n/el.php b/apps/contacts/l10n/el.php index d58b199b67..3b8a9738bd 100644 --- a/apps/contacts/l10n/el.php +++ b/apps/contacts/l10n/el.php @@ -1,7 +1,18 @@ "Αυτό δεν είναι βιβλίο διευθύνσεων σας.", -"Contact could not be found." => "Η επαφή δεν μπρόρεσε να βρεθεί.", +"Error (de)activating addressbook." => "Σφάλμα (απ)ενεργοποίησης βιβλίου διευθύνσεων", +"There was an error adding the contact." => "Σφάλμα κατά την προσθήκη επαφής.", +"Cannot add empty property." => "Αδύνατη προσθήκη κενής ιδιότητας.", +"At least one of the address fields has to be filled out." => "Πρέπει να συμπληρωθεί τουλάχιστον ένα από τα παιδία διεύθυνσης.", +"Error adding contact property." => "Σφάλμα προσθήκης ιδιότητας επαφής.", +"Error adding addressbook." => "Σφάλμα προσθήκης βιβλίου διευθύνσεων.", +"Error activating addressbook." => "Σφάλμα ενεργοποίησης βιβλίου διευθύνσεων", "Information about vCard is incorrect. Please reload the page." => "Οι πληροφορίες σχετικά με vCard είναι εσφαλμένες. Παρακαλώ επαναφορτώστε τη σελίδα.", +"Error deleting contact property." => "Σφάλμα διαγραφής ιδιότητας επαφής.", +"Error updating contact property." => "Σφάλμα ενημέρωσης ιδιότητας επαφής.", +"Error updating addressbook." => "Σφάλμα ενημέρωσης βιβλίου διευθύνσεων.", +"Contacts" => "Επαφές", +"This is not your addressbook." => "Αυτό δεν είναι το βιβλίο διευθύνσεων σας.", +"Contact could not be found." => "Η επαφή δεν μπόρεσε να βρεθεί.", "Address" => "Διεύθυνση", "Telephone" => "Τηλέφωνο", "Email" => "Email", @@ -10,13 +21,23 @@ "Home" => "Σπίτι", "Mobile" => "Κινητό", "Text" => "Κείμενο", -"Voice" => "Φωνή", +"Voice" => "Ομιλία", "Fax" => "Φαξ", "Video" => "Βίντεο", "Pager" => "Βομβητής", -"This is not your contact." => "Αυτή δεν είναι επαφή σας.", +"Contact" => "Επαφή", "Add Contact" => "Προσθήκη επαφής", -"Name" => "Όνομα", +"Addressbooks" => "Βιβλία διευθύνσεων", +"New Address Book" => "Νέο βιβλίο διευθύνσεων", +"CardDav Link" => "Σύνδεσμος CardDav", +"Download" => "Λήψη", +"Edit" => "Επεξεργασία", +"Delete" => "Διαγραφή", +"Download contact" => "Λήψη επαφής", +"Delete contact" => "Διαγραφή επαφής", +"Birthday" => "Γενέθλια", +"Preferred" => "Προτιμώμενο", +"Phone" => "Τηλέφωνο", "Type" => "Τύπος", "PO Box" => "Ταχ. Θυρίδα", "Extended" => "Εκτεταμένη", @@ -25,22 +46,13 @@ "Region" => "Περιοχή", "Zipcode" => "Τ.Κ.", "Country" => "Χώρα", -"Create Contact" => "Δημιουργία επαφής", -"Choose active Address Books" => "Επιλέξτε τα ενεργά βιβλία διευθύνσεων", -"New Address Book" => "Νέο βιβλίο διευθύνσεων", -"CardDav Link" => "Σύνδεσμος CardDav", -"Download" => "Μεταφόρτωση", -"Edit" => "Επεξεργασία", -"Delete" => "Διαγραφή", -"Delete contact" => "Διαγραφή επαφής", "Add" => "Προσθήκη", +"Addressbook" => "Βιβλίο διευθύνσεων", +"New Addressbook" => "Νέο βιβλίο διευθύνσεων", +"Edit Addressbook" => "Επεξεργασία βιβλίου διευθύνσεων", "Displayname" => "Προβαλόμενο όνομα", "Active" => "Ενεργό", "Save" => "Αποθήκευση", "Submit" => "Καταχώρηση", -"Cancel" => "Ακύρωση", -"Birthday" => "Γενέθλια", -"Preferred" => "Προτιμώμενο", -"Phone" => "Τηλέφωνο", -"Update" => "Ενημέρωση" +"Cancel" => "Ακύρωση" ); diff --git a/apps/contacts/l10n/eo.php b/apps/contacts/l10n/eo.php index 121186499b..d4d9bf8e48 100644 --- a/apps/contacts/l10n/eo.php +++ b/apps/contacts/l10n/eo.php @@ -1,7 +1,18 @@ "Eraro dum (mal)aktivigo de adresaro.", +"There was an error adding the contact." => "Eraro okazis dum aldono de kontakto.", +"Cannot add empty property." => "Ne eblas aldoni malplenan propraĵon.", +"At least one of the address fields has to be filled out." => "Almenaŭ unu el la adreskampoj necesas pleniĝi.", +"Error adding contact property." => "Eraro dum aldono de kontaktopropraĵo.", +"Error adding addressbook." => "Eraro dum aldono de adresaro.", +"Error activating addressbook." => "Eraro dum aktivigo de adresaro.", +"Information about vCard is incorrect. Please reload the page." => "Informo pri vCard estas malĝusta. Bonvolu reŝargi la paĝon.", +"Error deleting contact property." => "Eraro dum forigo de kontaktopropraĵo.", +"Error updating contact property." => "Eraro dum ĝisdatigo de kontaktopropraĵo.", +"Error updating addressbook." => "Eraro dum ĝisdatigo de adresaro.", +"Contacts" => "Kontaktoj", "This is not your addressbook." => "Ĉi tiu ne estas via adresaro.", "Contact could not be found." => "Ne eblis trovi la kontakton.", -"Information about vCard is incorrect. Please reload the page." => "Informo pri vCard estas malĝusta. Bonvolu reŝargi la paĝon.", "Address" => "Adreso", "Telephone" => "Telefono", "Email" => "Retpoŝtadreso", @@ -14,9 +25,20 @@ "Fax" => "Fakso", "Video" => "Videaĵo", "Pager" => "Televokilo", -"This is not your contact." => "Tiu ĉi ne estas via kontakto.", +"Contact" => "Kontakto", "Add Contact" => "Aldoni kontakton", -"Name" => "Nomo", +"Addressbooks" => "Adresaroj", +"New Address Book" => "Nova adresaro", +"CardDav Link" => "CardDav-ligilo", +"Download" => "Elŝuti", +"Edit" => "Redakti", +"Delete" => "Forigi", +"Download contact" => "Elŝuti kontakton", +"Delete contact" => "Forigi kontakton", +"Birthday" => "Naskiĝotago", +"Preferred" => "Preferata", +"Phone" => "Telefono", +"Type" => "Tipo", "PO Box" => "Abonkesto", "Extended" => "Etendita", "Street" => "Strato", @@ -24,9 +46,13 @@ "Region" => "Regiono", "Zipcode" => "Poŝtokodo", "Country" => "Lando", -"Create Contact" => "Krei kontakton", -"Edit" => "Redakti", -"Delete" => "Forigi", -"Birthday" => "Naskiĝotago", -"Phone" => "Telefono" +"Add" => "Aldoni", +"Addressbook" => "Adresaro", +"New Addressbook" => "Nova adresaro", +"Edit Addressbook" => "Redakti adresaron", +"Displayname" => "Montronomo", +"Active" => "Aktiva", +"Save" => "Konservi", +"Submit" => "Sendi", +"Cancel" => "Nuligi" ); diff --git a/apps/contacts/l10n/es.php b/apps/contacts/l10n/es.php index b94c53e90d..f5d2fff617 100644 --- a/apps/contacts/l10n/es.php +++ b/apps/contacts/l10n/es.php @@ -1,18 +1,18 @@ "Error al (des)activar libreta de direcciones.", -"There was an error adding the contact." => "Ocurrió un error al agregar el contacto.", -"Cannot add empty property." => "No se puede agregar una propiedad vacía.", -"At least one of the address fields has to be filled out." => "Al menos uno de los campos de direcciones tiene que ser llenado.", -"Error adding contact property." => "Error al agregar una propiedad del contacto.", -"Error adding addressbook." => "Error al agregar la libreta de direcciones.", +"There was an error adding the contact." => "Se ha producido un error al añadir el contacto.", +"Cannot add empty property." => "No se puede añadir una propiedad vacía.", +"At least one of the address fields has to be filled out." => "Al menos uno de los campos de direcciones se tiene que rellenar.", +"Error adding contact property." => "Error al añadir una propiedad del contacto.", +"Error adding addressbook." => "Error al añadir la libreta de direcciones.", "Error activating addressbook." => "Error al activar la libreta de direcciones.", +"Information about vCard is incorrect. Please reload the page." => "La información sobre el vCard es incorrecta. Por favor vuelve a cargar la página.", "Error deleting contact property." => "Error al borrar una propiedad del contacto.", "Error updating contact property." => "Error al actualizar una propiedad del contacto.", "Error updating addressbook." => "Error al actualizar la libreta de direcciones.", "Contacts" => "Contactos", "This is not your addressbook." => "Esta no es tu agenda de contactos.", -"Contact could not be found." => "No se pudo encontrar el contacto.", -"Information about vCard is incorrect. Please reload the page." => "La información sobre el vCard es incorrecta. Por favor vuelve a cargar la página.", +"Contact could not be found." => "No se ha podido encontrar el contacto.", "Address" => "Dirección", "Telephone" => "Teléfono", "Email" => "Correo electrónico", @@ -23,42 +23,36 @@ "Text" => "Texto", "Voice" => "Voz", "Fax" => "Fax", -"Video" => "Video", +"Video" => "Vídeo", "Pager" => "Localizador", "Contact" => "Contacto", -"This is not your contact." => "Este no es tu contacto.", -"Add Contact" => "Agregar contacto", +"Add Contact" => "Añadir contacto", "Addressbooks" => "Libretas de direcciones", -"Addressbook" => "Libreta de direcciones", -"Name" => "Nombre", +"New Address Book" => "Nueva libreta de direcciones", +"CardDav Link" => "Enlace CardDav", +"Download" => "Descargar", +"Edit" => "Editar", +"Delete" => "Borrar", +"Download contact" => "Descargar contacto", +"Delete contact" => "Eliminar contacto", +"Birthday" => "Cumpleaños", +"Preferred" => "Preferido", +"Phone" => "Teléfono", "Type" => "Tipo", "PO Box" => "Código postal", "Extended" => "Extendido", "Street" => "Calle", "City" => "Ciudad", "Region" => "Región", -"Zipcode" => "Código Postal", +"Zipcode" => "Código postal", "Country" => "País", -"Create Contact" => "Crear contacto", -"Choose active Address Books" => "Elige libreta de direcciones activa", -"New Address Book" => "Nueva libreta de direcciones", -"CardDav Link" => "Link Card Dav", -"Download" => "Descargar", -"Edit" => "Editar", -"Delete" => "Borrar", -"Download contact" => "Descargar contacto", -"Delete contact" => "Eliminar contacto", "Add" => "Añadir", +"Addressbook" => "Libreta de direcciones", "New Addressbook" => "Nueva libreta de direcciones", "Edit Addressbook" => "Editar libreta de direcciones", "Displayname" => "Nombre a mostrar", "Active" => "Activo", "Save" => "Guardar", "Submit" => "Aceptar", -"Cancel" => "Cancelar", -"Birthday" => "Cumpleaños", -"Preferred" => "Preferido", -"Phone" => "Teléfono", -"Update" => "Actualizar", -"CardDAV syncing address:" => "Dirección de sincronización CardDAV:" +"Cancel" => "Cancelar" ); diff --git a/apps/contacts/l10n/et_EE.php b/apps/contacts/l10n/et_EE.php index 5054cbfc44..d2112584ed 100644 --- a/apps/contacts/l10n/et_EE.php +++ b/apps/contacts/l10n/et_EE.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Viga konktakti korralikul lisamisel.", "Error adding addressbook." => "Viga aadressiraamatu lisamisel.", "Error activating addressbook." => "Viga aadressiraamatu aktiveerimisel.", +"Information about vCard is incorrect. Please reload the page." => "Visiitkaardi info pole korrektne. Palun lae leht uuesti.", "Error deleting contact property." => "Viga konktaki korralikul kustutamisel.", "Error updating contact property." => "Viga konktaki korralikul uuendamisel.", "Error updating addressbook." => "Viga aadressiraamatu uuendamisel.", "Contacts" => "Kontaktid", "This is not your addressbook." => "See pole sinu aadressiraamat.", "Contact could not be found." => "Kontakti ei leitud.", -"Information about vCard is incorrect. Please reload the page." => "Visiitkaardi info pole korrektne. Palun lae leht uuesti.", "Address" => "Aadress", "Telephone" => "Telefon", "Email" => "E-post", @@ -26,11 +26,18 @@ "Video" => "Video", "Pager" => "Piipar", "Contact" => "Kontakt", -"This is not your contact." => "See pole sinu kontakt.", "Add Contact" => "Lisa kontakt", "Addressbooks" => "Aadressiraamatud", -"Addressbook" => "Aadressiraamat", -"Name" => "Nimi", +"New Address Book" => "Uus aadressiraamat", +"CardDav Link" => "CardDav link", +"Download" => "Lae alla", +"Edit" => "Muuda", +"Delete" => "Kustuta", +"Download contact" => "Lae kontakt alla", +"Delete contact" => "Kustuta kontakt", +"Birthday" => "Sünnipäev", +"Preferred" => "Eelistatud", +"Phone" => "Telefon", "Type" => "Tüüp", "PO Box" => "Postkontori postkast", "Extended" => "Laiendatud", @@ -39,26 +46,13 @@ "Region" => "Piirkond", "Zipcode" => "Postiindeks", "Country" => "Riik", -"Create Contact" => "Lisa kontakt", -"Choose active Address Books" => "Vali aktiivsed aadressiraamatud", -"New Address Book" => "Uus aadressiraamat", -"CardDav Link" => "CardDav link", -"Download" => "Lae alla", -"Edit" => "Muuda", -"Delete" => "Kustuta", -"Download contact" => "Lae kontakt alla", -"Delete contact" => "Kustuta kontakt", "Add" => "Lisa", +"Addressbook" => "Aadressiraamat", "New Addressbook" => "Uus aadressiraamat", "Edit Addressbook" => "Muuda aadressiraamatut", "Displayname" => "Näidatav nimi", "Active" => "Aktiivne", "Save" => "Salvesta", "Submit" => "Saada", -"Cancel" => "Loobu", -"Birthday" => "Sünnipäev", -"Preferred" => "Eelistatud", -"Phone" => "Telefon", -"Update" => "Uuenda", -"CardDAV syncing address:" => "CardDAV sünkroniseerimise aadress:" +"Cancel" => "Loobu" ); diff --git a/apps/contacts/l10n/eu.php b/apps/contacts/l10n/eu.php index 9b3501270b..fbac3b803d 100644 --- a/apps/contacts/l10n/eu.php +++ b/apps/contacts/l10n/eu.php @@ -1,7 +1,18 @@ "Errore bat egon da helbide-liburua (des)gaitzen", +"There was an error adding the contact." => "Errore bat egon da kontaktua gehitzerakoan", +"Cannot add empty property." => "Ezin da propieta hutsa gehitu.", +"At least one of the address fields has to be filled out." => "Behintzat helbide eremuetako bat bete behar da.", +"Error adding contact property." => "Errorea kontaktu propietatea gehitzean.", +"Error adding addressbook." => "Errore bat egon da helbide liburua gehitzean.", +"Error activating addressbook." => "Errore bat egon da helbide-liburua aktibatzen.", +"Information about vCard is incorrect. Please reload the page." => "vCard-aren inguruko informazioa okerra da. Mesedez birkargatu orrialdea.", +"Error deleting contact property." => "Errorea kontaktu propietatea ezabatzean.", +"Error updating contact property." => "Errorea kontaktu propietatea eguneratzean.", +"Error updating addressbook." => "Errore bat egon da helbide liburua eguneratzen.", +"Contacts" => "Kontaktuak", "This is not your addressbook." => "Hau ez da zure helbide liburua.", "Contact could not be found." => "Ezin izan da kontaktua aurkitu.", -"Information about vCard is incorrect. Please reload the page." => "vCard-aren inguruko informazioa okerra da. Mesedez birkargatu orrialdea.", "Address" => "Helbidea", "Telephone" => "Telefonoa", "Email" => "Eposta", @@ -14,19 +25,34 @@ "Fax" => "Fax-a", "Video" => "Bideoa", "Pager" => "Bilagailua", -"This is not your contact." => "Hau ez da zure kontaktua.", -"Add Contact" => "Gehitu Kontaktua", -"Name" => "Izena", +"Contact" => "Kontaktua", +"Add Contact" => "Gehitu kontaktua", +"Addressbooks" => "Helbide Liburuak", +"New Address Book" => "Helbide-liburu berria", +"CardDav Link" => "CardDav lotura", +"Download" => "Deskargatu", +"Edit" => "Editatu", +"Delete" => "Ezabatu", +"Download contact" => "Deskargatu kontaktua", +"Delete contact" => "Ezabatu kontaktua", +"Birthday" => "Jaioteguna", +"Preferred" => "Hobetsia", +"Phone" => "Telefonoa", +"Type" => "Mota", "PO Box" => "Posta kutxa", "Extended" => "Hedatua", "Street" => "Kalea", "City" => "Hiria", "Region" => "Eskualdea", -"Zipcode" => "Posta Kodea", +"Zipcode" => "Posta kodea", "Country" => "Herrialdea", -"Create Contact" => "Sortu Kontaktua", -"Edit" => "Editatu", -"Delete" => "Ezabatu", -"Birthday" => "Jaioteguna", -"Phone" => "Telefonoa" +"Add" => "Gehitu", +"Addressbook" => "Helbide-liburua", +"New Addressbook" => "Helbide-liburu berria", +"Edit Addressbook" => "Editatu helbide-liburua", +"Displayname" => "Bistaratzeko izena", +"Active" => "Aktibo", +"Save" => "Gorde", +"Submit" => "Bidali", +"Cancel" => "Ezeztatu" ); diff --git a/apps/contacts/l10n/fr.php b/apps/contacts/l10n/fr.php index d14d56d951..864c7a6ea4 100644 --- a/apps/contacts/l10n/fr.php +++ b/apps/contacts/l10n/fr.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Erreur lors de l'ajout du champ.", "Error adding addressbook." => "Erreur lors de l'ajout du carnet d'adresses.", "Error activating addressbook." => "Erreur lors de l'activation du carnet d'adresses.", +"Information about vCard is incorrect. Please reload the page." => "Les informations relatives à cette vCard sont incorrectes. Veuillez recharger la page.", "Error deleting contact property." => "Erreur lors de la suppression du champ.", "Error updating contact property." => "Erreur lors de la mise à jour du champ.", "Error updating addressbook." => "Erreur lors de la mise à jour du carnet d'adresses.", "Contacts" => "Contacts", "This is not your addressbook." => "Ce n'est pas votre carnet d'adresses.", "Contact could not be found." => "Ce contact n'a pas été trouvé.", -"Information about vCard is incorrect. Please reload the page." => "Les informations relatives à cette vCard sont incorrectes. Veuillez recharger la page.", "Address" => "Adresse", "Telephone" => "Téléphone", "Email" => "E-mail", @@ -26,11 +26,18 @@ "Video" => "Vidéo", "Pager" => "Bipeur", "Contact" => "Contact", -"This is not your contact." => "Ce n'est pas votre contact.", "Add Contact" => "Ajouter un Contact", "Addressbooks" => "Carnets d'adresses", -"Addressbook" => "Carnet d'adresses", -"Name" => "Nom", +"New Address Book" => "Nouveau Carnet d'adresses", +"CardDav Link" => "Lien CardDav", +"Download" => "Télécharger", +"Edit" => "Modifier", +"Delete" => "Supprimer", +"Download contact" => "Télécharger le contact", +"Delete contact" => "Supprimer le contact", +"Birthday" => "Anniversaire", +"Preferred" => "Préféré", +"Phone" => "Téléphone", "Type" => "Type", "PO Box" => "Boîte postale", "Extended" => "Étendu", @@ -39,26 +46,13 @@ "Region" => "Région", "Zipcode" => "Code postal", "Country" => "Pays", -"Create Contact" => "Créer le Contact", -"Choose active Address Books" => "Choisissez le Carnet d'adresses actif", -"New Address Book" => "Nouveau Carnet d'adresses", -"CardDav Link" => "Lien CardDav", -"Download" => "Télécharger", -"Edit" => "Modifier", -"Delete" => "Supprimer", -"Download contact" => "Télécharger le contact", -"Delete contact" => "Supprimer le contact", "Add" => "Ajouter", +"Addressbook" => "Carnet d'adresses", "New Addressbook" => "Nouveau carnet d'adresses", "Edit Addressbook" => "Éditer le carnet d'adresses", "Displayname" => "Nom", "Active" => "Carnet actif", "Save" => "Sauvegarder", "Submit" => "Envoyer", -"Cancel" => "Annuler", -"Birthday" => "Anniversaire", -"Preferred" => "Préféré", -"Phone" => "Téléphone", -"Update" => "Enregistrer", -"CardDAV syncing address:" => "Adresse de synchronisation CardDAV :" +"Cancel" => "Annuler" ); diff --git a/apps/contacts/l10n/gl.php b/apps/contacts/l10n/gl.php index 8333697fb6..bc3ec7449c 100644 --- a/apps/contacts/l10n/gl.php +++ b/apps/contacts/l10n/gl.php @@ -1,23 +1,23 @@ "Erro (des)activando o directorio", -"There was an error adding the contact." => "Houbo un erro engadindo o contacto", -"Cannot add empty property." => "Non se pode engadir unha propiedade baldeira", -"At least one of the address fields has to be filled out." => "A lo menos un dos campos de dirección ten que ser cuberto", -"Error adding contact property." => "Erro engadindo unha propiedade do contacto.", -"Error adding addressbook." => "Erro engadindo directorio.", -"Error activating addressbook." => "Erro activando o directorio.", -"Error deleting contact property." => "Erro borrando propiedade do contacto.", -"Error updating contact property." => "Erro actualizando a propiedade do contacto.", -"Error updating addressbook." => "Erro actualizando o directorio.", +"Error (de)activating addressbook." => "Produciuse un erro (des)activando a axenda.", +"There was an error adding the contact." => "Produciuse un erro engadindo o contacto.", +"Cannot add empty property." => "Non se pode engadir unha propiedade baleira.", +"At least one of the address fields has to be filled out." => "Polo menos un dos campos do enderezo ten que ser cuberto.", +"Error adding contact property." => "Produciuse un erro engadindo unha propiedade do contacto.", +"Error adding addressbook." => "Produciuse un erro engadindo a axenda.", +"Error activating addressbook." => "Produciuse un erro activando a axenda.", +"Information about vCard is incorrect. Please reload the page." => "A información sobre a vCard é incorrecta. Por favor volva cargar a páxina.", +"Error deleting contact property." => "Produciuse un erro borrando a propiedade do contacto.", +"Error updating contact property." => "Produciuse un erro actualizando a propiedade do contacto.", +"Error updating addressbook." => "Produciuse un erro actualizando a axenda.", "Contacts" => "Contactos", -"This is not your addressbook." => "Este non é o seu directorio", -"Contact could not be found." => "Non se atopou o contacto", -"Information about vCard is incorrect. Please reload the page." => "A información sobre a vCard é incorrecta. Por favor volte a cargar a páxina.", -"Address" => "Dirección", +"This is not your addressbook." => "Esta non é a súa axenda.", +"Contact could not be found." => "Non se atopou o contacto.", +"Address" => "Enderezo", "Telephone" => "Teléfono", "Email" => "Correo electrónico", "Organization" => "Organización", -"Work" => "Emprego", +"Work" => "Traballo", "Home" => "Casa", "Mobile" => "Móbil", "Text" => "Texto", @@ -26,39 +26,33 @@ "Video" => "Vídeo", "Pager" => "Paxinador", "Contact" => "Contacto", -"This is not your contact." => "Este non é un dos seus contactos", "Add Contact" => "Engadir contacto", -"Addressbooks" => "Directorios", -"Addressbook" => "Directorio", -"Name" => "Nome", -"Type" => "Escriba", -"PO Box" => "Apartado de Correos", -"Extended" => "Extendido", -"Street" => "Rúa", -"City" => "Cidade", -"Region" => "Rexión", -"Zipcode" => "Código Postal", -"Country" => "País", -"Create Contact" => "Crear contacto", -"Choose active Address Books" => "Escolla os Directorios Activos", -"New Address Book" => "Novo Directorio", +"Addressbooks" => "Axendas", +"New Address Book" => "Nova axenda", "CardDav Link" => "Ligazón CardDav", -"Download" => "Baixar", +"Download" => "Descargar", "Edit" => "Editar", "Delete" => "Eliminar", "Download contact" => "Descargar contacto", "Delete contact" => "Borrar contacto", +"Birthday" => "Aniversario", +"Preferred" => "Preferido", +"Phone" => "Teléfono", +"Type" => "Escribir", +"PO Box" => "Apartado de correos", +"Extended" => "Ampliado", +"Street" => "Rúa", +"City" => "Cidade", +"Region" => "Autonomía", +"Zipcode" => "Código postal", +"Country" => "País", "Add" => "Engadir", -"New Addressbook" => "Novo Directorio", -"Edit Addressbook" => "Editar Directorio", -"Displayname" => "Mostrar nome", +"Addressbook" => "Axenda", +"New Addressbook" => "Nova axenda", +"Edit Addressbook" => "Editar axenda", +"Displayname" => "Nome a mostrar", "Active" => "Activo", "Save" => "Gardar", "Submit" => "Enviar", -"Cancel" => "Cancelar", -"Birthday" => "Cumpleanos", -"Preferred" => "Preferido", -"Phone" => "Teléfono", -"Update" => "Actualizar", -"CardDAV syncing address:" => "Dirección de sincronización CardDAV:" +"Cancel" => "Cancelar" ); diff --git a/apps/contacts/l10n/he.php b/apps/contacts/l10n/he.php index 5818cab207..28640e0ea4 100644 --- a/apps/contacts/l10n/he.php +++ b/apps/contacts/l10n/he.php @@ -1,7 +1,7 @@ "המידע אודות vCard אינו נכון. נא לטעון מחדש את הדף.", "This is not your addressbook." => "זהו אינו ספר הכתובות שלך", "Contact could not be found." => "לא ניתן לאתר איש קשר", -"Information about vCard is incorrect. Please reload the page." => "המידע אודות vCard אינו נכון. נא לטעון מחדש את הדף.", "Address" => "כתובת", "Telephone" => "טלפון", "Email" => "דואר אלקטרוני", @@ -14,19 +14,16 @@ "Fax" => "פקס", "Video" => "וידאו", "Pager" => "זימונית", -"This is not your contact." => "זהו אינו איש קשר שלך", "Add Contact" => "הוספת איש קשר", -"Name" => "שם", +"Edit" => "עריכה", +"Delete" => "מחיקה", +"Birthday" => "יום הולדת", +"Phone" => "טלפון", "PO Box" => "תא דואר", "Extended" => "מורחב", "Street" => "רחוב", "City" => "עיר", "Region" => "אזור", "Zipcode" => "מיקוד", -"Country" => "מדינה", -"Create Contact" => "יצירת איש קשר", -"Edit" => "עריכה", -"Delete" => "מחיקה", -"Birthday" => "יום הולדת", -"Phone" => "טלפון" +"Country" => "מדינה" ); diff --git a/apps/contacts/l10n/hr.php b/apps/contacts/l10n/hr.php index 4937c0bcb2..9950b512fc 100644 --- a/apps/contacts/l10n/hr.php +++ b/apps/contacts/l10n/hr.php @@ -1,7 +1,7 @@ "Informacija o vCard je neispravna. Osvježite stranicu.", "This is not your addressbook." => "Ovo nije vaš adresar.", "Contact could not be found." => "Kontakt ne postoji.", -"Information about vCard is incorrect. Please reload the page." => "Informacija o vCard je neispravna. Osvježite stranicu.", "Address" => "Adresa", "Telephone" => "Telefon", "Email" => "E-mail", @@ -14,19 +14,16 @@ "Fax" => "Fax", "Video" => "Video", "Pager" => "Pager", -"This is not your contact." => "Ovo nije vaš kontakt.", "Add Contact" => "Dodaj kontakt", -"Name" => "Naziv", +"Edit" => "Uredi", +"Delete" => "Obriši", +"Birthday" => "Rođendan", +"Phone" => "Telefon", "PO Box" => "Poštanski Pretinac", "Extended" => "Prošireno", "Street" => "Ulica", "City" => "Grad", "Region" => "Regija", "Zipcode" => "Poštanski broj", -"Country" => "Država", -"Create Contact" => "Izradi Kontakt", -"Edit" => "Uredi", -"Delete" => "Obriši", -"Birthday" => "Rođendan", -"Phone" => "Telefon" +"Country" => "Država" ); diff --git a/apps/contacts/l10n/hu_HU.php b/apps/contacts/l10n/hu_HU.php index fa81831f96..d9e26ebd7b 100644 --- a/apps/contacts/l10n/hu_HU.php +++ b/apps/contacts/l10n/hu_HU.php @@ -1,7 +1,7 @@ "A vCardról szóló információ helytelen. Töltsd újra az oldalt.", "This is not your addressbook." => "Ez nem a te címjegyzéked.", "Contact could not be found." => "Kapcsolat nem található.", -"Information about vCard is incorrect. Please reload the page." => "A vCardról szóló információ helytelen. Töltsd újra az oldalt.", "Address" => "Cím", "Telephone" => "Telefonszám", "Email" => "E-mail", @@ -14,19 +14,16 @@ "Fax" => "Fax", "Video" => "Video", "Pager" => "Lapozó", -"This is not your contact." => "Nem a te kapcsolatod.", "Add Contact" => "Kontakt hozzáadása", -"Name" => "Név", +"Edit" => "Szerkesztés", +"Delete" => "Törlés", +"Birthday" => "Születésnap", +"Phone" => "Telefonszám", "PO Box" => "Postafiók", "Extended" => "Kiterjesztett", "Street" => "Utca", "City" => "Helység", "Region" => "Megye", "Zipcode" => "Irányítószám", -"Country" => "Ország", -"Create Contact" => "Kontakt létrehozása", -"Edit" => "Szerkesztés", -"Delete" => "Törlés", -"Birthday" => "Születésnap", -"Phone" => "Telefonszám" +"Country" => "Ország" ); diff --git a/apps/contacts/l10n/ia.php b/apps/contacts/l10n/ia.php index 0c1dd6e963..ad1bdf337e 100644 --- a/apps/contacts/l10n/ia.php +++ b/apps/contacts/l10n/ia.php @@ -1,4 +1,5 @@ "Contactos", "This is not your addressbook." => "Iste non es tu libro de adresses", "Contact could not be found." => "Contacto non poterea esser legite", "Address" => "Adresse", @@ -7,23 +8,38 @@ "Organization" => "Organisation", "Work" => "Travalio", "Home" => "Domo", +"Mobile" => "Mobile", "Text" => "Texto", "Voice" => "Voce", "Fax" => "Fax", "Video" => "Video", "Pager" => "Pager", -"This is not your contact." => "Iste non es tu contacto", +"Contact" => "Contacto", "Add Contact" => "Adder contacto", -"Name" => "Nomine", +"Addressbooks" => "Adressarios", +"New Address Book" => "Nove adressario", +"Download" => "Discargar", +"Edit" => "Modificar", +"Delete" => "Deler", +"Download contact" => "Discargar contacto", +"Delete contact" => "Deler contacto", +"Birthday" => "Anniversario", +"Preferred" => "Preferite", +"Phone" => "Phono", +"Type" => "Typo", "PO Box" => "Cassa postal", +"Extended" => "Extendite", "Street" => "Strata", "City" => "Citate", "Region" => "Region", "Zipcode" => "Codice postal", "Country" => "Pais", -"Create Contact" => "Crear contacto", -"Edit" => "Modificar", -"Delete" => "Deler", -"Birthday" => "Anniversario", -"Phone" => "Phono" +"Add" => "Adder", +"Addressbook" => "Adressario", +"New Addressbook" => "Nove adressario", +"Edit Addressbook" => "Modificar adressario", +"Active" => "Active", +"Save" => "Salveguardar", +"Submit" => "Submitter", +"Cancel" => "Cancellar" ); diff --git a/apps/contacts/l10n/it.php b/apps/contacts/l10n/it.php index b7f30bc034..7f3f4c6da2 100644 --- a/apps/contacts/l10n/it.php +++ b/apps/contacts/l10n/it.php @@ -1,18 +1,18 @@ "Errore nel (dis)attivare la rubrica", +"Error (de)activating addressbook." => "Errore nel (dis)attivare la rubrica.", "There was an error adding the contact." => "Si è verificato un errore nell'aggiunta del contatto.", "Cannot add empty property." => "Impossibile aggiungere una proprietà vuota.", -"At least one of the address fields has to be filled out." => "Deve essere riempito almeno un indirizzo .", +"At least one of the address fields has to be filled out." => "Deve essere riempito almeno un indirizzo.", "Error adding contact property." => "Errore durante l'aggiunta della proprietà del contatto.", -"Error adding addressbook." => "Errore nell'aggiunta della rubrica.", -"Error activating addressbook." => "Errore nell'attivazione della rubrica.", -"Error deleting contact property." => "Errore nella cancellazione della proprietà del contatto.", -"Error updating contact property." => "Errore nell'aggiornamento della proprietà del contatto.", -"Error updating addressbook." => "Errore nell'aggiornamento della rubrica.", +"Error adding addressbook." => "Errore durante l'aggiunta della rubrica.", +"Error activating addressbook." => "Errore durante l'attivazione della rubrica.", +"Information about vCard is incorrect. Please reload the page." => "Informazioni sulla vCard non corrette. Ricarica la pagina.", +"Error deleting contact property." => "Errore durante l'eliminazione della proprietà del contatto.", +"Error updating contact property." => "Errore durante l'aggiornamento della proprietà del contatto.", +"Error updating addressbook." => "Errore durante l'aggiornamento della rubrica.", "Contacts" => "Contatti", "This is not your addressbook." => "Questa non è la tua rubrica.", -"Contact could not be found." => "Il contatto non può essere trovato", -"Information about vCard is incorrect. Please reload the page." => "Informazioni sulla vCard incorrette. Ricaricare la pagina.", +"Contact could not be found." => "Il contatto non può essere trovato.", "Address" => "Indirizzo", "Telephone" => "Telefono", "Email" => "Email", @@ -26,39 +26,33 @@ "Video" => "Video", "Pager" => "Cercapersone", "Contact" => "Contatto", -"This is not your contact." => "Questo non è un tuo contatto.", "Add Contact" => "Aggiungi contatto", "Addressbooks" => "Rubriche", -"Addressbook" => "Rubrica", -"Name" => "Nome", +"New Address Book" => "Nuova rubrica", +"CardDav Link" => "Link CardDav", +"Download" => "Scarica", +"Edit" => "Modifica", +"Delete" => "Elimina", +"Download contact" => "Scarica contatto", +"Delete contact" => "Elimina contatto", +"Birthday" => "Compleanno", +"Preferred" => "Preferito", +"Phone" => "Telefono", "Type" => "Tipo", "PO Box" => "Casella postale", -"Extended" => "Estendi", +"Extended" => "Esteso", "Street" => "Via", "City" => "Città", "Region" => "Regione", "Zipcode" => "CAP", "Country" => "Stato", -"Create Contact" => "Crea contatto", -"Choose active Address Books" => "Seleziona le rubriche attive", -"New Address Book" => "Nuova rubrica", -"CardDav Link" => "Link CardDav", -"Download" => "Scarica", -"Edit" => "Modifica", -"Delete" => "Cancella", -"Download contact" => "Scarica contatto", -"Delete contact" => "Cancella contatto", "Add" => "Aggiungi", -"New Addressbook" => "Nuova Rubrica", -"Edit Addressbook" => "Modifica Rubrica", -"Displayname" => "Nome da mostrare", +"Addressbook" => "Rubrica", +"New Addressbook" => "Nuova rubrica", +"Edit Addressbook" => "Modifica rubrica", +"Displayname" => "Nome visualizzato", "Active" => "Attiva", "Save" => "Salva", -"Submit" => "Conferma", -"Cancel" => "Annulla", -"Birthday" => "Compleanno", -"Preferred" => "Preferito", -"Phone" => "Telefono", -"Update" => "Aggiorna", -"CardDAV syncing address:" => "Indirizzo di sincronizzazione CarDAV:" +"Submit" => "Invia", +"Cancel" => "Annulla" ); diff --git a/apps/contacts/l10n/ja_JP.php b/apps/contacts/l10n/ja_JP.php index e8417562b8..8091aff9c8 100644 --- a/apps/contacts/l10n/ja_JP.php +++ b/apps/contacts/l10n/ja_JP.php @@ -6,13 +6,13 @@ "Error adding contact property." => "連絡先の追加に失敗しました。", "Error adding addressbook." => "電話帳の追加に失敗しました。", "Error activating addressbook." => "電話帳の有効化に失敗しました。", +"Information about vCard is incorrect. Please reload the page." => "vCardの情報に誤りがあります。ページをリロードして下さい。", "Error deleting contact property." => "連絡先の削除に失敗しました。", "Error updating contact property." => "連絡先の更新に失敗しました。", "Error updating addressbook." => "電話帳の更新に失敗しました。", "Contacts" => "連絡先", "This is not your addressbook." => "これはあなたの電話帳ではありません。", "Contact could not be found." => "連絡先を見つける事ができません。", -"Information about vCard is incorrect. Please reload the page." => "vCardの情報に誤りがあります。ページをリロードして下さい。", "Address" => "住所", "Telephone" => "電話番号", "Email" => "メールアドレス", @@ -26,11 +26,18 @@ "Video" => "テレビ電話", "Pager" => "ポケベル", "Contact" => "連絡先", -"This is not your contact." => "あなたの連絡先ではありません。", "Add Contact" => "連絡先の追加", "Addressbooks" => "電話帳", -"Addressbook" => "電話帳", -"Name" => "氏名", +"New Address Book" => "新規電話帳", +"CardDav Link" => "CardDAV リンク", +"Download" => "ダウンロード", +"Edit" => "編集", +"Delete" => "削除", +"Download contact" => "連絡先のダウンロード", +"Delete contact" => "連絡先の削除", +"Birthday" => "生年月日", +"Preferred" => "推奨", +"Phone" => "電話番号", "Type" => "種類", "PO Box" => "私書箱", "Extended" => "拡張番地", @@ -39,26 +46,13 @@ "Region" => "地域", "Zipcode" => "郵便番号", "Country" => "国名", -"Create Contact" => "追加", -"Choose active Address Books" => "有効な電話帳の選択", -"New Address Book" => "新規電話帳", -"CardDav Link" => "CardDAV リンク", -"Download" => "ダウンロード", -"Edit" => "編集", -"Delete" => "削除", -"Download contact" => "連絡先のダウンロード", -"Delete contact" => "連絡先の削除", "Add" => "追加", +"Addressbook" => "電話帳", "New Addressbook" => "電話帳の新規作成", "Edit Addressbook" => "電話帳の編集", "Displayname" => "表示名", "Active" => "アクティブ", "Save" => "保存", "Submit" => "送信", -"Cancel" => "取り消し", -"Birthday" => "生年月日", -"Preferred" => "推奨", -"Phone" => "電話番号", -"Update" => "更新", -"CardDAV syncing address:" => "CardDAV 同期アドレス:" +"Cancel" => "取り消し" ); diff --git a/apps/contacts/l10n/ko.php b/apps/contacts/l10n/ko.php new file mode 100644 index 0000000000..f17c20678e --- /dev/null +++ b/apps/contacts/l10n/ko.php @@ -0,0 +1,58 @@ + "주소록을 (비)활성화하는 데 실패했습니다.", +"There was an error adding the contact." => "연락처를 추가하는 중 오류가 발생하였습니다.", +"Cannot add empty property." => "빈 속성을 추가할 수 없습니다.", +"At least one of the address fields has to be filled out." => "최소한 하나의 주소록 항목을 입력해야 합니다.", +"Error adding contact property." => "연락처 속성을 추가할 수 없습니다.", +"Error adding addressbook." => "주소록을 추가할 수 없습니다.", +"Error activating addressbook." => "주소록을 활성화할 수 없습니다.", +"Information about vCard is incorrect. Please reload the page." => "vCard 정보가 올바르지 않습니다. 페이지를 새로 고치십시오.", +"Error deleting contact property." => "연락처 속성을 삭제할 수 없습니다.", +"Error updating contact property." => "연락처 속성을 업데이트할 수 없습니다.", +"Error updating addressbook." => "주소록을 업데이트할 수 없습니다.", +"Contacts" => "연락처", +"This is not your addressbook." => "내 주소록이 아닙니다.", +"Contact could not be found." => "연락처를 찾을 수 없습니다.", +"Address" => "주소", +"Telephone" => "전화 번호", +"Email" => "전자 우편", +"Organization" => "조직", +"Work" => "직장", +"Home" => "자택", +"Mobile" => "휴대폰", +"Text" => "문자 번호", +"Voice" => "음성 번호", +"Fax" => "팩스 번호", +"Video" => "영상 번호", +"Pager" => "호출기", +"Contact" => "연락처", +"Add Contact" => "연락처 추가", +"Addressbooks" => "주소록", +"New Address Book" => "새 주소록", +"CardDav Link" => "CardDav 링크", +"Download" => "다운로드", +"Edit" => "편집", +"Delete" => "삭제", +"Download contact" => "연락처 다운로드", +"Delete contact" => "연락처 삭제", +"Birthday" => "생일", +"Preferred" => "선호함", +"Phone" => "전화 번호", +"Type" => "종류", +"PO Box" => "사서함", +"Extended" => "확장", +"Street" => "거리", +"City" => "도시", +"Region" => "지역", +"Zipcode" => "우편 번호", +"Country" => "국가", +"Add" => "추가", +"Addressbook" => "주소록", +"New Addressbook" => "새 주소록", +"Edit Addressbook" => "주소록 편집", +"Displayname" => "표시 이름", +"Active" => "활성", +"Save" => "저장", +"Submit" => "보내기", +"Cancel" => "취소" +); diff --git a/apps/contacts/l10n/lb.php b/apps/contacts/l10n/lb.php index e8f17a6fa5..82b84b09d7 100644 --- a/apps/contacts/l10n/lb.php +++ b/apps/contacts/l10n/lb.php @@ -1,7 +1,7 @@ "Informatioun iwwert vCard ass net richteg. Lued d'Säit wegl nei.", "This is not your addressbook." => "Dat do ass net däin Adressbuch.", "Contact could not be found." => "Konnt den Kontakt net fannen.", -"Information about vCard is incorrect. Please reload the page." => "Informatioun iwwert vCard ass net richteg. Lued d'Säit wegl nei.", "Address" => "Adress", "Telephone" => "Telefon's Nummer", "Email" => "Email", @@ -14,19 +14,16 @@ "Fax" => "Fax", "Video" => "Video", "Pager" => "Pager", -"This is not your contact." => "Dat do ass net däin Kontakt.", "Add Contact" => "Kontakt bäisetzen", -"Name" => "Numm", +"Edit" => "Editéieren", +"Delete" => "Läschen", +"Birthday" => "Gebuertsdag", +"Phone" => "Telefon", "PO Box" => "Postleetzuel", "Extended" => "Erweidert", "Street" => "Strooss", "City" => "Staat", "Region" => "Regioun", "Zipcode" => "Postleetzuel", -"Country" => "Land", -"Create Contact" => "Kontakt erstellen", -"Edit" => "Editéieren", -"Delete" => "Läschen", -"Birthday" => "Gebuertsdag", -"Phone" => "Telefon" +"Country" => "Land" ); diff --git a/apps/contacts/l10n/lt_LT.php b/apps/contacts/l10n/lt_LT.php index 8dc61e3621..435927a247 100644 --- a/apps/contacts/l10n/lt_LT.php +++ b/apps/contacts/l10n/lt_LT.php @@ -1,7 +1,12 @@ "Klaida (de)aktyvuojant adresų knygą.", +"There was an error adding the contact." => "Pridedant kontaktą įvyko klaida.", +"Error adding addressbook." => "Klaida pridedant adresų knygą.", +"Error activating addressbook." => "Klaida aktyvuojant adresų knygą.", +"Information about vCard is incorrect. Please reload the page." => "Informacija apie vCard yra neteisinga. ", +"Contacts" => "Kontaktai", "This is not your addressbook." => "Tai ne jūsų adresų knygelė.", "Contact could not be found." => "Kontaktas nerastas", -"Information about vCard is incorrect. Please reload the page." => "Informacija apie vCard yra neteisinga. ", "Address" => "Adresas", "Telephone" => "Telefonas", "Email" => "El. paštas", @@ -9,23 +14,36 @@ "Work" => "Darbo", "Home" => "Namų", "Mobile" => "Mobilusis", -"Text" => "Tekstas", +"Text" => "Žinučių", "Voice" => "Balso", "Fax" => "Faksas", "Video" => "Vaizdo", "Pager" => "Pranešimų gaviklis", -"This is not your contact." => "Tai ne jūsų kontaktas", +"Contact" => "Kontaktas", "Add Contact" => "Pridėti kontaktą", -"Name" => "Vardas", +"Addressbooks" => "Adresų knygos", +"New Address Book" => "Nauja adresų knyga", +"CardDav Link" => "CardDAV nuoroda", +"Download" => "Atsisiųsti", +"Edit" => "Keisti", +"Delete" => "Trinti", +"Download contact" => "Atsisųsti kontaktą", +"Delete contact" => "Ištrinti kontaktą", +"Birthday" => "Gimtadienis", +"Phone" => "Telefonas", +"Type" => "Tipas", "PO Box" => "Pašto dėžutė", "Street" => "Gatvė", "City" => "Miestas", "Region" => "Regionas", "Zipcode" => "Pašto indeksas", "Country" => "Šalis", -"Create Contact" => "Sukurti kontaktą", -"Edit" => "Keisti", -"Delete" => "Trinti", -"Birthday" => "Gimtadienis", -"Phone" => "Telefonas" +"Add" => "Pridėti", +"Addressbook" => "Adresų knyga", +"New Addressbook" => "Nauja adresų knyga", +"Edit Addressbook" => "Redaguoti adresų knygą", +"Displayname" => "Rodomas vardas", +"Active" => "Aktyvus", +"Save" => "Išsaugoti", +"Cancel" => "Atšaukti" ); diff --git a/apps/contacts/l10n/mk.php b/apps/contacts/l10n/mk.php new file mode 100644 index 0000000000..4ca8b3f6ee --- /dev/null +++ b/apps/contacts/l10n/mk.php @@ -0,0 +1,58 @@ + "Грешка (де)активирање на адресарот.", +"There was an error adding the contact." => "Имаше грешка при додавање на контактот.", +"Cannot add empty property." => "Неможе да се додаде празна вредност.", +"At least one of the address fields has to be filled out." => "Барем една од полињата за адреса треба да биде пополнето.", +"Error adding contact property." => "Грешка при додавање на вредност за контактот.", +"Error adding addressbook." => "Грешки при додавање на адресарот.", +"Error activating addressbook." => "Грешка при активирање на адресарот.", +"Information about vCard is incorrect. Please reload the page." => "Информацијата за vCard не е точна. Ве молам превчитајте ја страницава.", +"Error deleting contact property." => "Греш при бришење на вредноста за контакт.", +"Error updating contact property." => "Грешка при ажурирање на вредноста за контакт.", +"Error updating addressbook." => "Грешка при ажурирање на адресарот.", +"Contacts" => "Контакти", +"This is not your addressbook." => "Ова не е во Вашиот адресар.", +"Contact could not be found." => "Контактот неможе да биде најден.", +"Address" => "Адреса", +"Telephone" => "Телефон", +"Email" => "Е-пошта", +"Organization" => "Организација", +"Work" => "Работа", +"Home" => "Дома", +"Mobile" => "Мобилен", +"Text" => "Текст", +"Voice" => "Глас", +"Fax" => "Факс", +"Video" => "Видео", +"Pager" => "Пејџер", +"Contact" => "Контакт", +"Add Contact" => "Додади контакт", +"Addressbooks" => "Адресари", +"New Address Book" => "Нов адресар", +"CardDav Link" => "Врска за CardDav", +"Download" => "Преземи", +"Edit" => "Уреди", +"Delete" => "Избриши", +"Download contact" => "Преземи го контактот", +"Delete contact" => "Избриши го контактот", +"Birthday" => "Роденден", +"Preferred" => "Претпочитано", +"Phone" => "Телефон", +"Type" => "Тип", +"PO Box" => "Поштенски фах", +"Extended" => "Дополнително", +"Street" => "Улица", +"City" => "Град", +"Region" => "Регион", +"Zipcode" => "Поштенски код", +"Country" => "Држава", +"Add" => "Додади", +"Addressbook" => "Адресар", +"New Addressbook" => "Нов адресар", +"Edit Addressbook" => "Уреди адресар", +"Displayname" => "Прикажано име", +"Active" => "Активно", +"Save" => "Сними", +"Submit" => "Прати", +"Cancel" => "Откажи" +); diff --git a/apps/contacts/l10n/ms_MY.php b/apps/contacts/l10n/ms_MY.php new file mode 100644 index 0000000000..7d8a4c7d12 --- /dev/null +++ b/apps/contacts/l10n/ms_MY.php @@ -0,0 +1,58 @@ + "Ralat nyahaktif buku alamat.", +"There was an error adding the contact." => "Terdapat masalah menambah maklumat.", +"Cannot add empty property." => "Tidak boleh menambah ruang kosong.", +"At least one of the address fields has to be filled out." => "Sekurangnya satu ruangan alamat perlu diisikan.", +"Error adding contact property." => "Terdapat masalah menambah maklumat.", +"Error adding addressbook." => "Masalah menambah buku alamat.", +"Error activating addressbook." => "Masalah mengaktifkan buku alamat.", +"Information about vCard is incorrect. Please reload the page." => "Maklumat vCard tidak tepat. Sila reload semula halaman ini.", +"Error deleting contact property." => "Masalah memadam maklumat.", +"Error updating contact property." => "Masalah mengemaskini maklumat.", +"Error updating addressbook." => "Masalah mengemaskini buku alamat.", +"Contacts" => "Hubungan-hubungan", +"This is not your addressbook." => "Ini bukan buku alamat anda.", +"Contact could not be found." => "Hubungan tidak dapat ditemui", +"Address" => "Alamat", +"Telephone" => "Telefon", +"Email" => "Emel", +"Organization" => "Organisasi", +"Work" => "Kerja", +"Home" => "Rumah", +"Mobile" => "Mudah alih", +"Text" => "Teks", +"Voice" => "Suara", +"Fax" => "Fax", +"Video" => "Video", +"Pager" => "Alat Kelui", +"Contact" => "Hubungan", +"Add Contact" => "Tambah kenalan", +"Addressbooks" => "Senarai Buku Alamat", +"New Address Book" => "Buku Alamat Baru", +"CardDav Link" => "Sambungan CardDav", +"Download" => "Muat naik", +"Edit" => "Sunting", +"Delete" => "Padam", +"Download contact" => "Muat turun hubungan", +"Delete contact" => "Padam hubungan", +"Birthday" => "Hari lahir", +"Preferred" => "Pilihan", +"Phone" => "Telefon", +"Type" => "Jenis", +"PO Box" => "Peti surat", +"Extended" => "Sambungan", +"Street" => "Jalan", +"City" => "bandar", +"Region" => "Wilayah", +"Zipcode" => "Poskod", +"Country" => "Negara", +"Add" => "Tambah", +"Addressbook" => "Buku alamat", +"New Addressbook" => "Buku Alamat Baru", +"Edit Addressbook" => "Kemaskini Buku Alamat", +"Displayname" => "Paparan nama", +"Active" => "Aktif", +"Save" => "Simpan", +"Submit" => "Hantar", +"Cancel" => "Batal" +); diff --git a/apps/contacts/l10n/nb_NO.php b/apps/contacts/l10n/nb_NO.php new file mode 100644 index 0000000000..7624c9b8c2 --- /dev/null +++ b/apps/contacts/l10n/nb_NO.php @@ -0,0 +1,58 @@ + "Et problem oppsto med å (de)aktivere adresseboken.", +"There was an error adding the contact." => "Et problem oppsto med å legge til kontakten.", +"Cannot add empty property." => "Kan ikke legge til tomt felt.", +"At least one of the address fields has to be filled out." => "Minst en av adressefeltene må oppgis.", +"Error adding contact property." => "Et problem oppsto med å legge til kontaktfeltet.", +"Error adding addressbook." => "Et problem oppsto med å legge til adresseboken.", +"Error activating addressbook." => "Et problem oppsto med å aktivere adresseboken.", +"Information about vCard is incorrect. Please reload the page." => "Informasjonen om vCard-filen er ikke riktig. Last inn siden på nytt.", +"Error deleting contact property." => "Et problem oppsto med å fjerne kontaktfeltet.", +"Error updating contact property." => "Et problem oppsto med å legge til kontaktfeltet.", +"Error updating addressbook." => "Et problem oppsto med å oppdatere adresseboken.", +"Contacts" => "Kontakter", +"This is not your addressbook." => "Dette er ikke dine adressebok.", +"Contact could not be found." => "Kontakten ble ikke funnet.", +"Address" => "Adresse", +"Telephone" => "Telefon", +"Email" => "E-post", +"Organization" => "Organisasjon", +"Work" => "Arbeid", +"Home" => "Hjem", +"Mobile" => "Mobil", +"Text" => "Tekst", +"Voice" => "Svarer", +"Fax" => "Faks", +"Video" => "Video", +"Pager" => "Pager", +"Contact" => "Kontakt", +"Add Contact" => "Ny kontakt", +"Addressbooks" => "Adressebøker", +"New Address Book" => "Ny adressebok", +"CardDav Link" => "CardDAV-lenke", +"Download" => "Hent ned", +"Edit" => "Rediger", +"Delete" => "Slett", +"Download contact" => "Hend ned kontakten", +"Delete contact" => "Slett kontakt", +"Birthday" => "Bursdag", +"Preferred" => "Foretrukket", +"Phone" => "Telefon", +"Type" => "Type", +"PO Box" => "Postboks", +"Extended" => "Utvidet", +"Street" => "Gate", +"City" => "By", +"Region" => "Området", +"Zipcode" => "Postnummer", +"Country" => "Land", +"Add" => "Ny", +"Addressbook" => "Adressebok", +"New Addressbook" => "Ny adressebok", +"Edit Addressbook" => "Endre adressebok", +"Displayname" => "Visningsnavn", +"Active" => "Aktiv", +"Save" => "Lagre", +"Submit" => "Send inn", +"Cancel" => "Avbryt" +); diff --git a/apps/contacts/l10n/nl.php b/apps/contacts/l10n/nl.php index 5719330ba3..a3fb780853 100644 --- a/apps/contacts/l10n/nl.php +++ b/apps/contacts/l10n/nl.php @@ -1,7 +1,18 @@ "Fout bij het (de)activeren van het adresboek.", +"There was an error adding the contact." => "Er was een fout bij het toevoegen van het contact.", +"Cannot add empty property." => "Kan geen lege eigenschap toevoegen.", +"At least one of the address fields has to be filled out." => "Minstens één van de adresvelden moet ingevuld worden.", +"Error adding contact property." => "Fout bij het toevoegen van de contacteigenschap.", +"Error adding addressbook." => "Fout bij het toevoegen van het adresboek.", +"Error activating addressbook." => "Fout bij het activeren van het adresboek.", +"Information about vCard is incorrect. Please reload the page." => "Informatie over de vCard is onjuist. Herlaad de pagina.", +"Error deleting contact property." => "Fout bij het verwijderen van de contacteigenschap.", +"Error updating contact property." => "Fout bij het updaten van de contacteigenschap.", +"Error updating addressbook." => "Fout bij het updaten van het adresboek.", +"Contacts" => "Contacten", "This is not your addressbook." => "Dit is niet uw adresboek.", "Contact could not be found." => "Contact kon niet worden gevonden.", -"Information about vCard is incorrect. Please reload the page." => "Informatie over de vCard is onjuist. Herlaad de pagina.", "Address" => "Adres", "Telephone" => "Telefoon", "Email" => "E-mail", @@ -14,9 +25,19 @@ "Fax" => "Fax", "Video" => "Video", "Pager" => "Pieper", -"This is not your contact." => "Dit is niet uw contactpersoon.", +"Contact" => "Contact", "Add Contact" => "Contact toevoegen", -"Name" => "Naam", +"Addressbooks" => "Adresboeken", +"New Address Book" => "Nieuw Adresboek", +"CardDav Link" => "CardDav Link", +"Download" => "Download", +"Edit" => "Bewerken", +"Delete" => "Verwijderen", +"Download contact" => "Download contact", +"Delete contact" => "Verwijder contact", +"Birthday" => "Verjaardag", +"Preferred" => "Voorkeur", +"Phone" => "Telefoon", "Type" => "Type", "PO Box" => "Postbus", "Extended" => "Uitgebreide", @@ -25,22 +46,13 @@ "Region" => "Regio", "Zipcode" => "Postcode", "Country" => "Land", -"Create Contact" => "Contact aanmaken", -"Choose active Address Books" => "Kies actief Adresboek", -"New Address Book" => "Nieuw Adresboek", -"CardDav Link" => "CardDav Link", -"Download" => "Download", -"Edit" => "Bewerken", -"Delete" => "Verwijderen", -"Delete contact" => "Verwijder contact", "Add" => "Voeg toe", +"Addressbook" => "Adresboek", +"New Addressbook" => "Nieuw adresboek", +"Edit Addressbook" => "Wijzig adresboek", "Displayname" => "Weergavenaam", "Active" => "Actief", "Save" => "Opslaan", "Submit" => "Opslaan", -"Cancel" => "Anuleren", -"Birthday" => "Verjaardag", -"Preferred" => "Voorkeur", -"Phone" => "Telefoon", -"Update" => "Vernieuwe" +"Cancel" => "Anuleren" ); diff --git a/apps/contacts/l10n/nn_NO.php b/apps/contacts/l10n/nn_NO.php index d0e3041d24..5b3fc5b1ab 100644 --- a/apps/contacts/l10n/nn_NO.php +++ b/apps/contacts/l10n/nn_NO.php @@ -1,7 +1,18 @@ "Ein feil oppstod ved (de)aktivering av adressebok.", +"There was an error adding the contact." => "Det kom ei feilmelding då kontakta vart lagt til.", +"Cannot add empty property." => "Kan ikkje leggja til tomt felt.", +"At least one of the address fields has to be filled out." => "Minst eit av adressefelta må fyllast ut.", +"Error adding contact property." => "Eit problem oppstod ved å leggja til kontakteltet.", +"Error adding addressbook." => "Eit problem oppstod ved å leggja til adresseboka.", +"Error activating addressbook." => "Eit problem oppstod ved aktivering av adresseboka.", +"Information about vCard is incorrect. Please reload the page." => "Informasjonen om vCard-et er feil, ver venleg og last sida på nytt.", +"Error deleting contact property." => "Eit problem oppstod ved å slette kontaktfeltet.", +"Error updating contact property." => "Eit problem oppstod ved å endre kontaktfeltet.", +"Error updating addressbook." => "Eit problem oppstod ved å oppdatere adresseboka.", +"Contacts" => "Kotaktar", "This is not your addressbook." => "Dette er ikkje di adressebok.", "Contact could not be found." => "Fann ikkje kontakten.", -"Information about vCard is incorrect. Please reload the page." => "Informasjonen om vCard-et er feil, ver venleg og last sida på nytt.", "Address" => "Adresse", "Telephone" => "Telefonnummer", "Email" => "Epost", @@ -14,9 +25,20 @@ "Fax" => "Faks", "Video" => "Video", "Pager" => "Personsøkjar", -"This is not your contact." => "Dette er ikkje din kontakt.", +"Contact" => "Kontakt", "Add Contact" => "Legg til kontakt", -"Name" => "Namn", +"Addressbooks" => "Adressebøker", +"New Address Book" => "Ny adressebok", +"CardDav Link" => "CardDav lenkje", +"Download" => "Last ned", +"Edit" => "Endra", +"Delete" => "Slett", +"Download contact" => "Last ned kontakt", +"Delete contact" => "Slett kontakt", +"Birthday" => "Bursdag", +"Preferred" => "Føretrekt", +"Phone" => "Telefonnummer", +"Type" => "Skriv", "PO Box" => "Postboks", "Extended" => "Utvida", "Street" => "Gate", @@ -24,9 +46,13 @@ "Region" => "Region/fylke", "Zipcode" => "Postnummer", "Country" => "Land", -"Create Contact" => "Opprett kontakt", -"Edit" => "Endra", -"Delete" => "Slett", -"Birthday" => "Bursdag", -"Phone" => "Telefonnummer" +"Add" => "Legg til", +"Addressbook" => "Adressebok", +"New Addressbook" => "Ny adressebok", +"Edit Addressbook" => "Endre adressebok", +"Displayname" => "Visningsnamn", +"Active" => "Aktiv", +"Save" => "Lagre", +"Submit" => "Send", +"Cancel" => "Kanseller" ); diff --git a/apps/contacts/l10n/pl.php b/apps/contacts/l10n/pl.php index ad1e8c7baa..31d0689359 100644 --- a/apps/contacts/l10n/pl.php +++ b/apps/contacts/l10n/pl.php @@ -1,18 +1,18 @@ "Błąd podczas (de)aktywacji książki", -"There was an error adding the contact." => "Wystąpił błąd podczas dodawania kontaktu", -"Cannot add empty property." => "Nie można dodać pustego elementu", -"At least one of the address fields has to be filled out." => "Przynajmniej jedno pole adresu musi być wypełnione", -"Error adding contact property." => "Błąd podczas dodawania elementu", -"Error adding addressbook." => "Błąd podczas dodawania książki", -"Error activating addressbook." => "Błąd podczas aktywacji książki adresowej", -"Error deleting contact property." => "Błąd podczas kasowania elementu", -"Error updating contact property." => "Błąd podczas aktualizacji elementu", -"Error updating addressbook." => "Błąd podczas aktualizacji książki", +"Error (de)activating addressbook." => "Błąd (de)aktywowania książki adresowej.", +"There was an error adding the contact." => "Wystąpił błąd podczas dodawania kontaktu.", +"Cannot add empty property." => "Nie można dodać pustego elementu.", +"At least one of the address fields has to be filled out." => "Należy wypełnić przynajmniej jedno pole adresu.", +"Error adding contact property." => "Błąd dodawania elementu.", +"Error adding addressbook." => "Błąd dodawania książki adresowej.", +"Error activating addressbook." => "Błąd aktywowania książki adresowej.", +"Information about vCard is incorrect. Please reload the page." => "Informacje o vCard są nieprawidłowe. Proszę odświeżyć stronę.", +"Error deleting contact property." => "Błąd usuwania elementu.", +"Error updating contact property." => "Błąd uaktualniania elementu.", +"Error updating addressbook." => "Błąd uaktualniania książki adresowej.", "Contacts" => "Kontakty", "This is not your addressbook." => "To nie jest twoja książka adresowa.", -"Contact could not be found." => "Kontakt nie znaleziony.", -"Information about vCard is incorrect. Please reload the page." => "Informacje o vCard są nieprawidłowe. Proszę odświeżyć stronę.", +"Contact could not be found." => "Nie można odnaleźć kontaktu.", "Address" => "Adres", "Telephone" => "Telefon", "Email" => "E-mail", @@ -20,45 +20,39 @@ "Work" => "Praca", "Home" => "Dom", "Mobile" => "Komórka", -"Text" => "Tekst", +"Text" => "Połączenie tekstowe", "Voice" => "Połączenie głosowe", "Fax" => "Faks", "Video" => "Połączenie wideo", "Pager" => "Pager", "Contact" => "Kontakt", -"This is not your contact." => "To nie jest twój kontakt.", "Add Contact" => "Dodaj kontakt", "Addressbooks" => "Książki adresowe", -"Addressbook" => "Książka adresowa", -"Name" => "Nazwisko", +"New Address Book" => "Nowa książka adresowa", +"CardDav Link" => "Wyświetla odnośnik CardDav", +"Download" => "Pobiera książkę adresową", +"Edit" => "Edytuje książkę adresową", +"Delete" => "Usuwa książkę adresową", +"Download contact" => "Pobiera kontakt", +"Delete contact" => "Usuwa kontakt", +"Birthday" => "Urodziny", +"Preferred" => "Preferowane", +"Phone" => "Telefon", "Type" => "Typ", -"PO Box" => "PO Box", +"PO Box" => "Skrzynka pocztowa", "Extended" => "Rozszerzony", "Street" => "Ulica", "City" => "Miasto", "Region" => "Region", -"Zipcode" => "Błąd podczas dodawania elementu", +"Zipcode" => "Kod pocztowy", "Country" => "Kraj", -"Create Contact" => "Utwórz kontakt", -"Choose active Address Books" => "Wybierz aktywną książkę adresową", -"New Address Book" => "Nowa książka adresowa", -"CardDav Link" => "Link CardDav", -"Download" => "Sciągaj", -"Edit" => "Edytuj", -"Delete" => "Usuń", -"Download contact" => "Pobierz kontakt", -"Delete contact" => "Skasuj kontakt", "Add" => "Dodaj", +"Addressbook" => "Książka adresowa", "New Addressbook" => "Nowa książka adresowa", -"Edit Addressbook" => "Edytuj książkę", -"Displayname" => "Nazwa wyświetlana", -"Active" => "Aktywny", +"Edit Addressbook" => "Edytowanie książki adresowej", +"Displayname" => "Wyświetlana nazwa", +"Active" => "Aktywna", "Save" => "Zapisz", "Submit" => "Potwierdź", -"Cancel" => "Skasuj", -"Birthday" => "Urodziny", -"Preferred" => "Preferowane", -"Phone" => "Telefon", -"Update" => "Aktualizuj", -"CardDAV syncing address:" => "Adres synchronizacji CardDAV:" +"Cancel" => "Anuluj" ); diff --git a/apps/contacts/l10n/pt_BR.php b/apps/contacts/l10n/pt_BR.php index 9f68e50cf9..23a8672ff9 100644 --- a/apps/contacts/l10n/pt_BR.php +++ b/apps/contacts/l10n/pt_BR.php @@ -1,7 +1,18 @@ "Este não é o seu agenda de endereços.", -"Contact could not be found." => "Contato não pôde ser encontrado.", +"Error (de)activating addressbook." => "Erro ao (des)ativar agenda.", +"There was an error adding the contact." => "Ocorreu um erro ao adicionar o contato.", +"Cannot add empty property." => "Não é possível adicionar propriedade vazia.", +"At least one of the address fields has to be filled out." => "Pelo menos um dos campos de endereço tem que ser preenchido.", +"Error adding contact property." => "Erro ao adicionar propriedade de contato.", +"Error adding addressbook." => "Erro ao adicionar agenda.", +"Error activating addressbook." => "Erro ao ativar agenda.", "Information about vCard is incorrect. Please reload the page." => "Informações sobre vCard é incorreta. Por favor, recarregue a página.", +"Error deleting contact property." => "Erro ao excluir propriedade de contato.", +"Error updating contact property." => "Erro ao atualizar propriedades do contato.", +"Error updating addressbook." => "Erro ao atualizar agenda.", +"Contacts" => "Contatos", +"This is not your addressbook." => "Esta não é a sua agenda de endereços.", +"Contact could not be found." => "Contato não pôde ser encontrado.", "Address" => "Endereço", "Telephone" => "Telefone", "Email" => "E-mail", @@ -14,9 +25,20 @@ "Fax" => "Fax", "Video" => "Vídeo", "Pager" => "Pager", -"This is not your contact." => "Este não é o seu contato.", +"Contact" => "Contato", "Add Contact" => "Adicionar Contato", -"Name" => "Nome", +"Addressbooks" => "Agendas", +"New Address Book" => "Nova agenda", +"CardDav Link" => "Link CardDav", +"Download" => "Baixar", +"Edit" => "Editar", +"Delete" => "Excluir", +"Download contact" => "Baixar contato", +"Delete contact" => "Apagar contato", +"Birthday" => "Aniversário", +"Preferred" => "Preferido", +"Phone" => "Telefone", +"Type" => "Digite", "PO Box" => "Caixa Postal", "Extended" => "Estendido", "Street" => "Rua", @@ -24,9 +46,13 @@ "Region" => "Região", "Zipcode" => "CEP", "Country" => "País", -"Create Contact" => "Criar Contato", -"Edit" => "Editar", -"Delete" => "Excluir", -"Birthday" => "Aniversário", -"Phone" => "Telefone" +"Add" => "Adicionar", +"Addressbook" => "Agenda", +"New Addressbook" => "Nova agenda", +"Edit Addressbook" => "Editar agenda", +"Displayname" => "Nome de exibição", +"Active" => "Ativo", +"Save" => "Salvar", +"Submit" => "Enviar", +"Cancel" => "Cancelar" ); diff --git a/apps/contacts/l10n/pt_PT.php b/apps/contacts/l10n/pt_PT.php index 68c83f02c5..82a2a9f0ff 100644 --- a/apps/contacts/l10n/pt_PT.php +++ b/apps/contacts/l10n/pt_PT.php @@ -1,12 +1,23 @@ "Erro a (des)ativar o livro de endereços", +"There was an error adding the contact." => "Erro ao adicionar contato", +"Cannot add empty property." => "Não é possivel adicionar uma propriedade vazia", +"At least one of the address fields has to be filled out." => "Pelo menos um dos campos de endereço precisa de estar preenchido", +"Error adding contact property." => "Erro ao adicionar propriedade do contato", +"Error adding addressbook." => "Erro ao adicionar livro de endereços", +"Error activating addressbook." => "Erro ao ativar livro de endereços", +"Information about vCard is incorrect. Please reload the page." => "A informação sobre o vCard está incorreta. Por favor refresque a página", +"Error deleting contact property." => "Erro ao apagar propriedade do contato", +"Error updating contact property." => "Erro ao atualizar propriedade do contato", +"Error updating addressbook." => "Erro a atualizar o livro de endereços", +"Contacts" => "Contactos", "This is not your addressbook." => "Esta não é a sua lista de contactos", "Contact could not be found." => "O contacto não foi encontrado", -"Information about vCard is incorrect. Please reload the page." => "A informação sobre o vCard está incorreta. Por favor refresque a página", "Address" => "Morada", "Telephone" => "Telefone", "Email" => "Email", "Organization" => "Organização", -"Work" => "Trabalho", +"Work" => "Emprego", "Home" => "Casa", "Mobile" => "Telemovel", "Text" => "Texto", @@ -14,9 +25,20 @@ "Fax" => "Fax", "Video" => "Vídeo", "Pager" => "Pager", -"This is not your contact." => "Este não é o seu contacto", +"Contact" => "Contacto", "Add Contact" => "Adicionar Contacto", -"Name" => "Nome", +"Addressbooks" => "Livros de endereços", +"New Address Book" => "Novo livro de endereços", +"CardDav Link" => "Endereço CardDav", +"Download" => "Transferir", +"Edit" => "Editar", +"Delete" => "Apagar", +"Download contact" => "Transferir contacto", +"Delete contact" => "Apagar contato", +"Birthday" => "Aniversário", +"Preferred" => "Preferido", +"Phone" => "Telefone", +"Type" => "Tipo", "PO Box" => "Apartado", "Extended" => "Extendido", "Street" => "Rua", @@ -24,9 +46,13 @@ "Region" => "Região", "Zipcode" => "Código Postal", "Country" => "País", -"Create Contact" => "Criar Contacto", -"Edit" => "Editar", -"Delete" => "Apagar", -"Birthday" => "Aniversário", -"Phone" => "Telefone" +"Add" => "Adicionar", +"Addressbook" => "Livro de endereços", +"New Addressbook" => "Novo livro de endereços", +"Edit Addressbook" => "Editar livro de endereços", +"Displayname" => "Nome de exibição", +"Active" => "Ativo", +"Save" => "Guardar", +"Submit" => "Submeter", +"Cancel" => "Cancelar" ); diff --git a/apps/contacts/l10n/ro.php b/apps/contacts/l10n/ro.php index 47a8b59599..a62cd6b24e 100644 --- a/apps/contacts/l10n/ro.php +++ b/apps/contacts/l10n/ro.php @@ -1,12 +1,23 @@ "(Dez)activarea agendei a întâmpinat o eroare.", +"There was an error adding the contact." => "O eroare a împiedicat adăugarea contactului.", +"Cannot add empty property." => "Nu se poate adăuga un câmp gol.", +"At least one of the address fields has to be filled out." => "Cel puțin unul din câmpurile adresei trebuie completat.", +"Error adding contact property." => "Contactul nu a putut fi adăugat.", +"Error adding addressbook." => "Agenda nu a putut fi adăugată.", +"Error activating addressbook." => "Eroare la activarea agendei.", +"Information about vCard is incorrect. Please reload the page." => "Informațiile cărții de vizită sunt incorecte. Te rog reîncarcă pagina.", +"Error deleting contact property." => "Eroare la ștergerea proprietăților contactului.", +"Error updating contact property." => "Eroare la actualizarea proprietăților contactului.", +"Error updating addressbook." => "Eroare la actualizarea agendei.", +"Contacts" => "Contacte", "This is not your addressbook." => "Nu se găsește în agendă.", "Contact could not be found." => "Contactul nu a putut fi găsit.", -"Information about vCard is incorrect. Please reload the page." => "Informațiile despre vCard sunt incorecte. Reîncărcați pagina.", "Address" => "Adresă", "Telephone" => "Telefon", "Email" => "Email", "Organization" => "Organizație", -"Work" => "Servici", +"Work" => "Servicu", "Home" => "Acasă", "Mobile" => "Mobil", "Text" => "Text", @@ -14,9 +25,19 @@ "Fax" => "Fax", "Video" => "Video", "Pager" => "Pager", -"This is not your contact." => "Nu este contactul tău", +"Contact" => "Contact", "Add Contact" => "Adaugă contact", -"Name" => "Nume", +"Addressbooks" => "Agende", +"New Address Book" => "Agendă nouă", +"CardDav Link" => "Legătură CardDev", +"Download" => "Descarcă", +"Edit" => "Editează", +"Delete" => "Șterge", +"Download contact" => "Descarcă acest contact", +"Delete contact" => "Șterge contact", +"Birthday" => "Zi de naștere", +"Preferred" => "Preferat", +"Phone" => "Telefon", "Type" => "Tip", "PO Box" => "CP", "Extended" => "Extins", @@ -25,22 +46,13 @@ "Region" => "Regiune", "Zipcode" => "Cod poștal", "Country" => "Țară", -"Create Contact" => "Crează contact", -"Choose active Address Books" => "Alegeți una din agendele active", -"New Address Book" => "Agendă nouă", -"CardDav Link" => "Link CardDev", -"Download" => "Descarcă", -"Edit" => "Editează", -"Delete" => "Șterge", -"Delete contact" => "Șterge contact", "Add" => "Adaugă", -"Displayname" => "Numele afișat", +"Addressbook" => "Agendă", +"New Addressbook" => "Agendă nouă", +"Edit Addressbook" => "Modifică agenda", +"Displayname" => "Nume afișat", "Active" => "Activ", "Save" => "Salvează", "Submit" => "Trimite", -"Cancel" => "Anulează", -"Birthday" => "Zi de naștere", -"Preferred" => "Preferat", -"Phone" => "Telefon", -"Update" => "Update" +"Cancel" => "Anulează" ); diff --git a/apps/contacts/l10n/ru.php b/apps/contacts/l10n/ru.php index 5e48d5776a..62e595c3c2 100644 --- a/apps/contacts/l10n/ru.php +++ b/apps/contacts/l10n/ru.php @@ -6,13 +6,13 @@ "Error adding contact property." => "Ошибка добавления информации к контакту.", "Error adding addressbook." => "Ошибка добавления адресной книги.", "Error activating addressbook." => "Ошибка активации адресной книги.", +"Information about vCard is incorrect. Please reload the page." => "Информация о vCard некорректна. Пожалуйста, обновите страницу.", "Error deleting contact property." => "Ошибка удаления информации из контакта.", "Error updating contact property." => "Ошибка обновления информации контакта.", "Error updating addressbook." => "Ошибка обновления адресной книги.", "Contacts" => "Контакты", "This is not your addressbook." => "Это не ваша адресная книга.", "Contact could not be found." => "Контакт не найден.", -"Information about vCard is incorrect. Please reload the page." => "Информация о vCard некорректна. Пожалуйста, обновите страницу.", "Address" => "Адрес", "Telephone" => "Телефон", "Email" => "Ящик эл. почты", @@ -26,11 +26,18 @@ "Video" => "Видео", "Pager" => "Пейджер", "Contact" => "Контакт", -"This is not your contact." => "Это не контакт.", "Add Contact" => "Добавить Контакт", "Addressbooks" => "Адресные книги", -"Addressbook" => "Адресная книга", -"Name" => "Имя", +"New Address Book" => "Новая адресная книга", +"CardDav Link" => "Ссылка CardDAV", +"Download" => "Скачать", +"Edit" => "Редактировать", +"Delete" => "Удалить", +"Download contact" => "Скачать контакт", +"Delete contact" => "Удалить контакт", +"Birthday" => "День рождения", +"Preferred" => "Предпочитаемый", +"Phone" => "Телефон", "Type" => "Тип", "PO Box" => "АО", "Extended" => "Расширенный", @@ -39,26 +46,13 @@ "Region" => "Область", "Zipcode" => "Почтовый индекс", "Country" => "Страна", -"Create Contact" => "Создать Контакт", -"Choose active Address Books" => "Выберите активные адресные книги", -"New Address Book" => "Новая адресная книга", -"CardDav Link" => "Ссылка CardDAV", -"Download" => "Скачать", -"Edit" => "Редактировать", -"Delete" => "Удалить", -"Download contact" => "Скачать контакт", -"Delete contact" => "Удалить контакт", "Add" => "Добавить", +"Addressbook" => "Адресная книга", "New Addressbook" => "Новая адресная книга", -"Edit Addressbook" => "Править адресную книгу", +"Edit Addressbook" => "Редактировать адресную книгу", "Displayname" => "Отображаемое имя", "Active" => "Активно", "Save" => "Сохранить", "Submit" => "Отправить", -"Cancel" => "Отменить", -"Birthday" => "День рождения", -"Preferred" => "Предпочитаемый", -"Phone" => "Телефон", -"Update" => "Обновить", -"CardDAV syncing address:" => "Адрес синхронизации CardDAV:" +"Cancel" => "Отменить" ); diff --git a/apps/contacts/l10n/sk_SK.php b/apps/contacts/l10n/sk_SK.php index a28907aac8..7637cae4d9 100644 --- a/apps/contacts/l10n/sk_SK.php +++ b/apps/contacts/l10n/sk_SK.php @@ -1,12 +1,18 @@ "Chyba (de)aktivácie adresára.", "There was an error adding the contact." => "Vyskytla sa chyba pri pridávaní kontaktu.", +"Cannot add empty property." => "Nemôžem pridať prázdny údaj.", +"At least one of the address fields has to be filled out." => "Musí byť uvedený aspoň jeden adresný údaj.", +"Error adding contact property." => "Chyba pridania údaju kontaktu", +"Error adding addressbook." => "Chyba počas pridávania adresára.", "Error activating addressbook." => "Chyba aktivovania adresára.", +"Information about vCard is incorrect. Please reload the page." => "Informácie o vCard sú neplatné. Prosím obnovte stránku.", +"Error deleting contact property." => "Chyba odstránenia údaju kontaktu.", +"Error updating contact property." => "Chyba aktualizovania údaju kontaktu.", "Error updating addressbook." => "Chyba aktualizácie adresára.", "Contacts" => "Kontakty", "This is not your addressbook." => "Toto nie je váš adresár.", "Contact could not be found." => "Kontakt nebol nájdený.", -"Information about vCard is incorrect. Please reload the page." => "Informácie o vCard sú neplatné. Prosím obnovte stránku.", "Address" => "Adresa", "Telephone" => "Telefón", "Email" => "E-mail", @@ -20,11 +26,18 @@ "Video" => "Video", "Pager" => "Pager", "Contact" => "Kontakt", -"This is not your contact." => "Toto nie je váš kontakt.", "Add Contact" => "Pridať Kontakt.", "Addressbooks" => "Adresáre", -"Addressbook" => "Adresár", -"Name" => "Meno", +"New Address Book" => "Nový adresár", +"CardDav Link" => "CardDav odkaz", +"Download" => "Stiahnuť", +"Edit" => "Upraviť", +"Delete" => "Odstrániť", +"Download contact" => "Stiahnuť kontakt", +"Delete contact" => "Odstrániť kontakt", +"Birthday" => "Narodeniny", +"Preferred" => "Uprednostňované", +"Phone" => "Telefón", "Type" => "Typ", "PO Box" => "PO Box", "Extended" => "Rozšírené", @@ -33,26 +46,13 @@ "Region" => "Región", "Zipcode" => "PSČ", "Country" => "Krajina", -"Create Contact" => "Vytvoriť Kontakt.", -"Choose active Address Books" => "Zvoliť aktívny adresár", -"New Address Book" => "Nový adresár", -"CardDav Link" => "CardDav odkaz", -"Download" => "Stiahnuť", -"Edit" => "Upraviť", -"Delete" => "Odstrániť", -"Download contact" => "Stiahnuť kontakt", -"Delete contact" => "Odstrániť kontakt", "Add" => "Pridať", +"Addressbook" => "Adresár", "New Addressbook" => "Nový Adresár", "Edit Addressbook" => "Upraviť Adresár", "Displayname" => "Zobrazené meno", "Active" => "Aktívny", "Save" => "Uložiť", "Submit" => "Odoslať", -"Cancel" => "Zrušiť", -"Birthday" => "Narodeniny", -"Preferred" => "Uprednostňované", -"Phone" => "Telefón", -"Update" => "Aktualizovať", -"CardDAV syncing address:" => "Synchronizačná adresa CardDAV:" +"Cancel" => "Zrušiť" ); diff --git a/apps/contacts/l10n/sl.php b/apps/contacts/l10n/sl.php index 79039c36d6..9709698fb4 100644 --- a/apps/contacts/l10n/sl.php +++ b/apps/contacts/l10n/sl.php @@ -1,32 +1,58 @@ "To ni vaš adresar.", -"Contact could not be found." => "Kontakta ni bilo mogoče najti.", -"Information about vCard is incorrect. Please reload the page." => "Informacije o vVizitki (vCard) niso pravilne, Prosimo ponovno naložite okno.", +"Error (de)activating addressbook." => "Napaka med (de)aktivacijo imenika.", +"There was an error adding the contact." => "Med dodajanjem stika je prišlo do napake", +"Cannot add empty property." => "Ne morem dodati prazne lastnosti.", +"At least one of the address fields has to be filled out." => "Vsaj eno izmed polj je še potrebno izpolniti.", +"Error adding contact property." => "Napaka pri dodajanju informacije o stiku.", +"Error adding addressbook." => "Napaka pri dodajanju imenika.", +"Error activating addressbook." => "Napaka pri aktiviranju imenika.", +"Information about vCard is incorrect. Please reload the page." => "Informacije o vCard niso pravilne. Prosimo, če ponovno naložite stran.", +"Error deleting contact property." => "Napaka pri brisanju lastnosti stika.", +"Error updating contact property." => "Napaka pri posodabljanju lastnosti stika.", +"Error updating addressbook." => "Napaka pri posodabljanju imenika.", +"Contacts" => "Stiki", +"This is not your addressbook." => "To ni vaš imenik.", +"Contact could not be found." => "Stika ni bilo mogoče najti.", "Address" => "Naslov", "Telephone" => "Telefon", -"Email" => "Email", +"Email" => "E-pošta", "Organization" => "Organizacija", "Work" => "Delo", "Home" => "Doma", -"Mobile" => "Mobitel", -"Text" => "Tekst", -"Voice" => "Glas- Voice", +"Mobile" => "Mobilni telefon", +"Text" => "Besedilo", +"Voice" => "Glas", "Fax" => "Faks", "Video" => "Video", -"Pager" => "Pager", -"This is not your contact." => "To ni vaš kontakt", -"Add Contact" => "Dodaj Kontakt", -"Name" => "Ime", -"PO Box" => "PO Box", -"Extended" => "Razširjeno.", +"Pager" => "Pozivnik", +"Contact" => "Stik", +"Add Contact" => "Dodaj stik", +"Addressbooks" => "Imeniki", +"New Address Book" => "Nov imenik", +"CardDav Link" => "CardDav povezava", +"Download" => "Prenesi", +"Edit" => "Uredi", +"Delete" => "Izbriši", +"Download contact" => "Prenesi stik", +"Delete contact" => "Izbriši stik", +"Birthday" => "Rojstni dan", +"Preferred" => "Prednosten", +"Phone" => "Telefon", +"Type" => "Vrsta", +"PO Box" => "Poštni predal", +"Extended" => "Razširjeno", "Street" => "Ulica", "City" => "Mesto", "Region" => "Regija", "Zipcode" => "Poštna št.", "Country" => "Dežela", -"Create Contact" => "Ustvari Kontakt", -"Edit" => "Uredi", -"Delete" => "Izbriši", -"Birthday" => "Rojstni Dan", -"Phone" => "Telefon" +"Add" => "Dodaj", +"Addressbook" => "Imenik", +"New Addressbook" => "Nov imenik", +"Edit Addressbook" => "Uredi imenik", +"Displayname" => "Ime za prikaz", +"Active" => "Aktiven", +"Save" => "Shrani", +"Submit" => "Potrdi", +"Cancel" => "Prekliči" ); diff --git a/apps/contacts/l10n/sr.php b/apps/contacts/l10n/sr.php index 6404fb44b1..aad88f3603 100644 --- a/apps/contacts/l10n/sr.php +++ b/apps/contacts/l10n/sr.php @@ -1,8 +1,8 @@ "Подаци о вКарти су неисправни. Поново учитајте страницу.", "Contacts" => "Контакти", "This is not your addressbook." => "Ово није ваш адресар.", "Contact could not be found." => "Контакт се не може наћи.", -"Information about vCard is incorrect. Please reload the page." => "Подаци о вКарти су неисправни. Поново учитајте страницу.", "Address" => "Адреса", "Telephone" => "Телефон", "Email" => "Е-маил", @@ -16,11 +16,17 @@ "Video" => "Видео", "Pager" => "Пејџер", "Contact" => "Контакт", -"This is not your contact." => "Ово није ваш контакт.", "Add Contact" => "Додај контакт", "Addressbooks" => "Адресар", -"Addressbook" => "Адресар", -"Name" => "Име", +"New Address Book" => "Нови адресар", +"Download" => "Преузимање", +"Edit" => "Уреди", +"Delete" => "Обриши", +"Download contact" => "Преузми контакт", +"Delete contact" => "Обриши контакт", +"Birthday" => "Рођендан", +"Preferred" => "Пожељан", +"Phone" => "Телефон", "Type" => "Тип", "PO Box" => "Поштански број", "Extended" => "Прошири", @@ -29,24 +35,13 @@ "Region" => "Регија", "Zipcode" => "Зип код", "Country" => "Земља", -"Create Contact" => "Направи контакт", -"Choose active Address Books" => "Изаберите активне адресаре", -"New Address Book" => "Нови адресар", -"Download" => "Преузимање", -"Edit" => "Уреди", -"Delete" => "Обриши", -"Download contact" => "Преузми контакт", -"Delete contact" => "Обриши контакт", "Add" => "Додај", +"Addressbook" => "Адресар", "New Addressbook" => "Нови адресар", "Edit Addressbook" => "Уреди адресар", "Displayname" => "Приказано име", "Active" => "Активан", "Save" => "Сними", "Submit" => "Пошаљи", -"Cancel" => "Откажи", -"Birthday" => "Рођендан", -"Preferred" => "Пожељан", -"Phone" => "Телефон", -"Update" => "Ажурирај" +"Cancel" => "Откажи" ); diff --git a/apps/contacts/l10n/sr@latin.php b/apps/contacts/l10n/sr@latin.php index bfd4389645..a76c104790 100644 --- a/apps/contacts/l10n/sr@latin.php +++ b/apps/contacts/l10n/sr@latin.php @@ -1,7 +1,7 @@ "Podaci o vKarti su neispravni. Ponovo učitajte stranicu.", "This is not your addressbook." => "Ovo nije vaš adresar.", "Contact could not be found." => "Kontakt se ne može naći.", -"Information about vCard is incorrect. Please reload the page." => "Podaci o vKarti su neispravni. Ponovo učitajte stranicu.", "Address" => "Adresa", "Telephone" => "Telefon", "Email" => "E-mail", @@ -14,19 +14,16 @@ "Fax" => "Faks", "Video" => "Video", "Pager" => "Pejdžer", -"This is not your contact." => "Ovo nije vaš kontakt.", "Add Contact" => "Dodaj kontakt", -"Name" => "Ime", +"Edit" => "Uredi", +"Delete" => "Obriši", +"Birthday" => "Rođendan", +"Phone" => "Telefon", "PO Box" => "Poštanski broj", "Extended" => "Proširi", "Street" => "Ulica", "City" => "Grad", "Region" => "Regija", "Zipcode" => "Zip kod", -"Country" => "Zemlja", -"Create Contact" => "Napravi kontakt", -"Edit" => "Uredi", -"Delete" => "Obriši", -"Birthday" => "Rođendan", -"Phone" => "Telefon" +"Country" => "Zemlja" ); diff --git a/apps/contacts/l10n/sv.php b/apps/contacts/l10n/sv.php index c4935983f2..cee9682b84 100644 --- a/apps/contacts/l10n/sv.php +++ b/apps/contacts/l10n/sv.php @@ -1,7 +1,18 @@ "Fel när (av)aktivera adressbok", +"There was an error adding the contact." => "Det uppstod ett fel när kontakt skulle läggas till", +"Cannot add empty property." => "Kan inte lägga till en tom egenskap", +"At least one of the address fields has to be filled out." => "Minst ett fält måste fyllas i", +"Error adding contact property." => "Fel när kontaktegenskap skulle läggas till", +"Error adding addressbook." => "Fel när adressbok skulle läggas till", +"Error activating addressbook." => "Fel uppstod när adressbok skulle aktiveras", +"Information about vCard is incorrect. Please reload the page." => "Information om vCard är felaktigt. Vänligen ladda om sidan.", +"Error deleting contact property." => "Fel uppstod när kontaktegenskap skulle tas bort", +"Error updating contact property." => "Fel uppstod när kontaktegenskap skulle uppdateras", +"Error updating addressbook." => "Fel uppstod när adressbok skulle uppdateras", +"Contacts" => "Kontakter", "This is not your addressbook." => "Det här är inte din adressbok.", "Contact could not be found." => "Kontakt kunde inte hittas.", -"Information about vCard is incorrect. Please reload the page." => "Information om vCard är felaktigt. Vänligen ladda om sidan.", "Address" => "Adress", "Telephone" => "Telefon", "Email" => "E-post", @@ -14,9 +25,20 @@ "Fax" => "Fax", "Video" => "Video", "Pager" => "Personsökare", -"This is not your contact." => "Det här är inte din kontakt.", +"Contact" => "Kontakt", "Add Contact" => "Lägg till kontakt", -"Name" => "Namn", +"Addressbooks" => "Adressböcker", +"New Address Book" => "Ny adressbok", +"CardDav Link" => "CardDAV länk", +"Download" => "Nedladdning", +"Edit" => "Redigera", +"Delete" => "Radera", +"Download contact" => "Ladda ner kontakt", +"Delete contact" => "Radera kontakt", +"Birthday" => "Födelsedag", +"Preferred" => "Föredragen", +"Phone" => "Telefon", +"Type" => "Typ", "PO Box" => "Postbox", "Extended" => "Utökad", "Street" => "Gata", @@ -24,9 +46,13 @@ "Region" => "Län", "Zipcode" => "Postnummer", "Country" => "Land", -"Create Contact" => "Skapa kontakt", -"Edit" => "Redigera", -"Delete" => "Radera", -"Birthday" => "Födelsedag", -"Phone" => "Telefon" +"Add" => "Ny", +"Addressbook" => "Adressbok", +"New Addressbook" => "Ny adressbok", +"Edit Addressbook" => "Redigera adressbok", +"Displayname" => "Visningsnamn", +"Active" => "Aktiv", +"Save" => "Spara", +"Submit" => "Skicka in", +"Cancel" => "Avbryt" ); diff --git a/apps/contacts/l10n/th_TH.php b/apps/contacts/l10n/th_TH.php index 06e103bc32..47cfa3f6cd 100644 --- a/apps/contacts/l10n/th_TH.php +++ b/apps/contacts/l10n/th_TH.php @@ -6,13 +6,13 @@ "Error adding contact property." => "เกิดข้อผิดพลาดในการเพิ่มรายละเอียดการติดต่อ", "Error adding addressbook." => "เกิดข้อผิดพลาดในการเพิ่มสมุดบันทึกที่อยู่ใหม่", "Error activating addressbook." => "เกิดข้อผิดพลาดในการเปิดใช้งานสมุดบันทึกที่อยู่", +"Information about vCard is incorrect. Please reload the page." => "ข้อมูลเกี่ยวกับ vCard ไม่ถูกต้อง กรุณาโหลดหน้าเวปใหม่อีกครั้ง", "Error deleting contact property." => "เกิดข้อผิดพลาดในการลบรายละเอียดการติดต่อ", "Error updating contact property." => "เกิดข้อผิดพลาดในการอัพเดทข้อมูลการติดต่อ", "Error updating addressbook." => "เกิดข้อผิดพลาดในการอัพเดทสมุดบันทึกที่อยู่", "Contacts" => "ข้อมูลการติดต่อ", "This is not your addressbook." => "นี่ไม่ใช่สมุดบันทึกที่อยู่ของคุณ", "Contact could not be found." => "ไม่พบข้อมูลการติดต่อ", -"Information about vCard is incorrect. Please reload the page." => "ข้อมูลเกี่ยวกับ vCard ไม่ถูกต้อง กรุณาโหลดหน้าเวปใหม่อีกครั้ง", "Address" => "ที่อยู่", "Telephone" => "โทรศัพท์", "Email" => "อีเมล์", @@ -26,11 +26,18 @@ "Video" => "วีดีโอ", "Pager" => "เพจเจอร์", "Contact" => "ข้อมูลการติดต่อ", -"This is not your contact." => "นี่ไม่ใช่ข้อมูลการติดต่อของคุณ", "Add Contact" => "เพิ่มรายชื่อผู้ติดต่อใหม่", "Addressbooks" => "สมุดบันทึกที่อยู่", -"Addressbook" => "สมุดบันทึกที่อยู่", -"Name" => "ชื่อ", +"New Address Book" => "สร้างสมุดบันทึกข้อมูลการติดต่อใหม่", +"CardDav Link" => "ลิงค์ CardDav", +"Download" => "ดาวน์โหลด", +"Edit" => "แก้ไข", +"Delete" => "ลบ", +"Download contact" => "ดาวน์โหลดข้อมูลการติดต่อ", +"Delete contact" => "ลบข้อมูลการติดต่อ", +"Birthday" => "วันเกิด", +"Preferred" => "พิเศษ", +"Phone" => "โทรศัพท์", "Type" => "ประเภท", "PO Box" => "ตู้ ปณ.", "Extended" => "เพิ่ม", @@ -39,26 +46,13 @@ "Region" => "ภูมิภาค", "Zipcode" => "รหัสไปรษณีย์", "Country" => "ประเทศ", -"Create Contact" => "สร้างข้อมูลการติดต่อใหม่", -"Choose active Address Books" => "เลือกสมุดบันทึกข้อมูลติดต่อที่ต้องการใช้งาน", -"New Address Book" => "สร้างสมุดบันทึกข้อมูลการติดต่อใหม่", -"CardDav Link" => "ลิงค์ CardDav", -"Download" => "ดาวน์โหลด", -"Edit" => "แก้ไข", -"Delete" => "ลบ", -"Download contact" => "ดาวน์โหลดข้อมูลการติดต่อ", -"Delete contact" => "ลบข้อมูลการติดต่อ", "Add" => "เพิ่ม", +"Addressbook" => "สมุดบันทึกที่อยู่", "New Addressbook" => "สร้างสมุดบันทึกที่อยู่ใหม่", "Edit Addressbook" => "แก้ไขสมุดบันทึกที่อยู่", "Displayname" => "ชื่อที่ต้องการให้แสดง", "Active" => "เปิดใช้", "Save" => "บันทึก", "Submit" => "ส่งข้อมูล", -"Cancel" => "ยกเลิก", -"Birthday" => "วันเกิด", -"Preferred" => "พิเศษ", -"Phone" => "โทรศัพท์", -"Update" => "อัพเดท", -"CardDAV syncing address:" => "ที่อยู่ในการเชื่อมข้อมูลกับระบบบันทึกที่อยู่ CardDAV:" +"Cancel" => "ยกเลิก" ); diff --git a/apps/contacts/l10n/tr.php b/apps/contacts/l10n/tr.php index 75a48180bd..71d3c42ff6 100644 --- a/apps/contacts/l10n/tr.php +++ b/apps/contacts/l10n/tr.php @@ -1,7 +1,18 @@ "Adres defteri etkisizleştirilirken hata oluştu.", +"There was an error adding the contact." => "Kişi eklenirken hata oluştu.", +"Cannot add empty property." => "Boş özellik eklenemiyor.", +"At least one of the address fields has to be filled out." => "En az bir adres alanı doldurulmalı.", +"Error adding contact property." => "Kişi özelliği eklenirken hata oluştu.", +"Error adding addressbook." => "Adres defteri eklenirken hata oluştu.", +"Error activating addressbook." => "Adres defteri etkinleştirilirken hata oluştu.", +"Information about vCard is incorrect. Please reload the page." => "vCard bilgileri doğru değil. Lütfen sayfayı yenileyin.", +"Error deleting contact property." => "Kişi özelliği silinirken hata oluştu.", +"Error updating contact property." => "Kişi özelliği güncellenirken hata oluştu.", +"Error updating addressbook." => "Adres defteri güncellenirken hata oluştu.", +"Contacts" => "Kişiler", "This is not your addressbook." => "Bu sizin adres defteriniz değil.", "Contact could not be found." => "Kişi bulunamadı.", -"Information about vCard is incorrect. Please reload the page." => "vCard bilgileri doğru değil. Lütfen sayfayı yenileyin.", "Address" => "Adres", "Telephone" => "Telefon", "Email" => "Eposta", @@ -14,9 +25,20 @@ "Fax" => "Faks", "Video" => "Video", "Pager" => "Sayfalayıcı", -"This is not your contact." => "Bu sizin kişiniz değil.", +"Contact" => "Kişi", "Add Contact" => "Kişi Ekle", -"Name" => "Ad", +"Addressbooks" => "Adres defterleri", +"New Address Book" => "Yeni Adres Defteri", +"CardDav Link" => "CardDav Bağlantısı", +"Download" => "İndir", +"Edit" => "Düzenle", +"Delete" => "Sil", +"Download contact" => "Kişiyi indir", +"Delete contact" => "Kişiyi sil", +"Birthday" => "Doğum günü", +"Preferred" => "Tercih edilen", +"Phone" => "Telefon", +"Type" => "Tür", "PO Box" => "Posta Kutusu", "Extended" => "Uzatılmış", "Street" => "Sokak", @@ -24,9 +46,13 @@ "Region" => "Bölge", "Zipcode" => "Posta kodu", "Country" => "Ülke", -"Create Contact" => "Kişi Oluştur", -"Edit" => "Düzenle", -"Delete" => "Sil", -"Birthday" => "Doğum günü", -"Phone" => "Telefon" +"Add" => "Ekle", +"Addressbook" => "Adres defteri", +"New Addressbook" => "Yeni Adres defteri", +"Edit Addressbook" => "Adres Defterini Düzenle", +"Displayname" => "Görünen adı", +"Active" => "Aktif", +"Save" => "Kaydet", +"Submit" => "Gönder", +"Cancel" => "İptal" ); diff --git a/apps/contacts/l10n/uk.php b/apps/contacts/l10n/uk.php new file mode 100644 index 0000000000..4c27acf543 --- /dev/null +++ b/apps/contacts/l10n/uk.php @@ -0,0 +1,29 @@ + "Має бути заповнено щонайменше одне поле.", +"Error adding addressbook." => "Помилка при додаванні адресної книги.", +"This is not your addressbook." => "Це не ваша адресна книга.", +"Address" => "Адреса", +"Telephone" => "Телефон", +"Email" => "Ел.пошта", +"Organization" => "Організація", +"Mobile" => "Мобільний", +"Text" => "Текст", +"Voice" => "Голос", +"Fax" => "Факс", +"Video" => "Відео", +"Pager" => "Пейджер", +"Add Contact" => "Додати контакт", +"New Address Book" => "Нова адресна книга", +"Download" => "Завантажити", +"Delete" => "Видалити", +"Delete contact" => "Видалити контакт", +"Birthday" => "День народження", +"Phone" => "Телефон", +"Extended" => "Розширено", +"Street" => "Вулиця", +"City" => "Місто", +"Zipcode" => "Поштовий індекс", +"Country" => "Країна", +"Add" => "Додати", +"Displayname" => "Відображуване ім'я" +); diff --git a/apps/contacts/l10n/xgettextfiles b/apps/contacts/l10n/xgettextfiles index e2492431ff..e291074fba 100644 --- a/apps/contacts/l10n/xgettextfiles +++ b/apps/contacts/l10n/xgettextfiles @@ -5,7 +5,7 @@ ../ajax/createaddressbook.php ../ajax/deletebook.php ../ajax/deleteproperty.php -../ajax/getdetails.php +../ajax/contactdetails.php ../ajax/saveproperty.php ../ajax/updateaddressbook.php ../lib/app.php diff --git a/apps/contacts/l10n/zh_CN.php b/apps/contacts/l10n/zh_CN.php index 1904eff65c..2f1c0aa177 100644 --- a/apps/contacts/l10n/zh_CN.php +++ b/apps/contacts/l10n/zh_CN.php @@ -6,13 +6,13 @@ "Error adding contact property." => "添加联系人属性错误。", "Error adding addressbook." => "添加地址簿错误。", "Error activating addressbook." => "激活地址簿错误。", +"Information about vCard is incorrect. Please reload the page." => "vCard 的信息不正确。请重新加载页面。", "Error deleting contact property." => "删除联系人属性错误。", "Error updating contact property." => "更新联系人属性错误。", "Error updating addressbook." => "更新地址簿错误", "Contacts" => "联系人", "This is not your addressbook." => "这不是您的地址簿。", "Contact could not be found." => "无法找到联系人。", -"Information about vCard is incorrect. Please reload the page." => "vCard 的信息不正确。请重新加载页面。", "Address" => "地址", "Telephone" => "电话", "Email" => "电子邮件", @@ -26,11 +26,18 @@ "Video" => "视频", "Pager" => "传呼机", "Contact" => "联系人", -"This is not your contact." => "这不是您的联系人。", "Add Contact" => "添加联系人", "Addressbooks" => "地址簿", -"Addressbook" => "地址簿", -"Name" => "名称", +"New Address Book" => "新建地址簿", +"CardDav Link" => "CardDav 链接", +"Download" => "下载", +"Edit" => "编辑", +"Delete" => "删除", +"Download contact" => "下载联系人", +"Delete contact" => "删除联系人", +"Birthday" => "生日", +"Preferred" => "偏好", +"Phone" => "电话", "Type" => "类型", "PO Box" => "邮箱", "Extended" => "扩展", @@ -39,26 +46,13 @@ "Region" => "地区", "Zipcode" => "邮编", "Country" => "国家", -"Create Contact" => "创建联系人", -"Choose active Address Books" => "选择激活地址簿", -"New Address Book" => "新建地址簿", -"CardDav Link" => "CardDav 链接", -"Download" => "下载", -"Edit" => "编辑", -"Delete" => "删除", -"Download contact" => "下载联系人", -"Delete contact" => "删除联系人", "Add" => "添加", +"Addressbook" => "地址簿", "New Addressbook" => "新建地址簿", "Edit Addressbook" => "编辑地址簿", "Displayname" => "显示名称", "Active" => "激活", "Save" => "保存", "Submit" => "提交", -"Cancel" => "取消", -"Birthday" => "生日", -"Preferred" => "偏好", -"Phone" => "电话", -"Update" => "更新", -"CardDAV syncing address:" => "CardDAV 同步地址:" +"Cancel" => "取消" ); diff --git a/apps/contacts/l10n/zh_TW.php b/apps/contacts/l10n/zh_TW.php new file mode 100644 index 0000000000..645072a70f --- /dev/null +++ b/apps/contacts/l10n/zh_TW.php @@ -0,0 +1,58 @@ + "在啟用或關閉電話簿時發生錯誤", +"There was an error adding the contact." => "添加通訊錄發生錯誤", +"Cannot add empty property." => "不可添加空白內容", +"At least one of the address fields has to be filled out." => "至少必須填寫一欄地址", +"Error adding contact property." => "添加通訊錄內容中發生錯誤", +"Error adding addressbook." => "添加電話簿中發生錯誤", +"Error activating addressbook." => "啟用電話簿中發生錯誤", +"Information about vCard is incorrect. Please reload the page." => "有關 vCard 的資訊不正確,請重新載入此頁。", +"Error deleting contact property." => "刪除通訊錄內容中發生錯誤", +"Error updating contact property." => "更新通訊錄內容中發生錯誤", +"Error updating addressbook." => "電話簿更新中發生錯誤", +"Contacts" => "通訊錄", +"This is not your addressbook." => "這不是你的電話簿", +"Contact could not be found." => "通訊錄未發現", +"Address" => "地址", +"Telephone" => "電話", +"Email" => "電子郵件", +"Organization" => "組織", +"Work" => "公司", +"Home" => "住宅", +"Mobile" => "行動電話", +"Text" => "文字", +"Voice" => "語音", +"Fax" => "傳真", +"Video" => "影片", +"Pager" => "呼叫器", +"Contact" => "通訊錄", +"Add Contact" => "添加通訊錄", +"Addressbooks" => "電話簿", +"New Address Book" => "新電話簿", +"CardDav Link" => "CardDav 聯結", +"Download" => "下載", +"Edit" => "編輯", +"Delete" => "刪除", +"Download contact" => "下載通訊錄", +"Delete contact" => "刪除通訊錄", +"Birthday" => "生日", +"Preferred" => "首選", +"Phone" => "電話", +"Type" => "類型", +"PO Box" => "通訊地址", +"Extended" => "分機", +"Street" => "街道", +"City" => "城市", +"Region" => "地區", +"Zipcode" => "郵遞區號", +"Country" => "國家", +"Add" => "添加", +"Addressbook" => "電話簿", +"New Addressbook" => "新電話簿", +"Edit Addressbook" => "編輯電話簿", +"Displayname" => "顯示名稱", +"Active" => "作用中", +"Save" => "儲存", +"Submit" => "提出", +"Cancel" => "取消" +); diff --git a/apps/contacts/lib/addressbook.php b/apps/contacts/lib/addressbook.php old mode 100644 new mode 100755 index 9061fa1914..78e94762f2 --- a/apps/contacts/lib/addressbook.php +++ b/apps/contacts/lib/addressbook.php @@ -44,7 +44,7 @@ class OC_Contacts_Addressbook{ * @return array */ public static function all($uid){ - $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE userid = ? ORDER BY displayname' ); + $stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE userid = ? ORDER BY displayname' ); $result = $stmt->execute(array($uid)); $addressbooks = array(); @@ -71,7 +71,7 @@ class OC_Contacts_Addressbook{ * @return associative array */ public static function find($id){ - $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE id = ?' ); $result = $stmt->execute(array($id)); return $result->fetchRow(); @@ -93,10 +93,10 @@ class OC_Contacts_Addressbook{ $uri = self::createURI($name, $uris ); - $stmt = OC_DB::prepare( 'INSERT INTO *PREFIX*contacts_addressbooks (userid,displayname,uri,description,ctag) VALUES(?,?,?,?,?)' ); + $stmt = OCP\DB::prepare( 'INSERT INTO *PREFIX*contacts_addressbooks (userid,displayname,uri,description,ctag) VALUES(?,?,?,?,?)' ); $result = $stmt->execute(array($userid,$name,$uri,$description,1)); - return OC_DB::insertid('*PREFIX*contacts_addressbooks'); + return OCP\DB::insertid('*PREFIX*contacts_addressbooks'); } /** @@ -110,10 +110,10 @@ class OC_Contacts_Addressbook{ public static function addFromDAVData($principaluri,$uri,$name,$description){ $userid = self::extractUserID($principaluri); - $stmt = OC_DB::prepare( 'INSERT INTO *PREFIX*contacts_addressbooks (userid,displayname,uri,description,ctag) VALUES(?,?,?,?,?)' ); + $stmt = OCP\DB::prepare( 'INSERT INTO *PREFIX*contacts_addressbooks (userid,displayname,uri,description,ctag) VALUES(?,?,?,?,?)' ); $result = $stmt->execute(array($userid,$name,$uri,$description,1)); - return OC_DB::insertid('*PREFIX*contacts_addressbooks'); + return OCP\DB::insertid('*PREFIX*contacts_addressbooks'); } /** @@ -134,7 +134,7 @@ class OC_Contacts_Addressbook{ $description = $addressbook['description']; } - $stmt = OC_DB::prepare( 'UPDATE *PREFIX*contacts_addressbooks SET displayname=?,description=?, ctag=ctag+1 WHERE id=?' ); + $stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_addressbooks SET displayname=?,description=?, ctag=ctag+1 WHERE id=?' ); $result = $stmt->execute(array($name,$description,$id)); return true; @@ -166,9 +166,9 @@ class OC_Contacts_Addressbook{ */ public static function activeIds($uid = null){ if(is_null($uid)){ - $uid = OC_User::getUser(); + $uid = OCP\USER::getUser(); } - $prefbooks = OC_Preferences::getValue($uid,'contacts','openaddressbooks',null); + $prefbooks = OCP\Config::getUserValue($uid,'contacts','openaddressbooks',null); if(!$prefbooks){ $addressbooks = OC_Contacts_Addressbook::all($uid); if(count($addressbooks) == 0){ @@ -176,7 +176,7 @@ class OC_Contacts_Addressbook{ $addressbooks = OC_Contacts_Addressbook::all($uid); } $prefbooks = $addressbooks[0]['id']; - OC_Preferences::setValue($uid,'contacts','openaddressbooks',$prefbooks); + OCP\Config::setUserValue($uid,'contacts','openaddressbooks',$prefbooks); } return explode(';',$prefbooks); } @@ -192,12 +192,12 @@ class OC_Contacts_Addressbook{ $ids_sql = join(',', array_fill(0, count($active), '?')); $prep = 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE id IN ('.$ids_sql.') ORDER BY displayname'; try { - $stmt = OC_DB::prepare( $prep ); + $stmt = OCP\DB::prepare( $prep ); $result = $stmt->execute($active); } catch(Exception $e) { - OC_Log::write('contacts','OC_Contacts_Addressbook:active:, exception: '.$e->getMessage(),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_Addressbook:active, ids: '.join(',', $active),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_Addressbook::active, SQL:'.$prep,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_Addressbook:active:, exception: '.$e->getMessage(),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_Addressbook:active, ids: '.join(',', $active),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_Addressbook::active, SQL:'.$prep,OCP\Util::DEBUG); } while( $row = $result->fetchRow()){ @@ -235,7 +235,7 @@ class OC_Contacts_Addressbook{ $openaddressbooks = self::cleanArray($openaddressbooks, false); sort($openaddressbooks, SORT_NUMERIC); // FIXME: I alway end up with a ';' prepending when imploding the array..? - OC_Preferences::setValue(OC_User::getUser(),'contacts','openaddressbooks',implode(';', $openaddressbooks)); + OCP\Config::setUserValue(OCP\USER::getUser(),'contacts','openaddressbooks',implode(';', $openaddressbooks)); return true; } @@ -246,7 +246,7 @@ class OC_Contacts_Addressbook{ * @return boolean */ public static function isActive($id){ - //OC_Log::write('contacts','OC_Contacts_Addressbook::isActive('.$id.'):'.in_array($id, self::activeIds()), OC_Log::DEBUG); + //OCP\Util::writeLog('contacts','OC_Contacts_Addressbook::isActive('.$id.'):'.in_array($id, self::activeIds()), OCP\Util::DEBUG); return in_array($id, self::activeIds()); } @@ -258,7 +258,7 @@ class OC_Contacts_Addressbook{ public static function delete($id){ // FIXME: There's no error checking at all. self::setActive($id, false); - $stmt = OC_DB::prepare( 'DELETE FROM *PREFIX*contacts_addressbooks WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_addressbooks WHERE id = ?' ); $stmt->execute(array($id)); $cards = OC_Contacts_VCard::all($id); @@ -275,7 +275,7 @@ class OC_Contacts_Addressbook{ * @return boolean */ public static function touch($id){ - $stmt = OC_DB::prepare( 'UPDATE *PREFIX*contacts_addressbooks SET ctag = ctag + 1 WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_addressbooks SET ctag = ctag + 1 WHERE id = ?' ); $stmt->execute(array($id)); return true; diff --git a/apps/contacts/lib/app.php b/apps/contacts/lib/app.php old mode 100644 new mode 100755 index 475d2c8dc2..330d5b21b7 --- a/apps/contacts/lib/app.php +++ b/apps/contacts/lib/app.php @@ -15,49 +15,16 @@ class OC_Contacts_App { public static $l10n; public static $categories; - /** - * Render templates/part.details to json output - * @param int $id of contact - * @param Sabre_VObject_Component $vcard to render - */ - public static function renderDetails($id, $vcard, $new=false) { - $property_types = self::getAddPropertyOptions(); - $adr_types = self::getTypesOfProperty('ADR'); - $phone_types = self::getTypesOfProperty('TEL'); - $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); - $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); - $maxUploadFilesize = min($upload_max_filesize, $post_max_size); - - $freeSpace=OC_Filesystem::free_space('/'); - $freeSpace=max($freeSpace,0); - $maxUploadFilesize = min($maxUploadFilesize ,$freeSpace); - - $details = OC_Contacts_VCard::structureContact($vcard); - $name = $details['FN'][0]['value']; - $t = $new ? 'part.contact' : 'part.details'; - $tmpl = new OC_Template('contacts',$t); - $tmpl->assign('details',$details); - $tmpl->assign('id',$id); - $tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize); - $tmpl->assign( 'uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); - $tmpl->assign('property_types',$property_types); - $tmpl->assign('adr_types',$adr_types); - $tmpl->assign('phone_types',$phone_types); - $page = $tmpl->fetchPage(); - - OC_JSON::success(array('data' => array( 'id' => $id, 'name' => $name, 'page' => $page ))); - } - public static function getAddressbook($id) { $addressbook = OC_Contacts_Addressbook::find( $id ); - if( $addressbook === false || $addressbook['userid'] != OC_User::getUser()) { + if( $addressbook === false || $addressbook['userid'] != OCP\USER::getUser()) { if ($addressbook === false) { - OC_Log::write('contacts', 'Addressbook not found: '. $id, OC_Log::ERROR); - OC_JSON::error(array('data' => array( 'message' => self::$l10n->t('Addressbook not found.')))); + OCP\Util::writeLog('contacts', 'Addressbook not found: '. $id, OCP\Util::ERROR); + OCP\JSON::error(array('data' => array( 'message' => self::$l10n->t('Addressbook not found.')))); } else { - OC_Log::write('contacts', 'Addressbook('.$id.') is not from '.OC_User::getUser(), OC_Log::ERROR); - OC_JSON::error(array('data' => array( 'message' => self::$l10n->t('This is not your addressbook.')))); + OCP\Util::writeLog('contacts', 'Addressbook('.$id.') is not from '.OCP\USER::getUser(), OCP\Util::ERROR); + OCP\JSON::error(array('data' => array( 'message' => self::$l10n->t('This is not your addressbook.')))); } exit(); } @@ -67,8 +34,8 @@ class OC_Contacts_App { public static function getContactObject($id) { $card = OC_Contacts_VCard::find( $id ); if( $card === false ) { - OC_Log::write('contacts', 'Contact could not be found: '.$id, OC_Log::ERROR); - OC_JSON::error(array('data' => array( 'message' => self::$l10n->t('Contact could not be found.').' '.$id))); + OCP\Util::writeLog('contacts', 'Contact could not be found: '.$id, OCP\Util::ERROR); + OCP\JSON::error(array('data' => array( 'message' => self::$l10n->t('Contact could not be found.').' '.$id))); exit(); } @@ -86,13 +53,13 @@ class OC_Contacts_App { $vcard = OC_VObject::parse($card['carddata']); // Try to fix cards with missing 'N' field from pre ownCloud 4. Hot damn, this is ugly... if(!is_null($vcard) && !$vcard->__isset('N')) { - $appinfo = OC_App::getAppInfo('contacts'); - if($appinfo['version'] >= 5) { - OC_Log::write('contacts','OC_Contacts_App::getContactVCard. Deprecated check for missing N field', OC_Log::DEBUG); + $version = OCP\App::getAppVersion('contacts'); + if($version >= 5) { + OCP\Util::writeLog('contacts','OC_Contacts_App::getContactVCard. Deprecated check for missing N field', OCP\Util::DEBUG); } - OC_Log::write('contacts','getContactVCard, Missing N field', OC_Log::DEBUG); + OCP\Util::writeLog('contacts','getContactVCard, Missing N field', OCP\Util::DEBUG); if($vcard->__isset('FN')) { - OC_Log::write('contacts','getContactVCard, found FN field: '.$vcard->__get('FN'), OC_Log::DEBUG); + OCP\Util::writeLog('contacts','getContactVCard, found FN field: '.$vcard->__get('FN'), OCP\Util::DEBUG); $n = implode(';', array_reverse(array_slice(explode(' ', $vcard->__get('FN')), 0, 2))).';;;'; $vcard->setString('N', $n); OC_Contacts_VCard::edit( $id, $vcard); @@ -149,10 +116,17 @@ class OC_Contacts_App { 'WORK' => $l->t('Work'), 'TEXT' => $l->t('Text'), 'VOICE' => $l->t('Voice'), + 'MSG' => $l->t('Message'), 'FAX' => $l->t('Fax'), 'VIDEO' => $l->t('Video'), 'PAGER' => $l->t('Pager'), ); + case 'EMAIL': + return array( + 'WORK' => $l->t('Work'), + 'HOME' => $l->t('Home'), + 'INTERNET' => $l->t('Internet'), + ); } } @@ -171,7 +145,7 @@ class OC_Contacts_App { */ public static function scanCategories($vccontacts = null) { if (is_null($vccontacts)) { - $vcaddressbooks = OC_Contacts_Addressbook::all(OC_User::getUser()); + $vcaddressbooks = OC_Contacts_Addressbook::all(OCP\USER::getUser()); if(count($vcaddressbooks) > 0) { $vcaddressbookids = array(); foreach($vcaddressbooks as $vcaddressbook) { @@ -202,7 +176,7 @@ class OC_Contacts_App { $rev = $contact->getAsString('REV'); if ($rev) { $rev = DateTime::createFromFormat(DateTime::W3C, $rev); - OC_Response::setLastModifiedHeader($rev); + OCP\Response::setLastModifiedHeader($rev); } } } diff --git a/apps/contacts/lib/hooks.php b/apps/contacts/lib/hooks.php old mode 100644 new mode 100755 index b858c4a5a4..e3d5df3d51 --- a/apps/contacts/lib/hooks.php +++ b/apps/contacts/lib/hooks.php @@ -40,8 +40,8 @@ class OC_Contacts_Hooks{ } static public function getCalenderSources($parameters) { - $base_url = OC_Helper::linkTo('calendar', 'ajax/events.php').'?calendar_id='; - foreach(OC_Contacts_Addressbook::all(OC_User::getUser()) as $addressbook) { + $base_url = OCP\Util::linkTo('calendar', 'ajax/events.php').'?calendar_id='; + foreach(OC_Contacts_Addressbook::all(OCP\USER::getUser()) as $addressbook) { $parameters['sources'][] = array( 'url' => $base_url.'birthday_'. $addressbook['id'], @@ -64,6 +64,9 @@ class OC_Contacts_Hooks{ OC_Contacts_App::getAddressbook($aid); foreach(OC_Contacts_VCard::all($aid) as $card){ $vcard = OC_VObject::parse($card['carddata']); + if (!$vcard) { + continue; + } $birthday = $vcard->BDAY; if ($birthday) { $date = new DateTime($birthday); diff --git a/apps/contacts/lib/search.php b/apps/contacts/lib/search.php old mode 100644 new mode 100755 index 31d8542091..144138a7c2 --- a/apps/contacts/lib/search.php +++ b/apps/contacts/lib/search.php @@ -1,8 +1,8 @@ 0){ - $link = OC_Helper::linkTo('contacts', 'index.php').'?id='.urlencode($vcard['id']); + $link = OCP\Util::linkTo('contacts', 'index.php').'?id='.urlencode($vcard['id']); $results[]=new OC_Search_Result($vcard['fullname'],'', $link,$l->t('Contact'));//$name,$text,$link,$type } } diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php old mode 100644 new mode 100755 index 90b037f76e..91ae3a7514 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -53,20 +53,20 @@ class OC_Contacts_VCard{ $id_sql = join(',', array_fill(0, count($id), '?')); $prep = 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid IN ('.$id_sql.') ORDER BY fullname'; try { - $stmt = OC_DB::prepare( $prep ); + $stmt = OCP\DB::prepare( $prep ); $result = $stmt->execute($id); } catch(Exception $e) { - OC_Log::write('contacts','OC_Contacts_VCard:all:, exception: '.$e->getMessage(),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_VCard:all, ids: '.join(',', $id),OC_Log::DEBUG); - OC_Log::write('contacts','SQL:'.$prep,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard:all:, exception: '.$e->getMessage(),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard:all, ids: '.join(',', $id),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','SQL:'.$prep,OCP\Util::DEBUG); } } elseif($id) { try { - $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? ORDER BY fullname' ); + $stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? ORDER BY fullname' ); $result = $stmt->execute(array($id)); } catch(Exception $e) { - OC_Log::write('contacts','OC_Contacts_VCard:all:, exception: '.$e->getMessage(),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_VCard:all, ids: '. $id,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard:all:, exception: '.$e->getMessage(),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard:all, ids: '. $id,OCP\Util::DEBUG); } } $cards = array(); @@ -85,7 +85,7 @@ class OC_Contacts_VCard{ * @return associative array */ public static function find($id){ - $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE id = ?' ); $result = $stmt->execute(array($id)); return $result->fetchRow(); @@ -98,7 +98,7 @@ class OC_Contacts_VCard{ * @return associative array */ public static function findWhereDAVDataIs($aid,$uri){ - $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' ); + $stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' ); $result = $stmt->execute(array($aid,$uri)); return $result->fetchRow(); @@ -141,10 +141,38 @@ class OC_Contacts_VCard{ } /** - * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) + * @brief Checks if a contact with the same UID already exist in the address book. + * @param $aid Address book ID. + * @param $uid UID (passed by reference). + * @returns true if the UID has been changed. + */ + protected static function trueUID($aid, &$uid) { + $stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' ); + $uri = $uid.'.vcf'; + $result = $stmt->execute(array($aid,$uri)); + if($result->numRows() > 0){ + while(true) { + $tmpuid = substr(md5(rand().time()),0,10); + $uri = $tmpuid.'.vcf'; + $result = $stmt->execute(array($aid,$uri)); + if($result->numRows() > 0){ + continue; + } else { + $uid = $tmpuid; + return true; + } + } + } else { + return false; + } + } + + /** + * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing. + * @param aid Address book id. * @param vcard An OC_VObject of type VCARD (passed by reference). */ - protected static function updateValuesFromAdd(&$vcard) { // any suggestions for a better method name? ;-) + protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-) $stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE'); $typeprops = array('ADR', 'TEL', 'EMAIL'); $upgrade = false; @@ -153,7 +181,7 @@ class OC_Contacts_VCard{ // Add version if needed if($version && $version < '3.0') { $upgrade = true; - OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Updating from version: '.$version,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Updating from version: '.$version,OCP\Util::DEBUG); } foreach($vcard->children as &$property){ // Decode string properties and remove obsolete properties. @@ -162,9 +190,9 @@ class OC_Contacts_VCard{ } // Fix format of type parameters. if($upgrade && in_array($property->name, $typeprops)) { - OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. before: '.$property->serialize(),OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. before: '.$property->serialize(),OCP\Util::DEBUG); self::formatPropertyTypes($property); - OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. after: '.$property->serialize(),OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. after: '.$property->serialize(),OCP\Util::DEBUG); } if($property->name == 'FN'){ $fn = $property->value; @@ -194,7 +222,7 @@ class OC_Contacts_VCard{ $fn = 'Unknown Name'; } $vcard->setString('FN', $fn); - OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn,OCP\Util::DEBUG); } if(!$n || $n = ';;;;'){ // Fix missing 'N' field. Ugly hack ahead ;-) $slice = array_reverse(array_slice(explode(' ', $fn), 0, 2)); // Take 2 first name parts of 'FN' and reverse. @@ -203,19 +231,15 @@ class OC_Contacts_VCard{ } $n = implode(';', $slice).';;;'; $vcard->setString('N', $n); - OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n,OCP\Util::DEBUG); } if(!$uid) { $vcard->setUID(); - OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid,OC_Log::DEBUG); + $uid = $vcard->getAsString('UID'); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid,OCP\Util::DEBUG); } - $vcard->setString('VERSION','3.0'); - // Add product ID is missing. - $prodid = trim($vcard->getAsString('PRODID')); - if(!$prodid) { - $appinfo = OC_App::getAppInfo('contacts'); - $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appinfo['version'].'//EN'; - $vcard->setString('PRODID', $prodid); + if(self::trueUID($aid, $uid)) { + $vcard->setString('UID', $uid); } $now = new DateTime; $vcard->setString('REV', $now->format(DateTime::W3C)); @@ -228,19 +252,30 @@ class OC_Contacts_VCard{ * @param string $uri the uri of the card, default based on the UID * @return insertid on success or null if no card. */ - public static function add($aid, OC_VObject $card, $uri=null){ + public static function add($aid, OC_VObject $card, $uri=null, $isnew=false){ if(is_null($card)){ - OC_Log::write('contacts','OC_Contacts_VCard::add. No vCard supplied', OC_Log::ERROR); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::add. No vCard supplied', OCP\Util::ERROR); return null; }; - OC_Contacts_App::loadCategoriesFromVCard($card); + if(!$isnew) { + OC_Contacts_App::loadCategoriesFromVCard($card); + self::updateValuesFromAdd($aid, $card); + } - self::updateValuesFromAdd($card); + $card->setString('VERSION','3.0'); + // Add product ID is missing. + $prodid = trim($card->getAsString('PRODID')); + if(!$prodid) { + $appinfo = OCP\App::getAppInfo('contacts'); + $appversion = OCP\App::getAppVersion('contacts'); + $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN'; + $card->setString('PRODID', $prodid); + } $fn = $card->getAsString('FN'); if (empty($fn)) { - $fn = null; + $fn = ''; } if (!$uri) { @@ -249,9 +284,9 @@ class OC_Contacts_VCard{ } $data = $card->serialize(); - $stmt = OC_DB::prepare( 'INSERT INTO *PREFIX*contacts_cards (addressbookid,fullname,carddata,uri,lastmodified) VALUES(?,?,?,?,?)' ); + $stmt = OCP\DB::prepare( 'INSERT INTO *PREFIX*contacts_cards (addressbookid,fullname,carddata,uri,lastmodified) VALUES(?,?,?,?,?)' ); $result = $stmt->execute(array($aid,$fn,$data,$uri,time())); - $newid = OC_DB::insertid('*PREFIX*contacts_cards'); + $newid = OCP\DB::insertid('*PREFIX*contacts_cards'); OC_Contacts_Addressbook::touch($aid); @@ -275,7 +310,7 @@ class OC_Contacts_VCard{ * @param array $objects An array of [id, carddata]. */ public static function updateDataByID($objects){ - $stmt = OC_DB::prepare( 'UPDATE *PREFIX*contacts_cards SET carddata = ?, lastmodified = ? WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_cards SET carddata = ?, lastmodified = ? WHERE id = ?' ); $now = new DateTime; foreach($objects as $object) { $vcard = OC_VObject::parse($object[1]); @@ -284,10 +319,10 @@ class OC_Contacts_VCard{ $data = $vcard->serialize(); try { $result = $stmt->execute(array($data,time(),$object[0])); - //OC_Log::write('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0].': '.$object[1],OC_Log::DEBUG); + //OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0].': '.$object[1],OCP\Util::DEBUG); } catch(Exception $e) { - OC_Log::write('contacts','OC_Contacts_VCard::updateDataByID:, exception: '.$e->getMessage(),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0],OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateDataByID:, exception: '.$e->getMessage(),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0],OCP\Util::DEBUG); } } } @@ -317,7 +352,7 @@ class OC_Contacts_VCard{ $card->setString('REV', $now->format(DateTime::W3C)); $data = $card->serialize(); - $stmt = OC_DB::prepare( 'UPDATE *PREFIX*contacts_cards SET fullname = ?,carddata = ?, lastmodified = ? WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_cards SET fullname = ?,carddata = ?, lastmodified = ? WHERE id = ?' ); $result = $stmt->execute(array($fn,$data,time(),$id)); OC_Contacts_Addressbook::touch($oldcard['addressbookid']); @@ -345,7 +380,7 @@ class OC_Contacts_VCard{ */ public static function delete($id){ // FIXME: Add error checking. - $stmt = OC_DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE id = ?' ); $stmt->execute(array($id)); return true; @@ -359,7 +394,7 @@ class OC_Contacts_VCard{ */ public static function deleteFromDAVData($aid,$uri){ // FIXME: Add error checking. Deleting a card gives an Kontact/Akonadi error. - $stmt = OC_DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri=?' ); + $stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri=?' ); $stmt->execute(array($aid,$uri)); OC_Contacts_Addressbook::touch($aid); @@ -414,11 +449,13 @@ class OC_Contacts_VCard{ $details = array(); foreach($object->children as $property){ $temp = self::structureProperty($property); - if(array_key_exists($property->name,$details)){ - $details[$property->name][] = $temp; - } - else{ - $details[$property->name] = array($temp); + if(!is_null($temp)) { + if(array_key_exists($property->name,$details)){ + $details[$property->name][] = $temp; + } + else{ + $details[$property->name] = array($temp); + } } } return $details; @@ -442,9 +479,15 @@ class OC_Contacts_VCard{ //$value = htmlspecialchars($value); if($property->name == 'ADR' || $property->name == 'N'){ $value = self::unescapeDelimiters($value); - }/* elseif($property->name == 'CATEGORIES') { - $value = self::unescapeDelimiters($value, ','); - }*/ + } elseif($property->name == 'BDAY') { + if(strpos($value, '-') === false) { + if(strlen($value) >= 8) { + $value = substr($value, 0, 4).'-'.substr($value, 4, 2).'-'.substr($value, 6, 2); + } else { + return null; // Badly malformed :-( + } + } + } $temp = array( 'name' => $property->name, 'value' => $value, @@ -460,7 +503,7 @@ class OC_Contacts_VCard{ } // NOTE: Apparently Sabre_VObject_Reader can't always deal with value list parameters // like TYPE=HOME,CELL,VOICE. Tanghus. - if ($property->name == 'TEL' && $parameter->name == 'TYPE'){ + if (in_array($property->name, array('TEL', 'EMAIL')) && $parameter->name == 'TYPE'){ if (isset($temp['parameters'][$parameter->name])){ $temp['parameters'][$parameter->name][] = $parameter->value; } @@ -488,23 +531,23 @@ class OC_Contacts_VCard{ $id_sql = join(',', array_fill(0, count($id), '?')); $prep = 'UPDATE *PREFIX*contacts_cards SET addressbookid = ? WHERE id IN ('.$id_sql.')'; try { - $stmt = OC_DB::prepare( $prep ); + $stmt = OCP\DB::prepare( $prep ); //$aid = array($aid); $vals = array_merge((array)$aid, $id); $result = $stmt->execute($vals); } catch(Exception $e) { - OC_Log::write('contacts','OC_Contacts_VCard::moveToAddressBook:, exception: '.$e->getMessage(),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_VCard::moveToAddressBook, ids: '.join(',', $vals),OC_Log::DEBUG); - OC_Log::write('contacts','SQL:'.$prep,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::moveToAddressBook:, exception: '.$e->getMessage(),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::moveToAddressBook, ids: '.join(',', $vals),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','SQL:'.$prep,OCP\Util::DEBUG); return false; } } else { try { - $stmt = OC_DB::prepare( 'UPDATE *PREFIX*contacts_cards SET addressbookid = ? WHERE id = ?' ); + $stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_cards SET addressbookid = ? WHERE id = ?' ); $result = $stmt->execute(array($aid, $id)); } catch(Exception $e) { - OC_Log::write('contacts','OC_Contacts_VCard::moveToAddressBook:, exception: '.$e->getMessage(),OC_Log::DEBUG); - OC_Log::write('contacts','OC_Contacts_VCard::moveToAddressBook, id: '.$id,OC_Log::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::moveToAddressBook:, exception: '.$e->getMessage(),OCP\Util::DEBUG); + OCP\Util::writeLog('contacts','OC_Contacts_VCard::moveToAddressBook, id: '.$id,OCP\Util::DEBUG); return false; } } diff --git a/apps/contacts/photo.php b/apps/contacts/photo.php old mode 100644 new mode 100755 index 298f1215e3..729c2dfbcf --- a/apps/contacts/photo.php +++ b/apps/contacts/photo.php @@ -9,17 +9,27 @@ */ // Init owncloud -require_once('../../lib/base.php'); -OC_Util::checkLoggedIn(); -OC_Util::checkAppEnabled('contacts'); + +OCP\User::checkLoggedIn(); +OCP\App::checkAppEnabled('contacts'); function getStandardImage(){ - OC_Response::setExpiresHeader('P10D'); - OC_Response::enableCaching(); - OC_Response::redirect(OC_Helper::imagePath('contacts', 'person_large.png')); + OCP\Response::setExpiresHeader('P10D'); + OCP\Response::enableCaching(); + OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person_large.png')); } -$id = $_GET['id']; +$id = isset($_GET['id']) ? $_GET['id'] : null; +$caching = isset($_GET['refresh']) ? 0 : null; + +if(is_null($id)) { + getStandardImage(); +} + +if(!extension_loaded('gd') || !function_exists('gd_info')) { + OCP\Util::writeLog('contacts','photo.php. GD module not installed',OCP\Util::DEBUG); + getStandardImage(); +} $contact = OC_Contacts_App::getContactVCard($id); $image = new OC_Image(); @@ -28,21 +38,21 @@ if(!$image) { } // invalid vcard if( is_null($contact)) { - OC_Log::write('contacts','photo.php. The VCard for ID '.$id.' is not RFC compatible',OC_Log::ERROR); + OCP\Util::writeLog('contacts','photo.php. The VCard for ID '.$id.' is not RFC compatible',OCP\Util::ERROR); } else { - OC_Response::enableCaching(); + OCP\Response::enableCaching($caching); OC_Contacts_App::setLastModifiedHeader($contact); // Photo :-) if($image->loadFromBase64($contact->getAsString('PHOTO'))) { // OK - OC_Response::setETagHeader(md5($contact->getAsString('PHOTO'))); + OCP\Response::setETagHeader(md5($contact->getAsString('PHOTO'))); } else // Logo :-/ if($image->loadFromBase64($contact->getAsString('LOGO'))) { // OK - OC_Response::setETagHeader(md5($contact->getAsString('LOGO'))); + OCP\Response::setETagHeader(md5($contact->getAsString('LOGO'))); } if ($image->valid()) { $max_size = 200; diff --git a/apps/contacts/settings.php b/apps/contacts/settings.php old mode 100644 new mode 100755 index b88128823a..c88fed0b4d --- a/apps/contacts/settings.php +++ b/apps/contacts/settings.php @@ -1,6 +1,6 @@ fetchPage(); ?> diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php old mode 100644 new mode 100755 index 256d10afc0..7d212e71ba --- a/apps/contacts/templates/index.php +++ b/apps/contacts/templates/index.php @@ -1,25 +1,19 @@ -
    inc("part.contacts"); ?>
-
-
- <?php echo $l->t('Add Contact'); ?> - <?php echo $l->t('Addressbooks'); ?> -
-
+
+
+ + +
+
t("Choose active Address Books"); ?>"> +
"> "; - $tmpl = new OC_Template('contacts', 'part.chooseaddressbook.rowfields'); + $tmpl = new OCP\Template('contacts', 'part.chooseaddressbook.rowfields'); $tmpl->assign('addressbook', $option_addressbooks[$i]); $tmpl->assign('active', OC_Contacts_Addressbook::isActive($option_addressbooks[$i]['id'])); $tmpl->printpage(); @@ -14,11 +14,12 @@ for($i = 0; $i < count($option_addressbooks); $i++){
t('New Address Book') ?> + t('Import from VCF') ?>
-

">

+

">

diff --git a/apps/contacts/templates/part.chooseaddressbook.rowfields.php b/apps/contacts/templates/part.chooseaddressbook.rowfields.php old mode 100644 new mode 100755 index 8518c1acd1..780920ea3c --- a/apps/contacts/templates/part.chooseaddressbook.rowfields.php +++ b/apps/contacts/templates/part.chooseaddressbook.rowfields.php @@ -1,5 +1,18 @@ -"; - echo ""; - echo "t("CardDav Link") . "\" class=\"action\">t("Download") . "\" class=\"action\">t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\">t("Delete") . "\" class=\"action\">"; + + " type="checkbox" onClick="Contacts.UI.Addressbooks.activation(this, )" > + + + + + + ');" title="t("CardDav Link"); ?>" class="svg action globe"> + + + " title="t("Download"); ?>" class="svg action download"> + + + " class="svg action edit" onclick="Contacts.UI.Addressbooks.editAddressbook(this, );"> + + + );" title="t("Delete"); ?>" class="svg action delete"> + diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php old mode 100644 new mode 100755 index c09c20fd4e..74522be37c --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -2,63 +2,44 @@ $id = isset($_['id']) ? $_['id'] : ''; ?>
- - -
- -
-
- -
- - -
+ - -
+
+ + +
+ +
+ + +
+
- - +
- - - - - - - - + + + + + + + +
@@ -69,54 +50,68 @@ $id = isset($_['id']) ? $_['id'] : '';
-
-
    - -
+ + + + +
-
-
    - -
+ +
-
+
-
- -
+
+ +
+ + +
@@ -128,6 +123,7 @@ $(document).ready(function(){ if(jsondata.status == 'success'){ $('#leftcontent li[data-id=""]').addClass('active'); Contacts.UI.Card.loadContact(jsondata.data); + Contacts.UI.loadHandlers(); } else{ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); diff --git a/apps/contacts/templates/part.contactphoto.php b/apps/contacts/templates/part.contactphoto.php old mode 100644 new mode 100755 index bcb2f75815..bddf4cc8a8 --- a/apps/contacts/templates/part.contactphoto.php +++ b/apps/contacts/templates/part.contactphoto.php @@ -2,9 +2,15 @@ $id = $_['id']; $wattr = isset($_['width'])?'width="'.$_['width'].'"':''; $hattr = isset($_['height'])?'height="'.$_['height'].'"':''; -$rand = isset($_['refresh'])?'&'.rand().'='.rand():''; +$rand = isset($_['refresh'])?'&refresh='.rand():''; ?> - src="?id=" /> + + src="?id=" /> diff --git a/apps/contacts/templates/part.contacts.php b/apps/contacts/templates/part.contacts.php index a6ac0f1096..225a1ef0cf 100644 --- a/apps/contacts/templates/part.contacts.php +++ b/apps/contacts/templates/part.contacts.php @@ -8,5 +8,5 @@ } } ?> -
  • +
  • diff --git a/apps/contacts/templates/part.cropphoto.php b/apps/contacts/templates/part.cropphoto.php old mode 100644 new mode 100755 index 5faa4aa6ac..599951d9a9 --- a/apps/contacts/templates/part.cropphoto.php +++ b/apps/contacts/templates/part.cropphoto.php @@ -1,7 +1,7 @@ - +
    + action=""> diff --git a/apps/contacts/templates/part.edit_address_dialog.php b/apps/contacts/templates/part.edit_address_dialog.php old mode 100644 new mode 100755 index 507a3acaa0..8b3425033c --- a/apps/contacts/templates/part.edit_address_dialog.php +++ b/apps/contacts/templates/part.edit_address_dialog.php @@ -15,7 +15,7 @@ foreach(isset($adr['parameters']['TYPE'])?array($adr['parameters']['TYPE']):arra
    diff --git a/apps/contacts/templates/part.edit_name_dialog.php b/apps/contacts/templates/part.edit_name_dialog.php old mode 100644 new mode 100755 index bb774b62bd..be45f9a5b0 --- a/apps/contacts/templates/part.edit_name_dialog.php +++ b/apps/contacts/templates/part.edit_name_dialog.php @@ -16,7 +16,7 @@ $addressbooks = isset($_['addressbooks'])?$_['addressbooks']:null;
    diff --git a/apps/contacts/templates/part.import.php b/apps/contacts/templates/part.import.php old mode 100644 new mode 100755 index 570eda9b07..2dac7dd0ca --- a/apps/contacts/templates/part.import.php +++ b/apps/contacts/templates/part.import.php @@ -6,9 +6,9 @@

    t('Please choose the addressbook'); ?>

    @@ -41,14 +46,14 @@ t( 'Name' ); ?> - + - Download" /> t('Download')?> + Download" /> t('Download')?> t( 'Size' ); ?> - t( 'Modified' ); ?>t('Delete all')?> <?php echo $l->t('Delete')?>" /> + t( 'Modified' ); ?>t('Delete all')?> <?php echo $l->t('Delete')?>" /> diff --git a/files/templates/part.breadcrumb.php b/apps/files/templates/part.breadcrumb.php old mode 100644 new mode 100755 similarity index 77% rename from files/templates/part.breadcrumb.php rename to apps/files/templates/part.breadcrumb.php index 16da6bb97b..41d2c5c8e7 --- a/files/templates/part.breadcrumb.php +++ b/apps/files/templates/part.breadcrumb.php @@ -1,6 +1,6 @@ -
    svg" data-dir='' style='background-image:url("")'> +
    svg" data-dir='' style='background-image:url("")'> ">
    \ No newline at end of file diff --git a/files/templates/part.list.php b/apps/files/templates/part.list.php old mode 100644 new mode 100755 similarity index 75% rename from files/templates/part.list.php rename to apps/files/templates/part.list.php index 5a5941fc7a..4506630c16 --- a/files/templates/part.list.php +++ b/apps/files/templates/part.list.php @@ -1,9 +1,9 @@ 200) $relative_date_color = 200; $name = str_replace('+','%20',urlencode($file['name'])); @@ -11,7 +11,7 @@ $directory = str_replace('+','%20',urlencode($file['directory'])); $directory = str_replace('%2F','/', $directory); ?> ' data-write=''> - + @@ -21,9 +21,13 @@ + + + + - + diff --git a/apps/files_archive/appinfo/app.php b/apps/files_archive/appinfo/app.php old mode 100644 new mode 100755 index 67809ec980..67376c4a03 --- a/apps/files_archive/appinfo/app.php +++ b/apps/files_archive/appinfo/app.php @@ -6,14 +6,8 @@ * See the COPYING-README file. */ -OC::$CLASSPATH['OC_Archive'] = 'apps/files_archive/lib/archive.php'; -OC::$CLASSPATH['Archive_Tar'] = '3rdparty/Archive/Tar.php'; -foreach(array('ZIP','TAR') as $type){ - OC::$CLASSPATH['OC_Archive_'.$type] = 'apps/files_archive/lib/'.strtolower($type).'.php'; -} - OC::$CLASSPATH['OC_Filestorage_Archive']='apps/files_archive/lib/storage.php'; -OC_Hook::connect('OC_Filesystem','get_mountpoint','OC_Filestorage_Archive','autoMount'); +OCP\Util::connectHook('OC_Filesystem','get_mountpoint','OC_Filestorage_Archive','autoMount'); -OC_Util::addScript( 'files_archive', 'archive' ); +OCP\Util::addscript( 'files_archive', 'archive' ); diff --git a/apps/files_archive/lib/storage.php b/apps/files_archive/lib/storage.php old mode 100644 new mode 100755 index 700d963304..086a338db2 --- a/apps/files_archive/lib/storage.php +++ b/apps/files_archive/lib/storage.php @@ -104,7 +104,7 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ } public function touch($path, $mtime=null){ if(is_null($mtime)){ - $tmpFile=OC_Helper::tmpFile(); + $tmpFile=OCP\Files::tmpFile(); $this->archive->extractFile($path,$tmpFile); $this->archive->addfile($path,$tmpFile); }else{ diff --git a/apps/files_archive/tests/storage.php b/apps/files_archive/tests/storage.php old mode 100644 new mode 100755 index 4d0a83356b..7ebcce4ac6 --- a/apps/files_archive/tests/storage.php +++ b/apps/files_archive/tests/storage.php @@ -13,7 +13,7 @@ class Test_Filestorage_Archive_Zip extends Test_FileStorage { private $tmpFile; public function setUp(){ - $this->tmpFile=OC_Helper::tmpFile('.zip'); + $this->tmpFile=OCP\Files::tmpFile('.zip'); $this->instance=new OC_Filestorage_Archive(array('archive'=>$this->tmpFile)); } diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php old mode 100644 new mode 100755 index 68c445d5d7..679d0b95ed --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,14 +6,14 @@ OC::$CLASSPATH['OC_FileProxy_Encryption'] = 'apps/files_encryption/lib/proxy.php OC_FileProxy::register(new OC_FileProxy_Encryption()); -OC_Hook::connect('OC_User','post_login','OC_Crypt','loginListener'); +OCP\Util::connectHook('OC_User','post_login','OC_Crypt','loginListener'); stream_wrapper_register('crypt','OC_CryptStream'); -if(!isset($_SESSION['enckey']) and OC_User::isLoggedIn()){//force the user to re-loggin if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) - OC_User::logout(); +if(!isset($_SESSION['enckey']) and OCP\User::isLoggedIn()){//force the user to re-loggin if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) + OCP\User::logout(); header("Location: ".OC::$WEBROOT.'/'); exit(); } -OC_App::registerAdmin('files_encryption', 'settings'); +OCP\App::registerAdmin('files_encryption', 'settings'); diff --git a/apps/files_encryption/js/settings.js b/apps/files_encryption/js/settings.js index adbf0c8724..37d62265c9 100644 --- a/apps/files_encryption/js/settings.js +++ b/apps/files_encryption/js/settings.js @@ -16,4 +16,9 @@ $(document).ready(function(){ var blackList=$('#encryption_blacklist').val().join(','); OC.AppConfig.setValue('files_encryption','type_blacklist',blackList); } + + $('#enbale_encryption').change(function(){ + var checked=$('#enbale_encryption').is(':checked'); + OC.AppConfig.setValue('files_encryption','enable_encryption',(checked)?'true':'false'); + }) }) \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php old mode 100644 new mode 100755 index 8cf9451c63..37eaedc3fc --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -91,8 +91,8 @@ class OC_Crypt { } public static function changekeypasscode($oldPassword, $newPassword) { - if(OC_User::isLoggedIn()){ - $username=OC_USER::getUser(); + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); $view=new OC_FilesystemView('/'.$username); // read old key diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php old mode 100644 new mode 100755 index 21fa38e4b5..d6643f3268 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -33,6 +33,8 @@ class OC_CryptStream{ private $path; private $readBuffer;//for streams that dont support seeking private $meta=array();//header/meta for source stream + private $count; + private $writeCache; public function stream_open($path, $mode, $options, &$opened_path){ $path=str_replace('crypt://','',$path); @@ -41,12 +43,12 @@ class OC_CryptStream{ $this->path=self::$sourceStreams[basename($path)]['path']; }else{ $this->path=$path; - OC_Log::write('files_encryption','open encrypted '.$path. ' in '.$mode,OC_Log::DEBUG); + OCP\Util::writeLog('files_encryption','open encrypted '.$path. ' in '.$mode,OCP\Util::DEBUG); OC_FileProxy::$enabled=false;//disable fileproxies so we can open the source file $this->source=OC_FileSystem::fopen($path,$mode); OC_FileProxy::$enabled=true; if(!is_resource($this->source)){ - OC_Log::write('files_encryption','failed to open '.$path,OC_Log::ERROR); + OCP\Util::writeLog('files_encryption','failed to open '.$path,OCP\Util::ERROR); } } if(is_resource($this->source)){ @@ -56,6 +58,7 @@ class OC_CryptStream{ } public function stream_seek($offset, $whence=SEEK_SET){ + $this->flush(); fseek($this->source,$offset,$whence); } @@ -66,8 +69,9 @@ class OC_CryptStream{ public function stream_read($count){ //$count will always be 8192 https://bugs.php.net/bug.php?id=21641 //This makes this function a lot simpler but will breake everything the moment it's fixed + $this->writeCache=''; if($count!=8192){ - OC_Log::write('files_encryption','php bug 21641 no longer holds, decryption will not work',OC_Log::FATAL); + OCP\Util::writeLog('files_encryption','php bug 21641 no longer holds, decryption will not work',OCP\Util::FATAL); die(); } $data=fread($this->source,8192); @@ -83,6 +87,10 @@ class OC_CryptStream{ $length=strlen($data); $written=0; $currentPos=ftell($this->source); + if($this->writeCache){ + $data=$this->writeCache.$data; + $this->writeCache=''; + } if($currentPos%8192!=0){ //make sure we always start on a block start fseek($this->source,-($currentPos%8192),SEEK_CUR); @@ -90,21 +98,17 @@ class OC_CryptStream{ fseek($this->source,-($currentPos%8192),SEEK_CUR); $block=OC_Crypt::decrypt($encryptedBlock); $data=substr($block,0,$currentPos%8192).$data; + fseek($this->source,-($currentPos%8192),SEEK_CUR); } while(strlen($data)>0){ if(strlen($data)<8192){ - //fetch the current data in that block and append it to the input so we always write entire blocks - $oldPos=ftell($this->source); - $encryptedBlock=fread($this->source,8192); - fseek($this->source,$oldPos); - if($encryptedBlock){ - $block=OC_Crypt::decrypt($encryptedBlock); - $data.=substr($block,strlen($data)); - } + $this->writeCache=$data; + $data=''; + }else{ + $encrypted=OC_Crypt::encrypt(substr($data,0,8192)); + fwrite($this->source,$encrypted); + $data=substr($data,8192); } - $encrypted=OC_Crypt::encrypt(substr($data,0,8192)); - fwrite($this->source,$encrypted); - $data=substr($data,8192); } return $length; } @@ -138,8 +142,19 @@ class OC_CryptStream{ return feof($this->source); } + private function flush(){ + if($this->writeCache){ + $encrypted=OC_Crypt::encrypt($this->writeCache); + fwrite($this->source,$encrypted); + $this->writeCache=''; + } + } + public function stream_close(){ - OC_FileCache::put($this->path,array('encrypted'=>true)); + $this->flush(); + if($this->meta['mode']!='r' and $this->meta['mode']!='rb'){ + OC_FileCache::put($this->path,array('encrypted'=>true)); + } return fclose($this->source); } -} \ No newline at end of file +} diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php old mode 100644 new mode 100755 index a0de411a7b..06f963fc98 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -27,7 +27,6 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ private static $blackList=null; //mimetypes blacklisted from encryption - private static $metaData=array(); //metadata cache private static $enableEncryption=null; /** @@ -36,14 +35,14 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ * @return bool */ private static function shouldEncrypt($path){ - if(is_null($this->enableEncryption)){ - $this->enableEncryption=(OC_Appconfig::getValue('files_encryption','enabled','true')=='true'); + if(is_null(self::$enableEncryption)){ + self::$enableEncryption=(OCP\Config::getAppValue('files_encryption','enable_encryption','true')=='true'); } - if(!$this->enableEncryption){ + if(!self::$enableEncryption){ return false; } if(is_null(self::$blackList)){ - self::$blackList=explode(',',OC_Appconfig::getValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); + self::$blackList=explode(',',OCP\Config::getAppValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); } if(self::isEncrypted($path)){ return true; @@ -60,13 +59,8 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ * @return bool */ private static function isEncrypted($path){ - if(isset(self::$metaData[$path])){ - $metadata=self::$metaData[$path]; - }else{ - $metadata=OC_FileCache::getCached($path); - self::$metaData[$path]=$metadata; - } - return (bool)$metadata['encrypted']; + $metadata=OC_FileCache::getCached($path); + return isset($metadata['encrypted']) and (bool)$metadata['encrypted']; } public function preFile_put_contents($path,&$data){ @@ -96,14 +90,9 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ if(OC_Filesystem::file_exists($path) and OC_Filesystem::filesize($path)>0){ //first encrypt the target file so we don't end up with a half encrypted file - OC_Log::write('files_encryption','Decrypting '.$path.' before writing',OC_Log::DEBUG); + OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing',OCP\Util::DEBUG); $tmp=fopen('php://temp'); - while(!feof($result)){ - $chunk=fread($result,8192); - if($chunk){ - fwrite($tmp,$chunk); - } - } + OCP\Files::streamCopy($result,$tmp); fclose($result); OC_Filesystem::file_put_contents($path,$tmp); fclose($tmp); @@ -115,7 +104,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ public function postGetMimeType($path,$mime){ if(self::isEncrypted($path)){ - $mime=OC_Helper::getMimeType('crypt://'.$path,'w'); + $mime=OCP\Files::getMimeType('crypt://'.$path,'w'); } return $mime; } diff --git a/apps/files_encryption/settings.php b/apps/files_encryption/settings.php old mode 100644 new mode 100755 index 396ad1ba78..0a0d4d1abb --- a/apps/files_encryption/settings.php +++ b/apps/files_encryption/settings.php @@ -6,11 +6,13 @@ * See the COPYING-README file. */ -$tmpl = new OC_Template( 'files_encryption', 'settings'); -$blackList=explode(',',OC_Appconfig::getValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); +$tmpl = new OCP\Template( 'files_encryption', 'settings'); +$blackList=explode(',',OCP\Config::getAppValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); +$enabled=(OCP\Config::getAppValue('files_encryption','enable_encryption','true')=='true'); $tmpl->assign('blacklist',$blackList); +$tmpl->assign('encryption_enabled',$enabled); -OC_Util::addScript('files_encryption','settings'); -OC_Util::addScript('core','multiselect'); +OCP\Util::addscript('files_encryption','settings'); +OCP\Util::addscript('core','multiselect'); return $tmpl->fetchPage(); diff --git a/apps/files_encryption/templates/settings.php b/apps/files_encryption/templates/settings.php index 724a03836a..25b5a06f56 100644 --- a/apps/files_encryption/templates/settings.php +++ b/apps/files_encryption/templates/settings.php @@ -7,5 +7,6 @@ + > diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php old mode 100644 new mode 100755 index 1309325671..cf24a225d2 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -27,16 +27,27 @@ class Test_Encryption extends UnitTestCase { $this->assertNotEqual($encrypted,$source); $this->assertEqual($decrypted,$source); - $tmpFileEncrypted=OC_Helper::tmpFile(); + $tmpFileEncrypted=OCP\Files::tmpFile(); OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); $encrypted=file_get_contents($tmpFileEncrypted); $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); $this->assertNotEqual($encrypted,$source); $this->assertEqual($decrypted,$source); - $tmpFileDecrypted=OC_Helper::tmpFile(); + $tmpFileDecrypted=OCP\Files::tmpFile(); OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); $decrypted=file_get_contents($tmpFileDecrypted); $this->assertEqual($decrypted,$source); + + $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; + $source=file_get_contents($file); //binary file + $encrypted=OC_Crypt::encrypt($source,$key); + $decrypted=OC_Crypt::decrypt($encrypted,$key); + $this->assertEqual($decrypted,$source); + + $encrypted=OC_Crypt::blockEncrypt($source,$key); + $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); + $this->assertEqual($decrypted,$source); + } } diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php new file mode 100644 index 0000000000..0450de82ac --- /dev/null +++ b/apps/files_encryption/tests/proxy.php @@ -0,0 +1,46 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_CryptProxy extends UnitTestCase { + + public function setUp(){ + //set testing key + $_SESSION['enckey']=md5(time()); + + //clear all proxies and hooks so we can do clean testing + OC_FileProxy::clearProxies(); + OC_Hook::clear('OC_Filesystem'); + + //enable only the encryption hook + OC_FileProxy::register(new OC_FileProxy_Encryption()); + + //set up temporary storage + OC_Filesystem::clearMounts(); + OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); + + //set up the users home folder in the temp storage + $rootView=new OC_FilesystemView(''); + $rootView->mkdir('/'.OC_User::getUser()); + $rootView->mkdir('/'.OC_User::getUser().'/files'); + } + + public function testSimple(){ + $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; + $original=file_get_contents($file); + + OC_Filesystem::file_put_contents('/file',$original); + + OC_FileProxy::$enabled=false; + $stored=OC_Filesystem::file_get_contents('/file'); + OC_FileProxy::$enabled=true; + + $fromFile=OC_Filesystem::file_get_contents('/file'); + $this->assertNotEqual($original,$stored); + $this->assertEqual($original,$fromFile); + } +} diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php old mode 100644 new mode 100755 index 578b091a36..b23805d60b --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -22,7 +22,7 @@ class Test_CryptStream extends UnitTestCase { $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; $source=fopen($file,'r'); $target=$this->getStream('test2','w'); - OC_Helper::streamCopy($source,$target); + OCP\Files::streamCopy($source,$target); fclose($target); fclose($source); @@ -44,7 +44,7 @@ class Test_CryptStream extends UnitTestCase { $id=uniqid(); } if(!isset($this->tmpFiles[$id])){ - $file=OC_Helper::tmpFile(); + $file=OCP\Files::tmpFile(); $this->tmpFiles[$id]=$file; }else{ $file=$this->tmpFiles[$id]; diff --git a/apps/files_external/lib/ftp.php b/apps/files_external/lib/ftp.php old mode 100644 new mode 100755 index 802446b4fd..981eeab58b --- a/apps/files_external/lib/ftp.php +++ b/apps/files_external/lib/ftp.php @@ -108,7 +108,7 @@ class OC_FileStorage_FTP extends OC_Filestorage_Common{ }else{ $ext=''; } - $tmpFile=OC_Helper::tmpFile($ext); + $tmpFile=OCP\Files::tmpFile($ext); OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); if($this->file_exists($path)){ $this->getFile($path,$tmpFile); diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php old mode 100644 new mode 100755 index 0d6db1987f..d2285a6d82 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -29,6 +29,8 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { private $sig_method; private $entries; + private static $tempFiles = array(); + public function __construct($arguments) { $consumer_key = isset($arguments['consumer_key']) ? $arguments['consumer_key'] : 'anonymous'; $consumer_secret = isset($arguments['consumer_secret']) ? $arguments['consumer_secret'] : 'anonymous'; @@ -38,11 +40,11 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $this->entries = array(); } - private function sendRequest($feedUri, $http_method, $isDownload = false, $postData = null) { - $feedUri = trim($feedUri); + private function sendRequest($uri, $httpMethod, $postData = null, $extraHeaders = null, $isDownload = false, $returnHeaders = false, $isContentXML = true) { + $uri = trim($uri); // create an associative array from each key/value url query param pair. $params = array(); - $pieces = explode('?', $feedUri); + $pieces = explode('?', $uri); if (isset($pieces[1])) { $params = explode_assoc('=', '&', $pieces[1]); } @@ -51,16 +53,22 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { foreach ($params as $key => $value) { $tempStr .= '&' . urlencode($key) . '=' . urlencode($value); } - $feedUri = preg_replace('/&/', '?', $tempStr, 1); - $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $http_method, $feedUri, $params); + $uri = preg_replace('/&/', '?', $tempStr, 1); + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $httpMethod, $uri, $params); $request->sign_request($this->sig_method, $this->consumer, $this->oauth_token); $auth_header = $request->to_header(); - $headers = array($auth_header, 'Content-Type: application/atom+xml', 'GData-Version: 3.0'); - $curl = curl_init($feedUri); + $headers = array($auth_header, 'GData-Version: 3.0'); + if ($isContentXML) { + $headers = array_merge($headers, array('Content-Type: application/atom+xml')); + } + if (is_array($extraHeaders)) { + $headers = array_merge($headers, $extraHeaders); + } + $curl = curl_init($uri); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_FAILONERROR, false); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - switch ($http_method) { + switch ($httpMethod) { case 'GET': curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); break; @@ -72,30 +80,50 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { case 'PUT': $headers[] = 'If-Match: *'; curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpMethod); curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); break; case 'DELETE': $headers[] = 'If-Match: *'; curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpMethod); break; default: curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } if ($isDownload) { $tmpFile = OC_Helper::tmpFile(); - $fp = fopen($tmpFile, 'w'); - curl_setopt($curl, CURLOPT_FILE, $fp); - curl_exec($curl); - curl_close($curl); - return $tmpFile; + $handle = fopen($tmpFile, 'w'); + curl_setopt($curl, CURLOPT_FILE, $handle); + } + if ($returnHeaders) { + curl_setopt($curl, CURLOPT_HEADER, true); } $result = curl_exec($curl); + $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $dom = new DOMDocument(); - $dom->loadXML($result); - return $dom; + if ($result) { + // TODO https://developers.google.com/google-apps/documents-list/#handling_api_errors + // TODO Log error messages + if ($httpCode <= 308) { + if ($isDownload) { + return $tmpFile; + } else { + return $result; + } + } + } + return false; + } + + private function getFeed($feedUri, $httpMethod, $postData = null) { + $result = $this->sendRequest($feedUri, $httpMethod, $postData); + if ($result) { + $dom = new DOMDocument(); + $dom->loadXML($result); + return $dom; + } + return false; } private function getResource($path) { @@ -106,14 +134,14 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { // Strip the file extension; file could be a native Google Docs resource if ($pos = strpos($file, '.')) { $title = substr($file, 0, $pos); - $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$title, 'GET'); + $dom = $this->getFeed('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$title, 'GET'); // Check if request was successful and entry exists if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) { $this->entries[$file] = $entry; return $entry; } } - $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$file, 'GET'); + $dom = $this->getFeed('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$file, 'GET'); // Check if request was successful and entry exists if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) { $this->entries[$file] = $entry; @@ -143,15 +171,15 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { public function mkdir($path) { - $dir = dirname($path); + $collection = dirname($path); // Check if path parent is root directory - if ($dir == '/' || $dir == '\.' || $dir == '.') { - $feedUri = 'https://docs.google.com/feeds/default/private/full'; + if ($collection == '/' || $collection == '\.' || $collection == '.') { + $uri = 'https://docs.google.com/feeds/default/private/full'; // Get parent content link } else if ($dom = $this->getResource(basename($dir))) { - $feedUri = $dom->getElementsByTagName('content')->item(0)->getAttribute('src'); + $uri = $dom->getElementsByTagName('content')->item(0)->getAttribute('src'); } - if (isset($feedUri)) { + if (isset($uri)) { $title = basename($path); // Construct post data $postData = ''; @@ -159,7 +187,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $postData .= ''; $postData .= ''.$title.''; $postData .= ''; - if ($dom = $this->sendRequest($feedUri, 'POST', $postData)) { + if ($dom = $this->sendRequest($uri, 'POST', $postData)) { return true; } } @@ -182,7 +210,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { } $files = array(); while ($next) { - $dom = $this->sendRequest($next, 'GET'); + $dom = $this->getFeed($next, 'GET'); $links = $dom->getElementsByTagName('link'); foreach ($links as $link) { if ($link->getAttribute('rel') == 'next') { @@ -286,72 +314,170 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $links = $entry->getElementsByTagName('link'); foreach ($links as $link) { if ($link->getAttribute('rel') == 'self') { - $feedUri = $link->getAttribute('href'); + $uri = $link->getAttribute('href'); + break; } } } - if (isset($feedUri)) { - $this->sendRequest($feedUri, 'DELETE'); + if (isset($uri)) { + $this->sendRequest($uri, 'DELETE'); return true; } return false; } public function rename($path1, $path2) { - // TODO Add support for moving to different collections - // Get resource edit link to rename resource if ($entry = $this->getResource($path1)) { - $etag = $entry->getElementsByTagName('entry')->item(0)->getAttribute('gd:etag'); - $links = $entry->getElementsByTagName('link'); - foreach ($links as $link) { - if ($link->getAttribute('rel') == 'edit') { - $feedUri = $link->getAttribute('href'); + $collection = dirname($path2); + if (dirname($path1) == $collection) { + // Get resource edit link to rename resource + $etag = $entry->getAttribute('gd:etag'); + $links = $entry->getElementsByTagName('link'); + foreach ($links as $link) { + if ($link->getAttribute('rel') == 'edit') { + $uri = $link->getAttribute('href'); + break; + } + } + $title = basename($path); + // Construct post data + $postData = ''; + $postData .= ''; + $postData .= ''.$title.''; + $postData .= ''; + $this->sendRequest($uri, 'PUT', $postData); + return true; + } else { + // Move to different collection + if ($collectionEntry = $this->getResource($collection)) { + $feedUri = $colelctionEntry->getElementsByTagName('content')->item(0)->getAttribute('src'); + // Construct post data + $postData = ''; + $postData .= ''; + $postData .= ''.$entry->getElementsByTagName('id')->item(0).''; + $postData .= ''; + $this->sendRequest($uri, 'POST', $postData); + return true; } } } - if (isset($etag) && isset($feedUri)) { - $title = basename($path2); - // Construct post data - $postData = ''; - $postData .= ''; - $postData .= ''.$title.''; - $postData .= ''; - $this->sendRequest($feedUri, 'PUT', $postData); - return true; - } return false; } public function fopen($path, $mode) { - if ($entry = $this->getResource($path)) { - switch ($mode) { - case 'r': - case 'rb': + switch ($mode) { + case 'r': + case 'rb': + if ($entry = $this->getResource($path)) { $extension = $this->getExtension($entry); $downloadUri = $entry->getElementsByTagName('content')->item(0)->getAttribute('src'); // TODO Non-native documents don't need these additional parameters $downloadUri .= '&exportFormat='.$extension.'&format='.$extension; - $tmpFile = $this->sendRequest($downloadUri, 'GET', true); + $tmpFile = $this->sendRequest($downloadUri, 'GET', null, null, true); return fopen($tmpFile, 'r'); - case 'w': - case 'wb': - case 'a': - case 'ab': - case 'r+': - case 'w+': - case 'wb+': - case 'a+': - case 'x': - case 'x+': - case 'c': - case 'c+': - // TODO Edit documents - } - + } + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + if (strrpos($path,'.') !== false) { + $ext = substr($path,strrpos($path,'.')); + } else { + $ext = ''; + } + $tmpFile = OC_Helper::tmpFile($ext); + OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); + if ($this->file_exists($path)) { + $source = $this->fopen($path, 'r'); + file_put_contents($tmpFile, $source); + } + self::$tempFiles[$tmpFile] = $path; + return fopen('close://'.$tmpFile, $mode); } return false; } + public function writeBack($tmpFile) { + if (isset(self::$tempFiles[$tmpFile])) { + $this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + private function uploadFile($path, $target) { + $entry = $this->getResource($target); + if (!$entry) { + if (dirname($target) == '.' || dirname($target) == '/') { + $uploadUri = 'https://docs.google.com/feeds/upload/create-session/default/private/full/folder%3Aroot/contents'; + } else { + $entry = $this->getResource(dirname($target)); + } + } + if (!isset($uploadUri) && $entry) { + $etag = $entry->getAttribute('gd:etag'); + $links = $entry->getElementsByTagName('link'); + foreach ($links as $link) { + if ($link->getAttribute('rel') == 'http://schemas.google.com/g/2005#resumable-edit-media') { + $uploadUri = $link->getAttribute('href'); + break; + } + } + } + if (isset($uploadUri) && $handle = fopen($path, 'r')) { + $uploadUri .= '?convert=false'; + $mimetype = OC_Helper::getMimeType($path); + $size = filesize($path); + $headers = array('X-Upload-Content-Type: ' => $mimetype, 'X-Upload-Content-Length: ' => $size); + $postData = ''; + $postData .= ''; + $postData .= ''.basename($target).''; + $postData .= ''; + $result = $this->sendRequest($uploadUri, 'POST', $postData, $headers, false, true); + if ($result) { + // Get location to upload file + if (preg_match('@^Location: (.*)$@m', $result, $matches)) { + $uploadUri = trim($matches[1]); + } + } else { + return false; + } + // 512 kB chunks + $chunkSize = 524288; + $i = 0; + while (!feof($handle)) { + if ($i + $chunkSize > $size) { + if ($i == 0) { + $chunkSize = $size; + } else { + $chunkSize = $size % $i; + } + } + $end = $i + $chunkSize - 1; + $headers = array('Content-Length: '.$chunkSize, 'Content-Type: '.$mimetype, 'Content Range: bytes '.$i.'-'.$end.'/'.$size); + $postData = fread($handle, $chunkSize); + $result = $this->sendRequest($uploadUri, 'PUT', $postData, $headers, false, true, false); + if ($result) { + // Get next location to upload file chunk + if (preg_match('@^Location: (.*)$@m', $result, $matches)) { + $uploadUri = trim($matches[1]); + } + $i += $chunkSize; + } else { + return false; + } + } + // TODO Wait for resource entry + } + } + public function getMimeType($path, $entry = null) { // Entry can be passed, because extension is required for opendir and the entry can't be cached without the extension if ($entry == null) { @@ -393,7 +519,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { } public function free_space($path) { - if ($dom = $this->sendRequest('https://docs.google.com/feeds/metadata/default', 'GET')) { + if ($dom = $this->getFeed('https://docs.google.com/feeds/metadata/default', 'GET')) { // NOTE: Native Google Docs resources don't count towards quota $total = $dom->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'quotaBytesTotal')->item(0)->nodeValue; $used = $dom->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'quotaBytesUsed')->item(0)->nodeValue; diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php old mode 100644 new mode 100755 index e53eb1ff3b..e3ba9c240c --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -28,6 +28,8 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ private $rootContainer; private static $tempFiles=array(); + private $objects=array(); + private $containers=array(); const SUBCONTAINER_FILE='.subcontainers'; @@ -38,7 +40,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ */ private function getContainerName($path){ $path=trim($this->root.$path,'/'); - return md5($path); + return str_replace('/','\\',$path); } /** @@ -50,8 +52,12 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if($path=='' or $path=='/'){ return $this->rootContainer; } + if(isset($this->containers[$path])){ + return $this->containers[$path]; + } try{ $container=$this->conn->get_container($this->getContainerName($path)); + $this->containers[$path]=$container; return $container; }catch(NoSuchContainerException $e){ return null; @@ -87,12 +93,16 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ * @return CF_Object */ private function getObject($path){ + if(isset($this->objects[$path])){ + return $this->objects[$path]; + } $container=$this->getContainer(dirname($path)); if(is_null($container)){ return null; }else{ try{ $obj=$container->get_object(basename($path)); + $this->objects[$path]=$obj; return $obj; }catch(NoSuchObjectException $e){ return null; @@ -154,7 +164,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ * @return array */ private function getSubContainers($container){ - $tmpFile=OC_Helper::tmpFile(); + $tmpFile=OCP\Files::tmpFile(); $obj=$this->getSubContainerFile($container); try{ $obj->save_to_filename($tmpFile); @@ -180,7 +190,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if(!$name){ return false; } - $tmpFile=OC_Helper::tmpFile(); + $tmpFile=OCP\Files::tmpFile(); $obj=$this->getSubContainerFile($container); try{ $obj->save_to_filename($tmpFile); @@ -215,7 +225,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if(!$name){ return false; } - $tmpFile=OC_Helper::tmpFile(); + $tmpFile=OCP\Files::tmpFile(); $obj=$this->getSubContainerFile($container); try{ $obj->save_to_filename($tmpFile); @@ -295,6 +305,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ } $this->conn->delete_container($this->getContainerName($path)); + unset($this->containers[$path]); return true; } } @@ -309,12 +320,14 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if($sub){ $this->emptyContainer($path.'/'.$sub); $this->conn->delete_container($this->getContainerName($path.'/'.$sub)); + unset($this->containers[$path.'/'.$sub]); } } $objects=$this->getObjects($container); foreach($objects as $object){ $container->delete_object($object); + unset($this->objects[$path.'/'.$object]); } } @@ -381,6 +394,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if($this->objectExists($path)){ $container=$this->getContainer(dirname($path)); $container->delete_object(basename($path)); + unset($this->objects[$path]); }else{ return false; } @@ -447,6 +461,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ $sourceContainer=$this->getContainer(dirname($path1)); $targetContainer=$this->getContainer(dirname($path2)); $result=$sourceContainer->move_object_to(basename($path1),$targetContainer,basename($path2)); + unset($this->objects[$path1]); if($result){ $targetObj=$this->getObject($path2); $this->resetMTime($targetObj); @@ -486,7 +501,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ private function getTmpFile($path){ $obj=$this->getObject($path); if(!is_null($obj)){ - $tmpFile=OC_Helper::tmpFile(); + $tmpFile=OCP\Files::tmpFile(); $obj->save_to_filename($tmpFile); return $tmpFile; }else{ diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php old mode 100644 new mode 100755 index 7a2da5c8ec..07c90d4878 --- a/apps/files_external/lib/webdav.php +++ b/apps/files_external/lib/webdav.php @@ -150,7 +150,7 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{ }else{ $ext=''; } - $tmpFile=OC_Helper::tmpFile($ext); + $tmpFile=OCP\Files::tmpFile($ext); OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); if($this->file_exists($path)){ $this->getFile($path,$tmpFile); diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index edbbe9dfec..5b6517755f 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -23,7 +23,7 @@ return array( 'root'=>'/google', ), 'swift'=>array( - 'run'=>true, + 'run'=>false, 'user'=>'test:tester', 'token'=>'testing', 'host'=>'localhost:8080/auth', diff --git a/apps/files_external/tests/ftp.php b/apps/files_external/tests/ftp.php old mode 100644 new mode 100755 index e30fb9a1c3..68481b4e66 --- a/apps/files_external/tests/ftp.php +++ b/apps/files_external/tests/ftp.php @@ -23,7 +23,7 @@ if(!is_array($config) or !isset($config['ftp']) or !$config['ftp']['run']){ } public function tearDown(){ - OC_Helper::rmdirr($this->instance->constructUrl('')); + OCP\Files::rmdirr($this->instance->constructUrl('')); } } } diff --git a/apps/files_imageviewer/appinfo/app.php b/apps/files_imageviewer/appinfo/app.php old mode 100644 new mode 100755 index 0f77076b79..6c8d8c30ca --- a/apps/files_imageviewer/appinfo/app.php +++ b/apps/files_imageviewer/appinfo/app.php @@ -1,8 +1,8 @@ diff --git a/apps/files_imageviewer/css/jquery.fancybox-1.3.4.css b/apps/files_imageviewer/css/jquery.fancybox-1.3.4.css index 1dfd9b95d3..5fdf7af14c 100644 --- a/apps/files_imageviewer/css/jquery.fancybox-1.3.4.css +++ b/apps/files_imageviewer/css/jquery.fancybox-1.3.4.css @@ -35,7 +35,7 @@ left: 0; width: 40px; height: 480px; - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); } #fancybox-overlay { @@ -99,7 +99,7 @@ right: -15px; width: 30px; height: 30px; - background: transparent url('../img/fancybox.png') -40px 0px; + background: transparent url('%appswebroot%/apps/files_imageviewer/img/fancybox.png') -40px 0px; cursor: pointer; z-index: 1103; display: none; @@ -137,7 +137,7 @@ width: 35%; cursor: pointer; outline: none; - background: transparent url('../img/blank.gif'); + background: transparent url('%appswebroot%/apps/files_imageviewer/img/blank.gif'); z-index: 1102; display: none; } @@ -163,12 +163,12 @@ } #fancybox-left-ico { - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); background-position: -40px -30px; } #fancybox-right-ico { - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); background-position: -40px -60px; } @@ -199,13 +199,13 @@ top: -20px; left: 0; width: 100%; - background-image: url('../img/fancybox-x.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox-x.png'); } #fancybox-bg-ne { top: -20px; right: -20px; - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); background-position: -40px -162px; } @@ -213,14 +213,14 @@ top: 0; right: -20px; height: 100%; - background-image: url('../img/fancybox-y.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox-y.png'); background-position: -20px 0px; } #fancybox-bg-se { bottom: -20px; right: -20px; - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); background-position: -40px -182px; } @@ -228,14 +228,14 @@ bottom: -20px; left: 0; width: 100%; - background-image: url('../img/fancybox-x.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox-x.png'); background-position: 0px -20px; } #fancybox-bg-sw { bottom: -20px; left: -20px; - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); background-position: -40px -142px; } @@ -243,13 +243,13 @@ top: 0; left: -20px; height: 100%; - background-image: url('../img/fancybox-y.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox-y.png'); } #fancybox-bg-nw { top: -20px; left: -20px; - background-image: url('../img/fancybox.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png'); background-position: -40px -122px; } @@ -282,7 +282,7 @@ #fancybox-title-over { padding: 10px; - background-image: url('../img/fancy_title_over.png'); + background-image: url('%appswebroot%/apps/files_imageviewer/img/fancy_title_over.png'); display: block; } @@ -306,7 +306,7 @@ #fancybox-title-float-left { padding: 0 0 0 15px; - background: url('../img/fancybox.png') -40px -90px no-repeat; + background: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png') -40px -90px no-repeat; } #fancybox-title-float-main { @@ -314,25 +314,25 @@ line-height: 29px; font-weight: bold; padding: 0 0 3px 0; - background: url('../img/fancybox-x.png') 0px -40px; + background: url('%appswebroot%/apps/files_imageviewer/img/fancybox-x.png') 0px -40px; } #fancybox-title-float-right { padding: 0 0 0 15px; - background: url('../img/fancybox.png') -55px -90px no-repeat; + background: url('%appswebroot%/apps/files_imageviewer/img/fancybox.png') -55px -90px no-repeat; } /* IE6 */ -.fancybox-ie6 #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_close.png', sizingMethod='scale'); } +.fancybox-ie6 #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_close.png', sizingMethod='scale'); } -.fancybox-ie6 #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_nav_left.png', sizingMethod='scale'); } -.fancybox-ie6 #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_nav_right.png', sizingMethod='scale'); } +.fancybox-ie6 #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_nav_left.png', sizingMethod='scale'); } +.fancybox-ie6 #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_nav_right.png', sizingMethod='scale'); } -.fancybox-ie6 #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } -.fancybox-ie6 #fancybox-title-float-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_title_left.png', sizingMethod='scale'); } -.fancybox-ie6 #fancybox-title-float-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_title_main.png', sizingMethod='scale'); } -.fancybox-ie6 #fancybox-title-float-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_title_right.png', sizingMethod='scale'); } +.fancybox-ie6 #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } +.fancybox-ie6 #fancybox-title-float-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_title_left.png', sizingMethod='scale'); } +.fancybox-ie6 #fancybox-title-float-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_title_main.png', sizingMethod='scale'); } +.fancybox-ie6 #fancybox-title-float-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_title_right.png', sizingMethod='scale'); } .fancybox-ie6 #fancybox-bg-w, .fancybox-ie6 #fancybox-bg-e, .fancybox-ie6 #fancybox-left, .fancybox-ie6 #fancybox-right, #fancybox-hide-sel-frame { height: expression(this.parentNode.clientHeight + "px"); @@ -343,17 +343,17 @@ top: expression( (-20 + (document.documentElement.clientHeight ? document.documentElement.clientHeight/2 : document.body.clientHeight/2 ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop )) + 'px'); } -#fancybox-loading.fancybox-ie6 div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_loading.png', sizingMethod='scale'); } +#fancybox-loading.fancybox-ie6 div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_loading.png', sizingMethod='scale'); } /* IE6, IE7, IE8 */ .fancybox-ie .fancybox-bg { background: transparent !important; } -.fancybox-ie #fancybox-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_n.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_ne.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_e.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_se.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_s.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_sw.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_w.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/fancy_shadow_nw.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_n.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_ne.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_e.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_se.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_s.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_sw.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_w.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%appswebroot%/apps/files_imageviewer/img/fancy_shadow_nw.png', sizingMethod='scale'); } diff --git a/apps/files_imageviewer/js/jquery.fancybox-1.3.4.js b/apps/files_imageviewer/js/jquery.fancybox-1.3.4.js new file mode 100644 index 0000000000..be77275376 --- /dev/null +++ b/apps/files_imageviewer/js/jquery.fancybox-1.3.4.js @@ -0,0 +1,1156 @@ +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. + * + * Version: 1.3.4 (11/11/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +;(function($) { + var tmp, loading, overlay, wrap, outer, content, close, title, nav_left, nav_right, + + selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [], + + ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i, + + loadingTimer, loadingFrame = 1, + + titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('
    ')[0], { prop: 0 }), + + isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest, + + /* + * Private methods + */ + + _abort = function() { + loading.hide(); + + imgPreloader.onerror = imgPreloader.onload = null; + + if (ajaxLoader) { + ajaxLoader.abort(); + } + + tmp.empty(); + }, + + _error = function() { + if (false === selectedOpts.onError(selectedArray, selectedIndex, selectedOpts)) { + loading.hide(); + busy = false; + return; + } + + selectedOpts.titleShow = false; + + selectedOpts.width = 'auto'; + selectedOpts.height = 'auto'; + + tmp.html( '

    The requested content cannot be loaded.
    Please try again later.

    ' ); + + _process_inline(); + }, + + _start = function() { + var obj = selectedArray[ selectedIndex ], + href, + type, + title, + str, + emb, + ret; + + _abort(); + + selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox'))); + + ret = selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts); + + if (ret === false) { + busy = false; + return; + } else if (typeof ret == 'object') { + selectedOpts = $.extend(selectedOpts, ret); + } + + title = selectedOpts.title || (obj.nodeName ? $(obj).attr('title') : obj.title) || ''; + + if (obj.nodeName && !selectedOpts.orig) { + selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj); + } + + if (title === '' && selectedOpts.orig && selectedOpts.titleFromAlt) { + title = selectedOpts.orig.attr('alt'); + } + + href = selectedOpts.href || (obj.nodeName ? $(obj).attr('href') : obj.href) || null; + + if ((/^(?:javascript)/i).test(href) || href == '#') { + href = null; + } + + if (selectedOpts.type) { + type = selectedOpts.type; + + if (!href) { + href = selectedOpts.content; + } + + } else if (selectedOpts.content) { + type = 'html'; + + } else if (href) { + if (href.match(imgRegExp)) { + type = 'image'; + + } else if (href.match(swfRegExp)) { + type = 'swf'; + + } else if ($(obj).hasClass("iframe")) { + type = 'iframe'; + + } else if (href.indexOf("#") === 0) { + type = 'inline'; + + } else { + type = 'ajax'; + } + } + + if (!type) { + _error(); + return; + } + + if (type == 'inline') { + obj = href.substr(href.indexOf("#")); + type = $(obj).length > 0 ? 'inline' : 'ajax'; + } + + selectedOpts.type = type; + selectedOpts.href = href; + selectedOpts.title = title; + + if (selectedOpts.autoDimensions) { + if (selectedOpts.type == 'html' || selectedOpts.type == 'inline' || selectedOpts.type == 'ajax') { + selectedOpts.width = 'auto'; + selectedOpts.height = 'auto'; + } else { + selectedOpts.autoDimensions = false; + } + } + + if (selectedOpts.modal) { + selectedOpts.overlayShow = true; + selectedOpts.hideOnOverlayClick = false; + selectedOpts.hideOnContentClick = false; + selectedOpts.enableEscapeButton = false; + selectedOpts.showCloseButton = false; + } + + selectedOpts.padding = parseInt(selectedOpts.padding, 10); + selectedOpts.margin = parseInt(selectedOpts.margin, 10); + + tmp.css('padding', (selectedOpts.padding + selectedOpts.margin)); + + $('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function() { + $(this).replaceWith(content.children()); + }); + + switch (type) { + case 'html' : + tmp.html( selectedOpts.content ); + _process_inline(); + break; + + case 'inline' : + if ( $(obj).parent().is('#fancybox-content') === true) { + busy = false; + return; + } + + $('
    ') + .hide() + .insertBefore( $(obj) ) + .bind('fancybox-cleanup', function() { + $(this).replaceWith(content.children()); + }).bind('fancybox-cancel', function() { + $(this).replaceWith(tmp.children()); + }); + + $(obj).appendTo(tmp); + + _process_inline(); + break; + + case 'image': + busy = false; + + $.fancybox.showActivity(); + + imgPreloader = new Image(); + + imgPreloader.onerror = function() { + _error(); + }; + + imgPreloader.onload = function() { + busy = true; + + imgPreloader.onerror = imgPreloader.onload = null; + + _process_image(); + }; + + imgPreloader.src = href; + break; + + case 'swf': + selectedOpts.scrolling = 'no'; + + str = ''; + emb = ''; + + $.each(selectedOpts.swf, function(name, val) { + str += ''; + emb += ' ' + name + '="' + val + '"'; + }); + + str += ''; + + tmp.html(str); + + _process_inline(); + break; + + case 'ajax': + busy = false; + + $.fancybox.showActivity(); + + selectedOpts.ajax.win = selectedOpts.ajax.success; + + ajaxLoader = $.ajax($.extend({}, selectedOpts.ajax, { + url : href, + data : selectedOpts.ajax.data || {}, + error : function(XMLHttpRequest, textStatus, errorThrown) { + if ( XMLHttpRequest.status > 0 ) { + _error(); + } + }, + success : function(data, textStatus, XMLHttpRequest) { + var o = typeof XMLHttpRequest == 'object' ? XMLHttpRequest : ajaxLoader; + if (o.status == 200) { + if ( typeof selectedOpts.ajax.win == 'function' ) { + ret = selectedOpts.ajax.win(href, data, textStatus, XMLHttpRequest); + + if (ret === false) { + loading.hide(); + return; + } else if (typeof ret == 'string' || typeof ret == 'object') { + data = ret; + } + } + + tmp.html( data ); + _process_inline(); + } + } + })); + + break; + + case 'iframe': + _show(); + break; + } + }, + + _process_inline = function() { + var + w = selectedOpts.width, + h = selectedOpts.height; + + if (w.toString().indexOf('%') > -1) { + w = parseInt( ($(window).width() - (selectedOpts.margin * 2)) * parseFloat(w) / 100, 10) + 'px'; + + } else { + w = w == 'auto' ? 'auto' : w + 'px'; + } + + if (h.toString().indexOf('%') > -1) { + h = parseInt( ($(window).height() - (selectedOpts.margin * 2)) * parseFloat(h) / 100, 10) + 'px'; + + } else { + h = h == 'auto' ? 'auto' : h + 'px'; + } + + tmp.wrapInner('
    '); + + selectedOpts.width = tmp.width(); + selectedOpts.height = tmp.height(); + + _show(); + }, + + _process_image = function() { + selectedOpts.width = imgPreloader.width; + selectedOpts.height = imgPreloader.height; + + $("").attr({ + 'id' : 'fancybox-img', + 'src' : imgPreloader.src, + 'alt' : selectedOpts.title + }).appendTo( tmp ); + + _show(); + }, + + _show = function() { + var pos, equal; + + loading.hide(); + + if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { + $.event.trigger('fancybox-cancel'); + + busy = false; + return; + } + + busy = true; + + $(content.add( overlay )).unbind(); + + $(window).unbind("resize.fb scroll.fb"); + $(document).unbind('keydown.fb'); + + if (wrap.is(":visible") && currentOpts.titlePosition !== 'outside') { + wrap.css('height', wrap.height()); + } + + currentArray = selectedArray; + currentIndex = selectedIndex; + currentOpts = selectedOpts; + + if (currentOpts.overlayShow) { + overlay.css({ + 'background-color' : currentOpts.overlayColor, + 'opacity' : currentOpts.overlayOpacity, + 'cursor' : currentOpts.hideOnOverlayClick ? 'pointer' : 'auto', + 'height' : $(document).height() + }); + + if (!overlay.is(':visible')) { + if (isIE6) { + $('select:not(#fancybox-tmp select)').filter(function() { + return this.style.visibility !== 'hidden'; + }).css({'visibility' : 'hidden'}).one('fancybox-cleanup', function() { + this.style.visibility = 'inherit'; + }); + } + + overlay.show(); + } + } else { + overlay.hide(); + } + + final_pos = _get_zoom_to(); + + _process_title(); + + if (wrap.is(":visible")) { + $( close.add( nav_left ).add( nav_right ) ).hide(); + + pos = wrap.position(), + + start_pos = { + top : pos.top, + left : pos.left, + width : wrap.width(), + height : wrap.height() + }; + + equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height); + + content.fadeTo(currentOpts.changeFade, 0.3, function() { + var finish_resizing = function() { + content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish); + }; + + $.event.trigger('fancybox-change'); + + content + .empty() + .removeAttr('filter') + .css({ + 'border-width' : currentOpts.padding, + 'width' : final_pos.width - currentOpts.padding * 2, + 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 + }); + + if (equal) { + finish_resizing(); + + } else { + fx.prop = 0; + + $(fx).animate({prop: 1}, { + duration : currentOpts.changeSpeed, + easing : currentOpts.easingChange, + step : _draw, + complete : finish_resizing + }); + } + }); + + return; + } + + wrap.removeAttr("style"); + + content.css('border-width', currentOpts.padding); + + if (currentOpts.transitionIn == 'elastic') { + start_pos = _get_zoom_from(); + + content.html( tmp.contents() ); + + wrap.show(); + + if (currentOpts.opacity) { + final_pos.opacity = 0; + } + + fx.prop = 0; + + $(fx).animate({prop: 1}, { + duration : currentOpts.speedIn, + easing : currentOpts.easingIn, + step : _draw, + complete : _finish + }); + + return; + } + + if (currentOpts.titlePosition == 'inside' && titleHeight > 0) { + title.show(); + } + + content + .css({ + 'width' : final_pos.width - currentOpts.padding * 2, + 'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 + }) + .html( tmp.contents() ); + + wrap + .css(final_pos) + .fadeIn( currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish ); + }, + + _format_title = function(title) { + if (title && title.length) { + if (currentOpts.titlePosition == 'float') { + return '
    ' + title + '
    '; + } + + return '
    ' + title + '
    '; + } + + return false; + }, + + _process_title = function() { + titleStr = currentOpts.title || ''; + titleHeight = 0; + + title + .empty() + .removeAttr('style') + .removeClass(); + + if (currentOpts.titleShow === false) { + title.hide(); + return; + } + + titleStr = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(titleStr, currentArray, currentIndex, currentOpts) : _format_title(titleStr); + + if (!titleStr || titleStr === '') { + title.hide(); + return; + } + + title + .addClass('fancybox-title-' + currentOpts.titlePosition) + .html( titleStr ) + .appendTo( 'body' ) + .show(); + + switch (currentOpts.titlePosition) { + case 'inside': + title + .css({ + 'width' : final_pos.width - (currentOpts.padding * 2), + 'marginLeft' : currentOpts.padding, + 'marginRight' : currentOpts.padding + }); + + titleHeight = title.outerHeight(true); + + title.appendTo( outer ); + + final_pos.height += titleHeight; + break; + + case 'over': + title + .css({ + 'marginLeft' : currentOpts.padding, + 'width' : final_pos.width - (currentOpts.padding * 2), + 'bottom' : currentOpts.padding + }) + .appendTo( outer ); + break; + + case 'float': + title + .css('left', parseInt((title.width() - final_pos.width - 40)/ 2, 10) * -1) + .appendTo( wrap ); + break; + + default: + title + .css({ + 'width' : final_pos.width - (currentOpts.padding * 2), + 'paddingLeft' : currentOpts.padding, + 'paddingRight' : currentOpts.padding + }) + .appendTo( wrap ); + break; + } + + title.hide(); + }, + + _set_navigation = function() { + if (currentOpts.enableEscapeButton || currentOpts.enableKeyboardNav) { + $(document).bind('keydown.fb', function(e) { + if (e.keyCode == 27 && currentOpts.enableEscapeButton) { + e.preventDefault(); + $.fancybox.close(); + + } else if ((e.keyCode == 37 || e.keyCode == 39) && currentOpts.enableKeyboardNav && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') { + e.preventDefault(); + $.fancybox[ e.keyCode == 37 ? 'prev' : 'next'](); + } + }); + } + + if (!currentOpts.showNavArrows) { + nav_left.hide(); + nav_right.hide(); + return; + } + + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) { + nav_left.show(); + } + + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length -1)) { + nav_right.show(); + } + }, + + _finish = function () { + if (!$.support.opacity) { + content.get(0).style.removeAttribute('filter'); + wrap.get(0).style.removeAttribute('filter'); + } + + if (selectedOpts.autoDimensions) { + content.css('height', 'auto'); + } + + wrap.css('height', 'auto'); + + if (titleStr && titleStr.length) { + title.show(); + } + + if (currentOpts.showCloseButton) { + close.show(); + } + + _set_navigation(); + + if (currentOpts.hideOnContentClick) { + content.bind('click', $.fancybox.close); + } + + if (currentOpts.hideOnOverlayClick) { + overlay.bind('click', $.fancybox.close); + } + + $(window).bind("resize.fb", $.fancybox.resize); + + if (currentOpts.centerOnScroll) { + $(window).bind("scroll.fb", $.fancybox.center); + } + + if (currentOpts.type == 'iframe') { + $('').appendTo(content); + } + + wrap.show(); + + busy = false; + + $.fancybox.center(); + + currentOpts.onComplete(currentArray, currentIndex, currentOpts); + + _preload_images(); + }, + + _preload_images = function() { + var href, + objNext; + + if ((currentArray.length -1) > currentIndex) { + href = currentArray[ currentIndex + 1 ].href; + + if (typeof href !== 'undefined' && href.match(imgRegExp)) { + objNext = new Image(); + objNext.src = href; + } + } + + if (currentIndex > 0) { + href = currentArray[ currentIndex - 1 ].href; + + if (typeof href !== 'undefined' && href.match(imgRegExp)) { + objNext = new Image(); + objNext.src = href; + } + } + }, + + _draw = function(pos) { + var dim = { + width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10), + height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10), + + top : parseInt(start_pos.top + (final_pos.top - start_pos.top) * pos, 10), + left : parseInt(start_pos.left + (final_pos.left - start_pos.left) * pos, 10) + }; + + if (typeof final_pos.opacity !== 'undefined') { + dim.opacity = pos < 0.5 ? 0.5 : pos; + } + + wrap.css(dim); + + content.css({ + 'width' : dim.width - currentOpts.padding * 2, + 'height' : dim.height - (titleHeight * pos) - currentOpts.padding * 2 + }); + }, + + _get_viewport = function() { + return [ + $(window).width() - (currentOpts.margin * 2), + $(window).height() - (currentOpts.margin * 2), + $(document).scrollLeft() + currentOpts.margin, + $(document).scrollTop() + currentOpts.margin + ]; + }, + + _get_zoom_to = function () { + var view = _get_viewport(), + to = {}, + resize = currentOpts.autoScale, + double_padding = currentOpts.padding * 2, + ratio; + + if (currentOpts.width.toString().indexOf('%') > -1) { + to.width = parseInt((view[0] * parseFloat(currentOpts.width)) / 100, 10); + } else { + to.width = currentOpts.width + double_padding; + } + + if (currentOpts.height.toString().indexOf('%') > -1) { + to.height = parseInt((view[1] * parseFloat(currentOpts.height)) / 100, 10); + } else { + to.height = currentOpts.height + double_padding; + } + + if (resize && (to.width > view[0] || to.height > view[1])) { + if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') { + ratio = (currentOpts.width ) / (currentOpts.height ); + + if ((to.width ) > view[0]) { + to.width = view[0]; + to.height = parseInt(((to.width - double_padding) / ratio) + double_padding, 10); + } + + if ((to.height) > view[1]) { + to.height = view[1]; + to.width = parseInt(((to.height - double_padding) * ratio) + double_padding, 10); + } + + } else { + to.width = Math.min(to.width, view[0]); + to.height = Math.min(to.height, view[1]); + } + } + + to.top = parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - to.height - 40) * 0.5)), 10); + to.left = parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - to.width - 40) * 0.5)), 10); + + return to; + }, + + _get_obj_pos = function(obj) { + var pos = obj.offset(); + + pos.top += parseInt( obj.css('paddingTop'), 10 ) || 0; + pos.left += parseInt( obj.css('paddingLeft'), 10 ) || 0; + + pos.top += parseInt( obj.css('border-top-width'), 10 ) || 0; + pos.left += parseInt( obj.css('border-left-width'), 10 ) || 0; + + pos.width = obj.width(); + pos.height = obj.height(); + + return pos; + }, + + _get_zoom_from = function() { + var orig = selectedOpts.orig ? $(selectedOpts.orig) : false, + from = {}, + pos, + view; + + if (orig && orig.length) { + pos = _get_obj_pos(orig); + + from = { + width : pos.width + (currentOpts.padding * 2), + height : pos.height + (currentOpts.padding * 2), + top : pos.top - currentOpts.padding - 20, + left : pos.left - currentOpts.padding - 20 + }; + + } else { + view = _get_viewport(); + + from = { + width : currentOpts.padding * 2, + height : currentOpts.padding * 2, + top : parseInt(view[3] + view[1] * 0.5, 10), + left : parseInt(view[2] + view[0] * 0.5, 10) + }; + } + + return from; + }, + + _animate_loading = function() { + if (!loading.is(':visible')){ + clearInterval(loadingTimer); + return; + } + + $('div', loading).css('top', (loadingFrame * -40) + 'px'); + + loadingFrame = (loadingFrame + 1) % 12; + }; + + /* + * Public methods + */ + + $.fn.fancybox = function(options) { + if (!$(this).length) { + return this; + } + + $(this) + .data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {}))) + .unbind('click.fb') + .bind('click.fb', function(e) { + e.preventDefault(); + + if (busy) { + return; + } + + busy = true; + + $(this).blur(); + + selectedArray = []; + selectedIndex = 0; + + var rel = $(this).attr('rel') || ''; + + if (!rel || rel == '' || rel === 'nofollow') { + selectedArray.push(this); + + } else { + selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]"); + selectedIndex = selectedArray.index( this ); + } + + _start(); + + return; + }); + + return this; + }; + + $.fancybox = function(obj) { + var opts; + + if (busy) { + return; + } + + busy = true; + opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {}; + + selectedArray = []; + selectedIndex = parseInt(opts.index, 10) || 0; + + if ($.isArray(obj)) { + for (var i = 0, j = obj.length; i < j; i++) { + if (typeof obj[i] == 'object') { + $(obj[i]).data('fancybox', $.extend({}, opts, obj[i])); + } else { + obj[i] = $({}).data('fancybox', $.extend({content : obj[i]}, opts)); + } + } + + selectedArray = jQuery.merge(selectedArray, obj); + + } else { + if (typeof obj == 'object') { + $(obj).data('fancybox', $.extend({}, opts, obj)); + } else { + obj = $({}).data('fancybox', $.extend({content : obj}, opts)); + } + + selectedArray.push(obj); + } + + if (selectedIndex > selectedArray.length || selectedIndex < 0) { + selectedIndex = 0; + } + + _start(); + }; + + $.fancybox.showActivity = function() { + clearInterval(loadingTimer); + + loading.show(); + loadingTimer = setInterval(_animate_loading, 66); + }; + + $.fancybox.hideActivity = function() { + loading.hide(); + }; + + $.fancybox.next = function() { + return $.fancybox.pos( currentIndex + 1); + }; + + $.fancybox.prev = function() { + return $.fancybox.pos( currentIndex - 1); + }; + + $.fancybox.pos = function(pos) { + if (busy) { + return; + } + + pos = parseInt(pos); + + selectedArray = currentArray; + + if (pos > -1 && pos < currentArray.length) { + selectedIndex = pos; + _start(); + + } else if (currentOpts.cyclic && currentArray.length > 1) { + selectedIndex = pos >= currentArray.length ? 0 : currentArray.length - 1; + _start(); + } + + return; + }; + + $.fancybox.cancel = function() { + if (busy) { + return; + } + + busy = true; + + $.event.trigger('fancybox-cancel'); + + _abort(); + + selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts); + + busy = false; + }; + + // Note: within an iframe use - parent.$.fancybox.close(); + $.fancybox.close = function() { + if (busy || wrap.is(':hidden')) { + return; + } + + busy = true; + + if (currentOpts && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { + busy = false; + return; + } + + _abort(); + + $(close.add( nav_left ).add( nav_right )).hide(); + + $(content.add( overlay )).unbind(); + + $(window).unbind("resize.fb scroll.fb"); + $(document).unbind('keydown.fb'); + + content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank'); + + if (currentOpts.titlePosition !== 'inside') { + title.empty(); + } + + wrap.stop(); + + function _cleanup() { + overlay.fadeOut('fast'); + + title.empty().hide(); + wrap.hide(); + + $.event.trigger('fancybox-cleanup'); + + content.empty(); + + currentOpts.onClosed(currentArray, currentIndex, currentOpts); + + currentArray = selectedOpts = []; + currentIndex = selectedIndex = 0; + currentOpts = selectedOpts = {}; + + busy = false; + } + + if (currentOpts.transitionOut == 'elastic') { + start_pos = _get_zoom_from(); + + var pos = wrap.position(); + + final_pos = { + top : pos.top , + left : pos.left, + width : wrap.width(), + height : wrap.height() + }; + + if (currentOpts.opacity) { + final_pos.opacity = 1; + } + + title.empty().hide(); + + fx.prop = 1; + + $(fx).animate({ prop: 0 }, { + duration : currentOpts.speedOut, + easing : currentOpts.easingOut, + step : _draw, + complete : _cleanup + }); + + } else { + wrap.fadeOut( currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup); + } + }; + + $.fancybox.resize = function() { + if (overlay.is(':visible')) { + overlay.css('height', $(document).height()); + } + + $.fancybox.center(true); + }; + + $.fancybox.center = function() { + var view, align; + + if (busy) { + return; + } + + align = arguments[0] === true ? 1 : 0; + view = _get_viewport(); + + if (!align && (wrap.width() > view[0] || wrap.height() > view[1])) { + return; + } + + wrap + .stop() + .animate({ + 'top' : parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - content.height() - 40) * 0.5) - currentOpts.padding)), + 'left' : parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - content.width() - 40) * 0.5) - currentOpts.padding)) + }, typeof arguments[0] == 'number' ? arguments[0] : 200); + }; + + $.fancybox.init = function() { + if ($("#fancybox-wrap").length) { + return; + } + + $('body').append( + tmp = $('
    '), + loading = $('
    '), + overlay = $('
    '), + wrap = $('
    ') + ); + + outer = $('
    ') + .append('
    ') + .appendTo( wrap ); + + outer.append( + content = $('
    '), + close = $(''), + title = $('
    '), + + nav_left = $(''), + nav_right = $('') + ); + + close.click($.fancybox.close); + loading.click($.fancybox.cancel); + + nav_left.click(function(e) { + e.preventDefault(); + $.fancybox.prev(); + }); + + nav_right.click(function(e) { + e.preventDefault(); + $.fancybox.next(); + }); + + if ($.fn.mousewheel) { + wrap.bind('mousewheel.fb', function(e, delta) { + if (busy) { + e.preventDefault(); + + } else if ($(e.target).get(0).clientHeight == 0 || $(e.target).get(0).scrollHeight === $(e.target).get(0).clientHeight) { + e.preventDefault(); + $.fancybox[ delta > 0 ? 'prev' : 'next'](); + } + }); + } + + if (!$.support.opacity) { + wrap.addClass('fancybox-ie'); + } + + if (isIE6) { + loading.addClass('fancybox-ie6'); + wrap.addClass('fancybox-ie6'); + + $('').prependTo(outer); + } + }; + + $.fn.fancybox.defaults = { + padding : 10, + margin : 40, + opacity : false, + modal : false, + cyclic : false, + scrolling : 'auto', // 'auto', 'yes' or 'no' + + width : 560, + height : 340, + + autoScale : true, + autoDimensions : true, + centerOnScroll : false, + + ajax : {}, + swf : { wmode: 'transparent' }, + + hideOnOverlayClick : true, + hideOnContentClick : false, + + overlayShow : true, + overlayOpacity : 0.7, + overlayColor : '#777', + + titleShow : true, + titlePosition : 'float', // 'float', 'outside', 'inside' or 'over' + titleFormat : null, + titleFromAlt : false, + + transitionIn : 'fade', // 'elastic', 'fade' or 'none' + transitionOut : 'fade', // 'elastic', 'fade' or 'none' + + speedIn : 300, + speedOut : 300, + + changeSpeed : 300, + changeFade : 'fast', + + easingIn : 'swing', + easingOut : 'swing', + + showCloseButton : true, + showNavArrows : true, + enableEscapeButton : true, + enableKeyboardNav : true, + + onStart : function(){}, + onCancel : function(){}, + onComplete : function(){}, + onCleanup : function(){}, + onClosed : function(){}, + onError : function(){} + }; + + $(document).ready(function() { + $.fancybox.init(); + }); + +})(jQuery); \ No newline at end of file diff --git a/apps/files_imageviewer/js/jquery.mousewheel-3.0.4.js b/apps/files_imageviewer/js/jquery.mousewheel-3.0.4.js new file mode 100644 index 0000000000..b7932415ce --- /dev/null +++ b/apps/files_imageviewer/js/jquery.mousewheel-3.0.4.js @@ -0,0 +1,78 @@ +/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net) + * Licensed under the MIT License (LICENSE.txt). + * + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * Thanks to: Seamus Leahy for adding deltaX and deltaY + * + * Version: 3.0.4 + * + * Requires: 1.2.2+ + */ + +(function($) { + +var types = ['DOMMouseScroll', 'mousewheel']; + +$.event.special.mousewheel = { + setup: function() { + if ( this.addEventListener ) { + for ( var i=types.length; i; ) { + this.addEventListener( types[--i], handler, false ); + } + } else { + this.onmousewheel = handler; + } + }, + + teardown: function() { + if ( this.removeEventListener ) { + for ( var i=types.length; i; ) { + this.removeEventListener( types[--i], handler, false ); + } + } else { + this.onmousewheel = null; + } + } +}; + +$.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function(fn) { + return this.unbind("mousewheel", fn); + } +}); + + +function handler(event) { + var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; + event = $.event.fix(orgEvent); + event.type = "mousewheel"; + + // Old school scrollwheel delta + if ( event.wheelDelta ) { delta = event.wheelDelta/120; } + if ( event.detail ) { delta = -event.detail/3; } + + // New school multidimensional scroll (touchpads) deltas + deltaY = delta; + + // Gecko + if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { + deltaY = 0; + deltaX = -1*delta; + } + + // Webkit + if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; } + if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; } + + // Add event and delta to the front of the arguments + args.unshift(event, delta, deltaX, deltaY); + + return $.event.handle.apply(this, args); +} + +})(jQuery); \ No newline at end of file diff --git a/apps/files_imageviewer/js/lightbox.js b/apps/files_imageviewer/js/lightbox.js index 6c97466e6d..fe8b975309 100644 --- a/apps/files_imageviewer/js/lightbox.js +++ b/apps/files_imageviewer/js/lightbox.js @@ -6,19 +6,20 @@ $(document).ready(function() { FileActions.setDefault('image','View'); } OC.search.customResults.Images=function(row,item){ - var image=item.link.substr(item.link.indexOf('file=')+5); + var image=item.link.substr(item.link.indexOf('?file=')+6); var a=row.find('a'); a.attr('href','#'); a.click(function(){ - var file=image.split('/').pop(); - var dir=image.substr(0,image.length-file.length-1); + var pos=image.lastIndexOf('/') + var file=image.substr(pos + 1); + var dir=image.substr(0,pos); viewImage(dir,file); }); } }); function viewImage(dir, file) { - if(file.indexOf('.psd')){//can't view those + if(file.indexOf('.psd')>0){//can't view those return; } var location=OC.filePath('files','ajax','download.php')+'?files='+file+'&dir='+dir; diff --git a/apps/files_pdfviewer/appinfo/app.php b/apps/files_pdfviewer/appinfo/app.php index 0f0b40764d..06b1567067 100755 --- a/apps/files_pdfviewer/appinfo/app.php +++ b/apps/files_pdfviewer/appinfo/app.php @@ -1,7 +1,7 @@ diff --git a/apps/files_pdfviewer/appinfo/info.xml b/apps/files_pdfviewer/appinfo/info.xml old mode 100755 new mode 100644 diff --git a/apps/files_pdfviewer/css/history.png b/apps/files_pdfviewer/css/history.png old mode 100755 new mode 100644 diff --git a/apps/files_pdfviewer/css/viewer.css b/apps/files_pdfviewer/css/viewer.css old mode 100755 new mode 100644 diff --git a/apps/files_pdfviewer/js/pdfjs/LICENSE b/apps/files_pdfviewer/js/pdfjs/LICENSE old mode 100755 new mode 100644 diff --git a/apps/files_pdfviewer/js/pdfview.js b/apps/files_pdfviewer/js/pdfview.js old mode 100755 new mode 100644 diff --git a/apps/files_pdfviewer/js/viewer.js b/apps/files_pdfviewer/js/viewer.js old mode 100755 new mode 100644 diff --git a/apps/files_sharing/ajax/email.php b/apps/files_sharing/ajax/email.php new file mode 100755 index 0000000000..9eba203465 --- /dev/null +++ b/apps/files_sharing/ajax/email.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/apps/files_sharing/ajax/getitem.php b/apps/files_sharing/ajax/getitem.php old mode 100644 new mode 100755 index ba01adffb9..36ac372cad --- a/apps/files_sharing/ajax/getitem.php +++ b/apps/files_sharing/ajax/getitem.php @@ -1,36 +1,69 @@ $gid, 'permissions' => $rows[$i]['permissions'], 'users' => OC_Group::usersInGroup($gid), 'parentFolder' => false)); + } else { + $group = array(array('gid' => $gid, 'permissions' => $rows[$i]['permissions'], 'users' => OC_Group::usersInGroup($gid), 'parentFolder' => basename($path))); + } + if (!isset($item['groups'])) { + $item['groups'] = $group; + } else if (is_array($item['groups'])) { + $gidExists = false; + $currentGroups = $item['groups']; + // Check if the group is already included + foreach ($currentGroups as $g) { + if ($g['gid'] == $gid) { + $gidExists = true; + } + } + if (!$gidExists) { + $item['groups'] = array_merge($item['groups'], $group); + } + } + } else { + if ($path == $source) { + $user = array(array('uid' => $uid_shared_with, 'permissions' => $rows[$i]['permissions'], 'parentFolder' => false)); + } else { + $user = array(array('uid' => $uid_shared_with, 'permissions' => $rows[$i]['permissions'], 'parentFolder' => basename($path))); + } + if (!isset($item['users'])) { + $item['users'] = $user; + } else if (is_array($item['users'])) { + $item['users'] = array_merge($item['users'], $user); + } + } } - $parentUsers[basename($source)."-".$i] = $values[$i]; } - $users = array_merge($users, $parentUsers); } - $source = dirname($source); -} -if (!empty($users)) { - OC_JSON::encodedPrint($users); + $path = dirname($path); } + +OCP\JSON::success(array('data' => $item)); + +?> diff --git a/apps/files_sharing/ajax/getstatuses.php b/apps/files_sharing/ajax/getstatuses.php new file mode 100644 index 0000000000..8edcb21475 --- /dev/null +++ b/apps/files_sharing/ajax/getstatuses.php @@ -0,0 +1,25 @@ + $items)); + +?> \ No newline at end of file diff --git a/apps/files_sharing/ajax/setpermissions.php b/apps/files_sharing/ajax/setpermissions.php old mode 100644 new mode 100755 index 200202c704..2f4c5da978 --- a/apps/files_sharing/ajax/setpermissions.php +++ b/apps/files_sharing/ajax/setpermissions.php @@ -1,13 +1,14 @@ diff --git a/apps/files_sharing/ajax/share.php b/apps/files_sharing/ajax/share.php old mode 100644 new mode 100755 index 9b10260da5..4478144e92 --- a/apps/files_sharing/ajax/share.php +++ b/apps/files_sharing/ajax/share.php @@ -1,31 +1,33 @@ getToken(); + $path = ltrim($source, '/'); + $source = $userDirectory.$source; + // Check if the file exists or if the file is being reshared + if ($source && (OC_FILESYSTEM::file_exists($path) && OC_FILESYSTEM::is_readable($path) || OC_Share::getSource($source))) { + try { + $shared = new OC_Share($source, $uid_shared_with, $permissions); + // If this is a private link, return the token + if ($uid_shared_with == OC_Share::PUBLICLINK) { + OCP\JSON::success(array('data' => $shared->getToken())); + } else { + OCP\JSON::success(); + } + } catch (Exception $exception) { + OCP\Util::writeLog('files_sharing', 'Unexpected Error : '.$exception->getMessage(), OCP\Util::ERROR); + OCP\JSON::error(array('data' => array('message' => $exception->getMessage()))); } - } catch (Exception $exception) { - OC_Log::write('files_sharing',"Unexpected Error : ".$exception->getMessage(),OC_Log::ERROR); - echo "false"; + } else { + OCP\Util::writeLog('files_sharing', 'File does not exist or is not readable :'.$source, OCP\Util::ERROR); + OCP\JSON::error(array('data' => array('message' => 'File does not exist or is not readable'))); } } diff --git a/apps/files_sharing/ajax/toggleresharing.php b/apps/files_sharing/ajax/toggleresharing.php new file mode 100755 index 0000000000..673f00c5d1 --- /dev/null +++ b/apps/files_sharing/ajax/toggleresharing.php @@ -0,0 +1,11 @@ + diff --git a/apps/files_sharing/ajax/unshare.php b/apps/files_sharing/ajax/unshare.php old mode 100644 new mode 100755 index d8a72a00ef..9088bf4266 --- a/apps/files_sharing/ajax/unshare.php +++ b/apps/files_sharing/ajax/unshare.php @@ -1,12 +1,13 @@ diff --git a/apps/files_sharing/ajax/userautocomplete.php b/apps/files_sharing/ajax/userautocomplete.php old mode 100644 new mode 100755 index 38b673ee51..0e1bf6d588 --- a/apps/files_sharing/ajax/userautocomplete.php +++ b/apps/files_sharing/ajax/userautocomplete.php @@ -1,29 +1,32 @@ "; $groups[] = ""; foreach ($userGroups as $group) { $groupUsers = OC_Group::usersInGroup($group); + $userCount = 0; foreach ($groupUsers as $user) { if ($user != $self) { $users[] = ""; + $userCount++; } } - $groups[] = ""; + // Don't include the group if only the current user is a member of it + if ($userCount > 0) { + $groups[] = ""; + } } +$users = array_unique($users); $users[] = ""; $groups[] = ""; $users = array_merge($users, $groups); -OC_JSON::encodedPrint($users); +OCP\JSON::encodedPrint($users); ?> diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php old mode 100644 new mode 100755 index 8049e9b0ae..b3b9776d29 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -3,12 +3,19 @@ require_once('apps/files_sharing/sharedstorage.php'); OC::$CLASSPATH['OC_Share'] = "apps/files_sharing/lib_share.php"; -OC_Hook::connect("OC_Filesystem", "post_delete", "OC_Share", "deleteItem"); -OC_Hook::connect("OC_Filesystem", "post_rename", "OC_Share", "renameItem"); -OC_Hook::connect("OC_Filesystem", "post_write", "OC_Share", "updateItem"); -OC_Util::addScript("files_sharing", "share"); -OC_Util::addScript("3rdparty", "chosen/chosen.jquery.min"); -OC_Util::addStyle( 'files_sharing', 'sharing' ); -OC_Util::addStyle("3rdparty", "chosen/chosen"); - -?> \ No newline at end of file +OCP\App::registerAdmin('files_sharing', 'settings'); +OCP\Util::connectHook("OC_Filesystem", "post_delete", "OC_Share", "deleteItem"); +OCP\Util::connectHook("OC_Filesystem", "post_rename", "OC_Share", "renameItem"); +OCP\Util::connectHook("OC_Filesystem", "post_write", "OC_Share", "updateItem"); +OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OC_Share', 'removeUser'); +OCP\Util::connectHook('OC_User', 'post_addToGroup', 'OC_Share', 'addToGroupShare'); +OCP\Util::connectHook('OC_User', 'post_removeFromGroup', 'OC_Share', 'removeFromGroupShare'); +$dir = isset($_GET['dir']) ? $_GET['dir'] : '/'; +if ($dir != '/Shared' || OCP\Config::getAppValue('files_sharing', 'resharing', 'yes') == 'yes') { + OCP\Util::addscript("files_sharing", "share"); +} +OCP\Util::addscript("3rdparty", "chosen/chosen.jquery.min"); +OCP\Util::addStyle( 'files_sharing', 'sharing' ); +OCP\Util::addStyle("3rdparty", "chosen/chosen"); +OCP\CONFIG::setAppValue('core', 'public_files', '/apps/files_sharing/get.php'); +?> diff --git a/apps/files_sharing/css/sharing.css b/apps/files_sharing/css/sharing.css index db59a3d340..c4b4540e9d 100644 --- a/apps/files_sharing/css/sharing.css +++ b/apps/files_sharing/css/sharing.css @@ -1,9 +1,14 @@ +/* Copyright (c) 2011, Jan-Christoph Borchardt, http://jancborchardt.net + This file is licensed under the Affero General Public License version 3 or later. + See the COPYING-README file. */ + #dropdown { display:block; position:absolute; z-index:100; width:16em; right:0; margin-right:7em; background:#eee; padding:1em; -moz-box-shadow:0 1px 1px #777; -webkit-box-shadow:0 1px 1px #777; box-shadow:0 1px 1px #777; -moz-border-radius-bottomleft:1em; -webkit-border-bottom-left-radius:1em; border-bottom-left-radius:1em; -moz-border-radius-bottomright:1em; -webkit-border-bottom-right-radius:1em; border-bottom-right-radius:1em; } -#shared_list { padding:0.5em; list-style-type: none; } -#public { border-top:1px solid #ddd; padding-top:0.5em; } +#sharedWithList { padding:0.5em; list-style-type: none; } +#privateLink { border-top:1px solid #ddd; padding-top:0.5em; } a.unshare { float:right; display:inline; margin:0 .5em; padding:.3em .3em 0 .3em !important; opacity:.5; } a.unshare:hover { opacity:1; } -#share_with { width: 16em; } \ No newline at end of file +#share_with { width: 16em; } +#privateLink label, .edit { font-weight:normal; } diff --git a/apps/files_sharing/get.php b/apps/files_sharing/get.php old mode 100644 new mode 100755 index 3e42bf6a6c..caf25d00cf --- a/apps/files_sharing/get.php +++ b/apps/files_sharing/get.php @@ -1,14 +1,12 @@ assign("file", $subPath); $tmpl->printPage(); exit; @@ -27,7 +25,7 @@ if ($source !== false) { $files = array(); $rootLength = strlen($root); foreach (OC_Files::getdirectorycontent($source) as $i) { - $i['date'] = OC_Util::formatDate($i['mtime'] ); + $i['date'] = OCP\Util::formatDate($i['mtime'] ); if ($i['type'] == 'file') { $fileinfo = pathinfo($i['name']); $i['basename'] = $fileinfo['filename']; @@ -49,16 +47,16 @@ if ($source !== false) { } } // Load the files we need - OC_Util::addStyle("files", "files"); - $breadcrumbNav = new OC_Template("files", "part.breadcrumb", ""); + OCP\Util::addStyle("files", "files"); + $breadcrumbNav = new OCP\Template("files", "part.breadcrumb", ""); $breadcrumbNav->assign("breadcrumb", $breadcrumb); - $breadcrumbNav->assign("baseURL", OC_Helper::linkTo("files_sharing", "get.php")."?token=".$token."&path="); - $list = new OC_Template("files", "part.list", ""); + $breadcrumbNav->assign("baseURL", OCP\Util::linkTo("files_sharing", "get.php")."?token=".$token."&path="); + $list = new OCP\Template("files", "part.list", ""); $list->assign("files", $files); - $list->assign("baseURL", OC_Helper::linkTo("files_sharing", "get.php")."?token=".$token."&path="); - $list->assign("downloadURL", OC_Helper::linkTo("files_sharing", "get.php")."?token=".$token."&path="); + $list->assign("baseURL", OCP\Util::linkTo("files_sharing", "get.php")."?token=".$token."&path="); + $list->assign("downloadURL", OCP\Util::linkTo("files_sharing", "get.php")."?token=".$token."&path="); $list->assign("readonly", true); - $tmpl = new OC_Template("files", "index", "user"); + $tmpl = new OCP\Template("files", "index", "user"); $tmpl->assign("fileList", $list->fetchPage()); $tmpl->assign("breadcrumb", $breadcrumbNav->fetchPage()); $tmpl->assign("readonly", true); @@ -69,8 +67,8 @@ if ($source !== false) { //get time mimetype and set the headers $mimetype = OC_Filesystem::getMimeType($source); header("Content-Transfer-Encoding: binary"); - OC_Response::disableCaching(); - header('Content-Disposition: filename="'.basename($source).'"'); + OCP\Response::disableCaching(); + header('Content-Disposition: attachment; filename="'.basename($source).'"'); header("Content-Type: " . $mimetype); header("Content-Length: " . OC_Filesystem::filesize($source)); //download the file @@ -79,7 +77,7 @@ if ($source !== false) { } } else { header("HTTP/1.0 404 Not Found"); - $tmpl = new OC_Template("", "404", "guest"); + $tmpl = new OCP\Template("", "404", "guest"); $tmpl->printPage(); die(); } diff --git a/apps/files_sharing/js/settings.js b/apps/files_sharing/js/settings.js new file mode 100644 index 0000000000..bb7d79fecb --- /dev/null +++ b/apps/files_sharing/js/settings.js @@ -0,0 +1,9 @@ +$(document).ready(function() { + $('#allowResharing').bind('change', function() { + var checked = 1; + if (!this.checked) { + checked = 0; + } + $.post(OC.filePath('files_sharing','ajax','toggleresharing.php'), 'resharing='+checked); + }); +}); \ No newline at end of file diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 54d749d833..5b0c4ee809 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -1,100 +1,278 @@ -$(document).ready(function() { - var shared_status = {}; - if (typeof FileActions !== 'undefined') { - FileActions.register('all', 'Share', function(filename) { - if (scanFiles.scanning){return;}//workaround to prevent additional http request block scanning feedback - var icon; - if (typeof filename == 'undefined') { - return false; - } - var file = $('#dir').val()+'/'+filename; - if(shared_status[file]) - return shared_status[file].icon; - $.ajax({ - type: 'GET', - url: OC.linkTo('files_sharing', 'ajax/getitem.php'), - dataType: 'json', - data: {source: file}, - async: false, - success: function(users) { - if (users) { - icon = OC.imagePath('core', 'actions/shared'); - $.each(users, function(index, row) { - if (row.uid_shared_with == 'public') { - icon = OC.imagePath('core', 'actions/public'); - } - }); +OC.Share={ + icons:[], + itemUsers:[], + itemGroups:[], + itemPrivateLink:false, + usersAndGroups:[], + loadIcons:function() { + // Cache all icons for shared files + $.getJSON(OC.filePath('files_sharing', 'ajax', 'getstatuses.php'), function(result) { + if (result && result.status === 'success') { + $.each(result.data, function(item, hasPrivateLink) { + if (hasPrivateLink) { + OC.Share.icons[item] = OC.imagePath('core', 'actions/public'); } else { - icon = OC.imagePath('core', 'actions/share'); - } - shared_status[file]= { timestamp: new Date().getTime(), icon: icon }; - } - }); - return icon; - }, function(filename) { - if (($('#dropdown').length > 0)) { - $('#dropdown').hide('blind', function() { - var dropdownFile = $('#dropdown').data('file') - var file = $('#dir').val()+'/'+filename; - $('#dropdown').remove(); - $('tr').removeClass('mouseOver'); - if (dropdownFile != file) { - createDropdown(filename, file); + OC.Share.icons[item] = OC.imagePath('core', 'actions/shared'); } }); + } + }); + }, + loadItem:function(item) { + $.ajax({type: 'GET', url: OC.filePath('files_sharing', 'ajax', 'getitem.php'), data: { item: item }, async: false, success: function(result) { + if (result && result.status === 'success') { + var item = result.data; + OC.Share.itemUsers = item.users; + OC.Share.itemGroups = item.groups; + OC.Share.itemPrivateLink = item.privateLink; + } + }}); + }, + share:function(source, uid_shared_with, permissions, callback) { + $.post(OC.filePath('files_sharing', 'ajax', 'share.php'), { sources: source, uid_shared_with: uid_shared_with, permissions: permissions }, function(result) { + if (result && result.status === 'success') { + if (callback) { + callback(result.data); + } } else { - createDropdown(filename, $('#dir').val()+'/'+filename); + OC.dialogs.alert(result.data.message, 'Error while sharing'); + } + }); + }, + unshare:function(source, uid_shared_with, callback) { + $.post(OC.filePath('files_sharing', 'ajax', 'unshare.php'), { source: source, uid_shared_with: uid_shared_with }, function(result) { + if (result && result.status === 'success') { + if (callback) { + callback(); + } + } else { + OC.dialogs.alert('Error', 'Error while unsharing'); + } + }); + }, + changePermissions:function(source, uid_shared_with, permissions) { + $.post(OC.filePath('files_sharing','ajax','setpermissions.php'), { source: source, uid_shared_with: uid_shared_with, permissions: permissions }, function(result) { + if (!result || result.status !== 'success') { + OC.dialogs.alert('Error', 'Error while changing permissions'); + } + }); + }, + showDropDown:function(item, appendTo) { + OC.Share.loadItem(item); + var html = '