| // | Stig Bakken | // | Tomas V.V.Cox | // +--------------------------------------------------------------------+ // // $Id: PEAR.php,v 1.82.2.4 2004/10/22 05:53:36 cellog Exp $ // 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; * * @since PHP 4.0.2 * @author Stig Bakken * @see http://pear.php.net/manual/ */ 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; static $registered = false; if (!$registered) { register_shutdown_function("_PEAR_call_destructors"); $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; 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()) { $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) { return new $ec($code, $mode, $options, $userinfo); } else { return new $ec($message, $code, $mode, $options, $userinfo); } } // }}} // {{{ 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')) { return $this->raiseError($message, $code, null, null, $userinfo); } else { return PEAR::raiseError($message, $code, null, null, $userinfo); } } // }}} 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]); } } } // }}} 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 (function_exists("debug_backtrace")) { if (@!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { $this->backtrace = debug_backtrace(); } } 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_ErrorStack for exceptions", E_USER_WARNING); eval('$e = new Exception($this->message, $this->code);$e->PEAR_Error = $this;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 ($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: */ /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2004 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 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. | // +----------------------------------------------------------------------+ // | Author: Stig Bakken | // | Tomas V.V.Cox | // | Maintainer: Daniel Convissor | // +----------------------------------------------------------------------+ // // $Id: common.php,v 1.103 2004/06/24 15:24:56 danielc Exp $ /** * DB_common is a base class for DB implementations, and must be * inherited by all such * * @package DB * @version $Id: common.php,v 1.103 2004/06/24 15:24:56 danielc Exp $ * @category Database * @author Stig Bakken * @author Tomas V.V.Cox */ class DB_common extends PEAR { // {{{ properties /** * assoc of capabilities for this DB implementation * $features['limit'] => 'emulate' => emulate with fetch row by number * 'alter' => alter the query * false => skip rows * @var array */ var $features = array(); /** * assoc mapping native error codes to DB ones * @var array */ var $errorcode_map = array(); /** * DB type (mysql, oci8, odbc etc.) * @var string */ var $phptype; /** * @var string */ var $prepare_tokens; /** * @var string */ var $prepare_types; /** * @var string */ var $prepared_queries; /** * @var integer */ var $prepare_maxstmt = 0; /** * @var string */ var $last_query = ''; /** * @var integer */ var $fetchmode = DB_FETCHMODE_ORDERED; /** * @var string */ var $fetchmode_object_class = 'stdClass'; /** * Run-time configuration options. * * The 'optimize' option has been deprecated. Use the 'portability' * option instead. * * @see DB_common::setOption() * @var array */ var $options = array( 'persistent' => false, 'ssl' => false, 'debug' => 0, 'seqname_format' => '%s_seq', 'autofree' => false, 'portability' => DB_PORTABILITY_NONE, 'optimize' => 'performance', // Deprecated. Use 'portability'. ); /** * DB handle * @var resource */ var $dbh; // }}} // {{{ toString() /** * String conversation * * @return string * @access private */ function toString() { $info = strtolower(get_class($this)); $info .= ': (phptype=' . $this->phptype . ', dbsyntax=' . $this->dbsyntax . ')'; if ($this->connection) { $info .= ' [connected]'; } return $info; } // }}} // {{{ constructor /** * Constructor */ function DB_common() { $this->PEAR('DB_Error'); } // }}} // {{{ quoteString() /** * DEPRECATED: Quotes a string so it can be safely used within string * delimiters in a query * * @return string quoted string * * @see DB_common::quoteSmart(), DB_common::escapeSimple() * @deprecated Deprecated in release 1.2 or lower * @internal */ 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 input string to quote * * @return string The NULL string or the string quotes * in magic_quote_sybase style * * @see DB_common::quoteSmart(), DB_common::escapeSimple() * @deprecated Deprecated in release 1.6.0 * @internal */ function quote($string = null) { return ($string === null) ? 'NULL' : "'".str_replace("'", "''", $string)."'"; } // }}} // {{{ quoteIdentifier() /** * Quote a string so it can be safely used as a table or column name * * Delimiting style depends on which database driver is being used. * * NOTE: just because you CAN use delimited identifiers doesn't mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * Portability is broken by using the following characters inside * delimited identifiers: * + backtick (`) -- due to MySQL * + double quote (") -- due to Oracle * + brackets ([ or ]) -- due to Access * * Delimited identifiers are known to generally work correctly under * the following drivers: * + mssql * + mysql * + mysqli * + oci8 * + odbc(access) * + odbc(db2) * + pgsql * + sqlite * + sybase * * InterBase doesn't seem to be able to use delimited identifiers * via PHP 4. They work fine under PHP 5. * * @param string $str identifier name to be quoted * * @return string quoted identifier string * * @since 1.6.0 * @access public */ function quoteIdentifier($str) { return '"' . str_replace('"', '""', $str) . '"'; } // }}} // {{{ quoteSmart() /** * Format 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 data to be quoted * * @return mixed the format of the results depends on the input's * PHP type: * *
    *
  • * input -> returns *
  • *
  • * null -> the string NULL *
  • *
  • * integer or double -> the unquoted number *
  • *
  • * &type.bool; -> output depends on the driver in use * Most drivers return integers: 1 if * true or 0 if * false. * Some return strings: TRUE if * true or FALSE if * false. * Finally one returns strings: T if * true or F if * false. Here is a list of each DBMS, * the values returned and the suggested column type: *
      *
    • * dbase -> T/F * (Logical) *
    • *
    • * fbase -> TRUE/FALSE * (BOOLEAN) *
    • *
    • * ibase -> 1/0 * (SMALLINT) [1] *
    • *
    • * ifx -> 1/0 * (SMALLINT) [1] *
    • *
    • * msql -> 1/0 * (INTEGER) *
    • *
    • * mssql -> 1/0 * (BIT) *
    • *
    • * mysql -> 1/0 * (TINYINT(1)) *
    • *
    • * mysqli -> 1/0 * (TINYINT(1)) *
    • *
    • * oci8 -> 1/0 * (NUMBER(1)) *
    • *
    • * odbc -> 1/0 * (SMALLINT) [1] *
    • *
    • * pgsql -> TRUE/FALSE * (BOOLEAN) *
    • *
    • * sqlite -> 1/0 * (INTEGER) *
    • *
    • * sybase -> 1/0 * (TINYINT(1)) *
    • *
    * [1] Accommodate the lowest common denominator because not all * versions of have BOOLEAN. *
  • *
  • * 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 *
  • *
