diff options
Diffstat (limited to 'lib/phpFlickr/PEAR')
-rw-r--r-- | lib/phpFlickr/PEAR/DB.php | 1388 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/DB/common.php | 2157 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/DB/mysql.php | 1034 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/DB/pgsql.php | 1097 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/DB/storage.php | 504 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/HTTP/Request.php | 1484 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/HTTP/Request/Listener.php | 106 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/Net/Socket.php | 528 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/Net/URL.php | 410 | ||||
-rw-r--r-- | lib/phpFlickr/PEAR/PEAR.php | 1108 |
10 files changed, 9816 insertions, 0 deletions
diff --git a/lib/phpFlickr/PEAR/DB.php b/lib/phpFlickr/PEAR/DB.php new file mode 100644 index 000000000..a3eede070 --- /dev/null +++ b/lib/phpFlickr/PEAR/DB.php @@ -0,0 +1,1388 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Database independent query interface + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * 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 web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: DB.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + + +// {{{ constants +// {{{ error codes + +/**#@+ + * One of PEAR DB's portable error codes. + * @see DB_common::errorCode(), DB::errorMessage() + * + * {@internal If you add an error code here, make sure you also add a textual + * version of it in DB::errorMessage().}} + */ + +/** + * The code returned by many methods upon success + */ +define('DB_OK', 1); + +/** + * Unkown error + */ +define('DB_ERROR', -1); + +/** + * Syntax error + */ +define('DB_ERROR_SYNTAX', -2); + +/** + * Tried to insert a duplicate value into a primary or unique index + */ +define('DB_ERROR_CONSTRAINT', -3); + +/** + * An identifier in the query refers to a non-existant object + */ +define('DB_ERROR_NOT_FOUND', -4); + +/** + * Tried to create a duplicate object + */ +define('DB_ERROR_ALREADY_EXISTS', -5); + +/** + * The current driver does not support the action you attempted + */ +define('DB_ERROR_UNSUPPORTED', -6); + +/** + * The number of parameters does not match the number of placeholders + */ +define('DB_ERROR_MISMATCH', -7); + +/** + * A literal submitted did not match the data type expected + */ +define('DB_ERROR_INVALID', -8); + +/** + * The current DBMS does not support the action you attempted + */ +define('DB_ERROR_NOT_CAPABLE', -9); + +/** + * A literal submitted was too long so the end of it was removed + */ +define('DB_ERROR_TRUNCATED', -10); + +/** + * A literal number submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_NUMBER', -11); + +/** + * A literal date submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_DATE', -12); + +/** + * Attempt to divide something by zero + */ +define('DB_ERROR_DIVZERO', -13); + +/** + * A database needs to be selected + */ +define('DB_ERROR_NODBSELECTED', -14); + +/** + * Could not create the object requested + */ +define('DB_ERROR_CANNOT_CREATE', -15); + +/** + * Could not drop the database requested because it does not exist + */ +define('DB_ERROR_CANNOT_DROP', -17); + +/** + * An identifier in the query refers to a non-existant table + */ +define('DB_ERROR_NOSUCHTABLE', -18); + +/** + * An identifier in the query refers to a non-existant column + */ +define('DB_ERROR_NOSUCHFIELD', -19); + +/** + * The data submitted to the method was inappropriate + */ +define('DB_ERROR_NEED_MORE_DATA', -20); + +/** + * The attempt to lock the table failed + */ +define('DB_ERROR_NOT_LOCKED', -21); + +/** + * The number of columns doesn't match the number of values + */ +define('DB_ERROR_VALUE_COUNT_ON_ROW', -22); + +/** + * The DSN submitted has problems + */ +define('DB_ERROR_INVALID_DSN', -23); + +/** + * Could not connect to the database + */ +define('DB_ERROR_CONNECT_FAILED', -24); + +/** + * The PHP extension needed for this DBMS could not be found + */ +define('DB_ERROR_EXTENSION_NOT_FOUND',-25); + +/** + * The present user has inadequate permissions to perform the task requestd + */ +define('DB_ERROR_ACCESS_VIOLATION', -26); + +/** + * The database requested does not exist + */ +define('DB_ERROR_NOSUCHDB', -27); + +/** + * Tried to insert a null value into a column that doesn't allow nulls + */ +define('DB_ERROR_CONSTRAINT_NOT_NULL',-29); +/**#@-*/ + + +// }}} +// {{{ prepared statement-related + + +/**#@+ + * Identifiers for the placeholders used in prepared statements. + * @see DB_common::prepare() + */ + +/** + * Indicates a scalar (<kbd>?</kbd>) placeholder was used + * + * Quote and escape the value as necessary. + */ +define('DB_PARAM_SCALAR', 1); + +/** + * Indicates an opaque (<kbd>&</kbd>) placeholder was used + * + * The value presented is a file name. Extract the contents of that file + * and place them in this column. + */ +define('DB_PARAM_OPAQUE', 2); + +/** + * Indicates a misc (<kbd>!</kbd>) placeholder was used + * + * The value should not be quoted or escaped. + */ +define('DB_PARAM_MISC', 3); +/**#@-*/ + + +// }}} +// {{{ binary data-related + + +/**#@+ + * The different ways of returning binary data from queries. + */ + +/** + * Sends the fetched data straight through to output + */ +define('DB_BINMODE_PASSTHRU', 1); + +/** + * Lets you return data as usual + */ +define('DB_BINMODE_RETURN', 2); + +/** + * Converts the data to hex format before returning it + * + * For example the string "123" would become "313233". + */ +define('DB_BINMODE_CONVERT', 3); +/**#@-*/ + + +// }}} +// {{{ fetch modes + + +/**#@+ + * Fetch Modes. + * @see DB_common::setFetchMode() + */ + +/** + * Indicates the current default fetch mode should be used + * @see DB_common::$fetchmode + */ +define('DB_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('DB_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('DB_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('DB_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results, make the column name the first level + * of the array and put the row number in the second level of the array + * + * This is flipped from the normal behavior, which puts the row numbers + * in the first level of the array and the column names in the second level. + */ +define('DB_FETCHMODE_FLIPPED', 4); +/**#@-*/ + +/**#@+ + * Old fetch modes. Left here for compatibility. + */ +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); +define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED); +/**#@-*/ + + +// }}} +// {{{ tableInfo() && autoPrepare()-related + + +/**#@+ + * The type of information to return from the tableInfo() method. + * + * Bitwised constants, so they can be combined using <kbd>|</kbd> + * and removed using <kbd>^</kbd>. + * + * @see DB_common::tableInfo() + * + * {@internal Since the TABLEINFO constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}} + */ +define('DB_TABLEINFO_ORDER', 1); +define('DB_TABLEINFO_ORDERTABLE', 2); +define('DB_TABLEINFO_FULL', 3); +/**#@-*/ + + +/**#@+ + * The type of query to create with the automatic query building methods. + * @see DB_common::autoPrepare(), DB_common::autoExecute() + */ +define('DB_AUTOQUERY_INSERT', 1); +define('DB_AUTOQUERY_UPDATE', 2); +/**#@-*/ + + +// }}} +// {{{ portability modes + + +/**#@+ + * Portability Modes. + * + * Bitwised constants, so they can be combined using <kbd>|</kbd> + * and removed using <kbd>^</kbd>. + * + * @see DB_common::setOption() + * + * {@internal Since the PORTABILITY constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}} + */ + +/** + * Turn off all portability features + */ +define('DB_PORTABILITY_NONE', 0); + +/** + * Convert names of tables and fields to lower case + * when using the get*(), fetch*() and tableInfo() methods + */ +define('DB_PORTABILITY_LOWERCASE', 1); + +/** + * Right trim the data output by get*() and fetch*() + */ +define('DB_PORTABILITY_RTRIM', 2); + +/** + * Force reporting the number of rows deleted + */ +define('DB_PORTABILITY_DELETE_COUNT', 4); + +/** + * Enable hack that makes numRows() work in Oracle + */ +define('DB_PORTABILITY_NUMROWS', 8); + +/** + * Makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_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 DB_ERROR_NOSUCHFIELD. + */ +define('DB_PORTABILITY_ERRORS', 16); + +/** + * Convert null values to empty strings in data output by + * get*() and fetch*() + */ +define('DB_PORTABILITY_NULL_TO_EMPTY', 32); + +/** + * Turn on all portability features + */ +define('DB_PORTABILITY_ALL', 63); +/**#@-*/ + +// }}} + + +// }}} +// {{{ class DB + +/** + * Database independent query interface + * + * The main "DB" 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 DB is as follows (indentation means inheritance): + * <pre> + * DB The main DB class. This is simply a utility class + * with some "static" methods for creating DB objects as + * well as common utility functions for other DB classes. + * + * DB_common The base for each DB implementation. Provides default + * | implementations (in OO lingo virtual methods) for + * | the actual DB implementations as well as a bunch of + * | query utility functions. + * | + * +-DB_mysql The DB implementation for MySQL. Inherits DB_common. + * When calling DB::factory or DB::connect for MySQL + * connections, the object returned is an instance of this + * class. + * </pre> + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB +{ + // {{{ &factory() + + /** + * Create a new DB object for the specified database type but don't + * connect to the database + * + * @param string $type the database type (eg "mysql") + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function &factory($type, $options = false) + { + if (!is_array($options)) { + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/{$type}.php"; + } else { + @include_once "DB/{$type}.php"; + } + + $classname = "DB_${type}"; + + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj =& new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + return $obj; + } + + // }}} + // {{{ &connect() + + /** + * Create a new DB object including a connection to the specified database + * + * Example 1. + * <code> + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:password@host/database'; + * $options = array( + * 'debug' => 2, + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param mixed $dsn the string "data source name" or array in the + * format returned by DB::parseDSN() + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(), + * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(), + * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(), + * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(), + * DB_sybase::connect() + * + * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError() + */ + function &connect($dsn, $options = array()) + { + $dsninfo = DB::parseDSN($dsn); + $type = $dsninfo['phptype']; + + if (!is_array($options)) { + /* + * For backwards compatibility. $options used to be boolean, + * indicating whether the connection should be persistent. + */ + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/${type}.php"; + } else { + @include_once "DB/${type}.php"; + } + + $classname = "DB_${type}"; + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj =& new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + $err = $obj->connect($dsninfo, $obj->getOption('persistent')); + if (DB::isError($err)) { + $err->addUserInfo($dsn); + return $err; + } + + return $obj; + } + + // }}} + // {{{ apiVersion() + + /** + * Return the DB API version + * + * @return string the DB API version number + */ + function apiVersion() + { + return '@package_version@'; + } + + // }}} + // {{{ isError() + + /** + * Determines if a variable is a DB_Error object + * + * @param mixed $value the variable to check + * + * @return bool whether $value is DB_Error object + */ + function isError($value) + { + return is_a($value, 'DB_Error'); + } + + // }}} + // {{{ isConnection() + + /** + * Determines if a value is a DB_<driver> object + * + * @param mixed $value the value to test + * + * @return bool whether $value is a DB_<driver> object + */ + function isConnection($value) + { + return (is_object($value) && + is_subclass_of($value, 'db_common') && + method_exists($value, 'simpleQuery')); + } + + // }}} + // {{{ isManip() + + /** + * Tell whether a query is a data manipulation or data definition query + * + * Examples of data manipulation queries are INSERT, UPDATE and DELETE. + * Examples of data definition queries are CREATE, DROP, ALTER, GRANT, + * REVOKE. + * + * @param string $query the query + * + * @return boolean whether $query is a data manipulation query + */ + function isManip($query) + { + $manips = 'INSERT|UPDATE|DELETE|REPLACE|' + . 'CREATE|DROP|' + . 'LOAD DATA|SELECT .* INTO|COPY|' + . 'ALTER|GRANT|REVOKE|' + . 'LOCK|UNLOCK'; + if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) { + return true; + } + return false; + } + + // }}} + // {{{ errorMessage() + + /** + * Return a textual error message for a DB error code + * + * @param integer $value the DB error code + * + * @return string the error message or false if the error code was + * not recognized + */ + function errorMessage($value) + { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + DB_ERROR => 'unknown error', + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + DB_ERROR_ALREADY_EXISTS => 'already exists', + DB_ERROR_CANNOT_CREATE => 'can not create', + DB_ERROR_CANNOT_DROP => 'can not drop', + DB_ERROR_CONNECT_FAILED => 'connect failed', + DB_ERROR_CONSTRAINT => 'constraint violation', + DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + DB_ERROR_DIVZERO => 'division by zero', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'invalid date or time', + DB_ERROR_INVALID_DSN => 'invalid DSN', + DB_ERROR_INVALID_NUMBER => 'invalid number', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + DB_ERROR_NODBSELECTED => 'no database selected', + DB_ERROR_NOSUCHDB => 'no such database', + DB_ERROR_NOSUCHFIELD => 'no such field', + DB_ERROR_NOSUCHTABLE => 'no such table', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'not found', + DB_ERROR_NOT_LOCKED => 'not locked', + DB_ERROR_SYNTAX => 'syntax error', + DB_ERROR_UNSUPPORTED => 'not supported', + DB_ERROR_TRUNCATED => 'truncated', + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + DB_OK => 'no error', + ); + } + + if (DB::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? $errorMessages[$value] + : $errorMessages[DB_ERROR]; + } + + // }}} + // {{{ parseDSN() + + /** + * 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: + * <code> + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * </code> + * + * Most variations are allowed: + * <code> + * 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 + * </code> + * + * @param string $dsn 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 + */ + function parseDSN($dsn) + { + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + ); + + 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 + + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + // $dsn => proto(proto_opts)/database + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + } else { + // $dsn => protocol+hostspec/database (old format) + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if (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 ($parsed['protocol'] == 'tcp') { + if (strpos($proto_opts, ':') !== false) { + list($parsed['hostspec'], + $parsed['port']) = explode(':', $proto_opts); + } else { + $parsed['hostspec'] = $proto_opts; + } + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + if (($pos = strpos($dsn, '?')) === false) { + // /database + $parsed['database'] = rawurldecode($dsn); + } else { + // /database?param1=value1¶m2=value2 + $parsed['database'] = rawurldecode(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; + } + + // }}} +} + +// }}} +// {{{ class DB_Error + +/** + * DB_Error implements a class for reporting portable database error + * messages + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_Error extends PEAR_Error +{ + // {{{ constructor + + /** + * DB_Error constructor + * + * @param mixed $code DB error code, or string with error message + * @param int $mode what "error mode" to operate in + * @param int $level what error level to use for $mode & + * PEAR_ERROR_TRIGGER + * @param mixed $debuginfo additional debug info, such as the last query + * + * @see PEAR_Error + */ + function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + if (is_int($code)) { + $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, + $mode, $level, $debuginfo); + } else { + $this->PEAR_Error("DB Error: $code", DB_ERROR, + $mode, $level, $debuginfo); + } + } + + // }}} +} + +// }}} +// {{{ class DB_result + +/** + * This class implements a wrapper for a DB result set + * + * A new instance of this class will be returned by the DB implementation + * after processing a query that returns data. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_result +{ + // {{{ properties + + /** + * Should results be freed automatically when there are no more rows? + * @var boolean + * @see DB_common::$options + */ + var $autofree; + + /** + * A reference to the DB_<driver> object + * @var object + */ + var $dbh; + + /** + * The current default fetch mode + * @var integer + * @see DB_common::$fetchmode + */ + var $fetchmode; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + * @see DB_common::$fetchmode_object_class + */ + var $fetchmode_object_class; + + /** + * The number of rows to fetch from a limit query + * @var integer + */ + var $limit_count = null; + + /** + * The row to start fetching from in limit queries + * @var integer + */ + var $limit_from = null; + + /** + * The execute parameters that created this result + * @var array + * @since Property available since Release 1.7.0 + */ + var $parameters; + + /** + * The query string that created this result + * + * Copied here incase it changes in $dbh, which is referenced + * + * @var string + * @since Property available since Release 1.7.0 + */ + var $query; + + /** + * The query result resource id created by PHP + * @var resource + */ + var $result; + + /** + * The present row being dealt with + * @var integer + */ + var $row_counter = null; + + /** + * The prepared statement resource id created by PHP in $dbh + * + * This resource is only available when the result set was created using + * a driver's native execute() method, not PEAR DB's emulated one. + * + * Copied here incase it changes in $dbh, which is referenced + * + * {@internal Mainly here because the InterBase/Firebird API is only + * able to retrieve data from result sets if the statemnt handle is + * still in scope.}} + * + * @var resource + * @since Property available since Release 1.7.0 + */ + var $statement; + + + // }}} + // {{{ constructor + + /** + * This constructor sets the object's properties + * + * @param object &$dbh the DB object reference + * @param resource $result the result resource id + * @param array $options an associative array with result options + * + * @return void + */ + function DB_result(&$dbh, $result, $options = array()) + { + $this->autofree = $dbh->options['autofree']; + $this->dbh = &$dbh; + $this->fetchmode = $dbh->fetchmode; + $this->fetchmode_object_class = $dbh->fetchmode_object_class; + $this->parameters = $dbh->last_parameters; + $this->query = $dbh->last_query; + $this->result = $result; + $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt; + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + } + + /** + * Set options for the DB_result object + * + * @param string $key the option to set + * @param mixed $value the value to set the option to + * + * @return void + */ + function setOption($key, $value = null) + { + switch ($key) { + case 'limit_from': + $this->limit_from = $value; + break; + case 'limit_count': + $this->limit_count = $value; + } + } + + // }}} + // {{{ fetchRow() + + /** + * Fetch a row of data and return it by reference into an array + * + * The type of array returned can be controlled either by setting this + * method's <var>$fetchmode</var> parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + <var>DB_PORTABILITY_LOWERCASE</var> + * convert names of fields to lower case + * + * + <var>DB_PORTABILITY_RTRIM</var> + * right trim the data + * + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed an array or object containing the row's data, + * NULL when the end of the result set is reached + * or a DB_Error object on failure. + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ($this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + $tmp = null; + return $tmp; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // The default mode is specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = &new $object_class($arr); + } + } + return $arr; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ fetchInto() + + /** + * Fetch a row of data into an array which is passed by reference + * + * The type of array returned can be controlled either by setting this + * method's <var>$fetchmode</var> parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + <var>DB_PORTABILITY_LOWERCASE</var> + * convert names of fields to lower case + * + * + <var>DB_PORTABILITY_RTRIM</var> + * right trim the data + * + * @param array &$arr the variable where the data should be placed + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed DB_OK if a row is processed, NULL when the end of the + * result set is reached or a DB_Error object on failure + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ( + $this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + return null; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // default mode specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = new $object_class($arr); + } + } + return DB_OK; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ numCols() + + /** + * Get the the number of columns in a result set + * + * @return int the number of columns. A DB_Error object on failure. + */ + function numCols() + { + return $this->dbh->numCols($this->result); + } + + // }}} + // {{{ numRows() + + /** + * Get the number of rows in a result set + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows() + { + if ($this->dbh->features['numrows'] === 'emulate' + && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS) + { + if ($this->dbh->features['prepare']) { + $res = $this->dbh->query($this->query, $this->parameters); + } else { + $res = $this->dbh->query($this->query); + } + if (DB::isError($res)) { + return $res; + } + $i = 0; + while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) { + $i++; + } + return $i; + } else { + return $this->dbh->numRows($this->result); + } + } + + // }}} + // {{{ nextResult() + + /** + * Get the next result if a batch of queries was executed + * + * @return bool true if a new result is available or false if not + */ + function nextResult() + { + return $this->dbh->nextResult($this->result); + } + + // }}} + // {{{ free() + + /** + * Frees the resources allocated for this result set + * + * @return bool true on success. A DB_Error object on failure. + */ + function free() + { + $err = $this->dbh->freeResult($this->result); + if (DB::isError($err)) { + return $err; + } + $this->result = false; + $this->statement = false; + return true; + } + + // }}} + // {{{ tableInfo() + + /** + * @see DB_common::tableInfo() + * @deprecated Method deprecated some time before Release 1.2 + */ + function tableInfo($mode = null) + { + if (is_string($mode)) { + return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA); + } + return $this->dbh->tableInfo($this, $mode); + } + + // }}} + // {{{ getQuery() + + /** + * Determine the query string that created this result + * + * @return string the query string + * + * @since Method available since Release 1.7.0 + */ + function getQuery() + { + return $this->query; + } + + // }}} + // {{{ getRowCounter() + + /** + * Tells which row number is currently being processed + * + * @return integer the current row being looked at. Starts at 1. + */ + function getRowCounter() + { + return $this->row_counter; + } + + // }}} +} + +// }}} +// {{{ class DB_row + +/** + * PEAR DB Row Object + * + * The object contains a row of data from a result set. Each column's data + * is placed in a property named for the column. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + * @see DB_common::setFetchMode() + */ +class DB_row +{ + // {{{ constructor + + /** + * The constructor places a row's data into properties of this object + * + * @param array the array containing the row's data + * + * @return void + */ + function DB_row(&$arr) + { + foreach ($arr as $key => $value) { + $this->$key = &$arr[$key]; + } + } + + // }}} +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/lib/phpFlickr/PEAR/DB/common.php b/lib/phpFlickr/PEAR/DB/common.php new file mode 100644 index 000000000..04e71ff52 --- /dev/null +++ b/lib/phpFlickr/PEAR/DB/common.php @@ -0,0 +1,2157 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Contains the DB_common base class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * 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 web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: common.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + +/** + * DB_common is the base class from which each database driver class extends + * + * All common methods are declared here. If a given DBMS driver contains + * a particular method, that method will overload the one here. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_common extends PEAR +{ + // {{{ properties + + /** + * The current default fetch mode + * @var integer + */ + var $fetchmode = DB_FETCHMODE_ORDERED; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + */ + var $fetchmode_object_class = 'stdClass'; + + /** + * Was a connection present when the object was serialized()? + * @var bool + * @see DB_common::__sleep(), DB_common::__wake() + */ + var $was_connected = null; + + /** + * The most recently executed query + * @var string + */ + var $last_query = ''; + + /** + * Run-time configuration options + * + * The 'optimize' option has been deprecated. Use the 'portability' + * option instead. + * + * @var array + * @see DB_common::setOption() + */ + var $options = array( + 'result_buffering' => 500, + 'persistent' => false, + 'ssl' => false, + 'debug' => 0, + 'seqname_format' => '%s_seq', + 'autofree' => false, + 'portability' => DB_PORTABILITY_NONE, + 'optimize' => 'performance', // Deprecated. Use 'portability'. + ); + + /** + * The parameters from the most recently executed query + * @var array + * @since Property available since Release 1.7.0 + */ + var $last_parameters = array(); + + /** + * The elements from each prepared statement + * @var array + */ + var $prepare_tokens = array(); + + /** + * The data types of the various elements in each prepared statement + * @var array + */ + var $prepare_types = array(); + + /** + * The prepared queries + * @var array + */ + var $prepared_queries = array(); + + + // }}} + // {{{ DB_common + + /** + * This constructor calls <kbd>$this->PEAR('DB_Error')</kbd> + * + * @return void + */ + function DB_common() + { + $this->PEAR('DB_Error'); + } + + // }}} + // {{{ __sleep() + + /** + * Automatically indicates which properties should be saved + * when PHP's serialize() function is called + * + * @return array the array of properties names that should be saved + */ + function __sleep() + { + if ($this->connection) { + // Don't disconnect(), people use serialize() for many reasons + $this->was_connected = true; + } else { + $this->was_connected = false; + } + if (isset($this->autocommit)) { + return array('autocommit', + 'dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } else { + return array('dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } + } + + // }}} + // {{{ __wakeup() + + /** + * Automatically reconnects to the database when PHP's unserialize() + * function is called + * + * The reconnection attempt is only performed if the object was connected + * at the time PHP's serialize() function was run. + * + * @return void + */ + function __wakeup() + { + if ($this->was_connected) { + $this->connect($this->dsn, $this->options); + } + } + + // }}} + // {{{ __toString() + + /** + * Automatic string conversion for PHP 5 + * + * @return string a string describing the current PEAR DB object + * + * @since Method available since Release 1.7.0 + */ + function __toString() + { + $info = strtolower(get_class($this)); + $info .= ': (phptype=' . $this->phptype . + ', dbsyntax=' . $this->dbsyntax . + ')'; + if ($this->connection) { + $info .= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ toString() + + /** + * DEPRECATED: String conversion method + * + * @return string a string describing the current PEAR DB object + * + * @deprecated Method deprecated in Release 1.7.0 + */ + function toString() + { + return $this->__toString(); + } + + // }}} + // {{{ quoteString() + + /** + * DEPRECATED: Quotes a string so it can be safely used within string + * delimiters in a query + * + * @param string $string the string to be quoted + * + * @return string the quoted string + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Method deprecated some time before Release 1.2 + */ + function quoteString($string) + { + $string = $this->quote($string); + if ($string{0} == "'") { + return substr($string, 1, -1); + } + return $string; + } + + // }}} + // {{{ quote() + + /** + * DEPRECATED: Quotes a string so it can be safely used in a query + * + * @param string $string the string to quote + * + * @return string the quoted string or the string <samp>NULL</samp> + * if the value submitted is <kbd>null</kbd>. + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Deprecated in release 1.6.0 + */ + function quote($string = null) + { + return ($string === null) ? 'NULL' + : "'" . str_replace("'", "''", $string) . "'"; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes 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. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (<kbd>`</kbd>) -- due to MySQL + * + double quote (<kbd>"</kbd>) -- due to Oracle + * + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + odbc(access) + * + odbc(db2) + * + pgsql + * + sqlite + * + sybase (must execute <kbd>set quoted_identifier on</kbd> sometime + * prior to use) + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str the identifier name to be quoted + * + * @return string the quoted identifier + * + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '"' . str_replace('"', '""', $str) . '"'; + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * The output depends on the PHP data type of input and the database + * type being used. + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + * <ul> + * <li> + * <kbd>input</kbd> -> <samp>returns</samp> + * </li> + * <li> + * <kbd>null</kbd> -> the string <samp>NULL</samp> + * </li> + * <li> + * <kbd>integer</kbd> or <kbd>double</kbd> -> the unquoted number + * </li> + * <li> + * <kbd>bool</kbd> -> output depends on the driver in use + * Most drivers return integers: <samp>1</samp> if + * <kbd>true</kbd> or <samp>0</samp> if + * <kbd>false</kbd>. + * Some return strings: <samp>TRUE</samp> if + * <kbd>true</kbd> or <samp>FALSE</samp> if + * <kbd>false</kbd>. + * Finally one returns strings: <samp>T</samp> if + * <kbd>true</kbd> or <samp>F</samp> if + * <kbd>false</kbd>. Here is a list of each DBMS, + * the values returned and the suggested column type: + * <ul> + * <li> + * <kbd>dbase</kbd> -> <samp>T/F</samp> + * (<kbd>Logical</kbd>) + * </li> + * <li> + * <kbd>fbase</kbd> -> <samp>TRUE/FALSE</samp> + * (<kbd>BOOLEAN</kbd>) + * </li> + * <li> + * <kbd>ibase</kbd> -> <samp>1/0</samp> + * (<kbd>SMALLINT</kbd>) [1] + * </li> + * <li> + * <kbd>ifx</kbd> -> <samp>1/0</samp> + * (<kbd>SMALLINT</kbd>) [1] + * </li> + * <li> + * <kbd>msql</kbd> -> <samp>1/0</samp> + * (<kbd>INTEGER</kbd>) + * </li> + * <li> + * <kbd>mssql</kbd> -> <samp>1/0</samp> + * (<kbd>BIT</kbd>) + * </li> + * <li> + * <kbd>mysql</kbd> -> <samp>1/0</samp> + * (<kbd>TINYINT(1)</kbd>) + * </li> + * <li> + * <kbd>mysqli</kbd> -> <samp>1/0</samp> + * (<kbd>TINYINT(1)</kbd>) + * </li> + * <li> + * <kbd>oci8</kbd> -> <samp>1/0</samp> + * (<kbd>NUMBER(1)</kbd>) + * </li> + * <li> + * <kbd>odbc</kbd> -> <samp>1/0</samp> + * (<kbd>SMALLINT</kbd>) [1] + * </li> + * <li> + * <kbd>pgsql</kbd> -> <samp>TRUE/FALSE</samp> + * (<kbd>BOOLEAN</kbd>) + * </li> + * <li> + * <kbd>sqlite</kbd> -> <samp>1/0</samp> + * (<kbd>INTEGER</kbd>) + * </li> + * <li> + * <kbd>sybase</kbd> -> <samp>1/0</samp> + * (<kbd>TINYINT(1)</kbd>) + * </li> + * </ul> + * [1] Accommodate the lowest common denominator because not all + * versions of have <kbd>BOOLEAN</kbd>. + * </li> + * <li> + * other (including strings and numeric strings) -> + * the data with single quotes escaped by preceeding + * single quotes, backslashes are escaped by preceeding + * backslashes, then the whole string is encapsulated + * between single quotes + * </li> + * </ul> + * + * @see DB_common::escapeSimple() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in) || is_double($in)) { + return $in; + } elseif (is_bool($in)) { + return $in ? 1 : 0; + } elseif (is_null($in)) { + return 'NULL'; + } else { + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", $str); + } + + // }}} + // {{{ provides() + + /** + * Tells whether the present driver supports a given feature + * + * @param string $feature the feature you're curious about + * + * @return bool whether this driver supports $feature + */ + function provides($feature) + { + return $this->features[$feature]; + } + + // }}} + // {{{ setFetchMode() + + /** + * Sets the fetch mode that should be used by default for query results + * + * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC + * or DB_FETCHMODE_OBJECT + * @param string $object_class the class name of the object to be returned + * by the fetch methods when the + * DB_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 posibility to use + * and extend the 'DB_row' class. + * + * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case DB_FETCHMODE_OBJECT: + $this->fetchmode_object_class = $object_class; + case DB_FETCHMODE_ORDERED: + case DB_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError('invalid fetchmode mode'); + } + } + + // }}} + // {{{ setOption() + + /** + * Sets run-time configuration options for PEAR DB + * + * Options, their data types, default values and description: + * <ul> + * <li> + * <var>autofree</var> <kbd>boolean</kbd> = <samp>false</samp> + * <br />should results be freed automatically when there are no + * more rows? + * </li><li> + * <var>result_buffering</var> <kbd>integer</kbd> = <samp>500</samp> + * <br />how many rows of the result set should be buffered? + * <br />In mysql: mysql_unbuffered_query() is used instead of + * mysql_query() if this value is 0. (Release 1.7.0) + * <br />In oci8: this value is passed to ocisetprefetch(). + * (Release 1.7.0) + * </li><li> + * <var>debug</var> <kbd>integer</kbd> = <samp>0</samp> + * <br />debug level + * </li><li> + * <var>persistent</var> <kbd>boolean</kbd> = <samp>false</samp> + * <br />should the connection be persistent? + * </li><li> + * <var>portability</var> <kbd>integer</kbd> = <samp>DB_PORTABILITY_NONE</samp> + * <br />portability mode constant (see below) + * </li><li> + * <var>seqname_format</var> <kbd>string</kbd> = <samp>%s_seq</samp> + * <br />the sprintf() format string used on sequence names. This + * format is applied to sequence names passed to + * createSequence(), nextID() and dropSequence(). + * </li><li> + * <var>ssl</var> <kbd>boolean</kbd> = <samp>false</samp> + * <br />use ssl to connect? + * </li> + * </ul> + * + * ----------------------------------------- + * + * PORTABILITY MODES + * + * These modes are bitwised, so they can be combined using <kbd>|</kbd> + * and removed using <kbd>^</kbd>. See the examples section below on how + * to do this. + * + * <samp>DB_PORTABILITY_NONE</samp> + * turn off all portability features + * + * This mode gets automatically turned on if the deprecated + * <var>optimize</var> option gets set to <samp>performance</samp>. + * + * + * <samp>DB_PORTABILITY_LOWERCASE</samp> + * convert names of tables and fields to lower case when using + * <kbd>get*()</kbd>, <kbd>fetch*()</kbd> and <kbd>tableInfo()</kbd> + * + * This mode gets automatically turned on in the following databases + * if the deprecated option <var>optimize</var> gets set to + * <samp>portability</samp>: + * + oci8 + * + * + * <samp>DB_PORTABILITY_RTRIM</samp> + * right trim the data output by <kbd>get*()</kbd> <kbd>fetch*()</kbd> + * + * + * <samp>DB_PORTABILITY_DELETE_COUNT</samp> + * force reporting the number of rows deleted + * + * Some DBMS's don't count the number of rows deleted when performing + * simple <kbd>DELETE FROM tablename</kbd> queries. This portability + * mode tricks such DBMS's into telling the count by adding + * <samp>WHERE 1=1</samp> to the end of <kbd>DELETE</kbd> queries. + * + * This mode gets automatically turned on in the following databases + * if the deprecated option <var>optimize</var> gets set to + * <samp>portability</samp>: + * + fbsql + * + mysql + * + mysqli + * + sqlite + * + * + * <samp>DB_PORTABILITY_NUMROWS</samp> + * enable hack that makes <kbd>numRows()</kbd> work in Oracle + * + * This mode gets automatically turned on in the following databases + * if the deprecated option <var>optimize</var> gets set to + * <samp>portability</samp>: + * + oci8 + * + * + * <samp>DB_PORTABILITY_ERRORS</samp> + * makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_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 DB_ERROR_NOSUCHFIELD. + * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD + * + * <samp>DB_PORTABILITY_NULL_TO_EMPTY</samp> + * convert null values to empty strings in data output by get*() and + * fetch*(). Needed because Oracle considers empty strings to be null, + * while most other DBMS's know the difference between empty and null. + * + * + * <samp>DB_PORTABILITY_ALL</samp> + * turn on all portability features + * + * ----------------------------------------- + * + * Example 1. Simple setOption() example + * <code> + * $db->setOption('autofree', true); + * </code> + * + * Example 2. Portability for lowercasing and trimming + * <code> + * $db->setOption('portability', + * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); + * </code> + * + * Example 3. All portability options except trimming + * <code> + * $db->setOption('portability', + * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); + * </code> + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::$options + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + $this->options[$option] = $value; + + /* + * Backwards compatibility check for the deprecated 'optimize' + * option. Done here in case settings change after connecting. + */ + if ($option == 'optimize') { + if ($value == 'portability') { + switch ($this->phptype) { + case 'oci8': + $this->options['portability'] = + DB_PORTABILITY_LOWERCASE | + DB_PORTABILITY_NUMROWS; + break; + case 'fbsql': + case 'mysql': + case 'mysqli': + case 'sqlite': + $this->options['portability'] = + DB_PORTABILITY_DELETE_COUNT; + break; + } + } else { + $this->options['portability'] = DB_PORTABILITY_NONE; + } + } + + return DB_OK; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ getOption() + + /** + * Returns the value of an option + * + * @param string $option the option name you're curious about + * + * @return mixed the option's value + */ + function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute() + * + * Creates a query that can be run multiple times. Each time it is run, + * the placeholders, if any, will be replaced by the contents of + * execute()'s $data argument. + * + * Three types of placeholders can be used: + * + <kbd>?</kbd> scalar value (i.e. strings, integers). The system + * will automatically quote and escape the data. + * + <kbd>!</kbd> value is inserted 'as is' + * + <kbd>&</kbd> requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Example 1. + * <code> + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * </code> + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders: + * <pre> + * "UPDATE foo SET col=? WHERE col='over \& under'" + * </pre> + * + * With some database backends, this is emulated. + * + * {@internal ibase and oci8 have their own prepare() methods.}} + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. A DB_Error object + * on failure. + * + * @see DB_common::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1, + PREG_SPLIT_DELIM_CAPTURE); + $token = 0; + $types = array(); + $newtokens = array(); + + foreach ($tokens as $val) { + switch ($val) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + break; + default: + $newtokens[] = preg_replace('/\\\([&?!])/', "\\1", $val); + } + } + + $this->prepare_tokens[] = &$newtokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = implode(' ', $newtokens); + + return $k; + } + + // }}} + // {{{ autoPrepare() + + /** + * Automaticaly generates an insert or update query and pass it to prepare() + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return resource the query handle + * + * @uses DB_common::prepare(), DB_common::buildManipSQL() + */ + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (DB::isError($query)) { + return $query; + } + return $this->prepare($query); + } + + // }}} + // {{{ autoExecute() + + /** + * Automaticaly generates an insert or update query and call prepare() + * and execute() with it + * + * @param string $table the table name + * @param array $fields_values the associative array where $key is a + * field name and $value its value + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @uses DB_common::autoPrepare(), DB_common::execute() + */ + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, + $where); + if (DB::isError($sth)) { + return $sth; + } + $ret =& $this->execute($sth, array_values($fields_values)); + $this->freePrepared($sth); + return $ret; + + } + + // }}} + // {{{ buildManipSQL() + + /** + * Produces an SQL query string for autoPrepare() + * + * Example: + * <pre> + * buildManipSQL('table_sql', array('field1', 'field2', 'field3'), + * DB_AUTOQUERY_INSERT); + * </pre> + * + * That returns + * <samp> + * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * </samp> + * + * NOTES: + * - 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 + * query, all the records of the table will be updated! + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return string the sql query for autoPrepare() + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + if (count($table_fields) == 0) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + $first = true; + switch ($mode) { + case DB_AUTOQUERY_INSERT: + $values = ''; + $names = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $names .= ','; + $values .= ','; + } + $names .= $value; + $values .= '?'; + } + return "INSERT INTO $table ($names) VALUES ($values)"; + case DB_AUTOQUERY_UPDATE: + $set = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $set .= ','; + } + $set .= "$value = ?"; + } + $sql = "UPDATE $table SET $set"; + if ($where) { + $sql .= " WHERE $where"; + } + return $sql; + default: + return $this->raiseError(DB_ERROR_SYNTAX); + } + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare() + * + * Example 1. + * <code> + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res =& $db->execute($sth, $data); + * </code> + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * {@internal ibase and oci8 have their own execute() methods.}} + * + * @see DB_common::prepare() + */ + function &execute($stmt, $data = array()) + { + $realquery = $this->executeEmulateQuery($stmt, $data); + if (DB::isError($realquery)) { + return $realquery; + } + $result = $this->simpleQuery($realquery); + + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp =& new DB_result($this, $result); + return $tmp; + } + } + + // }}} + // {{{ executeEmulateQuery() + + /** + * Emulates executing prepared statements if the DBMS not support them + * + * @param resource $stmt a DB statement resource returned from execute() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a string containing the real query run when emulating + * prepare/execute. A DB_Error object on failure. + * + * @access protected + * @see DB_common::execute() + */ + function executeEmulateQuery($stmt, $data = array()) + { + $stmt = (int)$stmt; + $data = (array)$data; + $this->last_parameters = $data; + + if (count($this->prepare_types[$stmt]) != count($data)) { + $this->last_query = $this->prepared_queries[$stmt]; + return $this->raiseError(DB_ERROR_MISMATCH); + } + + $realquery = $this->prepare_tokens[$stmt][0]; + + $i = 0; + foreach ($data as $value) { + if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) { + $realquery .= $this->quoteSmart($value); + } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($value, 'rb'); + if (!$fp) { + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + } + $realquery .= $this->quoteSmart(fread($fp, filesize($value))); + fclose($fp); + } else { + $realquery .= $value; + } + + $realquery .= $this->prepare_tokens[$stmt][++$i]; + } + + return $realquery; + } + + // }}} + // {{{ executeMultiple() + + /** + * Performs several execute() calls on the same statement handle + * + * $data 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 $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::prepare(), DB_common::execute() + */ + function executeMultiple($stmt, $data) + { + foreach ($data as $value) { + $res =& $this->execute($stmt, $value); + if (DB::isError($res)) { + return $res; + } + } + return DB_OK; + } + + // }}} + // {{{ freePrepared() + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_common::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + $stmt = (int)$stmt; + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * It is defined here to ensure all drivers have this method available. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(), + * DB_sqlite::modifyQuery() + */ + function modifyQuery($query) + { + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * It is defined here to assure that all implementations + * have this method defined. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return $query; + } + + // }}} + // {{{ query() + + /** + * Sends a query to the database server + * + * The query string can be either a normal statement to be sent directly + * to the server OR if <var>$params</var> are passed the query can have + * placeholders and it will be passed through prepare() and execute(). + * + * @param string $query the SQL query or the statement to prepare + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @see DB_result, DB_common::prepare(), DB_common::execute() + */ + function &query($query, $params = array()) + { + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $ret =& $this->execute($sth, $params); + $this->freePrepared($sth, false); + return $ret; + } else { + $this->last_parameters = array(); + $result = $this->simpleQuery($query); + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp =& new DB_result($this, $result); + return $tmp; + } + } + } + + // }}} + // {{{ limitQuery() + + /** + * Generates and executes a LIMIT query + * + * @param string $query the query + * @param intr $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + */ + function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count, $params); + if (DB::isError($query)){ + return $query; + } + $result =& $this->query($query, $params); + if (is_a($result, 'DB_result')) { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetches the first column of the first row from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed the returned value of the query. + * A DB_Error object on failure. + */ + function &getOne($query, $params = array()) + { + $params = (array)$params; + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row[0]; + } + + // }}} + // {{{ getRow() + + /** + * Fetches the first row of data returned from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * + * @return array the first row of results as an array. + * A DB_Error object on failure. + */ + function &getRow($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, $fetchmode); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetches a single column from a query result and returns it as an + * indexed array + * + * @param string $query the SQL query + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return array the results as an array. A DB_Error object on failure. + * + * @see DB_common::query() + */ + function &getCol($query, $col = 0, $params = array()) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + + if (!is_array($row = $res->fetchRow($fetchmode))) { + $ret = array(); + } else { + if (!array_key_exists($col, $row)) { + $ret =& $this->raiseError(DB_ERROR_NOSUCHFIELD); + } else { + $ret = array($row[$col]); + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + } + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetches an entire query result and returns 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 DB error code is + * returned on errors. If the result set contains fewer than two + * columns, a DB_ERROR_TRUNCATED error is returned. + * + * For example, if the table "mytable" contains: + * + * <pre> + * ID TEXT DATE + * -------------------------------- + * 1 'one' 944679408 + * 2 'two' 944679408 + * 3 'three' 944679408 + * </pre> + * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + * <pre> + * array( + * '1' => 'one', + * '2' => 'two', + * '3' => 'three', + * ) + * </pre> + * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + * <pre> + * array( + * '1' => array('one', '944679408'), + * '2' => array('two', '944679408'), + * '3' => array('three', '944679408') + * ) + * </pre> + * + * 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: + * + * <pre> + * getAssoc('SELECT category,id,name FROM mytable', false, null, + * DB_FETCHMODE_ASSOC, 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') + * ) + * ) + * </pre> + * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string $query the SQL query + * @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 mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * @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 the associative array containing the query results. + * A DB_Error object on failure. + */ + function &getAssoc($query, $force_array = false, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT, $group = false) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + if ($fetchmode == DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + $cols = $res->numCols(); + + if ($cols < 2) { + $tmp =& $this->raiseError(DB_ERROR_TRUNCATED); + return $tmp; + } + + $results = array(); + + if ($cols > 2 || $force_array) { + // return array values + // XXX this part can be optimized + if ($fetchmode == DB_FETCHMODE_ASSOC) { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { + reset($row); + $key = current($row); + unset($row[key($row)]); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } elseif ($fetchmode == DB_FETCHMODE_OBJECT) { + while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) { + $arr = get_object_vars($row); + $key = current($arr); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } else { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + // we shift away the first element to get + // indices running from 0 again + $key = array_shift($row); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } + if (DB::isError($row)) { + $results = $row; + } + } else { + // return scalar values + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + if ($group) { + $results[$row[0]][] = $row[1]; + } else { + $results[$row[0]] = $row[1]; + } + } + if (DB::isError($row)) { + $results = $row; + } + } + + $res->free(); + + return $results; + } + + // }}} + // {{{ getAll() + + /** + * Fetches all of the rows from a query result + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use: + * + DB_FETCHMODE_ORDERED + * + DB_FETCHMODE_ASSOC + * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED + * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED + * + * @return array the nested array. A DB_Error object on failure. + */ + function &getAll($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if ($res === DB_OK || DB::isError($res)) { + return $res; + } + + $results = array(); + while (DB_OK === $res->fetchInto($row, $fetchmode)) { + if ($fetchmode & DB_FETCHMODE_FLIPPED) { + foreach ($row as $key => $val) { + $results[$key][] = $val; + } + } else { + $results[] = $row; + } + } + + $res->free(); + + if (DB::isError($row)) { + $tmp =& $this->raiseError($row); + return $tmp; + } + return $results; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numRows() + + /** + * Determines the number of rows in a query result + * + * @param resource $result the query result idenifier produced by PHP + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows($result) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getSequenceName() + + /** + * Generates the name used inside the database for a sequence + * + * The createSequence() docblock contains notes about storing sequence + * names. + * + * @param string $sqn the sequence's public name + * + * @return string the sequence's name in the backend + * + * @access protected + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::nextID(), DB_common::setOption() + */ + function getSequenceName($sqn) + { + return sprintf($this->getOption('seqname_format'), + preg_replace('/[^a-z0-9_.]/i', '_', $sqn)); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::getSequenceName() + */ + function nextId($seq_name, $ondemand = true) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * The name of a given sequence is determined by passing the string + * provided in the <var>$seq_name</var> argument through PHP's sprintf() + * function using the value from the <var>seqname_format</var> option as + * the sprintf()'s format argument. + * + * <var>seqname_format</var> is set via setOption(). + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function createSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function dropSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ raiseError() + + /** + * Communicates an error and invoke error callbacks, etc + * + * Basically a wrapper for PEAR::raiseError without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @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. + * @param mixed native error code, integer or string depending the + * backend + * + * @return object the PEAR_Error object + * + * @see PEAR_Error + */ + function &raiseError($code = DB_ERROR, $mode = null, $options = null, + $userinfo = null, $nativecode = null) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + $tmp = PEAR::raiseError($code, null, $mode, $options, + null, null, true); + return $tmp; + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; + } else { + $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; + } + + $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo, + 'DB_Error', true); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return mixed the DBMS' error code. A DB_Error object on failure. + */ + function errorNative() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * Uses the <var>$errorcode_map</var> property defined in each driver. + * + * @param string|int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + */ + function errorCode($nativecode) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ errorMessage() + + /** + * Maps a DB error code to a textual message + * + * @param integer $dbcode the DB error code + * + * @return string the error message corresponding to the error code + * submitted. FALSE if the error code is unknown. + * + * @see DB::errorMessage() + */ + function errorMessage($dbcode) + { + return DB::errorMessage($this->errorcode_map[$dbcode]); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which <var>$mode</var> + * you select. The sample output below is based on this query: + * <pre> + * SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId + * FROM tblFoo + * JOIN tblBar ON tblFoo.fldId = tblBar.fldId + * </pre> + * + * <ul> + * <li> + * + * <kbd>null</kbd> (default) + * <pre> + * [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 + * ) + * </pre> + * + * </li><li> + * + * <kbd>DB_TABLEINFO_ORDER</kbd> + * + * <p>In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * <samp>num_fields</samp> element while the <samp>order</samp> + * 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.</p> + * + * <p>If a result set has identical field names, the last one is + * used.</p> + * + * <pre> + * [num_fields] => 3 + * [order] => Array ( + * [fldId] => 2 + * [fldTrans] => 1 + * ) + * </pre> + * + * </li><li> + * + * <kbd>DB_TABLEINFO_ORDERTABLE</kbd> + * + * <p>Similar to <kbd>DB_TABLEINFO_ORDER</kbd> 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.</p> + * + * <pre> + * [num_fields] => 3 + * [ordertable] => Array ( + * [tblFoo] => Array ( + * [fldId] => 0 + * [fldPhone] => 1 + * ) + * [tblBar] => Array ( + * [fldId] => 2 + * ) + * ) + * </pre> + * + * </li> + * </ul> + * + * The <samp>flags</samp> 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. + * + <samp>primary_key</samp> + * + <samp>unique_key</samp> + * + <samp>multiple_key</samp> + * + <samp>not_null</samp> + * + * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp> + * elements if <var>$result</var> is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has <samp>DB_PORTABILITY_LOWERCASE</samp> + * turned on, the names of tables and fields will be lowercased. + * + * @param object|string $result DB_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: + * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>, + * <kbd>DB_TABLEINFO_ORDER</kbd> or + * <kbd>DB_TABLEINFO_FULL</kbd> (which does both). + * These are bitwise, so the first two can be + * combined using <kbd>|</kbd>. + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + /* + * If the DB_<driver> class has a tableInfo() method, that one + * overrides this one. But, if the driver doesn't have one, + * this method runs and tells users about that fact. + */ + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getTables() + + /** + * Lists the tables in the current database + * + * @return array the list of tables. A DB_Error object on failure. + * + * @deprecated Method deprecated some time before Release 1.2 + */ + function getTables() + { + return $this->getListOf('tables'); + } + + // }}} + // {{{ getListOf() + + /** + * Lists internal database information + * + * @param string $type type of information being sought. + * Common items being sought are: + * tables, databases, users, views, functions + * Each DBMS's has its own capabilities. + * + * @return array an array listing the items sought. + * A DB DB_Error object on failure. + */ + function getListOf($type) + { + $sql = $this->getSpecialQuery($type); + if ($sql === null) { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } elseif (is_int($sql) || DB::isError($sql)) { + // Previous error + return $this->raiseError($sql); + } elseif (is_array($sql)) { + // Already the result + return $sql; + } + // Launch this query + return $this->getCol($sql); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ _rtrimArrayValues() + + /** + * Right-trims all strings in an array + * + * @param array $array the array to be trimmed (passed by reference) + * + * @return void + * + * @access protected + */ + function _rtrimArrayValues(&$array) + { + foreach ($array as $key => $value) { + if (is_string($value)) { + $array[$key] = rtrim($value); + } + } + } + + // }}} + // {{{ _convertNullArrayValuesToEmpty() + + /** + * Converts all null values in an array to empty strings + * + * @param array $array the array to be de-nullified (passed by reference) + * + * @return void + * + * @access protected + */ + function _convertNullArrayValuesToEmpty(&$array) + { + foreach ($array as $key => $value) { + if (is_null($value)) { + $array[$key] = ''; + } + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/lib/phpFlickr/PEAR/DB/mysql.php b/lib/phpFlickr/PEAR/DB/mysql.php new file mode 100644 index 000000000..3ae0adf86 --- /dev/null +++ b/lib/phpFlickr/PEAR/DB/mysql.php @@ -0,0 +1,1034 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's mysql extension + * for interacting with MySQL databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * 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 web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysql.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysql extension + * for interacting with MySQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_mysql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.2.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_mysql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysql driver supports the following extra DSN options: + * + new_link If set to true, causes subsequent calls to connect() + * to return a new connection link instead of the + * existing one. WARNING: this is not portable to + * other DBMS's. Available since PEAR DB 1.7.0. + * + client_flags Any combination of MYSQL_CLIENT_* constants. + * Only used if PHP is at version 4.3.0 or greater. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['protocol'] && $dsn['protocol'] == 'unix') { + $params[0] = ':' . $dsn['socket']; + } else { + $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] + : 'localhost'; + if ($dsn['port']) { + $params[0] .= ':' . $dsn['port']; + } + } + $params[] = $dsn['username'] ? $dsn['username'] : null; + $params[] = $dsn['password'] ? $dsn['password'] : null; + + if (!$persistent) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($dsn['client_flags']) + ? $dsn['client_flags'] : null; + } + + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + if (!@mysql_select_db($dsn['database'], $this->connection)) { + return $this->mysqlRaiseError(); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * Generally uses mysql_query(). If you want to use + * mysql_unbuffered_query() set the "result_buffering" option to 0 using + * setOptions(). This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); + $result = @mysql_query('BEGIN', $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + } + $this->transaction_opcount++; + } + if (!$this->options['result_buffering']) { + $result = @mysql_unbuffered_query($query, $this->connection); + } else { + $result = @mysql_query($query, $this->connection); + } + if (!$result) { + return $this->mysqlRaiseError(); + } + if (is_resource($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result + * + * This method has not been implemented yet. + * + * @param a valid sql result resource + * + * @return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysql_fetch_array($result, MYSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return @mysql_free_result($result); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysql_num_fields($result); + if (!$cols) { + return $this->mysqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysql_num_rows($result); + if ($rows === null) { + return $this->mysqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('COMMIT', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('ROLLBACK', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (DB::isManip($this->last_query)) { + return @mysql_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysql::createSequence(), DB_mysql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("UPDATE ${seqname} ". + 'SET id=LAST_INSERT_ID(id+1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysql_insert_id($this->connection); + if ($id != 0) { + return $id; + } + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, so fill + // it and return 1 and obtain a user-level lock + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + // Failed to get the lock + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($res)) { + return $res; + } + // so reset to zero + return $this->query("UPDATE ${seqname} SET id = 0"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query('DELETE FROM ' . $seqname + . " WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * MySQL can't handle the backtick character (<kbd>`</kbd>) in + * table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . $str . '`'; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($str, $this->connection); + } else { + return @mysql_escape_string($str); + } + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_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); + } + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query)) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysql::errorNative(), DB_common::errorCode() + */ + function mysqlRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysql_errno($this->connection) . ' ** ' . + @mysql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_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 DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysql_list_fields($this->dsn['database'], + $result, $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/lib/phpFlickr/PEAR/DB/pgsql.php b/lib/phpFlickr/PEAR/DB/pgsql.php new file mode 100644 index 000000000..1e58f48d6 --- /dev/null +++ b/lib/phpFlickr/PEAR/DB/pgsql.php @@ -0,0 +1,1097 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * 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 web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Rui Hirokawa <hirokawa@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: pgsql.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Rui Hirokawa <hirokawa@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_pgsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'pgsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'pgsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.3.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + */ + var $affected = 0; + + /** + * The current row being looked at in fetchInto() + * @var array + * @access private + */ + var $row = array(); + + /** + * The number of rows in a given result set + * @var array + * @access private + */ + var $_num_rows = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_pgsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's pgsql driver supports the following extra DSN options: + * + connect_timeout How many seconds to wait for a connection to + * be established. Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. Available only + * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0. + * + options Command line options to be sent to the server. + * Available since PEAR DB 1.6.4. + * + service Specifies a service name in pg_service.conf that + * holds additional connection parameters. + * Available since PEAR DB 1.7.0. + * + sslmode How should SSL be used when connecting? Values: + * disable, allow, prefer or require. + * Available since PEAR DB 1.7.0. + * + tty This was used to specify where to send server + * debug output. Available since PEAR DB 1.6.4. + * + * Example of connecting to a new link via a socket: + * <code> + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('pgsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($dsn['hostspec']) { + $params[0] .= 'host=' . $dsn['hostspec']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($dsn['socket']) { + $params[0] .= 'host=' . $dsn['socket']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } + if ($dsn['database']) { + $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\''; + } + if ($dsn['username']) { + $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\''; + } + if ($dsn['password']) { + $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\''; + } + if (!empty($dsn['options'])) { + $params[0] .= ' options=' . $dsn['options']; + } + if (!empty($dsn['tty'])) { + $params[0] .= ' tty=' . $dsn['tty']; + } + if (!empty($dsn['connect_timeout'])) { + $params[0] .= ' connect_timeout=' . $dsn['connect_timeout']; + } + if (!empty($dsn['sslmode'])) { + $params[0] .= ' sslmode=' . $dsn['sslmode']; + } + if (!empty($dsn['service'])) { + $params[0] .= ' service=' . $dsn['service']; + } + + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @pg_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @pg_exec($this->connection, 'begin;'); + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @pg_exec($this->connection, $query); + if (!$result) { + return $this->pgsqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($ismanip) { + $this->affected = @pg_affected_rows($result); + return DB_OK; + } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) { + /* PostgreSQL commands: + ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, + CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, + GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, + REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, + UNLISTEN, UPDATE, VACUUM + */ + $this->row[(int)$result] = 0; // reset the row counter. + $numrows = $this->numRows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->_num_rows[(int)$result] = $numrows; + $this->affected = 0; + return $result; + } else { + $this->affected = 0; + return DB_OK; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal pgsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $result_int = (int)$result; + $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int]; + if ($rownum >= $this->_num_rows[$result_int]) { + return null; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @pg_fetch_row($result, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + $this->row[$result_int] = ++$rownum; + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + if (is_resource($result)) { + unset($this->row[(int)$result]); + unset($this->_num_rows[(int)$result]); + $this->affected = 0; + return @pg_freeresult($result); + } + return false; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + * + null = the string <samp>NULL</samp> + * + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp> + * + integer or double = the unquoted number + * + other (including strings and numeric strings) = + * the data escaped according to MySQL's settings + * then encapsulated between single quotes + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in) || is_double($in)) { + return $in; + } elseif (is_bool($in)) { + return $in ? 'TRUE' : 'FALSE'; + } elseif (is_null($in)) { + return 'NULL'; + } else { + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * {@internal PostgreSQL treats a backslash as an escape character, + * so they are escaped as well. + * + * Not using pg_escape_string() yet because it requires PostgreSQL + * to be at version 7.2 or greater.}} + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", str_replace('\\', '\\\\', $str)); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @pg_numfields($result); + if (!$cols) { + return $this->pgsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + // (disabled) hack to shut up error messages from libpq.a + //@fclose(@fopen("php://stderr", "w")); + $result = @pg_exec($this->connection, 'end;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @pg_exec($this->connection, 'abort;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->affected; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_pgsql::createSequence(), DB_pgsql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result =& $this->query("SELECT NEXTVAL('${seqname}')"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $result = $this->query("CREATE SEQUENCE ${seqname}"); + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ pgsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_pgsql::errorNative(), DB_pgsql::errorCode() + */ + function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal Error messages are used instead of error codes + * in order to support older versions of PostgreSQL.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @pg_errormessage($this->connection); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/(relation|sequence|table).*does not exist|class .* not found/i' + => DB_ERROR_NOSUCHTABLE, + '/index .* does not exist/' + => DB_ERROR_NOT_FOUND, + '/column .* does not exist/i' + => DB_ERROR_NOSUCHFIELD, + '/relation .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => DB_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => DB_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => DB_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => DB_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => DB_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => DB_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => DB_ERROR_SYNTAX, + '/syntax error at/' + => DB_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => DB_ERROR_SYNTAX, + '/permission denied/' + => DB_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => DB_ERROR_CONSTRAINT, + '/referential integrity violation/' + => DB_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * @param object|string $result DB_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 DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @pg_numfields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@pg_fieldname($id, $i)), + 'type' => @pg_fieldtype($id, $i), + 'len' => @pg_fieldsize($id, $i), + 'flags' => $got_string + ? $this->_pgFieldFlags($id, $i, $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @pg_freeresult($id); + } + return $res; + } + + // }}} + // {{{ _pgFieldFlags() + + /** + * Get a column's flags + * + * Supports "not_null", "default_value", "primary_key", "unique_key" + * and "multiple_key". The default value is passed through + * rawurlencode() in case there are spaces in it. + * + * @param int $resource the PostgreSQL result identifier + * @param int $num_field the field number + * + * @return string the flags + * + * @access private + */ + function _pgFieldFlags($resource, $num_field, $table_name) + { + $field_name = @pg_fieldname($resource, $num_field); + + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef + FROM pg_attribute f, pg_class tab, pg_type typ + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + if (@pg_numrows($result) > 0) { + $row = @pg_fetch_row($result, 0); + $flags = ($row[0] == 't') ? 'not_null ' : ''; + + if ($row[1] == 't') { + $result = @pg_exec($this->connection, "SELECT a.adsrc + FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid + AND f.attrelid = a.adrelid AND f.attname = '$field_name' + AND tab.relname = '$table_name' AND f.attnum = a.adnum"); + $row = @pg_fetch_row($result, 0); + $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]); + $flags .= 'default_' . rawurlencode($num) . ' '; + } + } else { + $flags = ''; + } + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey + FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attrelid = i.indrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + $count = @pg_numrows($result); + + for ($i = 0; $i < $count ; $i++) { + $row = @pg_fetch_row($result, $i); + $keys = explode(' ', $row[2]); + + if (in_array($num_field + 1, $keys)) { + $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : ''; + $flags .= ($row[1] == 't') ? 'primary_key ' : ''; + if (count($keys) > 1) + $flags .= 'multiple_key '; + } + } + + return trim($flags); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return '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_'"; + case 'schema.tables': + return "SELECT schemaname || '.' || tablename" + . ' AS "Name"' + . ' FROM pg_catalog.pg_tables' + . ' WHERE schemaname NOT IN' + . " ('pg_catalog', 'information_schema', 'pg_toast')"; + case 'views': + // Table cols: viewname | viewowner | definition + return 'SELECT viewname from pg_views WHERE schemaname' + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'users': + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil + return 'SELECT usename FROM pg_user'; + case 'databases': + return 'SELECT datname FROM pg_database'; + case 'functions': + case 'procedures': + return 'SELECT proname FROM pg_proc WHERE proowner <> 1'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/lib/phpFlickr/PEAR/DB/storage.php b/lib/phpFlickr/PEAR/DB/storage.php new file mode 100644 index 000000000..f597b36d7 --- /dev/null +++ b/lib/phpFlickr/PEAR/DB/storage.php @@ -0,0 +1,504 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Provides an object interface to a table row + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * 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 web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <stig@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: storage.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB class so it can be extended from + */ +require_once 'DB.php'; + +/** + * Provides an object interface to a table row + * + * It lets you add, delete and change rows using objects rather than SQL + * statements. + * + * @category Database + * @package DB + * @author Stig Bakken <stig@php.net> + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_storage extends PEAR +{ + // {{{ properties + + /** the name of the table (or view, if the backend database supports + updates in views) we hold data from */ + var $_table = null; + + /** which column(s) in the table contains primary keys, can be a + string for single-column primary keys, or an array of strings + for multiple-column primary keys */ + var $_keycolumn = null; + + /** DB connection handle used for all transactions */ + var $_dbh = null; + + /** an assoc with the names of database fields stored as properties + in this object */ + var $_properties = array(); + + /** an assoc with the names of the properties in this object that + have been changed since they were fetched from the database */ + var $_changes = array(); + + /** flag that decides if data in this object can be changed. + objects that don't have their table's key column in their + property lists will be flagged as read-only. */ + var $_readonly = false; + + /** function or method that implements a validator for fields that + are set, this validator function returns true if the field is + valid, false if not */ + var $_validator = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * @param $table string the name of the database table + * + * @param $keycolumn mixed string with name of key column, or array of + * strings if the table has a primary key of more than one column + * + * @param $dbh object database connection object + * + * @param $validator mixed function or method used to validate + * each new value, called with three parameters: the name of the + * field/column that is changing, a reference to the new value and + * a reference to this object + * + */ + function DB_storage($table, $keycolumn, &$dbh, $validator = null) + { + $this->PEAR('DB_Error'); + $this->_table = $table; + $this->_keycolumn = $keycolumn; + $this->_dbh = $dbh; + $this->_readonly = false; + $this->_validator = $validator; + } + + // }}} + // {{{ _makeWhere() + + /** + * Utility method to build a "WHERE" clause to locate ourselves in + * the table. + * + * XXX future improvement: use rowids? + * + * @access private + */ + function _makeWhere($keyval = null) + { + if (is_array($this->_keycolumn)) { + if ($keyval === null) { + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + $keyval[] = $this->{$this->_keycolumn[$i]}; + } + } + $whereclause = ''; + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + if ($i > 0) { + $whereclause .= ' AND '; + } + $whereclause .= $this->_keycolumn[$i]; + if (is_null($keyval[$i])) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); + } + } + } else { + if ($keyval === null) { + $keyval = @$this->{$this->_keycolumn}; + } + $whereclause = $this->_keycolumn; + if (is_null($keyval)) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval); + } + } + return $whereclause; + } + + // }}} + // {{{ setup() + + /** + * Method used to initialize a DB_storage object from the + * configured table. + * + * @param $keyval mixed the key[s] of the row to fetch (string or array) + * + * @return int DB_OK on success, a DB error if not + */ + function setup($keyval) + { + $whereclause = $this->_makeWhere($keyval); + $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; + $sth = $this->_dbh->query($query); + if (DB::isError($sth)) { + return $sth; + } + $row = $sth->fetchRow(DB_FETCHMODE_ASSOC); + if (DB::isError($row)) { + return $row; + } + if (!$row) { + return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null, + $query, null, true); + } + foreach ($row as $key => $value) { + $this->_properties[$key] = true; + $this->$key = $value; + } + return DB_OK; + } + + // }}} + // {{{ insert() + + /** + * Create a new (empty) row in the configured table for this + * object. + */ + function insert($newpk) + { + if (is_array($this->_keycolumn)) { + $primarykey = $this->_keycolumn; + } else { + $primarykey = array($this->_keycolumn); + } + settype($newpk, "array"); + for ($i = 0; $i < sizeof($primarykey); $i++) { + $pkvals[] = $this->_dbh->quote($newpk[$i]); + } + + $sth = $this->_dbh->query("INSERT INTO $this->_table (" . + implode(",", $primarykey) . ") VALUES(" . + implode(",", $pkvals) . ")"); + if (DB::isError($sth)) { + return $sth; + } + if (sizeof($newpk) == 1) { + $newpk = $newpk[0]; + } + $this->setup($newpk); + } + + // }}} + // {{{ toString() + + /** + * Output a simple description of this DB_storage object. + * @return string object description + */ + function toString() + { + $info = strtolower(get_class($this)); + $info .= " (table="; + $info .= $this->_table; + $info .= ", keycolumn="; + if (is_array($this->_keycolumn)) { + $info .= "(" . implode(",", $this->_keycolumn) . ")"; + } else { + $info .= $this->_keycolumn; + } + $info .= ", dbh="; + if (is_object($this->_dbh)) { + $info .= $this->_dbh->toString(); + } else { + $info .= "null"; + } + $info .= ")"; + if (sizeof($this->_properties)) { + $info .= " [loaded, key="; + $keyname = $this->_keycolumn; + if (is_array($keyname)) { + $info .= "("; + for ($i = 0; $i < sizeof($keyname); $i++) { + if ($i > 0) { + $info .= ","; + } + $info .= $this->$keyname[$i]; + } + $info .= ")"; + } else { + $info .= $this->$keyname; + } + $info .= "]"; + } + if (sizeof($this->_changes)) { + $info .= " [modified]"; + } + return $info; + } + + // }}} + // {{{ dump() + + /** + * Dump the contents of this object to "standard output". + */ + function dump() + { + foreach ($this->_properties as $prop => $foo) { + print "$prop = "; + print htmlentities($this->$prop); + print "<br />\n"; + } + } + + // }}} + // {{{ &create() + + /** + * Static method used to create new DB storage objects. + * @param $data assoc. array where the keys are the names + * of properties/columns + * @return object a new instance of DB_storage or a subclass of it + */ + function &create($table, &$data) + { + $classname = strtolower(get_class($this)); + $obj =& new $classname($table); + foreach ($data as $name => $value) { + $obj->_properties[$name] = true; + $obj->$name = &$value; + } + return $obj; + } + + // }}} + // {{{ loadFromQuery() + + /** + * Loads data into this object from the given query. If this + * object already contains table data, changes will be saved and + * the object re-initialized first. + * + * @param $query SQL query + * + * @param $params parameter list in case you want to use + * prepare/execute mode + * + * @return int DB_OK on success, DB_WARNING_READ_ONLY if the + * returned object is read-only (because the object's specified + * key column was not found among the columns returned by $query), + * or another DB error code in case of errors. + */ +// XXX commented out for now +/* + function loadFromQuery($query, $params = null) + { + if (sizeof($this->_properties)) { + if (sizeof($this->_changes)) { + $this->store(); + $this->_changes = array(); + } + $this->_properties = array(); + } + $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); + if (DB::isError($rowdata)) { + return $rowdata; + } + reset($rowdata); + $found_keycolumn = false; + while (list($key, $value) = each($rowdata)) { + if ($key == $this->_keycolumn) { + $found_keycolumn = true; + } + $this->_properties[$key] = true; + $this->$key = &$value; + unset($value); // have to unset, or all properties will + // refer to the same value + } + if (!$found_keycolumn) { + $this->_readonly = true; + return DB_WARNING_READ_ONLY; + } + return DB_OK; + } + */ + + // }}} + // {{{ set() + + /** + * Modify an attriute value. + */ + function set($property, $newvalue) + { + // only change if $property is known and object is not + // read-only + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + if (@isset($this->_properties[$property])) { + if (empty($this->_validator)) { + $valid = true; + } else { + $valid = @call_user_func($this->_validator, + $this->_table, + $property, + $newvalue, + $this->$property, + $this); + } + if ($valid) { + $this->$property = $newvalue; + if (empty($this->_changes[$property])) { + $this->_changes[$property] = 0; + } else { + $this->_changes[$property]++; + } + } else { + return $this->raiseError(null, DB_ERROR_INVALID, null, + null, "invalid field: $property", + null, true); + } + return true; + } + return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null, + null, "unknown field: $property", + null, true); + } + + // }}} + // {{{ &get() + + /** + * Fetch an attribute value. + * + * @param string attribute name + * + * @return attribute contents, or null if the attribute name is + * unknown + */ + function &get($property) + { + // only return if $property is known + if (isset($this->_properties[$property])) { + return $this->$property; + } + $tmp = null; + return $tmp; + } + + // }}} + // {{{ _DB_storage() + + /** + * Destructor, calls DB_storage::store() if there are changes + * that are to be kept. + */ + function _DB_storage() + { + if (sizeof($this->_changes)) { + $this->store(); + } + $this->_properties = array(); + $this->_changes = array(); + $this->_table = null; + } + + // }}} + // {{{ store() + + /** + * Stores changes to this object in the database. + * + * @return DB_OK or a DB error + */ + function store() + { + foreach ($this->_changes as $name => $foo) { + $params[] = &$this->$name; + $vars[] = $name . ' = ?'; + } + if ($vars) { + $query = 'UPDATE ' . $this->_table . ' SET ' . + implode(', ', $vars) . ' WHERE ' . + $this->_makeWhere(); + $stmt = $this->_dbh->prepare($query); + $res = $this->_dbh->execute($stmt, $params); + if (DB::isError($res)) { + return $res; + } + $this->_changes = array(); + } + return DB_OK; + } + + // }}} + // {{{ remove() + + /** + * Remove the row represented by this object from the database. + * + * @return mixed DB_OK or a DB error + */ + function remove() + { + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + $query = 'DELETE FROM ' . $this->_table .' WHERE '. + $this->_makeWhere(); + $res = $this->_dbh->query($query); + if (DB::isError($res)) { + return $res; + } + foreach ($this->_properties as $prop => $foo) { + unset($this->$prop); + } + $this->_properties = array(); + $this->_changes = array(); + return DB_OK; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/lib/phpFlickr/PEAR/HTTP/Request.php b/lib/phpFlickr/PEAR/HTTP/Request.php new file mode 100644 index 000000000..55e227b3a --- /dev/null +++ b/lib/phpFlickr/PEAR/HTTP/Request.php @@ -0,0 +1,1484 @@ +<?php +/** + * Class for performing HTTP requests + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2007, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors 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 HTTP + * @package HTTP_Request + * @author Richard Heyes <richard@phpguru.org> + * @author Alexey Borzov <avb@php.net> + * @copyright 2002-2007 Richard Heyes + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Request.php 127 2008-01-17 20:21:37Z dcoulter $ + * @link http://pear.php.net/package/HTTP_Request/ + */ + +/** + * PEAR and PEAR_Error classes (for error handling) + */ +require_once 'PEAR.php'; +/** + * Socket class + */ +require_once 'Net/Socket.php'; +/** + * URL handling class + */ +require_once 'Net/URL.php'; + +/**#@+ + * Constants for HTTP request methods + */ +define('HTTP_REQUEST_METHOD_GET', 'GET', true); +define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true); +define('HTTP_REQUEST_METHOD_POST', 'POST', true); +define('HTTP_REQUEST_METHOD_PUT', 'PUT', true); +define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true); +define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true); +define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true); +/**#@-*/ + +/**#@+ + * Constants for HTTP request error codes + */ +define('HTTP_REQUEST_ERROR_FILE', 1); +define('HTTP_REQUEST_ERROR_URL', 2); +define('HTTP_REQUEST_ERROR_PROXY', 4); +define('HTTP_REQUEST_ERROR_REDIRECTS', 8); +define('HTTP_REQUEST_ERROR_RESPONSE', 16); +define('HTTP_REQUEST_ERROR_GZIP_METHOD', 32); +define('HTTP_REQUEST_ERROR_GZIP_READ', 64); +define('HTTP_REQUEST_ERROR_GZIP_DATA', 128); +define('HTTP_REQUEST_ERROR_GZIP_CRC', 256); +/**#@-*/ + +/**#@+ + * Constants for HTTP protocol versions + */ +define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true); +define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true); +/**#@-*/ + +if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) { + /** + * Whether string functions are overloaded by their mbstring equivalents + */ + define('HTTP_REQUEST_MBSTRING', true); +} else { + /** + * @ignore + */ + define('HTTP_REQUEST_MBSTRING', false); +} + +/** + * Class for performing HTTP requests + * + * Simple example (fetches yahoo.com and displays it): + * <code> + * $a = &new HTTP_Request('http://www.yahoo.com/'); + * $a->sendRequest(); + * echo $a->getResponseBody(); + * </code> + * + * @category HTTP + * @package HTTP_Request + * @author Richard Heyes <richard@phpguru.org> + * @author Alexey Borzov <avb@php.net> + * @version Release: 1.4.2 + */ +class HTTP_Request +{ + /**#@+ + * @access private + */ + /** + * Instance of Net_URL + * @var Net_URL + */ + var $_url; + + /** + * Type of request + * @var string + */ + var $_method; + + /** + * HTTP Version + * @var string + */ + var $_http; + + /** + * Request headers + * @var array + */ + var $_requestHeaders; + + /** + * Basic Auth Username + * @var string + */ + var $_user; + + /** + * Basic Auth Password + * @var string + */ + var $_pass; + + /** + * Socket object + * @var Net_Socket + */ + var $_sock; + + /** + * Proxy server + * @var string + */ + var $_proxy_host; + + /** + * Proxy port + * @var integer + */ + var $_proxy_port; + + /** + * Proxy username + * @var string + */ + var $_proxy_user; + + /** + * Proxy password + * @var string + */ + var $_proxy_pass; + + /** + * Post data + * @var array + */ + var $_postData; + + /** + * Request body + * @var string + */ + var $_body; + + /** + * A list of methods that MUST NOT have a request body, per RFC 2616 + * @var array + */ + var $_bodyDisallowed = array('TRACE'); + + /** + * Files to post + * @var array + */ + var $_postFiles = array(); + + /** + * Connection timeout. + * @var float + */ + var $_timeout; + + /** + * HTTP_Response object + * @var HTTP_Response + */ + var $_response; + + /** + * Whether to allow redirects + * @var boolean + */ + var $_allowRedirects; + + /** + * Maximum redirects allowed + * @var integer + */ + var $_maxRedirects; + + /** + * Current number of redirects + * @var integer + */ + var $_redirects; + + /** + * Whether to append brackets [] to array variables + * @var bool + */ + var $_useBrackets = true; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Whether to save response body in response object property + * @var bool + */ + var $_saveBody = true; + + /** + * Timeout for reading from socket (array(seconds, microseconds)) + * @var array + */ + var $_readTimeout = null; + + /** + * Options to pass to Net_Socket::connect. See stream_context_create + * @var array + */ + var $_socketOptions = null; + /**#@-*/ + + /** + * Constructor + * + * Sets up the object + * @param string The url to fetch/access + * @param array Associative array of parameters which can have the following keys: + * <ul> + * <li>method - Method to use, GET, POST etc (string)</li> + * <li>http - HTTP Version to use, 1.0 or 1.1 (string)</li> + * <li>user - Basic Auth username (string)</li> + * <li>pass - Basic Auth password (string)</li> + * <li>proxy_host - Proxy server host (string)</li> + * <li>proxy_port - Proxy server port (integer)</li> + * <li>proxy_user - Proxy auth username (string)</li> + * <li>proxy_pass - Proxy auth password (string)</li> + * <li>timeout - Connection timeout in seconds (float)</li> + * <li>allowRedirects - Whether to follow redirects or not (bool)</li> + * <li>maxRedirects - Max number of redirects to follow (integer)</li> + * <li>useBrackets - Whether to append [] to array variable names (bool)</li> + * <li>saveBody - Whether to save response body in response object property (bool)</li> + * <li>readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li> + * <li>socketOptions - Options to pass to Net_Socket object (array)</li> + * </ul> + * @access public + */ + function HTTP_Request($url = '', $params = array()) + { + $this->_method = HTTP_REQUEST_METHOD_GET; + $this->_http = HTTP_REQUEST_HTTP_VER_1_1; + $this->_requestHeaders = array(); + $this->_postData = array(); + $this->_body = null; + + $this->_user = null; + $this->_pass = null; + + $this->_proxy_host = null; + $this->_proxy_port = null; + $this->_proxy_user = null; + $this->_proxy_pass = null; + + $this->_allowRedirects = false; + $this->_maxRedirects = 3; + $this->_redirects = 0; + + $this->_timeout = null; + $this->_response = null; + + foreach ($params as $key => $value) { + $this->{'_' . $key} = $value; + } + + if (!empty($url)) { + $this->setURL($url); + } + + // Default useragent + $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )'); + + // We don't do keep-alives by default + $this->addHeader('Connection', 'close'); + + // Basic authentication + if (!empty($this->_user)) { + $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass)); + } + + // Proxy authentication (see bug #5913) + if (!empty($this->_proxy_user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass)); + } + + // Use gzip encoding if possible + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) { + $this->addHeader('Accept-Encoding', 'gzip'); + } + } + + /** + * Generates a Host header for HTTP/1.1 requests + * + * @access private + * @return string + */ + function _generateHostHeader() + { + if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) { + $host = $this->_url->host . ':' . $this->_url->port; + + } else { + $host = $this->_url->host; + } + + return $host; + } + + /** + * Resets the object to its initial state (DEPRECATED). + * Takes the same parameters as the constructor. + * + * @param string $url The url to be requested + * @param array $params Associative array of parameters + * (see constructor for details) + * @access public + * @deprecated deprecated since 1.2, call the constructor if this is necessary + */ + function reset($url, $params = array()) + { + $this->HTTP_Request($url, $params); + } + + /** + * Sets the URL to be requested + * + * @param string The url to be requested + * @access public + */ + function setURL($url) + { + $this->_url = &new Net_URL($url, $this->_useBrackets); + + if (!empty($this->_url->user) || !empty($this->_url->pass)) { + $this->setBasicAuth($this->_url->user, $this->_url->pass); + } + + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) { + $this->addHeader('Host', $this->_generateHostHeader()); + } + + // set '/' instead of empty path rather than check later (see bug #8662) + if (empty($this->_url->path)) { + $this->_url->path = '/'; + } + } + + /** + * Returns the current request URL + * + * @return string Current request URL + * @access public + */ + function getUrl() + { + return empty($this->_url)? '': $this->_url->getUrl(); + } + + /** + * Sets a proxy to be used + * + * @param string Proxy host + * @param int Proxy port + * @param string Proxy username + * @param string Proxy password + * @access public + */ + function setProxy($host, $port = 8080, $user = null, $pass = null) + { + $this->_proxy_host = $host; + $this->_proxy_port = $port; + $this->_proxy_user = $user; + $this->_proxy_pass = $pass; + + if (!empty($user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + } + + /** + * Sets basic authentication parameters + * + * @param string Username + * @param string Password + */ + function setBasicAuth($user, $pass) + { + $this->_user = $user; + $this->_pass = $pass; + + $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + + /** + * Sets the method to be used, GET, POST etc. + * + * @param string Method to use. Use the defined constants for this + * @access public + */ + function setMethod($method) + { + $this->_method = $method; + } + + /** + * Sets the HTTP version to use, 1.0 or 1.1 + * + * @param string Version to use. Use the defined constants for this + * @access public + */ + function setHttpVer($http) + { + $this->_http = $http; + } + + /** + * Adds a request header + * + * @param string Header name + * @param string Header value + * @access public + */ + function addHeader($name, $value) + { + $this->_requestHeaders[strtolower($name)] = $value; + } + + /** + * Removes a request header + * + * @param string Header name to remove + * @access public + */ + function removeHeader($name) + { + if (isset($this->_requestHeaders[strtolower($name)])) { + unset($this->_requestHeaders[strtolower($name)]); + } + } + + /** + * Adds a querystring parameter + * + * @param string Querystring parameter name + * @param string Querystring parameter value + * @param bool Whether the value is already urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + $this->_url->addQueryString($name, $value, $preencoded); + } + + /** + * Sets the querystring to literally what you supply + * + * @param string The querystring data. Should be of the format foo=bar&x=y etc + * @param bool Whether data is already urlencoded or not, default = already encoded + * @access public + */ + function addRawQueryString($querystring, $preencoded = true) + { + $this->_url->addRawQueryString($querystring, $preencoded); + } + + /** + * Adds postdata items + * + * @param string Post data name + * @param string Post data value + * @param bool Whether data is already urlencoded or not, default = not + * @access public + */ + function addPostData($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->_postData[$name] = $value; + } else { + $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value); + } + } + + /** + * Recursively applies the callback function to the value + * + * @param mixed Callback function + * @param mixed Value to process + * @access private + * @return mixed Processed value + */ + function _arrayMapRecursive($callback, $value) + { + if (!is_array($value)) { + return call_user_func($callback, $value); + } else { + $map = array(); + foreach ($value as $k => $v) { + $map[$k] = $this->_arrayMapRecursive($callback, $v); + } + return $map; + } + } + + /** + * Adds a file to upload + * + * This also changes content-type to 'multipart/form-data' for proper upload + * + * @access public + * @param string name of file-upload field + * @param mixed file name(s) + * @param mixed content-type(s) of file(s) being uploaded + * @return bool true on success + * @throws PEAR_Error + */ + function addFile($inputName, $fileName, $contentType = 'application/octet-stream') + { + if (!is_array($fileName) && !is_readable($fileName)) { + return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE); + } elseif (is_array($fileName)) { + foreach ($fileName as $name) { + if (!is_readable($name)) { + return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE); + } + } + } + $this->addHeader('Content-Type', 'multipart/form-data'); + $this->_postFiles[$inputName] = array( + 'name' => $fileName, + 'type' => $contentType + ); + return true; + } + + /** + * Adds raw postdata (DEPRECATED) + * + * @param string The data + * @param bool Whether data is preencoded or not, default = already encoded + * @access public + * @deprecated deprecated since 1.3.0, method setBody() should be used instead + */ + function addRawPostData($postdata, $preencoded = true) + { + $this->_body = $preencoded ? $postdata : urlencode($postdata); + } + + /** + * Sets the request body (for POST, PUT and similar requests) + * + * @param string Request body + * @access public + */ + function setBody($body) + { + $this->_body = $body; + } + + /** + * Clears any postdata that has been added (DEPRECATED). + * + * Useful for multiple request scenarios. + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearPostData() + { + $this->_postData = null; + } + + /** + * Appends a cookie to "Cookie:" header + * + * @param string $name cookie name + * @param string $value cookie value + * @access public + */ + function addCookie($name, $value) + { + $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : ''; + $this->addHeader('Cookie', $cookies . $name . '=' . $value); + } + + /** + * Clears any cookies that have been added (DEPRECATED). + * + * Useful for multiple request scenarios + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearCookies() + { + $this->removeHeader('Cookie'); + } + + /** + * Sends the request + * + * @access public + * @param bool Whether to store response body in Response object property, + * set this to false if downloading a LARGE file and using a Listener + * @return mixed PEAR error on error, true otherwise + */ + function sendRequest($saveBody = true) + { + if (!is_a($this->_url, 'Net_URL')) { + return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL); + } + + $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host; + $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port; + + // 4.3.0 supports SSL connections using OpenSSL. The function test determines + // we running on at least 4.3.0 + if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) { + if (isset($this->_proxy_host)) { + return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY); + } + $host = 'ssl://' . $host; + } + + // magic quotes may fuck up file uploads and chunked response processing + $magicQuotes = ini_get('magic_quotes_runtime'); + ini_set('magic_quotes_runtime', false); + + // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive + // connection token to a proxy server... + if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) && + 'Keep-Alive' == $this->_requestHeaders['connection']) + { + $this->removeHeader('connection'); + } + + $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) || + (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']); + $sockets = &PEAR::getStaticProperty('HTTP_Request', 'sockets'); + $sockKey = $host . ':' . $port; + unset($this->_sock); + + // There is a connected socket in the "static" property? + if ($keepAlive && !empty($sockets[$sockKey]) && + !empty($sockets[$sockKey]->fp)) + { + $this->_sock =& $sockets[$sockKey]; + $err = null; + } else { + $this->_notify('connect'); + $this->_sock =& new Net_Socket(); + $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions); + } + PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest()); + + if (!PEAR::isError($err)) { + if (!empty($this->_readTimeout)) { + $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]); + } + + $this->_notify('sentRequest'); + + // Read the response + $this->_response = &new HTTP_Response($this->_sock, $this->_listeners); + $err = $this->_response->process( + $this->_saveBody && $saveBody, + HTTP_REQUEST_METHOD_HEAD != $this->_method + ); + + if ($keepAlive) { + $keepAlive = (isset($this->_response->_headers['content-length']) + || (isset($this->_response->_headers['transfer-encoding']) + && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked')); + if ($keepAlive) { + if (isset($this->_response->_headers['connection'])) { + $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive'; + } else { + $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol; + } + } + } + } + + ini_set('magic_quotes_runtime', $magicQuotes); + + if (PEAR::isError($err)) { + return $err; + } + + if (!$keepAlive) { + $this->disconnect(); + // Store the connected socket in "static" property + } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) { + $sockets[$sockKey] =& $this->_sock; + } + + // Check for redirection + if ( $this->_allowRedirects + AND $this->_redirects <= $this->_maxRedirects + AND $this->getResponseCode() > 300 + AND $this->getResponseCode() < 399 + AND !empty($this->_response->_headers['location'])) { + + + $redirect = $this->_response->_headers['location']; + + // Absolute URL + if (preg_match('/^https?:\/\//i', $redirect)) { + $this->_url = &new Net_URL($redirect); + $this->addHeader('Host', $this->_generateHostHeader()); + // Absolute path + } elseif ($redirect{0} == '/') { + $this->_url->path = $redirect; + + // Relative path + } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $redirect = Net_URL::resolvePath($redirect); + $this->_url->path = $redirect; + + // Filename, no path + } else { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $this->_url->path = $redirect; + } + + $this->_redirects++; + return $this->sendRequest($saveBody); + + // Too many redirects + } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) { + return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS); + } + + return true; + } + + /** + * Disconnect the socket, if connected. Only useful if using Keep-Alive. + * + * @access public + */ + function disconnect() + { + if (!empty($this->_sock) && !empty($this->_sock->fp)) { + $this->_notify('disconnect'); + $this->_sock->disconnect(); + } + } + + /** + * Returns the response code + * + * @access public + * @return mixed Response code, false if not set + */ + function getResponseCode() + { + return isset($this->_response->_code) ? $this->_response->_code : false; + } + + /** + * Returns either the named header or all if no name given + * + * @access public + * @param string The header name to return, do not set to get all headers + * @return mixed either the value of $headername (false if header is not present) + * or an array of all headers + */ + function getResponseHeader($headername = null) + { + if (!isset($headername)) { + return isset($this->_response->_headers)? $this->_response->_headers: array(); + } else { + $headername = strtolower($headername); + return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false; + } + } + + /** + * Returns the body of the response + * + * @access public + * @return mixed response body, false if not set + */ + function getResponseBody() + { + return isset($this->_response->_body) ? $this->_response->_body : false; + } + + /** + * Returns cookies set in response + * + * @access public + * @return mixed array of response cookies, false if none are present + */ + function getResponseCookies() + { + return isset($this->_response->_cookies) ? $this->_response->_cookies : false; + } + + /** + * Builds the request string + * + * @access private + * @return string The request string + */ + function _buildRequest() + { + $separator = ini_get('arg_separator.output'); + ini_set('arg_separator.output', '&'); + $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : ''; + ini_set('arg_separator.output', $separator); + + $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : ''; + $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : ''; + $path = $this->_url->path . $querystring; + $url = $host . $port . $path; + + if (!strlen($url)) { + $url = '/'; + } + + $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n"; + + if (in_array($this->_method, $this->_bodyDisallowed) || + (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method || + (empty($this->_postData) && empty($this->_postFiles))))) + { + $this->removeHeader('Content-Type'); + } else { + if (empty($this->_requestHeaders['content-type'])) { + // Add default content-type + $this->addHeader('Content-Type', 'application/x-www-form-urlencoded'); + } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) { + $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime()); + $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary); + } + } + + // Request Headers + if (!empty($this->_requestHeaders)) { + foreach ($this->_requestHeaders as $name => $value) { + $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); + $request .= $canonicalName . ': ' . $value . "\r\n"; + } + } + + // No post data or wrong method, so simply add a final CRLF + if (in_array($this->_method, $this->_bodyDisallowed) || + (HTTP_REQUEST_METHOD_POST != $this->_method && 0 == strlen($this->_body))) { + + $request .= "\r\n"; + + // Post data if it's an array + } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && + (!empty($this->_postData) || !empty($this->_postFiles))) { + + // "normal" POST request + if (!isset($boundary)) { + $postdata = implode('&', array_map( + create_function('$a', 'return $a[0] . \'=\' . $a[1];'), + $this->_flattenArray('', $this->_postData) + )); + + // multipart request, probably with file uploads + } else { + $postdata = ''; + if (!empty($this->_postData)) { + $flatData = $this->_flattenArray('', $this->_postData); + foreach ($flatData as $item) { + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"'; + $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n"; + } + } + foreach ($this->_postFiles as $name => $value) { + if (is_array($value['name'])) { + $varname = $name . ($this->_useBrackets? '[]': ''); + } else { + $varname = $name; + $value['name'] = array($value['name']); + } + foreach ($value['name'] as $key => $filename) { + $fp = fopen($filename, 'r'); + $data = fread($fp, filesize($filename)); + fclose($fp); + $basename = basename($filename); + $type = is_array($value['type'])? @$value['type'][$key]: $value['type']; + + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"'; + $postdata .= "\r\nContent-Type: " . $type; + $postdata .= "\r\n\r\n" . $data . "\r\n"; + } + } + $postdata .= '--' . $boundary . "--\r\n"; + } + $request .= 'Content-Length: ' . + (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) . + "\r\n\r\n"; + $request .= $postdata; + + // Explicitly set request body + } elseif (0 < strlen($this->_body)) { + + $request .= 'Content-Length: ' . + (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) . + "\r\n\r\n"; + $request .= $this->_body; + + // Terminate headers with CRLF on POST request with no body, too + } else { + + $request .= "\r\n"; + } + + return $request; + } + + /** + * Helper function to change the (probably multidimensional) associative array + * into the simple one. + * + * @param string name for item + * @param mixed item's values + * @return array array with the following items: array('item name', 'item value'); + * @access private + */ + function _flattenArray($name, $values) + { + if (!is_array($values)) { + return array(array($name, $values)); + } else { + $ret = array(); + foreach ($values as $k => $v) { + if (empty($name)) { + $newName = $k; + } elseif ($this->_useBrackets) { + $newName = $name . '[' . $k . ']'; + } else { + $newName = $name; + } + $ret = array_merge($ret, $this->_flattenArray($newName, $v)); + } + return $ret; + } + } + + + /** + * Adds a Listener to the list of listeners that are notified of + * the object's events + * + * Events sent by HTTP_Request object + * - 'connect': on connection to server + * - 'sentRequest': after the request was sent + * - 'disconnect': on disconnection from server + * + * Events sent by HTTP_Response object + * - 'gotHeaders': after receiving response headers (headers are passed in $data) + * - 'tick': on receiving a part of response body (the part is passed in $data) + * - 'gzTick': on receiving a gzip-encoded part of response body (ditto) + * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped) + * + * @param HTTP_Request_Listener listener to attach + * @return boolean whether the listener was successfully attached + * @access public + */ + function attach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener')) { + return false; + } + $this->_listeners[$listener->getId()] =& $listener; + return true; + } + + + /** + * Removes a Listener from the list of listeners + * + * @param HTTP_Request_Listener listener to detach + * @return boolean whether the listener was successfully detached + * @access public + */ + function detach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener') || + !isset($this->_listeners[$listener->getId()])) { + return false; + } + unset($this->_listeners[$listener->getId()]); + return true; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::attach() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} + + +/** + * Response class to complement the Request class + * + * @category HTTP + * @package HTTP_Request + * @author Richard Heyes <richard@phpguru.org> + * @author Alexey Borzov <avb@php.net> + * @version Release: 1.4.2 + */ +class HTTP_Response +{ + /** + * Socket object + * @var Net_Socket + */ + var $_sock; + + /** + * Protocol + * @var string + */ + var $_protocol; + + /** + * Return code + * @var string + */ + var $_code; + + /** + * Response headers + * @var array + */ + var $_headers; + + /** + * Cookies set in response + * @var array + */ + var $_cookies; + + /** + * Response body + * @var string + */ + var $_body = ''; + + /** + * Used by _readChunked(): remaining length of the current chunk + * @var string + */ + var $_chunkLength = 0; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Bytes left to read from message-body + * @var null|int + */ + var $_toRead; + + /** + * Constructor + * + * @param Net_Socket socket to read the response from + * @param array listeners attached to request + */ + function HTTP_Response(&$sock, &$listeners) + { + $this->_sock =& $sock; + $this->_listeners =& $listeners; + } + + + /** + * Processes a HTTP response + * + * This extracts response code, headers, cookies and decodes body if it + * was encoded in some way + * + * @access public + * @param bool Whether to store response body in object property, set + * this to false if downloading a LARGE file and using a Listener. + * This is assumed to be true if body is gzip-encoded. + * @param bool Whether the response can actually have a message-body. + * Will be set to false for HEAD requests. + * @throws PEAR_Error + * @return mixed true on success, PEAR_Error in case of malformed response + */ + function process($saveBody = true, $canHaveBody = true) + { + do { + $line = $this->_sock->readLine(); + if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) { + return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE); + } else { + $this->_protocol = 'HTTP/' . $http_version; + $this->_code = intval($returncode); + } + while ('' !== ($header = $this->_sock->readLine())) { + $this->_processHeader($header); + } + } while (100 == $this->_code); + + $this->_notify('gotHeaders', $this->_headers); + + // RFC 2616, section 4.4: + // 1. Any response message which "MUST NOT" include a message-body ... + // is always terminated by the first empty line after the header fields + // 3. ... If a message is received with both a + // Transfer-Encoding header field and a Content-Length header field, + // the latter MUST be ignored. + $canHaveBody = $canHaveBody && $this->_code >= 200 && + $this->_code != 204 && $this->_code != 304; + + // If response body is present, read it and decode + $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']); + $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']); + $hasBody = false; + if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) || + 0 != $this->_headers['content-length'])) + { + if ($chunked || !isset($this->_headers['content-length'])) { + $this->_toRead = null; + } else { + $this->_toRead = $this->_headers['content-length']; + } + while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) { + if ($chunked) { + $data = $this->_readChunked(); + } elseif (is_null($this->_toRead)) { + $data = $this->_sock->read(4096); + } else { + $data = $this->_sock->read(min(4096, $this->_toRead)); + $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data); + } + if ('' == $data) { + break; + } else { + $hasBody = true; + if ($saveBody || $gzipped) { + $this->_body .= $data; + } + $this->_notify($gzipped? 'gzTick': 'tick', $data); + } + } + } + + if ($hasBody) { + // Uncompress the body if needed + if ($gzipped) { + $body = $this->_decodeGzip($this->_body); + if (PEAR::isError($body)) { + return $body; + } + $this->_body = $body; + $this->_notify('gotBody', $this->_body); + } else { + $this->_notify('gotBody'); + } + } + return true; + } + + + /** + * Processes the response header + * + * @access private + * @param string HTTP header + */ + function _processHeader($header) + { + if (false === strpos($header, ':')) { + return; + } + list($headername, $headervalue) = explode(':', $header, 2); + $headername = strtolower($headername); + $headervalue = ltrim($headervalue); + + if ('set-cookie' != $headername) { + if (isset($this->_headers[$headername])) { + $this->_headers[$headername] .= ',' . $headervalue; + } else { + $this->_headers[$headername] = $headervalue; + } + } else { + $this->_parseCookie($headervalue); + } + } + + + /** + * Parse a Set-Cookie header to fill $_cookies array + * + * @access private + * @param string value of Set-Cookie header + */ + function _parseCookie($headervalue) + { + $cookie = array( + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false + ); + + // Only a name=value pair + if (!strpos($headervalue, ';')) { + $pos = strpos($headervalue, '='); + $cookie['name'] = trim(substr($headervalue, 0, $pos)); + $cookie['value'] = trim(substr($headervalue, $pos + 1)); + + // Some optional parameters are supplied + } else { + $elements = explode(';', $headervalue); + $pos = strpos($elements[0], '='); + $cookie['name'] = trim(substr($elements[0], 0, $pos)); + $cookie['value'] = trim(substr($elements[0], $pos + 1)); + + for ($i = 1; $i < count($elements); $i++) { + if (false === strpos($elements[$i], '=')) { + $elName = trim($elements[$i]); + $elValue = null; + } else { + list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + } + $elName = strtolower($elName); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName || 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->_cookies[] = $cookie; + } + + + /** + * Read a part of response body encoded with chunked Transfer-Encoding + * + * @access private + * @return string + */ + function _readChunked() + { + // at start of the next chunk? + if (0 == $this->_chunkLength) { + $line = $this->_sock->readLine(); + if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) { + $this->_chunkLength = hexdec($matches[1]); + // Chunk with zero length indicates the end + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // make this an eof() + return ''; + } + } else { + return ''; + } + } + $data = $this->_sock->read($this->_chunkLength); + $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data); + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // Trailing CRLF + } + return $data; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::_notify() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } + + + /** + * Decodes the message-body encoded by gzip + * + * The real decoding work is done by gzinflate() built-in function, this + * method only parses the header and checks data for compliance with + * RFC 1952 + * + * @access private + * @param string gzip-encoded data + * @return string decoded data + */ + function _decodeGzip($data) + { + if (HTTP_REQUEST_MBSTRING) { + $oldEncoding = mb_internal_encoding(); + mb_internal_encoding('iso-8859-1'); + } + $length = strlen($data); + // If it doesn't look like gzip-encoded data, don't bother + if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) { + return $data; + } + $method = ord(substr($data, 2, 1)); + if (8 != $method) { + return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD); + } + $flags = ord(substr($data, 3, 1)); + if ($flags & 224) { + return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA); + } + + // header is 10 bytes minimum. may be longer, though. + $headerLength = 10; + // extra fields, need to skip 'em + if ($flags & 4) { + if ($length - $headerLength - 2 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $extraLength = unpack('v', substr($data, 10, 2)); + if ($length - $headerLength - 2 - $extraLength[1] < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $headerLength += $extraLength[1] + 2; + } + // file name, need to skip that + if ($flags & 8) { + if ($length - $headerLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $filenameLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $headerLength += $filenameLength + 1; + } + // comment, need to skip that also + if ($flags & 16) { + if ($length - $headerLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $commentLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $headerLength += $commentLength + 1; + } + // have a CRC for header. let's check + if ($flags & 1) { + if ($length - $headerLength - 2 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA); + } + $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); + $crcStored = unpack('v', substr($data, $headerLength, 2)); + if ($crcReal != $crcStored[1]) { + return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC); + } + $headerLength += 2; + } + // unpacked data CRC and size at the end of encoded data + $tmp = unpack('V2', substr($data, -8)); + $dataCrc = $tmp[1]; + $dataSize = $tmp[2]; + + // finally, call the gzinflate() function + $unpacked = @gzinflate(substr($data, $headerLength, -8), $dataSize); + if (false === $unpacked) { + return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ); + } elseif ($dataSize != strlen($unpacked)) { + return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ); + } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) { + return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC); + } + if (HTTP_REQUEST_MBSTRING) { + mb_internal_encoding($oldEncoding); + } + return $unpacked; + } +} // End class HTTP_Response +?> diff --git a/lib/phpFlickr/PEAR/HTTP/Request/Listener.php b/lib/phpFlickr/PEAR/HTTP/Request/Listener.php new file mode 100644 index 000000000..b00514206 --- /dev/null +++ b/lib/phpFlickr/PEAR/HTTP/Request/Listener.php @@ -0,0 +1,106 @@ +<?php +/** + * Listener for HTTP_Request and HTTP_Response objects + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2007, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o 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. + * o The names of the authors 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 HTTP + * @package HTTP_Request + * @author Alexey Borzov <avb@php.net> + * @copyright 2002-2007 Richard Heyes + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Listener.php 127 2008-01-17 20:21:37Z dcoulter $ + * @link http://pear.php.net/package/HTTP_Request/ + */ + +/** + * Listener for HTTP_Request and HTTP_Response objects + * + * This class implements the Observer part of a Subject-Observer + * design pattern. + * + * @category HTTP + * @package HTTP_Request + * @author Alexey Borzov <avb@php.net> + * @version Release: 1.4.2 + */ +class HTTP_Request_Listener +{ + /** + * A listener's identifier + * @var string + */ + var $_id; + + /** + * Constructor, sets the object's identifier + * + * @access public + */ + function HTTP_Request_Listener() + { + $this->_id = md5(uniqid('http_request_', 1)); + } + + + /** + * Returns the listener's identifier + * + * @access public + * @return string + */ + function getId() + { + return $this->_id; + } + + + /** + * This method is called when Listener is notified of an event + * + * @access public + * @param object an object the listener is attached to + * @param string Event name + * @param mixed Additional data + * @abstract + */ + function update(&$subject, $event, $data = null) + { + echo "Notified of event: '$event'\n"; + if (null !== $data) { + echo "Additional data: "; + var_dump($data); + } + } +} +?> diff --git a/lib/phpFlickr/PEAR/Net/Socket.php b/lib/phpFlickr/PEAR/Net/Socket.php new file mode 100644 index 000000000..96f2c4bf1 --- /dev/null +++ b/lib/phpFlickr/PEAR/Net/Socket.php @@ -0,0 +1,528 @@ +<?php +// +// +----------------------------------------------------------------------+ +// | PHP Version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2003 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 2.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/2_02.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. | +// +----------------------------------------------------------------------+ +// | Authors: Stig Bakken <ssb@php.net> | +// | Chuck Hagenbuch <chuck@horde.org> | +// +----------------------------------------------------------------------+ +// +// $Id: Socket.php 32 2005-08-01 06:21:02Z dancoulter $ + +require_once 'PEAR.php'; + +define('NET_SOCKET_READ', 1); +define('NET_SOCKET_WRITE', 2); +define('NET_SOCKET_ERROR', 3); + +/** + * Generalized Socket class. + * + * @version 1.1 + * @author Stig Bakken <ssb@php.net> + * @author Chuck Hagenbuch <chuck@horde.org> + */ +class Net_Socket extends PEAR { + + /** + * Socket file pointer. + * @var resource $fp + */ + var $fp = null; + + /** + * Whether the socket is blocking. Defaults to true. + * @var boolean $blocking + */ + var $blocking = true; + + /** + * Whether the socket is persistent. Defaults to false. + * @var boolean $persistent + */ + var $persistent = false; + + /** + * The IP address to connect to. + * @var string $addr + */ + var $addr = ''; + + /** + * The port number to connect to. + * @var integer $port + */ + var $port = 0; + + /** + * Number of seconds to wait on socket connections before assuming + * there's no more data. Defaults to no timeout. + * @var integer $timeout + */ + var $timeout = false; + + /** + * Number of bytes to read at a time in readLine() and + * readAll(). Defaults to 2048. + * @var integer $lineLength + */ + var $lineLength = 2048; + + /** + * Connect to the specified port. If called when the socket is + * already connected, it disconnects and connects again. + * + * @param string $addr IP address or host name. + * @param integer $port TCP port number. + * @param boolean $persistent (optional) Whether the connection is + * persistent (kept open between requests + * by the web server). + * @param integer $timeout (optional) How long to wait for data. + * @param array $options See options for stream_context_create. + * + * @access public + * + * @return boolean | PEAR_Error True on success or a PEAR_Error on failure. + */ + function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null) + { + if (is_resource($this->fp)) { + @fclose($this->fp); + $this->fp = null; + } + + if (!$addr) { + return $this->raiseError('$addr cannot be empty'); + } elseif (strspn($addr, '.0123456789') == strlen($addr) || + strstr($addr, '/') !== false) { + $this->addr = $addr; + } else { + $this->addr = @gethostbyname($addr); + } + + $this->port = $port % 65536; + + if ($persistent !== null) { + $this->persistent = $persistent; + } + + if ($timeout !== null) { + $this->timeout = $timeout; + } + + $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; + $errno = 0; + $errstr = ''; + if ($options && function_exists('stream_context_create')) { + if ($this->timeout) { + $timeout = $this->timeout; + } else { + $timeout = 0; + } + $context = stream_context_create($options); + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context); + } else { + if ($this->timeout) { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); + } + } + + if (!$fp) { + return $this->raiseError($errstr, $errno); + } + + $this->fp = $fp; + + return $this->setBlocking($this->blocking); + } + + /** + * Disconnects from the peer, closes the socket. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function disconnect() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + @fclose($this->fp); + $this->fp = null; + return true; + } + + /** + * Find out if the socket is in blocking mode. + * + * @access public + * @return boolean The current blocking mode. + */ + function isBlocking() + { + return $this->blocking; + } + + /** + * Sets whether the socket connection should be blocking or + * not. A read call to a non-blocking socket will return immediately + * if there is no data available, whereas it will block until there + * is data for blocking sockets. + * + * @param boolean $mode True for blocking sockets, false for nonblocking. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setBlocking($mode) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $this->blocking = $mode; + socket_set_blocking($this->fp, $this->blocking); + return true; + } + + /** + * Sets the timeout value on socket descriptor, + * expressed in the sum of seconds and microseconds + * + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setTimeout($seconds, $microseconds) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_set_timeout($this->fp, $seconds, $microseconds); + } + + /** + * Returns information about an existing socket resource. + * Currently returns four entries in the result array: + * + * <p> + * timed_out (bool) - The socket timed out waiting for data<br> + * blocked (bool) - The socket was blocked<br> + * eof (bool) - Indicates EOF event<br> + * unread_bytes (int) - Number of bytes left in the socket buffer<br> + * </p> + * + * @access public + * @return mixed Array containing information about existing socket resource or an error object otherwise + */ + function getStatus() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_get_status($this->fp); + } + + /** + * Get a specified line of data + * + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function gets($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fgets($this->fp, $size); + } + + /** + * Read a specified amount of data. This is guaranteed to return, + * and has the added benefit of getting everything in one fread() + * chunk; if you know the size of the data you're getting + * beforehand, this is definitely the way to go. + * + * @param integer $size The number of bytes to read from the socket. + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function read($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fread($this->fp, $size); + } + + /** + * Write a specified amount of data. + * + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function write($data, $blocksize = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (is_null($blocksize) && !OS_WINDOWS) { + return fwrite($this->fp, $data); + } else { + if (is_null($blocksize)) { + $blocksize = 1024; + } + + $pos = 0; + $size = strlen($data); + while ($pos < $size) { + $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); + if ($written === false) { + return false; + } + $pos += $written; + } + + return $pos; + } + } + + /** + * Write a line of data to the socket, followed by a trailing "\r\n". + * + * @access public + * @return mixed fputs result, or an error + */ + function writeLine($data) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return fwrite($this->fp, $data . "\r\n"); + } + + /** + * Tests for end-of-file on a socket descriptor. + * + * @access public + * @return bool + */ + function eof() + { + return (is_resource($this->fp) && feof($this->fp)); + } + + /** + * Reads a byte of data + * + * @access public + * @return 1 byte of data from the socket, or a PEAR_Error if + * not connected. + */ + function readByte() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return ord(@fread($this->fp, 1)); + } + + /** + * Reads a word of data + * + * @access public + * @return 1 word of data from the socket, or a PEAR_Error if + * not connected. + */ + function readWord() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 2); + return (ord($buf[0]) + (ord($buf[1]) << 8)); + } + + /** + * Reads an int of data + * + * @access public + * @return integer 1 int of data from the socket, or a PEAR_Error if + * not connected. + */ + function readInt() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return (ord($buf[0]) + (ord($buf[1]) << 8) + + (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); + } + + /** + * Reads a zero-terminated string of data + * + * @access public + * @return string, or a PEAR_Error if + * not connected. + */ + function readString() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $string = ''; + while (($char = @fread($this->fp, 1)) != "\x00") { + $string .= $char; + } + return $string; + } + + /** + * Reads an IP Address and returns it in a dot formated string + * + * @access public + * @return Dot formated string, or a PEAR_Error if + * not connected. + */ + function readIPAddress() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return sprintf("%s.%s.%s.%s", ord($buf[0]), ord($buf[1]), + ord($buf[2]), ord($buf[3])); + } + + /** + * Read until either the end of the socket or a newline, whichever + * comes first. Strips the trailing newline from the returned data. + * + * @access public + * @return All available data up to a newline, without that + * newline, or until the end of the socket, or a PEAR_Error if + * not connected. + */ + function readLine() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $line = ''; + $timeout = time() + $this->timeout; + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $line .= @fgets($this->fp, $this->lineLength); + if (substr($line, -1) == "\n") { + return rtrim($line, "\r\n"); + } + } + return $line; + } + + /** + * Read until the socket closes, or until there is no more data in + * the inner PHP buffer. If the inner buffer is empty, in blocking + * mode we wait for at least 1 byte of data. Therefore, in + * blocking mode, if there is no data at all to be read, this + * function will never exit (unless the socket is closed on the + * remote end). + * + * @access public + * + * @return string All data until the socket closes, or a PEAR_Error if + * not connected. + */ + function readAll() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $data = ''; + while (!feof($this->fp)) { + $data .= @fread($this->fp, $this->lineLength); + } + return $data; + } + + /** + * Runs the equivalent of the select() system call on the socket + * with a timeout specified by tv_sec and tv_usec. + * + * @param integer $state Which of read/write/error to check for. + * @param integer $tv_sec Number of seconds for timeout. + * @param integer $tv_usec Number of microseconds for timeout. + * + * @access public + * @return False if select fails, integer describing which of read/write/error + * are ready, or PEAR_Error if not connected. + */ + function select($state, $tv_sec, $tv_usec = 0) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $read = null; + $write = null; + $except = null; + if ($state & NET_SOCKET_READ) { + $read[] = $this->fp; + } + if ($state & NET_SOCKET_WRITE) { + $write[] = $this->fp; + } + if ($state & NET_SOCKET_ERROR) { + $except[] = $this->fp; + } + if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) { + return false; + } + + $result = 0; + if (count($read)) { + $result |= NET_SOCKET_READ; + } + if (count($write)) { + $result |= NET_SOCKET_WRITE; + } + if (count($except)) { + $result |= NET_SOCKET_ERROR; + } + return $result; + } + +} diff --git a/lib/phpFlickr/PEAR/Net/URL.php b/lib/phpFlickr/PEAR/Net/URL.php new file mode 100644 index 000000000..38e26fd15 --- /dev/null +++ b/lib/phpFlickr/PEAR/Net/URL.php @@ -0,0 +1,410 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Copyright (c) 2002-2004, Richard Heyes | +// | All rights reserved. | +// | | +// | Redistribution and use in source and binary forms, with or without | +// | modification, are permitted provided that the following conditions | +// | are met: | +// | | +// | o Redistributions of source code must retain the above copyright | +// | notice, this list of conditions and the following disclaimer. | +// | o 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.| +// | o The names of the authors 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. | +// | | +// +-----------------------------------------------------------------------+ +// | Author: Richard Heyes <richard at php net> | +// +-----------------------------------------------------------------------+ +// +// $Id: URL.php 32 2005-08-01 06:21:02Z dancoulter $ +// +// Net_URL Class + +class Net_URL +{ + /** + * Full url + * @var string + */ + var $url; + + /** + * Protocol + * @var string + */ + var $protocol; + + /** + * Username + * @var string + */ + var $username; + + /** + * Password + * @var string + */ + var $password; + + /** + * Host + * @var string + */ + var $host; + + /** + * Port + * @var integer + */ + var $port; + + /** + * Path + * @var string + */ + var $path; + + /** + * Query string + * @var array + */ + var $querystring; + + /** + * Anchor + * @var string + */ + var $anchor; + + /** + * Whether to use [] + * @var bool + */ + var $useBrackets; + + /** + * PHP4 Constructor + * + * @see __construct() + */ + function Net_URL($url = null, $useBrackets = true) + { + $this->__construct($url, $useBrackets); + } + + /** + * PHP5 Constructor + * + * Parses the given url and stores the various parts + * Defaults are used in certain cases + * + * @param string $url Optional URL + * @param bool $useBrackets Whether to use square brackets when + * multiple querystrings with the same name + * exist + */ + function __construct($url = null, $useBrackets = true) + { + $HTTP_SERVER_VARS = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + + $this->useBrackets = $useBrackets; + $this->url = $url; + $this->user = ''; + $this->pass = ''; + $this->host = ''; + $this->port = 80; + $this->path = ''; + $this->querystring = array(); + $this->anchor = ''; + + // Only use defaults if not an absolute URL given + if (!preg_match('/^[a-z0-9]+:\/\//i', $url)) { + + $this->protocol = (@$HTTP_SERVER_VARS['HTTPS'] == 'on' ? 'https' : 'http'); + + /** + * Figure out host/port + */ + if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) AND preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) { + $host = $matches[1]; + if (!empty($matches[3])) { + $port = $matches[3]; + } else { + $port = $this->getStandardPort($this->protocol); + } + } + + $this->user = ''; + $this->pass = ''; + $this->host = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost'); + $this->port = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol)); + $this->path = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/'; + $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null; + $this->anchor = ''; + } + + // Parse the url and store the various parts + if (!empty($url)) { + $urlinfo = parse_url($url); + + // Default querystring + $this->querystring = array(); + + foreach ($urlinfo as $key => $value) { + switch ($key) { + case 'scheme': + $this->protocol = $value; + $this->port = $this->getStandardPort($value); + break; + + case 'user': + case 'pass': + case 'host': + case 'port': + $this->$key = $value; + break; + + case 'path': + if ($value{0} == '/') { + $this->path = $value; + } else { + $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path); + $this->path = sprintf('%s/%s', $path, $value); + } + break; + + case 'query': + $this->querystring = $this->_parseRawQueryString($value); + break; + + case 'fragment': + $this->anchor = $value; + break; + } + } + } + } + + /** + * Returns full url + * + * @return string Full url + * @access public + */ + function getURL() + { + $querystring = $this->getQueryString(); + + $this->url = $this->protocol . '://' + . $this->user . (!empty($this->pass) ? ':' : '') + . $this->pass . (!empty($this->user) ? '@' : '') + . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port) + . $this->path + . (!empty($querystring) ? '?' . $querystring : '') + . (!empty($this->anchor) ? '#' . $this->anchor : ''); + + return $this->url; + } + + /** + * Adds a querystring item + * + * @param string $name Name of item + * @param string $value Value of item + * @param bool $preencoded Whether value is urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->querystring[$name] = $value; + } else { + $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value); + } + } + + /** + * Removes a querystring item + * + * @param string $name Name of item + * @access public + */ + function removeQueryString($name) + { + if (isset($this->querystring[$name])) { + unset($this->querystring[$name]); + } + } + + /** + * Sets the querystring to literally what you supply + * + * @param string $querystring The querystring data. Should be of the format foo=bar&x=y etc + * @access public + */ + function addRawQueryString($querystring) + { + $this->querystring = $this->_parseRawQueryString($querystring); + } + + /** + * Returns flat querystring + * + * @return string Querystring + * @access public + */ + function getQueryString() + { + if (!empty($this->querystring)) { + foreach ($this->querystring as $name => $value) { + if (is_array($value)) { + foreach ($value as $k => $v) { + $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v); + } + } elseif (!is_null($value)) { + $querystring[] = $name . '=' . $value; + } else { + $querystring[] = $name; + } + } + $querystring = implode(ini_get('arg_separator.output'), $querystring); + } else { + $querystring = ''; + } + + return $querystring; + } + + /** + * Parses raw querystring and returns an array of it + * + * @param string $querystring The querystring to parse + * @return array An array of the querystring data + * @access private + */ + function _parseRawQuerystring($querystring) + { + $parts = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY); + $return = array(); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + $value = substr($part, strpos($part, '=') + 1); + $key = substr($part, 0, strpos($part, '=')); + } else { + $value = null; + $key = $part; + } + if (substr($key, -2) == '[]') { + $key = substr($key, 0, -2); + if (@!is_array($return[$key])) { + $return[$key] = array(); + $return[$key][] = $value; + } else { + $return[$key][] = $value; + } + } elseif (!$this->useBrackets AND !empty($return[$key])) { + $return[$key] = (array)$return[$key]; + $return[$key][] = $value; + } else { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Resolves //, ../ and ./ from a path and returns + * the result. Eg: + * + * /foo/bar/../boo.php => /foo/boo.php + * /foo/bar/../../boo.php => /boo.php + * /foo/bar/.././/boo.php => /foo/boo.php + * + * This method can also be called statically. + * + * @param string $url URL path to resolve + * @return string The result + */ + function resolvePath($path) + { + $path = explode('/', str_replace('//', '/', $path)); + + for ($i=0; $i<count($path); $i++) { + if ($path[$i] == '.') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } elseif ($path[$i] == '..' AND ($i > 1 OR ($i == 1 AND $path[0] != '') ) ) { + unset($path[$i]); + unset($path[$i-1]); + $path = array_values($path); + $i -= 2; + + } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } else { + continue; + } + } + + return implode('/', $path); + } + + /** + * Returns the standard port number for a protocol + * + * @param string $scheme The protocol to lookup + * @return integer Port number or NULL if no scheme matches + * + * @author Philippe Jausions <Philippe.Jausions@11abacus.com> + */ + function getStandardPort($scheme) + { + switch (strtolower($scheme)) { + case 'http': return 80; + case 'https': return 443; + case 'ftp': return 21; + case 'imap': return 143; + case 'imaps': return 993; + case 'pop3': return 110; + case 'pop3s': return 995; + default: return null; + } + } + + /** + * Forces the URL to a particular protocol + * + * @param string $protocol Protocol to force the URL to + * @param integer $port Optional port (standard port is used by default) + */ + function setProtocol($protocol, $port = null) + { + $this->protocol = $protocol; + $this->port = is_null($port) ? $this->getStandardPort() : $port; + } + +} +?> diff --git a/lib/phpFlickr/PEAR/PEAR.php b/lib/phpFlickr/PEAR/PEAR.php new file mode 100644 index 000000000..e442d8c7a --- /dev/null +++ b/lib/phpFlickr/PEAR/PEAR.php @@ -0,0 +1,1108 @@ +<?php +/** + * PEAR, the PHP Extension and Application Repository + * + * PEAR class and PEAR_Error class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * 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 web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category pear + * @package PEAR + * @author Sterling Hughes <sterling@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: PEAR.php 127 2008-01-17 20:21:37Z dcoulter $ + * @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); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + 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(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.6.2 + * @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. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + 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)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ 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) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + 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. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + 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. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (is_a($data, 'PEAR_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } else { + return $data->getCode() == $code; + } + } + return false; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + 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; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + 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; + } + } + 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() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @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 int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $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 + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + if ($skipmsg) { + $a = &new $ec($code, $mode, $options, $userinfo); + return $a; + } else { + $a = &new $ec($message, $code, $mode, $options, $userinfo); + return $a; + } + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } else { + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $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); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + 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 + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + 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); + } + return true; + } + + // }}} +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR::getStaticProperty('PEAR', 'destructlifo')) { + $_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) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !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 <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.6.2 + * @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; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + if (!PEAR::getStaticProperty('PEAR_Error', '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; + } else { + 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)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + 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_EXCEPTION) { + 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() { + 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() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + 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)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> |