* * @since 1.6.0 * @see DB_common::escapeSimple() * @access public */ 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() /** * Escape 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 * * @since 1.6.0 * @see DB_common::quoteSmart() * @access public */ function escapeSimple($str) { return str_replace("'", "''", $str); } // }}} // {{{ provides() /** * Tell whether a DB implementation or its backend extension * supports a given feature * * @param array $feature name of the feature (see the DB class doc) * @return bool whether this DB implementation supports $feature * @access public */ function provides($feature) { return $this->features[$feature]; } // }}} // {{{ errorCode() /** * Map native error codes to DB's portable ones * * Requires that the DB implementation's constructor fills * in the $errorcode_map property. * * @param mixed $nativecode the native error code, as returned by the * backend database extension (string or integer) * * @return int a portable DB error code, or DB_ERROR if this DB * implementation has no mapping for the given error code. * * @access public */ 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() /** * Map a DB error code to a textual message. This is actually * just a wrapper for DB::errorMessage() * * @param integer $dbcode the DB error code * * @return string the corresponding error message, of false * if the error code was unknown * * @access public */ function errorMessage($dbcode) { return DB::errorMessage($this->errorcode_map[$dbcode]); } // }}} // {{{ raiseError() /** * Communicate 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 a PEAR error object * * @access public * @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) . ']'; } $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'DB_Error', true); return $tmp; } // }}} // {{{ setFetchMode() /** * Sets which fetch mode should be used by default on queries * on this connection * * @param integer $fetchmode DB_FETCHMODE_ORDERED or * DB_FETCHMODE_ASSOC, possibly bit-wise OR'ed with * DB_FETCHMODE_FLIPPED. * * @param string $object_class The class 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 * @see DB_FETCHMODE_ASSOC * @see DB_FETCHMODE_FLIPPED * @see DB_FETCHMODE_OBJECT * @see DB_row::DB_row() * @access public */ 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() /** * Set run-time configuration options for PEAR DB * * Options, their data types, default values and description: *
    *
  • * autofree boolean = false *
    should results be freed automatically when there are no * more rows? *
  • * debug integer = 0 *
    debug level *
  • * persistent boolean = false *
    should the connection be persistent? *
  • * portability integer = DB_PORTABILITY_NONE *
    portability mode constant (see below) *
  • * seqname_format string = %s_seq *
    the sprintf() format string used on sequence names. This * format is applied to sequence names passed to * createSequence(), nextID() and dropSequence(). *
  • * ssl boolean = false *
    use ssl to connect? *
  • *
* * ----------------------------------------- * * PORTABILITY MODES * * These modes are bitwised, so they can be combined using | * and removed using ^. See the examples section below on how * to do this. * * DB_PORTABILITY_NONE * turn off all portability features * * This mode gets automatically turned on if the deprecated * optimize option gets set to performance. * * * DB_PORTABILITY_LOWERCASE * convert names of tables and fields to lower case when using * get*(), fetch*() and tableInfo() * * This mode gets automatically turned on in the following databases * if the deprecated option optimize gets set to * portability: * + oci8 * * * DB_PORTABILITY_RTRIM * right trim the data output by get*() fetch*() * * * DB_PORTABILITY_DELETE_COUNT * force reporting the number of rows deleted * * Some DBMS's don't count the number of rows deleted when performing * simple DELETE FROM tablename queries. This portability * mode tricks such DBMS's into telling the count by adding * WHERE 1=1 to the end of DELETE queries. * * This mode gets automatically turned on in the following databases * if the deprecated option optimize gets set to * portability: * + fbsql * + mysql * + mysqli * + sqlite * * * DB_PORTABILITY_NUMROWS * enable hack that makes numRows() work in Oracle * * This mode gets automatically turned on in the following databases * if the deprecated option optimize gets set to * portability: * + oci8 * * * DB_PORTABILITY_ERRORS * 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 * * * DB_PORTABILITY_NULL_TO_EMPTY * 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. * * * DB_PORTABILITY_ALL * turn on all portability features * * ----------------------------------------- * * Example 1. Simple setOption() example * setOption('autofree', true); * ?> * * Example 2. Portability for lowercasing and trimming * setOption('portability', * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); * ?> * * Example 3. All portability options except trimming * setOption('portability', * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); * ?> * * @param string $option option name * @param mixed $value value for the option * * @return int DB_OK on success. 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 option name * * @return mixed the option 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: * + ? scalar value (i.e. strings, integers). The system * will automatically quote and escape the data. * + ! value is inserted 'as is' * + & requires a file name. The file's contents get * inserted into the query (i.e. saving binary * data in a db) * * Example 1. * prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); * $data = array( * "John's text", * "'it''s good'", * 'filename.txt' * ); * $res = $dbh->execute($sth, $data); * ?> * * Use backslashes to escape placeholder characters if you don't want * them to be interpreted as placeholders: *
     *    "UPDATE foo SET col=? WHERE col='over \& under'"
     * 
* * With some database backends, this is emulated. * * {@internal ibase and oci8 have their own prepare() methods.}} * * @param string $query query to be prepared * * @return mixed DB statement resource on success. DB_Error on failure. * * @see DB_common::execute() * @access public */ function prepare($query) { $tokens = preg_split('/((?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 generate an insert or update query and pass it to prepare() * * @param string $table name of the table * @param array $table_fields ordered array containing the fields names * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE) * @param string $where in case of update queries, this string will be put after the sql WHERE statement * @return resource handle for the query * @see DB_common::prepare(), DB_common::buildManipSQL() * @access public */ function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, $where = false) { $query = $this->buildManipSQL($table, $table_fields, $mode, $where); return $this->prepare($query); } // }}} // {{{ autoExecute() /** * Automaticaly generate an insert or update query and call prepare() * and execute() with it * * @param string $table name of the table * @param array $fields_values assoc ($key=>$value) where $key is a field name and $value its value * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE) * @param string $where in case of update queries, this string will be put after the sql WHERE statement * @return mixed a new DB_Result or a DB_Error when fail * @see DB_common::autoPrepare(), DB_common::buildManipSQL() * @access public */ function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, $where = false) { $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, $where); $ret =& $this->execute($sth, array_values($fields_values)); $this->freePrepared($sth); return $ret; } // }}} // {{{ buildManipSQL() /** * Make automaticaly an sql query for prepare() * * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), DB_AUTOQUERY_INSERT) * will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) * NB : - This belongs more to a SQL Builder class, but this is a simple facility * - Be carefull ! If you don't give a $where param with an UPDATE query, all * the records of the table will be updated ! * * @param string $table name of the table * @param array $table_fields ordered array containing the fields names * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE) * @param string $where in case of update queries, this string will be put after the sql WHERE statement * @return string sql query for prepare() * @access public */ function buildManipSQL($table, $table_fields, $mode, $where = false) { if (count($table_fields) == 0) { $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: $this->raiseError(DB_ERROR_SYNTAX); } } // }}} // {{{ execute() /** * Executes a DB statement prepared with prepare() * * Example 1. * prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); * $data = array( * "John's text", * "'it''s good'", * 'filename.txt' * ); * $res =& $dbh->execute($sth, $data); * ?> * * @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 object a new DB_Result or a DB_Error when fail * * {@internal ibase and oci8 have their own execute() methods.}} * * @see DB_common::prepare() * @access public */ function &execute($stmt, $data = array()) { $realquery = $this->executeEmulateQuery($stmt, $data); if (DB::isError($realquery)) { return $realquery; } $result = $this->simpleQuery($realquery); if (DB::isError($result) || $result === DB_OK) { return $result; } else { $tmp =& new DB_result($this, $result); return $tmp; } } // }}} // {{{ executeEmulateQuery() /** * Emulates the execute statement, when not supported * * @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 code is returned on failure. * * @see DB_common::execute() * @access private */ function executeEmulateQuery($stmt, $data = array()) { $stmt = (int)$stmt; if (!is_array($data)) { $data = array($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() /** * This function does 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 mixed DB_OK or DB_Error * * @see DB_common::prepare(), DB_common::execute() * @access public */ function executeMultiple($stmt, $data) { foreach ($data as $value) { $res =& $this->execute($stmt, $value); if (DB::isError($res)) { return $res; } } return DB_OK; } // }}} // {{{ freePrepared() /** * Free the resource used in a prepared query * * @param $stmt The resurce returned by the prepare() function * @see DB_common::prepare() */ function freePrepared($stmt) { $stmt = (int)$stmt; // Free the internal prepared vars 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() /** * This method is used by backends to alter queries for various * reasons * * It is defined here to assure that all implementations * have this method defined. * * @param string $query query to modify * * @return the new (modified) query * * @access private */ function modifyQuery($query) { return $query; } // }}} // {{{ modifyLimitQuery() /** * This method is used by backends to alter limited queries * * @param string $query query to modify * @param integer $from the row to start to fetching * @param integer $count the numbers of rows to fetch * * @return the new (modified) query * * @access private */ function modifyLimitQuery($query, $from, $count, $params = array()) { return $query; } // }}} // {{{ query() /** * Send a query to the database and return any results with a * DB_result object * * The query string can be either a normal statement to be sent directly * to the server OR if $params 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 DB_result object or DB_OK on success, a DB * error on failure * * @see DB_result, DB_common::prepare(), DB_common::execute() * @access public */ 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); return $ret; } else { $result = $this->simpleQuery($query); if (DB::isError($result) || $result === DB_OK) { return $result; } else { $tmp =& new DB_result($this, $result); return $tmp; } } } // }}} // {{{ limitQuery() /** * Generates a limited query * * @param string $query query * @param integer $from the row to start to fetching * @param integer $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 DB_Result object, DB_OK or a DB_Error * * @access public */ 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() /** * Fetch the first column of the first row of data returned from * a query * * Takes care of doing the query and freeing the results when finished. * * @param string $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. DB_Error on failure. * * @access public */ function &getOne($query, $params = array()) { settype($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 (DB::isError($res)) { return $res; } $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); $res->free(); if ($err !== DB_OK) { return $err; } return $row[0]; } // }}} // {{{ getRow() /** * Fetch the first row of data returned from a query * * Takes care of doing the query and freeing the results when finished. * * @param string $query the SQL query * @param array $params array to be used in execution of the statement. * Quantity of array elements must match quantity * of placeholders in query. This function does * NOT support scalars. * @param int $fetchmode the fetch mode to use * * @return array the first row of results as an array indexed from * 0, or a DB error code. * * @access public */ 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(); } } 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() /** * Fetch a single column from a result set and return 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 an indexed array with the data from the first * row at index 0, or a DB error code * * @see DB_common::query() * @access public */ function &getCol($query, $col = 0, $params = array()) { settype($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 (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() /** * Fetch the entire result set of a query and return it as an * associative array using the first column as the key * * If the result set contains more than two columns, the value * will be an array of the values from column 2-n. If the result * set contains only two columns, the returned value will be a * scalar with the value of the second column (unless forced to an * array with the $force_array parameter). A 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: * *
     *  ID      TEXT       DATE
     * --------------------------------
     *  1       'one'      944679408
     *  2       'two'      944679408
     *  3       'three'    944679408
     * 
* * Then the call getAssoc('SELECT id,text FROM mytable') returns: *
     *   array(
     *     '1' => 'one',
     *     '2' => 'two',
     *     '3' => 'three',
     *   )
     * 
* * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: *
     *   array(
     *     '1' => array('one', '944679408'),
     *     '2' => array('two', '944679408'),
     *     '3' => array('three', '944679408')
     *   )
     * 
* * If the more than one row occurs with the same value in the * first column, the last row overwrites all previous ones by * default. Use the $group parameter if you don't want to * overwrite like this. Example: * *
     * getAssoc('SELECT category,id,name FROM mytable', 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')
     *            )
     *   )
     * 
* * 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 boolean $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 boolean $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 associative array with results from the query. * DB Error on failure. * * @access public */ function &getAssoc($query, $force_array = false, $params = array(), $fetchmode = DB_FETCHMODE_DEFAULT, $group = false) { settype($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 (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() /** * Fetch all the rows returned from a query * * @param string $query the SQL query * @param array $params array to be used in execution of the statement. * Quantity of array elements must match quantity * of placeholders in query. This function does * NOT support scalars. * @param int $fetchmode the fetch mode to use * * @return array an nested array. DB error on failure. * * @access public */ 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 (DB::isError($res) || $res === DB_OK) { 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() /** * enable automatic Commit * * @param boolean $onoff * @return mixed DB_Error * * @access public */ function autoCommit($onoff=false) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ commit() /** * starts a Commit * * @return mixed DB_Error * * @access public */ function commit() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ rollback() /** * starts a rollback * * @return mixed DB_Error * * @access public */ function rollback() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ numRows() /** * Returns the number of rows in a result object * * @param object DB_Result the result object to check * * @return mixed DB_Error or the number of rows * * @access public */ function numRows($result) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ affectedRows() /** * Returns the affected rows of a query * * @return mixed DB_Error or number of rows * * @access public */ function affectedRows() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ errorNative() /** * Returns an errormessage, provides by the database * * @return mixed DB_Error or message * * @access public */ function errorNative() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ getSequenceName() /** * Generate 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 * * @see DB_common::createSequence(), DB_common::dropSequence(), * DB_common::nextID(), DB_common::setOption() * @access private */ 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. DB_Error if problem. * * @see DB_common::createSequence(), DB_common::dropSequence(), * DB_common::getSequenceName() * @access public */ 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 $seq_name argument through PHP's sprintf() * function using the value from the seqname_format option as * the sprintf()'s format argument. * * seqname_format is set via setOption(). * * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object is returned if * problems arise. * * @see DB_common::dropSequence(), DB_common::getSequenceName(), * DB_common::nextID() * @access public */ 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. DB_Error if problems. * * @see DB_common::createSequence(), DB_common::getSequenceName(), * DB_common::nextID() * @access public */ function dropSequence($seq_name) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ tableInfo() /** * Returns information about a table or a result set * * The format of the resulting array depends on which $mode * you select. The sample output below is based on this query: *
     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
     *    FROM tblFoo
     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
     * 
* *
    *
  • * * null (default) *
         *   [0] => Array (
         *       [table] => tblFoo
         *       [name] => fldId
         *       [type] => int
         *       [len] => 11
         *       [flags] => primary_key not_null
         *   )
         *   [1] => Array (
         *       [table] => tblFoo
         *       [name] => fldPhone
         *       [type] => string
         *       [len] => 20
         *       [flags] =>
         *   )
         *   [2] => Array (
         *       [table] => tblBar
         *       [name] => fldId
         *       [type] => int
         *       [len] => 11
         *       [flags] => primary_key not_null
         *   )
         *   
    * *
  • * * DB_TABLEINFO_ORDER * *

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

    * *

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

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

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

    * *
         *   [num_fields] => 3
         *   [ordertable] => Array (
         *       [tblFoo] => Array (
         *           [fldId] => 0
         *           [fldPhone] => 1
         *       )
         *       [tblBar] => Array (
         *           [fldId] => 2
         *       )
         *   )
         *   
    * *
  • *
* * The flags element contains a space separated list * of extra information about the field. This data is inconsistent * between DBMS's due to the way each DBMS works. * + primary_key * + unique_key * + multiple_key * + not_null * * Most DBMS's only provide the table and flags * elements if $result is a table name. The following DBMS's * provide full information from queries: * + fbsql * + mysql * * If the 'portability' option has DB_PORTABILITY_LOWERCASE * 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: * DB_TABLEINFO_ORDERTABLE, * DB_TABLEINFO_ORDER or * DB_TABLEINFO_FULL (which does both). * These are bitwise, so the first two can be * combined using |. * @return array an associative array with the information requested. * If something goes wrong an error object is returned. * * @see DB_common::setOption() * @access public */ function tableInfo($result, $mode = null) { /* * If the DB_ 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() /** * @deprecated Deprecated in release 1.2 or lower */ function getTables() { return $this->getListOf('tables'); } // }}} // {{{ getListOf() /** * list internal DB info * valid values for $type are db dependent, * often: databases, users, view, functions * * @param string $type type of requested info * * @return mixed DB_Error or the requested data * * @access public */ function getListOf($type) { $sql = $this->getSpecialQuery($type); if ($sql === null) { // No support 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; } return $this->getCol($sql); // Launch this query } // }}} // {{{ getSpecialQuery() /** * Returns the query needed to get some backend info * * @param string $type What kind of info you want to retrieve * * @return string The SQL query string * * @access public */ function getSpecialQuery($type) { return $this->raiseError(DB_ERROR_UNSUPPORTED); } // }}} // {{{ _rtrimArrayValues() /** * Right trim all strings in an array * * @param array $array the array to be trimmed (passed by reference) * @return void * @access private */ function _rtrimArrayValues(&$array) { foreach ($array as $key => $value) { if (is_string($value)) { $array[$key] = rtrim($value); } } } // }}} // {{{ _convertNullArrayValuesToEmpty() /** * Convert all null values in an array to empty strings * * @param array $array the array to be de-nullified (passed by reference) * @return void * @access private */ function _convertNullArrayValuesToEmpty(&$array) { foreach ($array as $key => $value) { if (is_null($value)) { $array[$key] = ''; } } } // }}} } /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: */ /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2004 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 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. | // +----------------------------------------------------------------------+ // | Author: Stig Bakken | // | Maintainer: Daniel Convissor | // +----------------------------------------------------------------------+ // // $Id: mysql.php,v 1.74 2004/04/29 22:42:57 danielc Exp $ // XXX legend: // // XXX ERRORMSG: The error message from the mysql function should // be registered here. // // TODO/wishlist: // longReadlen // binmode /** * Database independent query interface definition for PHP's MySQL * extension. * * This is for MySQL versions 4.0 and below. * * @package DB * @version $Id: mysql.php,v 1.74 2004/04/29 22:42:57 danielc Exp $ * @category Database * @author Stig Bakken */ class DB_mysql extends DB_common { // {{{ properties var $connection; var $phptype, $dbsyntax; var $prepare_tokens = array(); var $prepare_types = array(); var $num_rows = array(); var $transaction_opcount = 0; var $autocommit = true; var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */ var $_db = false; // }}} // {{{ constructor /** * DB_mysql constructor. * * @access public */ function DB_mysql() { $this->DB_common(); $this->phptype = 'mysql'; $this->dbsyntax = 'mysql'; $this->features = array( 'prepare' => false, 'pconnect' => true, 'transactions' => true, 'limit' => 'alter' ); $this->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, 1046 => DB_ERROR_NODBSELECTED, 1048 => DB_ERROR_CONSTRAINT, 1050 => DB_ERROR_ALREADY_EXISTS, 1051 => DB_ERROR_NOSUCHTABLE, 1054 => DB_ERROR_NOSUCHFIELD, 1062 => DB_ERROR_ALREADY_EXISTS, 1064 => DB_ERROR_SYNTAX, 1100 => DB_ERROR_NOT_LOCKED, 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, 1146 => DB_ERROR_NOSUCHTABLE, 1216 => DB_ERROR_CONSTRAINT, 1217 => DB_ERROR_CONSTRAINT, ); } // }}} // {{{ connect() /** * Connect to a database and log in as the specified user. * * @param $dsn the data source name (see DB::parseDSN for syntax) * @param $persistent (optional) whether the connection should * be persistent * @access public * @return int DB_OK on success, a DB error on failure */ function connect($dsninfo, $persistent = false) { if (!DB::assertExtension('mysql')) { return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); } $this->dsn = $dsninfo; if ($dsninfo['protocol'] && $dsninfo['protocol'] == 'unix') { $dbhost = ':' . $dsninfo['socket']; } else { $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost'; if ($dsninfo['port']) { $dbhost .= ':' . $dsninfo['port']; } } $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; if ($dbhost && $dsninfo['username'] && isset($dsninfo['password'])) { $conn = @$connect_function($dbhost, $dsninfo['username'], $dsninfo['password']); } elseif ($dbhost && $dsninfo['username']) { $conn = @$connect_function($dbhost, $dsninfo['username']); } elseif ($dbhost) { $conn = @$connect_function($dbhost); } else { $conn = false; } if (!$conn) { if (($err = @mysql_error()) != '') { return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null, null, $err); } elseif (empty($php_errormsg)) { return $this->raiseError(DB_ERROR_CONNECT_FAILED); } else { return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null, null, $php_errormsg); } } if ($dsninfo['database']) { if (!@mysql_select_db($dsninfo['database'], $conn)) { switch(mysql_errno($conn)) { case 1049: return $this->raiseError(DB_ERROR_NOSUCHDB, null, null, null, @mysql_error($conn)); case 1044: return $this->raiseError(DB_ERROR_ACCESS_VIOLATION, null, null, null, @mysql_error($conn)); default: return $this->raiseError(DB_ERROR, null, null, null, @mysql_error($conn)); } } // fix to allow calls to different databases in the same script $this->_db = $dsninfo['database']; } $this->connection = $conn; return DB_OK; } // }}} // {{{ disconnect() /** * Log out and disconnect from the database. * * @access public * * @return bool true on success, false if not connected. */ function disconnect() { $ret = @mysql_close($this->connection); $this->connection = null; return $ret; } // }}} // {{{ simpleQuery() /** * Send a query to MySQL and return the results as a MySQL resource * identifier. * * @param the SQL query * * @access public * * @return mixed returns a valid MySQL result for successful SELECT * queries, DB_OK for other successful queries. A DB error is * returned 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++; } $result = @mysql_query($query, $this->connection); if (!$result) { return $this->mysqlRaiseError(); } if (is_resource($result)) { $numrows = $this->numrows($result); if (is_object($numrows)) { return $numrows; } $this->num_rows[(int)$result] = $numrows; 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 * * @access public * * @return false */ function nextResult($result) { return false; } // }}} // {{{ fetchInto() /** * Fetch a row and insert the data into an existing array. * * Formating of the array and the data therein are configurable. * See DB_result::fetchInto() for more information. * * @param resource $result query result identifier * @param array $arr (reference) array where data from the row * should be placed * @param int $fetchmode how the resulting array should be indexed * @param int $rownum the row number to fetch * * @return mixed DB_OK on success, null when end of result set is * reached or on failure * * @see DB_result::fetchInto() * @access private */ 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) { // See: http://bugs.php.net/bug.php?id=22328 // for why we can't check errors on fetching return null; /* $errno = @mysql_errno($this->connection); if (!$errno) { return null; } return $this->mysqlRaiseError($errno); */ } 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() /** * Free the internal resources associated with $result. * * @param $result MySQL result identifier * * @access public * * @return bool true on success, false if $result is invalid */ function freeResult($result) { unset($this->num_rows[(int)$result]); return @mysql_free_result($result); } // }}} // {{{ numCols() /** * Get the number of columns in a result set. * * @param $result MySQL result identifier * * @access public * * @return int the number of columns per row in $result */ function numCols($result) { $cols = @mysql_num_fields($result); if (!$cols) { return $this->mysqlRaiseError(); } return $cols; } // }}} // {{{ numRows() /** * Get the number of rows in a result set. * * @param $result MySQL result identifier * * @access public * * @return int the number of rows in $result */ function numRows($result) { $rows = @mysql_num_rows($result); if ($rows === null) { return $this->mysqlRaiseError(); } return $rows; } // }}} // {{{ autoCommit() /** * Enable/disable automatic commits */ 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() /** * Commit the current transaction. */ 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() /** * Roll back (undo) the current transaction. */ 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() /** * Gets the number of rows affected by the data manipulation * query. For other queries, this function returns 0. * * @return number of rows affected by the last query */ function affectedRows() { if (DB::isManip($this->last_query)) { return @mysql_affected_rows($this->connection); } else { return 0; } } // }}} // {{{ errorNative() /** * Get the native error code of the last error (if any) that * occured on the current connection. * * @access public * * @return int native MySQL error code */ function errorNative() { return @mysql_errno($this->connection); } // }}} // {{{ 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. DB_Error if problem. * * @internal * @see DB_common::nextID() * @access public */ 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 // 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, bail with a DB_ERROR_NOT_LOCKED error 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; /** ONDEMAND TABLE CREATION **/ } elseif ($ondemand && DB::isError($result) && $result->getCode() == DB_ERROR_NOSUCHTABLE) { $result = $this->createSequence($seq_name); if (DB::isError($result)) { return $this->raiseError($result); } else { $repeat = 1; } /** BACKWARDS COMPAT **/ } elseif (DB::isError($result) && $result->getCode() == DB_ERROR_ALREADY_EXISTS) { // 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 is returned if * problems arise. * * @internal * @see DB_common::createSequence() * @access public */ 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. DB_Error if problems. * * @internal * @see DB_common::dropSequence() * @access public */ 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 mixed DB_Error or true */ 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() /** * Quote a string so it can be safely used as a table or column name * * Quoting style depends on which database driver is being used. * * MySQL can't handle the backtick character (`) in * table or column names. * * @param string $str identifier name to be quoted * * @return string quoted identifier string * * @since 1.6.0 * @access public * @internal */ function quoteIdentifier($str) { return '`' . $str . '`'; } // }}} // {{{ quote() /** * @deprecated Deprecated in release 1.6.0 * @internal */ function quote($str) { return $this->quoteSmart($str); } // }}} // {{{ escapeSimple() /** * Escape a string according to the current DBMS's standards * * @param string $str the string to be escaped * * @return string the escaped string * * @internal */ 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() 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() function modifyLimitQuery($query, $from, $count, $params = array()) { if (DB::isManip($query)) { return $query . " LIMIT $count"; } else { return $query . " LIMIT $from, $count"; } } // }}} // {{{ mysqlRaiseError() /** * Gather information about an error, then use that info to create a * DB error object and finally return that object. * * @param integer $errno PEAR error number (usually a DB constant) if * manually raising an error * @return object DB error object * @see DB_common::errorCode() * @see DB_common::raiseError() */ 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)); } // }}} // {{{ 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 * @param int $mode a valid tableInfo mode * @return array an associative array with the information requested * or an error object if something is wrong * @access public * @internal * @see DB_common::tableInfo() */ function tableInfo($result, $mode = null) { if (isset($result->result)) { /* * Probably received a result object. * Extract the result resource identifier. */ $id = $result->result; $got_string = false; } elseif (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; } 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); // made this IF due to performance (one if is faster than $count if's) if (!$mode) { for ($i=0; $i<$count; $i++) { $res[$i]['table'] = $case_func(@mysql_field_table($id, $i)); $res[$i]['name'] = $case_func(@mysql_field_name($id, $i)); $res[$i]['type'] = @mysql_field_type($id, $i); $res[$i]['len'] = @mysql_field_len($id, $i); $res[$i]['flags'] = @mysql_field_flags($id, $i); } } else { // full $res['num_fields']= $count; for ($i=0; $i<$count; $i++) { $res[$i]['table'] = $case_func(@mysql_field_table($id, $i)); $res[$i]['name'] = $case_func(@mysql_field_name($id, $i)); $res[$i]['type'] = @mysql_field_type($id, $i); $res[$i]['len'] = @mysql_field_len($id, $i); $res[$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() /** * Returns the query needed to get some backend info * @param string $type What kind of info you want to retrieve * @return string The SQL query string */ function getSpecialQuery($type) { switch ($type) { case 'tables': return 'SHOW TABLES'; case 'views': return DB_ERROR_NOT_CAPABLE; case 'users': $sql = 'select distinct User from user'; if ($this->dsn['database'] != 'mysql') { $dsn = $this->dsn; $dsn['database'] = 'mysql'; if (DB::isError($db = DB::connect($dsn))) { return $db; } $sql = $db->getCol($sql); $db->disconnect(); // XXX Fixme the mysql driver should take care of this if (!@mysql_select_db($this->dsn['database'], $this->connection)) { return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); } } return $sql; case 'databases': return 'SHOW DATABASES'; default: return null; } } // }}} } /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: */ /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2004 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 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 | // | Tomas V.V.Cox | // | Maintainer: Daniel Convissor | // +----------------------------------------------------------------------+ // // $Id: DB.php,v 1.59 2004/07/08 21:15:11 danielc Exp $ // // Database independent query interface. // {{{ constants // {{{ error codes /* * The method mapErrorCode in each DB_dbtype implementation maps * native error codes to one of these. * * If you add an error code here, make sure you also add a textual * version of it in DB::errorMessage(). */ define('DB_OK', 1); define('DB_ERROR', -1); define('DB_ERROR_SYNTAX', -2); define('DB_ERROR_CONSTRAINT', -3); define('DB_ERROR_NOT_FOUND', -4); define('DB_ERROR_ALREADY_EXISTS', -5); define('DB_ERROR_UNSUPPORTED', -6); define('DB_ERROR_MISMATCH', -7); define('DB_ERROR_INVALID', -8); define('DB_ERROR_NOT_CAPABLE', -9); define('DB_ERROR_TRUNCATED', -10); define('DB_ERROR_INVALID_NUMBER', -11); define('DB_ERROR_INVALID_DATE', -12); define('DB_ERROR_DIVZERO', -13); define('DB_ERROR_NODBSELECTED', -14); define('DB_ERROR_CANNOT_CREATE', -15); define('DB_ERROR_CANNOT_DELETE', -16); define('DB_ERROR_CANNOT_DROP', -17); define('DB_ERROR_NOSUCHTABLE', -18); define('DB_ERROR_NOSUCHFIELD', -19); define('DB_ERROR_NEED_MORE_DATA', -20); define('DB_ERROR_NOT_LOCKED', -21); define('DB_ERROR_VALUE_COUNT_ON_ROW', -22); define('DB_ERROR_INVALID_DSN', -23); define('DB_ERROR_CONNECT_FAILED', -24); define('DB_ERROR_EXTENSION_NOT_FOUND',-25); define('DB_ERROR_ACCESS_VIOLATION', -26); define('DB_ERROR_NOSUCHDB', -27); define('DB_ERROR_CONSTRAINT_NOT_NULL',-29); // }}} // {{{ prepared statement-related /* * These constants are used when storing information about prepared * statements (using the "prepare" method in DB_dbtype). * * The prepare/execute model in DB is mostly borrowed from the ODBC * extension, in a query the "?" character means a scalar parameter. * There are two extensions though, a "&" character means an opaque * parameter. An opaque parameter is simply a file name, the real * data are in that file (useful for putting uploaded files into your * database and such). The "!" char means a parameter that must be * left as it is. * They modify the quote behavoir: * DB_PARAM_SCALAR (?) => 'original string quoted' * DB_PARAM_OPAQUE (&) => 'string from file quoted' * DB_PARAM_MISC (!) => original string */ define('DB_PARAM_SCALAR', 1); define('DB_PARAM_OPAQUE', 2); define('DB_PARAM_MISC', 3); // }}} // {{{ binary data-related /* * These constants define different ways of returning binary data * from queries. Again, this model has been borrowed from the ODBC * extension. * * DB_BINMODE_PASSTHRU sends the data directly through to the browser * when data is fetched from the database. * DB_BINMODE_RETURN lets you return data as usual. * DB_BINMODE_CONVERT returns data as well, only it is converted to * hex format, for example the string "123" would become "313233". */ define('DB_BINMODE_PASSTHRU', 1); define('DB_BINMODE_RETURN', 2); define('DB_BINMODE_CONVERT', 3); // }}} // {{{ fetch modes /** * This is a special constant that tells DB the user hasn't specified * any particular get mode, so the default should be used. */ 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: normally the first level of arrays * is the row number, and the second level indexed by column number or name. * DB_FETCHMODE_FLIPPED switches this order, so the first level of arrays * is the column name, and the second level the row number. */ define('DB_FETCHMODE_FLIPPED', 4); /* 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 /** * these are constants for the tableInfo-function * they are bitwised or'ed. so if there are more constants to be defined * in the future, adjust DB_TABLEINFO_FULL accordingly */ define('DB_TABLEINFO_ORDER', 1); define('DB_TABLEINFO_ORDERTABLE', 2); define('DB_TABLEINFO_FULL', 3); /* * Used by autoPrepare() */ define('DB_AUTOQUERY_INSERT', 1); define('DB_AUTOQUERY_UPDATE', 2); // }}} // {{{ portability modes /** * Portability: turn off all portability features. * @see DB_common::setOption() */ define('DB_PORTABILITY_NONE', 0); /** * Portability: convert names of tables and fields to lower case * when using the get*(), fetch*() and tableInfo() methods. * @see DB_common::setOption() */ define('DB_PORTABILITY_LOWERCASE', 1); /** * Portability: right trim the data output by get*() and fetch*(). * @see DB_common::setOption() */ define('DB_PORTABILITY_RTRIM', 2); /** * Portability: force reporting the number of rows deleted. * @see DB_common::setOption() */ define('DB_PORTABILITY_DELETE_COUNT', 4); /** * Portability: enable hack that makes numRows() work in Oracle. * @see DB_common::setOption() */ define('DB_PORTABILITY_NUMROWS', 8); /** * Portability: makes certain error messages in certain drivers compatible * with those from other DBMS's. * * + mysql, mysqli: change unique/primary key constraints * 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. * * @see DB_common::setOption() */ define('DB_PORTABILITY_ERRORS', 16); /** * Portability: convert null values to empty strings in data output by * get*() and fetch*(). * @see DB_common::setOption() */ define('DB_PORTABILITY_NULL_TO_EMPTY', 32); /** * Portability: turn on all portability features. * @see DB_common::setOption() */ define('DB_PORTABILITY_ALL', 63); // }}} // }}} // {{{ class DB /** * 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): * * 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. * * @package DB * @author Stig Bakken * @author Tomas V.V.Cox * @since PHP 4.0 * @version $Id: DB.php,v 1.59 2004/07/08 21:15:11 danielc Exp $ * @category Database */ class DB { // {{{ &factory() /** * Create a new DB object for the specified database type. * * Allows creation of a DB_ object from which the object's * methods can be utilized without actually connecting to a database. * * @param string $type database type, for example "mysql" * @param array $options associative array of option names and values * * @return object a new DB object. On error, an error object. * * @see DB_common::setOption() * @access public */ 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", '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 and connect to the specified database. * * Example 1. * 2, * 'portability' => DB_PORTABILITY_ALL, * ); * * $dbh =& DB::connect($dsn, $options); * if (DB::isError($dbh)) { * die($dbh->getMessage()); * } * ?> * * @param mixed $dsn string "data source name" or an array in the * format returned by DB::parseDSN() * * @param array $options an associative array of option names and * their values * * @return object a newly created DB connection object, or a DB * error object on error * * @see DB::parseDSN(), DB_common::setOption(), DB::isError() * @access public */ 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 int the DB API version number * * @access public */ function apiVersion() { return 2; } // }}} // {{{ isError() /** * Tell whether a result code from a DB method is an error * * @param int $value result code * * @return bool whether $value is an error * * @access public */ function isError($value) { return is_a($value, 'DB_Error'); } // }}} // {{{ isConnection() /** * Tell whether a value is a DB connection * * @param mixed $value value to test * * @return bool whether $value is a DB connection * * @access public */ 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 query (insert, * update or delete) or a data definition query (create, drop, * alter, grant, revoke). * * @access public * * @param string $query the query * * @return boolean whether $query is a data manipulation query */ function isManip($query) { $manips = 'INSERT|UPDATE|DELETE|LOAD DATA|'.'REPLACE|CREATE|DROP|'. '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 error code * * @return string 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_ALREADY_EXISTS => 'already exists', DB_ERROR_CANNOT_CREATE => 'can not create', DB_ERROR_CANNOT_DELETE => 'can not delete', DB_ERROR_CANNOT_DROP => 'can not drop', DB_ERROR_CONSTRAINT => 'constraint violation', DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', DB_ERROR_DIVZERO => 'division by zero', DB_ERROR_INVALID => 'invalid', DB_ERROR_INVALID_DATE => 'invalid date or time', DB_ERROR_INVALID_NUMBER => 'invalid number', DB_ERROR_MISMATCH => 'mismatch', DB_ERROR_NODBSELECTED => 'no database selected', 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_VALUE_COUNT_ON_ROW => 'value count on row', DB_ERROR_INVALID_DSN => 'invalid DSN', DB_ERROR_CONNECT_FAILED => 'connect failed', DB_OK => 'no error', DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', DB_ERROR_NOSUCHDB => 'no such database', DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', DB_ERROR_TRUNCATED => 'truncated' ); } 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: * * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true * * * Most variations are allowed: * * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 * phptype://username:password@hostspec/database_name * phptype://username:password@hostspec * phptype://username@hostspec * phptype://hostspec/database * phptype://hostspec * phptype(dbsyntax) * phptype * * * @param string $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 * * @author Tomas V.V.Cox */ 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 // $dsn => proto(proto_opts)/database if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { $proto = $match[1]; $proto_opts = $match[2] ? $match[2] : false; $dsn = $match[3]; // $dsn => protocol+hostspec/database (old format) } else { if (strpos($dsn, '+') !== false) { list($proto, $dsn) = explode('+', $dsn, 2); } if (strpos($dsn, '/') !== 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) { // /database if (($pos = strpos($dsn, '?')) === false) { $parsed['database'] = rawurldecode($dsn); // /database?param1=value1¶m2=value2 } else { $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; } // }}} // {{{ assertExtension() /** * Load a PHP database extension if it is not loaded already. * * @access public * * @param string $name the base name of the extension (without the .so or * .dll suffix) * * @return boolean true if the extension was already or successfully * loaded, false if it could not be loaded */ function assertExtension($name) { if (!extension_loaded($name)) { $dlext = OS_WINDOWS ? '.dll' : '.so'; $dlprefix = OS_WINDOWS ? 'php_' : ''; @dl($dlprefix . $name . $dlext); return extension_loaded($name); } return true; } // }}} } // }}} // {{{ class DB_Error /** * DB_Error implements a class for reporting portable database error * messages. * * @package DB * @author Stig Bakken */ class DB_Error extends PEAR_Error { // {{{ constructor /** * DB_Error constructor. * * @param mixed $code DB error code, or string with error message. * @param integer $mode what "error mode" to operate in * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER * @param mixed $debuginfo additional debug info, such as the last query * * @access public * * @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. * * @package DB * @author Stig Bakken */ class DB_result { // {{{ properties var $dbh; var $result; var $row_counter = null; /** * for limit queries, the row to start fetching * @var integer */ var $limit_from = null; /** * for limit queries, the number of rows to fetch * @var integer */ var $limit_count = null; // }}} // {{{ constructor /** * DB_result constructor. * @param resource &$dbh DB object reference * @param resource $result result resource id * @param array $options assoc array with optional result options */ function DB_result(&$dbh, $result, $options = array()) { $this->dbh = &$dbh; $this->result = $result; foreach ($options as $key => $value) { $this->setOption($key, $value); } $this->limit_type = $dbh->features['limit']; $this->autofree = $dbh->options['autofree']; $this->fetchmode = $dbh->fetchmode; $this->fetchmode_object_class = $dbh->fetchmode_object_class; } function setOption($key, $value = null) { switch ($key) { case 'limit_from': $this->limit_from = $value; break; case 'limit_count': $this->limit_count = $value; break; } } // }}} // {{{ 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 $fetchmode 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(). * * + DB_PORTABILITY_LOWERCASE * convert names of fields to lower case * * + DB_PORTABILITY_RTRIM * right trim the data * * @param int $fetchmode how the resulting array should be indexed * @param int $rownum the row number to fetch * * @return array a row of data, null on no more rows or PEAR_Error * object on error * * @see DB_common::setOption(), DB_common::setFetchMode() * @access public */ 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->limit_type == 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->limit_type == '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 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 $fetchmode 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(). * * + DB_PORTABILITY_LOWERCASE * convert names of fields to lower case * * + DB_PORTABILITY_RTRIM * right trim the data * * @param array &$arr (reference) array where data from the row * should be placed * @param int $fetchmode how the resulting array should be indexed * @param int $rownum the row number to fetch * * @return mixed DB_OK on success, null on no more rows or * a DB_Error object on error * * @see DB_common::setOption(), DB_common::setFetchMode() * @access public */ 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->limit_type == 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->limit_type == '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 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, or a DB error * * @access public */ function numCols() { return $this->dbh->numCols($this->result); } // }}} // {{{ numRows() /** * Get the number of rows in a result set. * * @return int the number of rows, or a DB error * * @access public */ function numRows() { 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. * * @access public */ function nextResult() { return $this->dbh->nextResult($this->result); } // }}} // {{{ free() /** * Frees the resources allocated for this result set. * @return int error code * * @access public */ function free() { $err = $this->dbh->freeResult($this->result); if (DB::isError($err)) { return $err; } $this->result = false; return true; } // }}} // {{{ tableInfo() /** * @deprecated * @internal * @see DB_common::tableInfo() */ function tableInfo($mode = null) { if (is_string($mode)) { return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA); } return $this->dbh->tableInfo($this, $mode); } // }}} // {{{ getRowCounter() /** * returns the actual row number * @return integer */ function getRowCounter() { return $this->row_counter; } // }}} } // }}} // {{{ class DB_row /** * Pear DB Row Object * @see DB_common::setFetchMode() */ class DB_row { // {{{ constructor /** * constructor * * @param resource row data as array */ function DB_row(&$arr) { foreach ($arr as $key => $value) { $this->$key = &$arr[$key]; } } // }}} } // }}} /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: */ ?>