diff --git a/web_upload/includes/adodb/adodb.inc.php b/web_upload/includes/adodb/adodb.inc.php index ba50bcfa..afc9ea7b 100644 --- a/web_upload/includes/adodb/adodb.inc.php +++ b/web_upload/includes/adodb/adodb.inc.php @@ -1,42 +1,42 @@ fields is available on EOF $ADODB_FETCH_MODE, // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default... $ADODB_GETONE_EOF, @@ -70,35 +69,52 @@ class library to hide the differences between the different database API's (enca // GLOBAL SETUP //============================================================================================== - $ADODB_EXTENSION = defined('ADODB_EXTENSION'); - - // ******************************************************** - // Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3). - // Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi - // - // 0 = ignore empty fields. All empty fields in array are ignored. - // 1 = force null. All empty, php null and string 'null' fields are changed to sql NULL values. - // 2 = force empty. All empty, php null and string 'null' fields are changed to sql empty '' or 0 values. - // 3 = force value. Value is left as it is. Php null and string 'null' are set to sql NULL values and empty fields '' are set to empty '' sql values. - + /********************************************************* + * Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3). + * Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:adodb_force_type + * + * 0 = ignore empty fields. All empty fields in array are ignored. + * 1 = force null. All empty, php null and string 'null' fields are + * changed to sql NULL values. + * 2 = force empty. All empty, php null and string 'null' fields are + * changed to sql empty '' or 0 values. + * 3 = force value. Value is left as it is. Php null and string 'null' + * are set to sql NULL values and empty fields '' are set to empty '' sql values. + * 4 = force value. Like 1 but numeric empty fields are set to zero. + */ define('ADODB_FORCE_IGNORE',0); define('ADODB_FORCE_NULL',1); define('ADODB_FORCE_EMPTY',2); define('ADODB_FORCE_VALUE',3); + define('ADODB_FORCE_NULL_AND_ZERO',4); // ******************************************************** - if (!$ADODB_EXTENSION || ADODB_EXTENSION < 4.0) { + /** + * Constants for returned values from the charMax and textMax methods. + * If not specifically defined in the driver, methods return the NOTSET value. + */ + define ('ADODB_STRINGMAX_NOTSET', -1); + define ('ADODB_STRINGMAX_NOLIMIT',-2); + + /* + * Defines the the default meta type returned + * when ADOdb encounters a type that it is not + * defined in the metaTypes. + */ + if (!defined('ADODB_DEFAULT_METATYPE')) + define ('ADODB_DEFAULT_METATYPE','N'); - define('ADODB_BAD_RS','

Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;

'); + define('ADODB_BAD_RS','

Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;

'); // allow [ ] @ ` " and . in table names - define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)'); + define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)'); // prefetching used by oracle - if (!defined('ADODB_PREFETCH_ROWS')) { - define('ADODB_PREFETCH_ROWS',10); - } + if (!defined('ADODB_PREFETCH_ROWS')) { + define('ADODB_PREFETCH_ROWS',10); + } /** @@ -113,10 +129,10 @@ class library to hide the differences between the different database API's (enca * - BOTH: array(0 => 456, 'id' => 456, 1 => 'john', 'name' => 'john') * - DEFAULT: driver-dependent */ - define('ADODB_FETCH_DEFAULT', 0); - define('ADODB_FETCH_NUM', 1); - define('ADODB_FETCH_ASSOC', 2); - define('ADODB_FETCH_BOTH', 3); + define('ADODB_FETCH_DEFAULT', 0); + define('ADODB_FETCH_NUM', 1); + define('ADODB_FETCH_ASSOC', 2); + define('ADODB_FETCH_BOTH', 3); /** * Associative array case constants @@ -133,53 +149,23 @@ class library to hide the differences between the different database API's (enca * NOTE: This functionality is not implemented everywhere, it currently * works only with: mssql, odbc, oci8 and ibase derived drivers */ - define('ADODB_ASSOC_CASE_LOWER', 0); - define('ADODB_ASSOC_CASE_UPPER', 1); - define('ADODB_ASSOC_CASE_NATIVE', 2); - - - if (!defined('TIMESTAMP_FIRST_YEAR')) { - define('TIMESTAMP_FIRST_YEAR',100); - } - - /** - * AutoExecute constants - * (moved from adodb-pear.inc.php since they are only used in here) - */ - define('DB_AUTOQUERY_INSERT', 1); - define('DB_AUTOQUERY_UPDATE', 2); + define('ADODB_ASSOC_CASE_LOWER', 0); + define('ADODB_ASSOC_CASE_UPPER', 1); + define('ADODB_ASSOC_CASE_NATIVE', 2); - // PHP's version scheme makes converting to numbers difficult - workaround - $_adodb_ver = (float) PHP_VERSION; - if ($_adodb_ver >= 5.2) { - define('ADODB_PHPVER',0x5200); - } else if ($_adodb_ver >= 5.0) { - define('ADODB_PHPVER',0x5000); - } else { - die("PHP5 or later required. You are running ".PHP_VERSION); - } - unset($_adodb_ver); + if (!defined('TIMESTAMP_FIRST_YEAR')) { + define('TIMESTAMP_FIRST_YEAR',100); } - /** - Accepts $src and $dest arrays, replacing string $data - */ - function ADODB_str_replace($src, $dest, $data) { - if (ADODB_PHPVER >= 0x4050) { - return str_replace($src,$dest,$data); - } + * AutoExecute constants + * (moved from adodb-pear.inc.php since they are only used in here) + */ + define('DB_AUTOQUERY_INSERT', 1); + define('DB_AUTOQUERY_UPDATE', 2); + - $s = reset($src); - $d = reset($dest); - while ($s !== false) { - $data = str_replace($s,$d,$data); - $s = next($src); - $d = next($dest); - } - return $data; - } function ADODB_Setup() { GLOBAL @@ -209,15 +195,10 @@ function ADODB_Setup() { } } - - // Initialize random number generator for randomizing cache flushes - // -- note Since PHP 4.2.0, the seed becomes optional and defaults to a random value if omitted. - srand(((double)microtime())*1000000); - /** * ADODB version as a string. */ - $ADODB_vers = 'v5.20.4 30-Mar-2016'; + $ADODB_vers = 'v5.23.0-dev Unreleased'; /** * Determines whether recordset->RecordCount() is used. @@ -259,12 +240,25 @@ class ADOFieldObject { } + /** + * Parse date string to prevent injection attack. + * + * @param string $s + * + * @return string + */ function _adodb_safedate($s) { return str_replace(array("'", '\\'), '', $s); } - // parse date string to prevent injection attack - // date string will have one quote at beginning e.g. '3434343' + /** + * Parse date string to prevent injection attack. + * Date string will have one quote at beginning e.g. '3434343' + * + * @param string $s + * + * @return string + */ function _adodb_safedateq($s) { $len = strlen($s); if ($s[0] !== "'") { @@ -288,20 +282,29 @@ function _adodb_safedateq($s) { return strlen($s2) == 0 ? 'null' : $s2; } - - // for transaction handling - + /** + * For transaction handling. + * + * @param $dbms + * @param $fn + * @param $errno + * @param $errmsg + * @param $p1 + * @param $p2 + * @param $thisConnection + */ function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) { //print "Errorno ($fn errno=$errno m=$errmsg) "; $thisConnection->_transOK = false; if ($thisConnection->_oldRaiseFn) { - $fn = $thisConnection->_oldRaiseFn; - $fn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection); + $errfn = $thisConnection->_oldRaiseFn; + $errfn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection); } } - //------------------ - // class for caching + /** + * Class ADODB_Cache_File + */ class ADODB_Cache_File { var $createdir = true; // requires creation of temp dirs @@ -313,18 +316,42 @@ function __construct() { } } - // write serialised recordset to cache item/file - function writecache($filename, $contents, $debug, $secs2cache) { + /** + * Write serialised RecordSet to cache item/file. + * + * @param $filename + * @param $contents + * @param $debug + * @param $secs2cache + * + * @return bool|int + */ + function writecache($filename, $contents, $debug, $secs2cache) { return adodb_write_file($filename, $contents,$debug); } - // load serialised recordset and unserialise it + /** + * load serialised RecordSet and unserialise it + * + * @param $filename + * @param $err + * @param $secs2cache + * @param $rsClass + * + * @return ADORecordSet + */ function &readcache($filename, &$err, $secs2cache, $rsClass) { $rs = csv2rs($filename,$err,$secs2cache,$rsClass); return $rs; } - // flush all items in cache + /** + * Flush all items in cache. + * + * @param bool $debug + * + * @return bool|void + */ function flushall($debug=false) { global $ADODB_CACHE_DIR; @@ -339,7 +366,12 @@ function flushall($debug=false) { return $rez; } - // flush one file in cache + /** + * Flush one file in cache. + * + * @param string $f + * @param bool $debug + */ function flushcache($f, $debug=false) { if (!@unlink($f)) { if ($debug) { @@ -348,6 +380,11 @@ function flushcache($f, $debug=false) { } } + /** + * @param string $hash + * + * @return string + */ function getdirname($hash) { global $ADODB_CACHE_DIR; if (!isset($this->notSafeMode)) { @@ -356,7 +393,14 @@ function getdirname($hash) { return ($this->notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($hash,0,2) : $ADODB_CACHE_DIR; } - // create temp directories + /** + * Create temp directories. + * + * @param string $hash + * @param bool $debug + * + * @return string + */ function createdir($hash, $debug) { global $ADODB_CACHE_PERMS; @@ -412,11 +456,17 @@ abstract class ADOConnection { // // PUBLIC VARS // - var $Queries = 0; var $dataProvider = 'native'; var $databaseType = ''; /// RDBMS currently in use, eg. odbc, mysql, mssql var $database = ''; /// Name of database to be used. + + /** + * @var string If the driver is PDO, then the dsnType is e.g. sqlsrv, otherwise empty + */ + public $dsnType = ''; + var $host = ''; /// The hostname of the database server + var $port = ''; /// The port of the database server var $user = ''; /// The username which is used to connect to the database server. var $password = ''; /// Password for the username. For security, we no longer store it. var $debug = false; /// if set to true will output sql statements @@ -432,6 +482,8 @@ abstract class ADOConnection { var $false = '0'; /// string that represents FALSE for a database var $replaceQuote = "\\'"; /// string to use to replace quotes var $nameQuote = '"'; /// string to use to quote identifiers and names + var $leftBracket = '['; /// left square bracked for t-sql styled column names + var $rightBracket = ']'; /// right square bracked for t-sql styled column names var $charSet=false; /// character set to use - only for interbase, postgres and oci8 var $metaDatabasesSQL = ''; var $metaTablesSQL = ''; @@ -450,15 +502,45 @@ abstract class ADOConnection { var $hasTransactions = true; /// has transactions //-- var $genID = 0; /// sequence id used by GenID(); - var $raiseErrorFn = false; /// error function to call + + /** @var bool|callable Error function to call */ + var $raiseErrorFn = false; + var $isoDates = false; /// accepts dates in ISO format var $cacheSecs = 3600; /// cache for 1 hour - // memcache - var $memCache = false; /// should we use memCache instead of caching in files - var $memCacheHost; /// memCache host - var $memCachePort = 11211; /// memCache port - var $memCacheCompress = false; /// Use 'true' to store the item compressed (uses zlib) + /***************************************** + * memcached server options + ******************************************/ + /* + * Should we use memCache instead of caching in files + */ + public $memCache = false; + /* + * A string, array of hosts or array of memcache connection + * options (see adodb.org) + */ + public $memCacheHost; + + /* + * Default port, may be ignored if connection object array + * is set + */ + public $memCachePort = 11211; + + /* + * Use 'true' to store the item compressed + * uses zlib, Direct option for memcache, else + * For memcached, use the memcacheOptions feature + */ + public $memCacheCompress = false; + + /* + * If using mecached, an array of options + * @link https://www.php.net/manual/en/memcached.constants.php + */ + public $memCacheOptions = array(); + var $sysDate = false; /// name of function that returns the current date var $sysTimeStamp = false; /// name of function that returns the current timestamp @@ -476,8 +558,12 @@ abstract class ADOConnection { var $autoRollback = false; // autoRollback on PConnect(). var $poorAffectedRows = false; // affectedRows not working or unreliable + /** @var bool|callable Execute function to call */ var $fnExecute = false; + + /** @var bool|callable Cache execution function to call */ var $fnCacheExecute = false; + var $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char var $rsPrefix = "ADORecordSet_"; @@ -494,7 +580,8 @@ abstract class ADOConnection { // var $_oldRaiseFn = false; var $_transOK = null; - var $_connectionID = false; /// The returned link identifier whenever a successful database connection is made. + /** @var resource Identifier for the native database connection */ + var $_connectionID = false; var $_errorMsg = false; /// A variable which was used to keep the returned last error message. The value will /// then returned by the errorMsg() function var $_errorCode = false; /// Last error code, not guaranteed to be used - only by oci8 @@ -507,32 +594,77 @@ abstract class ADOConnection { var $_logsql = false; var $_transmode = ''; // transaction mode - /* - * Additional parameters that may be passed to drivers in the connect string - * Driver must be coded to accept the parameters + /** + * Additional parameters that may be passed to drivers in the connect string. + * + * Data is stored as an array of arrays and not a simple associative array, + * because some drivers (e.g. mysql) allow multiple parameters with the same + * key to be set. + * @link https://github.com/ADOdb/ADOdb/issues/187 + * + * @see setConnectionParameter() + * + * @var array $connectionParameters Set of ParameterName => Value pairs */ protected $connectionParameters = array(); + /* + * A simple associative array of user-defined custom actual/meta types + */ + public $customActualTypes = array(); + + /* + * An array of user-defined custom meta/actual types. + * $this->customMetaTypes[$meta] = array( + * 'actual'=>'', + * 'dictionary'=>'', + * 'handler'=>'', + * 'callback'=>'' + * ); + */ + public $customMetaTypes = array(); + + /** - * Adds a parameter to the connection string. - * - * These parameters are added to the connection string when connecting, - * if the driver is coded to use it. - * - * @param string $parameter The name of the parameter to set - * @param string $value The value of the parameter - * - * @return null - * - * @example, for mssqlnative driver ('CharacterSet','UTF-8') - */ - final public function setConnectionParameter($parameter,$value) + * Default Constructor. + * We define it even though it does not actually do anything. This avoids + * getting a PHP Fatal error: Cannot call constructor if a subclass tries + * to call its parent constructor. + */ + public function __construct() { + } - $this->connectionParameters[$parameter] = $value; - + /** + * Adds a parameter to the connection string. + * + * Parameters must be added before the connection is established; + * they are then passed on to the connect statement, which will. + * process them if the driver supports this feature. + * + * Example usage: + * - mssqlnative: setConnectionParameter('CharacterSet','UTF-8'); + * - mysqli: setConnectionParameter(MYSQLI_SET_CHARSET_NAME,'utf8mb4'); + * + * If used in a portable environment, parameters set in this manner should + * be predicated on the database provider, as unexpected results may occur + * if applied to the wrong database. + * + * @param string $parameter The name of the parameter to set + * @param string $value The value of the parameter + * + * @return bool True if success, false otherwise (e.g. parameter is not valid) + */ + public function setConnectionParameter($parameter, $value) { + $this->connectionParameters[] = array($parameter=>$value); + return true; } + /** + * ADOdb version. + * + * @return string + */ static function Version() { global $ADODB_vers; @@ -554,19 +686,74 @@ static function Version() { } /** - Get server version info... + * Set a custom meta type with a corresponding actual + * + * @param string $metaType The Custom ADOdb metatype + * @param string $dictionaryType The database dictionary type + * @param string $actualType The database actual type + * @param bool $handleAsType handle like an existing Metatype + * @param mixed $callBack A pre-processing function + * + * @return bool success if the actual exists + */ + final public function setCustomMetaType( + $metaType, + $dictionaryType, + $actualType, + $handleAsType=false, + $callback=false){ + + $this->customMetaTypes[strtoupper($metaType)] = array( + 'actual'=>$actualType, + 'dictionary'=>strtoupper($dictionaryType), + 'handler'=>$handleAsType, + 'callback'=>$callback + ); + + /* + * Create a reverse lookup for the actualType + */ + $this->customActualTypes[$actualType] = $metaType; + + return true; + } + + /** + * Get a list of custom meta types. + * + * @return string[] + */ + final public function getCustomMetaTypes() + { + return $this->customMetaTypes; + } - @returns An array with 2 elements: $arr['string'] is the description string, - and $arr[version] is the version (also a string). - */ + + /** + * Get server version info. + * + * @return string[] Array with 2 string elements: version and description + */ function ServerInfo() { return array('description' => '', 'version' => ''); } + /** + * Return true if connected to the database. + * + * @return bool + */ function IsConnected() { return !empty($this->_connectionID); } + /** + * Find version string. + * + * @param string $str + * + * @return string + */ function _findvers($str) { if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) { return $arr[1]; @@ -576,9 +763,13 @@ function _findvers($str) { } /** - * All error messages go through this bottleneck function. - * You can define your own handler by defining the function name in ADODB_OUTP. - */ + * All error messages go through this bottleneck function. + * + * You can define your own handler by defining the function name in ADODB_OUTP. + * + * @param string $msg Message to print + * @param bool $newline True to add a newline after printing $msg + */ static function outp($msg,$newline=true) { global $ADODB_FLUSH,$ADODB_OUTP; @@ -587,8 +778,7 @@ static function outp($msg,$newline=true) { $fn($msg,$newline); return; } else if (isset($ADODB_OUTP)) { - $fn = $ADODB_OUTP; - $fn($msg,$newline); + call_user_func($ADODB_OUTP,$msg,$newline); return; } @@ -609,6 +799,10 @@ static function outp($msg,$newline=true) { } + /** + * Return the database server's current date and time. + * @return int|false + */ function Time() { $rs = $this->_Execute("select $this->sysTimeStamp"); if ($rs && !$rs->EOF) { @@ -619,23 +813,43 @@ function Time() { } /** - * Connect to database + * Parses the hostname to extract the port. + * Overwrites $this->host and $this->port, only if a port is specified. + * The Hostname can be fully or partially qualified, + * ie: "db.mydomain.com:5432" or "ldaps://ldap.mydomain.com:636" + * Any specified scheme such as ldap:// or ldaps:// is maintained. + */ + protected function parseHostNameAndPort() { + $parsed_url = parse_url($this->host); + if (is_array($parsed_url) && isset($parsed_url['host']) && isset($parsed_url['port'])) { + if ( isset($parsed_url['scheme']) ) { + // If scheme is specified (ie: ldap:// or ldaps://, make sure we retain that. + $this->host = $parsed_url['scheme'] . "://" . $parsed_url['host']; + } else { + $this->host = $parsed_url['host']; + } + $this->port = $parsed_url['port']; + } + } + + /** + * Connect to database. * - * @param [argHostname] Host to connect to - * @param [argUsername] Userid to login - * @param [argPassword] Associated password - * @param [argDatabaseName] database - * @param [forceNew] force new connection + * @param string $argHostname Host to connect to + * @param string $argUsername Userid to login + * @param string $argPassword Associated password + * @param string $argDatabaseName Database name + * @param bool $forceNew Force new connection * - * @return true or false + * @return bool */ function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) { if ($argHostname != "") { $this->host = $argHostname; } - if ( strpos($this->host, ':') > 0 && isset($this->port) ) { - list($this->host, $this->port) = explode(":", $this->host, 2); - } + // Overwrites $this->host and $this->port if a port is specified. + $this->parseHostNameAndPort(); + if ($argUsername != "") { $this->user = $argUsername; } @@ -659,53 +873,64 @@ function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDa } if (isset($rez)) { $err = $this->ErrorMsg(); + $errno = $this->ErrorNo(); if (empty($err)) { $err = "Connection error to server '$argHostname' with user '$argUsername'"; } - $ret = false; } else { $err = "Missing extension for ".$this->dataProvider; - $ret = 0; + $errno = 0; } if ($fn = $this->raiseErrorFn) { - $fn($this->databaseType,'CONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this); + $fn($this->databaseType, 'CONNECT', $errno, $err, $this->host, $this->database, $this); } $this->_connectionID = false; if ($this->debug) { ADOConnection::outp( $this->host.': '.$err); } - return $ret; + return false; } + /** + * Always force a new connection to database. + * + * @param string $argHostname Host to connect to + * @param string $argUsername Userid to login + * @param string $argPassword Associated password + * @param string $argDatabaseName Database name + * + * @return bool + */ function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) { return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName); } - /** - * Always force a new connection to database - currently only works with oracle + * Always force a new connection to database. + * + * Currently this only works with Oracle. * - * @param [argHostname] Host to connect to - * @param [argUsername] Userid to login - * @param [argPassword] Associated password - * @param [argDatabaseName] database + * @param string $argHostname Host to connect to + * @param string $argUsername Userid to login + * @param string $argPassword Associated password + * @param string $argDatabaseName Database name * - * @return true or false + * @return bool */ function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") { return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true); } /** - * Establish persistent connect to database + * Establish persistent connection to database. * - * @param [argHostname] Host to connect to - * @param [argUsername] Userid to login - * @param [argPassword] Associated password - * @param [argDatabaseName] database + * @param string $argHostname Host to connect to + * @param string $argUsername Userid to login + * @param string $argPassword Associated password + * @param string $argDatabaseName Database name * - * @return return true or false + * @return bool */ function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") { @@ -716,9 +941,9 @@ function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argD if ($argHostname != "") { $this->host = $argHostname; } - if ( strpos($this->host, ':') > 0 && isset($this->port) ) { - list($this->host, $this->port) = explode(":", $this->host, 2); - } + // Overwrites $this->host and $this->port if a port is specified. + $this->parseHostNameAndPort(); + if ($argUsername != "") { $this->user = $argUsername; } @@ -742,7 +967,7 @@ function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argD $ret = false; } else { $err = "Missing extension for ".$this->dataProvider; - $ret = 0; + $ret = false; } if ($fn = $this->raiseErrorFn) { $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this); @@ -755,7 +980,13 @@ function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argD return $ret; } - function outp_throw($msg,$src='WARN',$sql='') { + /** + * Throw an exception if the handler is defined or prints the message if not. + * @param string $msg Message + * @param string $src the name of the calling function (in uppercase) + * @param string $sql Optional offending SQL statement + */ + function outp_throw($msg, $src='WARN', $sql='') { if (defined('ADODB_ERROR_HANDLER') && ADODB_ERROR_HANDLER == 'adodb_throw') { adodb_throw($this->databaseType,$src,-9999,$msg,$sql,false,$this); return; @@ -763,7 +994,11 @@ function outp_throw($msg,$src='WARN',$sql='') { ADOConnection::outp($msg); } - // create cache class. Code is backward compat with old memcache implementation + /** + * Create cache class. + * + * Code is backwards-compatible with old memcache implementation. + */ function _CreateCache() { global $ADODB_CACHE, $ADODB_CACHE_CLASS; @@ -779,8 +1014,16 @@ function _CreateCache() { } } - // Format date column in sql string given an input format that understands Y M D - function SQLDate($fmt, $col=false) { + /** + * Format date column in sql string. + * + * See https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:sqldate + * for documentation on supported formats. + * + * @param string $fmt Format string + * @param string $col Date column; use system date if not specified. + */ + function SQLDate($fmt, $col = '') { if (!$col) { $col = $this->sysDate; } @@ -788,66 +1031,79 @@ function SQLDate($fmt, $col=false) { } /** - * Should prepare the sql statement and return the stmt resource. - * For databases that do not support this, we return the $sql. To ensure - * compatibility with databases that do not support prepare: + * Prepare an SQL statement and return the statement resource. * - * $stmt = $db->Prepare("insert into table (id, name) values (?,?)"); - * $db->Execute($stmt,array(1,'Jill')) or die('insert failed'); - * $db->Execute($stmt,array(2,'Joe')) or die('insert failed'); + * For databases that do not support prepared statements, we return the + * provided SQL statement as-is, to ensure compatibility: * - * @param sql SQL to send to database + * $stmt = $db->prepare("insert into table (id, name) values (?,?)"); + * $db->execute($stmt, array(1,'Jill')) or die('insert failed'); + * $db->execute($stmt, array(2,'Joe')) or die('insert failed'); * - * @return return FALSE, or the prepared statement, or the original sql if - * if the database does not support prepare. + * @param string $sql SQL to send to database * + * @return mixed|false The prepared statement, or the original sql if the + * database does not support prepare. */ function Prepare($sql) { return $sql; } /** + * Releases a previously prepared statement. + * + * @param mixed $stmt Statement resource, as returned by {@see prepare()} + * + * @return bool + */ + function releaseStatement(&$stmt) { + return true; + } + + /** + * Prepare a Stored Procedure and return the statement resource. + * * Some databases, eg. mssql require a different function for preparing * stored procedures. So we cannot use Prepare(). * - * Should prepare the stored procedure and return the stmt resource. - * For databases that do not support this, we return the $sql. To ensure - * compatibility with databases that do not support prepare: - * - * @param sql SQL to send to database + * For databases that do not support this, we return the $sql. * - * @return return FALSE, or the prepared statement, or the original sql if - * if the database does not support prepare. + * @param string $sql SQL to send to database + * @param bool $param * + * @return mixed|false The prepared statement, or the original sql if the + * database does not support prepare. */ function PrepareSP($sql,$param=true) { return $this->Prepare($sql,$param); } /** - * PEAR DB Compat - */ + * PEAR DB Compat - alias for qStr. + * @param $s + * @return string + */ function Quote($s) { - return $this->qstr($s,false); + return $this->qstr($s); } /** - * Requested by "Karsten Dambekalns" + * Quotes a string so that all strings are escaped. + * Wrapper for qstr with magic_quotes = false. + * + * @param string &$s */ - function QMagic($s) { - return $this->qstr($s,get_magic_quotes_gpc()); - } - function q(&$s) { //if (!empty($this->qNull && $s == 'null') { // return $s; //} - $s = $this->qstr($s,false); + $s = $this->qstr($s); } /** - * PEAR DB Compat - do not use internally. - */ + * PEAR DB Compat - do not use internally. + * @return int + */ function ErrorNative() { return $this->ErrorNo(); } @@ -855,39 +1111,54 @@ function ErrorNative() { /** * PEAR DB Compat - do not use internally. + * @param string $seq_name + * @return int */ function nextId($seq_name) { return $this->GenID($seq_name); } /** - * Lock a row, will escalate and lock the table if row locking not supported - * will normally free the lock at the end of the transaction + * Lock a row. + * Will escalate and lock the table if row locking is not supported. + * Will normally free the lock at the end of the transaction. * - * @param $table name of table to lock - * @param $where where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock + * @param string $table name of table to lock + * @param string $where where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock + * @param string $col + * + * @return bool */ function RowLock($table,$where,$col='1 as adodbignore') { return false; } + /** + * @param string $table + * @return true + */ function CommitLock($table) { return $this->CommitTrans(); } + /** + * @param string $table + * @return true + */ function RollbackLock($table) { return $this->RollbackTrans(); } /** - * PEAR DB Compat - do not use internally. - * - * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical - * for easy porting :-) - * - * @param mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM - * @returns The previous fetch mode - */ + * PEAR DB Compat - do not use internally. + * + * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical + * for easy porting :-) + * + * @param int $mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM + * + * @return int Previous fetch mode + */ function SetFetchMode($mode) { $old = $this->fetchMode; $this->fetchMode = $mode; @@ -899,10 +1170,14 @@ function SetFetchMode($mode) { return $old; } - /** - * PEAR DB Compat - do not use internally. - */ + * PEAR DB Compat - do not use internally. + * + * @param string $sql + * @param array|bool $inputarr + * + * @return ADORecordSet|bool + */ function Query($sql, $inputarr=false) { $rs = $this->Execute($sql, $inputarr); if (!$rs && defined('ADODB_PEAR')) { @@ -911,10 +1186,9 @@ function Query($sql, $inputarr=false) { return $rs; } - /** - * PEAR DB Compat - do not use internally - */ + * PEAR DB Compat - do not use internally + */ function LimitQuery($sql, $offset, $count, $params=false) { $rs = $this->SelectLimit($sql, $count, $offset, $params); if (!$rs && defined('ADODB_PEAR')) { @@ -925,22 +1199,26 @@ function LimitQuery($sql, $offset, $count, $params=false) { /** - * PEAR DB Compat - do not use internally - */ + * PEAR DB Compat - do not use internally + */ function Disconnect() { return $this->Close(); } /** - * Returns a placeholder for query parameters + * Returns a placeholder for query parameters. + * * e.g. $DB->Param('a') will return * - '?' for most databases * - ':a' for Oracle * - '$1', '$2', etc. for PostgreSQL - * @param string $name parameter's name, false to force a reset of the - * number to 1 (for databases that require positioned - * params such as PostgreSQL; note that ADOdb will - * automatically reset this when executing a query ) + * + * @param mixed $name parameter's name. + * For databases that require positioned params (e.g. PostgreSQL), + * a "falsy" value can be used to force resetting the placeholder + * count; using boolean 'false' will reset it without actually + * returning a placeholder. ADOdb will also automatically reset + * the count when executing a query. * @param string $type (unused) * @return string query parameter placeholder */ @@ -948,36 +1226,54 @@ function Param($name,$type='C') { return '?'; } - /* - InParameter and OutParameter are self-documenting versions of Parameter(). - */ - function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) { + /** + * Self-documenting version of Parameter(). + * + * @param $stmt + * @param &$var + * @param $name + * @param int $maxLen + * @param bool $type + * + * @return bool + */ + function InParameter(&$stmt, &$var, $name, $maxLen=4000, $type=false) { return $this->Parameter($stmt,$var,$name,false,$maxLen,$type); } - /* - */ + /** + * Self-documenting version of Parameter(). + * + * @param $stmt + * @param $var + * @param $name + * @param int $maxLen + * @param bool $type + * + * @return bool + */ function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) { return $this->Parameter($stmt,$var,$name,true,$maxLen,$type); } - - /* - Usage in oracle - $stmt = $db->Prepare('select * from table where id =:myid and group=:group'); - $db->Parameter($stmt,$id,'myid'); - $db->Parameter($stmt,$group,'group',64); - $db->Execute(); - - @param $stmt Statement returned by Prepare() or PrepareSP(). - @param $var PHP variable to bind to - @param $name Name of stored procedure variable name to bind to. - @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8. - @param [$maxLen] Holds an maximum length of the variable. - @param [$type] The data type of $var. Legal values depend on driver. - - */ + /** + * + * Usage in oracle + * $stmt = $db->Prepare('select * from table where id =:myid and group=:group'); + * $db->Parameter($stmt,$id,'myid'); + * $db->Parameter($stmt,$group,'group',64); + * $db->Execute(); + * + * @param mixed &$stmt Statement returned by Prepare() or PrepareSP(). + * @param mixed &$var PHP variable to bind to + * @param string $name Name of stored procedure variable name to bind to. + * @param int|bool $isOutput Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8. + * @param int $maxLen Holds an maximum length of the variable. + * @param mixed $type The data type of $var. Legal values depend on driver. + * + * @return bool + */ function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) { return false; } @@ -1024,13 +1320,16 @@ function StartTrans($errfn = 'ADODB_TransMonitor') { /** - Used together with StartTrans() to end a transaction. Monitors connection - for sql errors, and will commit or rollback as appropriate. - - @autoComplete if true, monitor sql errors and commit and rollback as appropriate, - and if set to false force rollback even if no SQL error detected. - @returns true on commit, false on rollback. - */ + * Complete a transation. + * + * Used together with StartTrans() to end a transaction. Monitors connection + * for sql errors, and will commit or rollback as appropriate. + * + * @param bool autoComplete if true, monitor sql errors and commit and + * rollback as appropriate, and if set to false + * force rollback even if no SQL error detected. + * @returns true on commit, false on rollback. + */ function CompleteTrans($autoComplete = true) { if ($this->transOff > 1) { $this->transOff -= 1; @@ -1054,16 +1353,16 @@ function CompleteTrans($autoComplete = true) { $this->_transOK = false; $this->RollbackTrans(); if ($this->debug) { - ADOCOnnection::outp("Smart Rollback occurred"); + ADOConnection::outp("Smart Rollback occurred"); } } return $this->_transOK; } - /* - At the end of a StartTrans/CompleteTrans block, perform a rollback. - */ + /** + * At the end of a StartTrans/CompleteTrans block, perform a rollback. + */ function FailTrans() { if ($this->debug) if ($this->transOff == 0) { @@ -1076,8 +1375,8 @@ function FailTrans() { } /** - Check if transaction has failed, only for Smart Transactions. - */ + * Check if transaction has failed, only for Smart Transactions. + */ function HasFailedTrans() { if ($this->transOff > 0) { return $this->_transOK == false; @@ -1088,12 +1387,14 @@ function HasFailedTrans() { /** * Execute SQL * - * @param sql SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text) - * @param [inputarr] holds the input data to bind to. Null elements will be set to null. - * @return RecordSet or false + * @param string $sql SQL statement to execute, or possibly an array + * holding prepared statement ($sql[0] will hold sql text) + * @param array|bool $inputarr holds the input data to bind to. + * Null elements will be set to null. + * + * @return ADORecordSet|bool */ - function Execute($sql,$inputarr=false) { - $this->Queries++; + public function Execute($sql, $inputarr = false) { if ($this->fnExecute) { $fn = $this->fnExecute; $ret = $fn($this,$sql,$inputarr); @@ -1144,14 +1445,13 @@ function Execute($sql,$inputarr=false) { ); return false; } - + // clean memory unset($element0); foreach($inputarr as $arr) { $sql = ''; $i = 0; - //Use each() instead of foreach to reduce memory usage -mikefedyk - while(list(, $v) = each($arr)) { + foreach ($arr as $v) { $sql .= $sqlarr[$i]; // from Ron Baldwin // Only quote string types @@ -1225,13 +1525,14 @@ function _Execute($sql,$inputarr=false) { if( is_string($sql) ) { // Strips keyword used to help generate SELECT COUNT(*) queries // from SQL if it exists. - $sql = ADODB_str_replace( '_ADODB_COUNT', '', $sql ); + // TODO: obsoleted by #715 - kept for backwards-compatibility + $sql = str_replace( '_ADODB_COUNT', '', $sql ); } if ($this->debug) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } $this->_queryID = _adodb_debug_execute($this, $sql,$inputarr); } else { @@ -1262,8 +1563,17 @@ function _Execute($sql,$inputarr=false) { return $rs; } + if ($this->dataProvider == 'pdo' && $this->databaseType != 'pdo') { + // PDO uses a slightly different naming convention for the + // recordset class if the database type is changed, so we must + // treat it specifically. The mysql driver leaves the + // databaseType as pdo + $rsclass = $this->rsPrefix . 'pdo_' . $this->databaseType; + } else { + $rsclass = $this->rsPrefix . $this->databaseType; + } + // return real recordset from select statement - $rsclass = $this->rsPrefix.$this->databaseType; $rs = new $rsclass($this->_queryID,$this->fetchMode); $rs->connection = $this; // Pablo suggestion $rs->Init(); @@ -1300,12 +1610,14 @@ function DropSequence($seqname='adodbseq') { } /** - * Generates a sequence id and stores it in $this->genID; + * Generates a sequence id and stores it in $this->genID. + * * GenID is only available if $this->hasGenID = true; * - * @param seqname name of sequence to use - * @param startID if sequence does not exist, start at this ID - * @return 0 if not supported, otherwise a sequence id + * @param string $seqname Name of sequence to use + * @param int $startID If sequence does not exist, start at this ID + * + * @return int Sequence id, 0 if not supported */ function GenID($seqname='adodbseq',$startID=1) { if (!$this->hasGenID) { @@ -1340,16 +1652,22 @@ function GenID($seqname='adodbseq',$startID=1) { } /** - * @param $table string name of the table, not needed by all databases (eg. mysql), default '' - * @param $column string name of the column, not needed by all databases (eg. mysql), default '' - * @return the last inserted ID. Not all databases support this. + * Returns the last inserted ID. + * + * Not all databases support this feature. Some do not require to specify + * table or column name (e.g. MySQL). + * + * @param string $table Table name, default '' + * @param string $column Column name, default '' + * + * @return int The last inserted ID. */ function Insert_ID($table='',$column='') { if ($this->_logsql && $this->lastInsID) { return $this->lastInsID; } if ($this->hasInsertID) { - return $this->_insertid($table,$column); + return $this->_insertID($table,$column); } if ($this->debug) { ADOConnection::outp( '

Insert_ID error

'); @@ -1358,12 +1676,37 @@ function Insert_ID($table='',$column='') { return false; } + /** + * Enable or disable the Last Insert Id functionality. + * + * If the Driver supports it, this function allows setting {@see $hasInsertID}. + * + * @param bool $enable False to disable + */ + public function enableLastInsertID($enable = true) {} + + /** + * Return the id of the last row that has been inserted in a table. + * + * @param string $table + * @param string $column + * + * @return int|false + */ + protected function _insertID($table = '', $column = '') + { + return false; + } /** * Portable Insert ID. Pablo Roca * - * @return the last inserted ID. All databases support this. But aware possible - * problems in multiuser environments. Heavy test this before deploying. + * @param string $table + * @param string $id + + * @return mixed The last inserted ID. All databases support this, but be + * aware of possible problems in multiuser environments. + * Heavily test this before deploying. */ function PO_Insert_ID($table="", $id="") { if ($this->hasInsertID){ @@ -1374,8 +1717,8 @@ function PO_Insert_ID($table="", $id="") { } /** - * @return # rows affected by UPDATE/DELETE - */ + * @return int|false Number of rows affected by UPDATE/DELETE + */ function Affected_Rows() { if ($this->hasAffectedRows) { if ($this->fnExecute === 'adodb_log_sql') { @@ -1395,7 +1738,7 @@ function Affected_Rows() { /** - * @return the last error message + * @return string the last error message */ function ErrorMsg() { if ($this->_errorMsg) { @@ -1407,7 +1750,7 @@ function ErrorMsg() { /** - * @return the last error number. Normally 0 means no error. + * @return int the last error number. Normally 0 means no error. */ function ErrorNo() { return ($this->_errorMsg) ? -1 : 0; @@ -1450,21 +1793,36 @@ function MetaPrimaryKeys($table, $owner=false) { } /** - * @returns assoc array where keys are tables, and values are foreign keys + * Returns a list of Foreign Keys associated with a specific table. + * + * If there are no foreign keys then the function returns false. + * + * @param string $table The name of the table to get the foreign keys for. + * @param string $owner Table owner/schema. + * @param bool $upper If true, only matches the table with the uppercase name. + * @param bool $associative Returns the result in associative mode; + * if ADODB_FETCH_MODE is already associative, then + * this parameter is discarded. + * + * @return string[]|false An array where keys are tables, and values are foreign keys; + * false if no foreign keys could be found. */ - function MetaForeignKeys($table, $owner=false, $upper=false) { + function metaForeignKeys($table, $owner = '', $upper = false, $associative = false) { return false; } + /** * Choose a database to connect to. Many databases do not support this. * - * @param dbName is the name of the database to select - * @return true or false + * @param string $dbName the name of the database to select + * @return bool */ function SelectDB($dbName) {return false;} /** + * Select a limited number of rows. + * * Will select, getting rows from $offset (1-based), for $nrows. * This simulates the MySQL "select * from table limit $offset,$nrows" , and * the PostgreSQL "select * from table limit $nrows offset $offset". Note that @@ -1476,14 +1834,18 @@ function SelectDB($dbName) {return false;} * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set) * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set * - * @param sql - * @param [offset] is the row to start calculations from (1-based) - * @param [nrows] is the number of rows to get - * @param [inputarr] array of bind variables - * @param [secs2cache] is a private parameter only used by jlim - * @return the recordset ($rs->databaseType == 'array') + * @param string $sql + * @param int $offset Row to start calculations from (1-based) + * @param int $nrows Number of rows to get + * @param array|bool $inputarr Array of bind variables + * @param int $secs2cache Private parameter only used by jlim + * + * @return ADORecordSet The recordset ($rs->databaseType == 'array') */ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) { + $nrows = (int)$nrows; + $offset = (int)$offset; + if ($this->hasTop && $nrows > 0) { // suggested by Reinhard Balling. Access requires top after distinct // Informix requires first before distinct - F Riosa @@ -1495,32 +1857,47 @@ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) { } if ($offset <= 0) { - // access includes ties in result - if ($isaccess) { - $sql = preg_replace( - '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.((integer)$nrows).' ',$sql); + // access includes ties in result + if ($isaccess) { + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i', + '\\1 '.$this->hasTop.' '.$nrows.' ', + $sql + ); - if ($secs2cache != 0) { - $ret = $this->CacheExecute($secs2cache, $sql,$inputarr); - } else { - $ret = $this->Execute($sql,$inputarr); - } - return $ret; // PHP5 fix - } else if ($ismssql){ - $sql = preg_replace( - '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.((integer)$nrows).' ',$sql); + if ($secs2cache != 0) { + $ret = $this->CacheExecute($secs2cache, $sql,$inputarr); } else { - $sql = preg_replace( - '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.((integer)$nrows).' ',$sql); + $ret = $this->Execute($sql,$inputarr); } + return $ret; // PHP5 fix + } else if ($ismssql){ + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i', + '\\1 '.$this->hasTop.' '.$nrows.' ', + $sql + ); + } else { + $sql = preg_replace( + '/(^\s*select\s)/i', + '\\1 '.$this->hasTop.' '.$nrows.' ', + $sql + ); + } } else { $nn = $nrows + $offset; if ($isaccess || $ismssql) { $sql = preg_replace( - '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql); + '/(^\s*select\s+(distinctrow|distinct)?)/i', + '\\1 '.$this->hasTop.' '.$nn.' ', + $sql + ); } else { $sql = preg_replace( - '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql); + '/(^\s*select\s)/i', + '\\1 '.$this->hasTop.' '.$nn.' ', + $sql + ); } } } @@ -1529,17 +1906,19 @@ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) { // 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS. global $ADODB_COUNTRECS; - $savec = $ADODB_COUNTRECS; - $ADODB_COUNTRECS = false; - + try { + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; - if ($secs2cache != 0) { - $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); - } else { - $rs = $this->Execute($sql,$inputarr); + if ($secs2cache != 0) { + $rs = $this->CacheExecute($secs2cache, $sql, $inputarr); + } else { + $rs = $this->Execute($sql, $inputarr); + } + } finally { + $ADODB_COUNTRECS = $savec; } - $ADODB_COUNTRECS = $savec; if ($rs && !$rs->EOF) { $rs = $this->_rs2rs($rs,$nrows,$offset); } @@ -1548,10 +1927,12 @@ function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) { } /** - * Create serializable recordset. Breaks rs link to connection. - * - * @param rs the recordset to serialize - */ + * Create serializable recordset. Breaks rs link to connection. + * + * @param ADORecordSet $rs the recordset to serialize + * + * @return ADORecordSet_array|bool the new recordset + */ function SerializableRS(&$rs) { $rs2 = $this->_rs2rs($rs); $ignore = false; @@ -1561,18 +1942,21 @@ function SerializableRS(&$rs) { } /** - * Convert database recordset to an array recordset - * input recordset's cursor should be at beginning, and - * old $rs will be closed. - * - * @param rs the recordset to copy - * @param [nrows] number of rows to retrieve (optional) - * @param [offset] offset by number of rows (optional) - * @return the new recordset - */ + * Convert a database recordset to an array recordset. + * + * Input recordset's cursor should be at beginning, and old $rs will be closed. + * + * @param ADORecordSet $rs the recordset to copy + * @param int $nrows number of rows to retrieve (optional) + * @param int $offset offset by number of rows (optional) + * @param bool $close + * + * @return ADORecordSet_array|ADORecordSet|bool the new recordset + */ function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) { if (! $rs) { - return false; + $ret = false; + return $ret; } $dbtype = $rs->databaseType; if (!$dbtype) { @@ -1597,7 +1981,7 @@ function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) { $arrayClass = $this->arrayClass; - $rs2 = new $arrayClass(); + $rs2 = new $arrayClass($fakeQueryId=1); $rs2->connection = $this; $rs2->sql = $rs->sql; $rs2->dataProvider = $this->dataProvider; @@ -1606,24 +1990,59 @@ function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) { return $rs2; } - /* - * Return all rows. Compat with PEAR DB - */ + /** + * Return all rows. + * + * Compat with PEAR DB. + * + * @param string $sql SQL statement + * @param array|bool $inputarr Input bind array + * + * @return array|false + */ function GetAll($sql, $inputarr=false) { - $arr = $this->GetArray($sql,$inputarr); - return $arr; + return $this->GetArray($sql,$inputarr); } - function GetAssoc($sql, $inputarr=false,$force_array = false, $first2cols = false) { + /** + * Execute statement and return rows in an array. + * + * The function executes a statement and returns all of the returned rows in + * an array, or false if the statement execution fails or if only 1 column + * is requested in the SQL statement. + * If no records match the provided SQL statement, an empty array is returned. + * + * @param string $sql SQL statement + * @param array|bool $inputarr input bind array + * @param bool $force_array + * @param bool $first2cols + * + * @return array|bool + */ + public function GetAssoc($sql, $inputarr = false, $force_array = false, $first2cols = false) { $rs = $this->Execute($sql, $inputarr); + if (!$rs) { + /* + * Execution failure + */ return false; } - $arr = $rs->GetAssoc($force_array,$first2cols); - return $arr; + return $rs->GetAssoc($force_array, $first2cols); } - function CacheGetAssoc($secs2cache, $sql=false, $inputarr=false,$force_array = false, $first2cols = false) { + /** + * Search for the results of an executed query in the cache. + * + * @param int $secs2cache + * @param string|bool $sql SQL statement + * @param array|bool $inputarr input bind array + * @param bool $force_array + * @param bool $first2cols + * + * @return false|array + */ + public function CacheGetAssoc($secs2cache, $sql = false, $inputarr = false,$force_array = false, $first2cols = false) { if (!is_numeric($secs2cache)) { $first2cols = $force_array; $force_array = $inputarr; @@ -1632,25 +2051,29 @@ function CacheGetAssoc($secs2cache, $sql=false, $inputarr=false,$force_array = f if (!$rs) { return false; } - $arr = $rs->GetAssoc($force_array,$first2cols); - return $arr; + return $rs->GetAssoc($force_array, $first2cols); } /** - * Return first element of first row of sql statement. Recordset is disposed - * for you. - * - * @param sql SQL statement - * @param [inputarr] input bind array - */ - function GetOne($sql,$inputarr=false) { + * Return first element of first row of sql statement. Recordset is disposed + * for you. + * + * @param string $sql SQL statement + * @param array|bool $inputarr input bind array + * @return mixed + */ + public function GetOne($sql, $inputarr=false) { global $ADODB_COUNTRECS,$ADODB_GETONE_EOF; - $crecs = $ADODB_COUNTRECS; - $ADODB_COUNTRECS = false; + try { + $crecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs = $this->Execute($sql, $inputarr); + } finally { + $ADODB_COUNTRECS = $crecs; + } $ret = false; - $rs = $this->Execute($sql,$inputarr); if ($rs) { if ($rs->EOF) { $ret = $ADODB_GETONE_EOF; @@ -1660,7 +2083,6 @@ function GetOne($sql,$inputarr=false) { $rs->Close(); } - $ADODB_COUNTRECS = $crecs; return $ret; } @@ -1697,6 +2119,17 @@ function CacheGetOne($secs2cache,$sql=false,$inputarr=false) { return $ret; } + /** + * Executes a statement and returns each row's first column in an array. + * + * @param string $sql SQL statement + * @param array|bool $inputarr input bind array + * @param bool $trim enables space trimming of the returned value. + * This is only relevant if the returned string + * is coming from a CHAR type field. + * + * @return array|bool 1D array containning the first row of the query + */ function GetCol($sql, $inputarr = false, $trim = false) { $rs = $this->Execute($sql, $inputarr); @@ -1742,23 +2175,17 @@ function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false) { return $rv; } - function Transpose(&$rs,$addfieldnames=true) { - $rs2 = $this->_rs2rs($rs); - if (!$rs2) { - return false; - } - - $rs2->_transpose($addfieldnames); - return $rs2; - } - - /* - Calculate the offset of a date for a particular database and generate - appropriate SQL. Useful for calculating future/past dates and storing - in a database. - - If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour. - */ + /** + * Calculate the offset of a date for a particular database + * and generate appropriate SQL. + * + * Useful for calculating future/past dates and storing in a database. + * + * @param double $dayFraction 1.5 means 1.5 days from now, 1.0/24 for 1 hour + * @param string|false $date Reference date, false for system time + * + * @return string + */ function OffsetDate($dayFraction,$date=false) { if (!$date) { $date = $this->sysDate; @@ -1768,21 +2195,27 @@ function OffsetDate($dayFraction,$date=false) { /** - * - * @param sql SQL statement - * @param [inputarr] input bind array - */ + * Executes a statement and returns a the entire recordset in an array. + * + * @param string $sql SQL statement + * @param array|bool $inputarr input bind array + * + * @return array|false + */ function GetArray($sql,$inputarr=false) { global $ADODB_COUNTRECS; - $savec = $ADODB_COUNTRECS; - $ADODB_COUNTRECS = false; - $rs = $this->Execute($sql,$inputarr); - $ADODB_COUNTRECS = $savec; + try { + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs = $this->Execute($sql, $inputarr); + } finally { + $ADODB_COUNTRECS = $savec; + } + if (!$rs) if (defined('ADODB_PEAR')) { - $cls = ADODB_PEAR_Error(); - return $cls; + return ADODB_PEAR_Error(); } else { return false; } @@ -1792,22 +2225,23 @@ function GetArray($sql,$inputarr=false) { } function CacheGetAll($secs2cache,$sql=false,$inputarr=false) { - $arr = $this->CacheGetArray($secs2cache,$sql,$inputarr); - return $arr; + return $this->CacheGetArray($secs2cache,$sql,$inputarr); } function CacheGetArray($secs2cache,$sql=false,$inputarr=false) { global $ADODB_COUNTRECS; - $savec = $ADODB_COUNTRECS; - $ADODB_COUNTRECS = false; - $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); - $ADODB_COUNTRECS = $savec; + try { + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs = $this->CacheExecute($secs2cache, $sql, $inputarr); + } finally { + $ADODB_COUNTRECS = $savec; + } if (!$rs) if (defined('ADODB_PEAR')) { - $cls = ADODB_PEAR_Error(); - return $cls; + return ADODB_PEAR_Error(); } else { return false; } @@ -1823,21 +2257,25 @@ function GetRandRow($sql, $arr= false) { } /** - * Return one row of sql statement. Recordset is disposed for you. - * Note that SelectLimit should not be called. - * - * @param sql SQL statement - * @param [inputarr] input bind array - */ + * Return one row of sql statement. Recordset is disposed for you. + * Note that SelectLimit should not be called. + * + * @param string $sql SQL statement + * @param array|bool $inputarr input bind array + * + * @return array|false Array containing the first row of the query + */ function GetRow($sql,$inputarr=false) { global $ADODB_COUNTRECS; - $crecs = $ADODB_COUNTRECS; - $ADODB_COUNTRECS = false; - - $rs = $this->Execute($sql,$inputarr); + try { + $crecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs = $this->Execute($sql, $inputarr); + } finally { + $ADODB_COUNTRECS = $crecs; + } - $ADODB_COUNTRECS = $crecs; if ($rs) { if (!$rs->EOF) { $arr = $rs->fields; @@ -1851,6 +2289,12 @@ function GetRow($sql,$inputarr=false) { return false; } + /** + * @param int $secs2cache + * @param string|false $sql + * @param mixed[]|bool $inputarr + * @return mixed[]|bool + */ function CacheGetRow($secs2cache,$sql=false,$inputarr=false) { $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); if ($rs) { @@ -1867,29 +2311,29 @@ function CacheGetRow($secs2cache,$sql=false,$inputarr=false) { } /** - * Insert or replace a single record. Note: this is not the same as MySQL's replace. - * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL. - * Also note that no table locking is done currently, so it is possible that the - * record be inserted twice by two programs... - * - * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname'); - * - * $table table name - * $fieldArray associative array of data (you must quote strings yourself). - * $keyCol the primary key field name or if compound key, array of field names - * autoQuote set to true to use a hueristic to quote strings. Works with nulls and numbers - * but does not work with dates nor SQL functions. - * has_autoinc the primary key is an auto-inc field, so skip in insert. - * - * Currently blob replace not supported - * - * returns 0 = fail, 1 = update, 2 = insert - */ + * Insert or replace a single record. Note: this is not the same as MySQL's replace. + * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL. + * Also note that no table locking is done currently, so it is possible that the + * record be inserted twice by two programs... + * + * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname'); + * + * $table table name + * $fieldArray associative array of data (you must quote strings yourself). + * $keyCol the primary key field name or if compound key, array of field names + * autoQuote set to true to use a heuristic to quote strings. Works with nulls and numbers + * but does not work with dates nor SQL functions. + * has_autoinc the primary key is an auto-inc field, so skip in insert. + * + * Currently blob replace not supported + * + * returns 0 = fail, 1 = update, 2 = insert + */ function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc); @@ -1907,12 +2351,13 @@ function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=fa * * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set * - * @param [secs2cache] seconds to cache data, set to 0 to force query. This is optional - * @param sql - * @param [offset] is the row to start calculations from (1-based) - * @param [nrows] is the number of rows to get - * @param [inputarr] array of bind variables - * @return the recordset ($rs->databaseType == 'array') + * @param int $secs2cache Seconds to cache data, set to 0 to force query. This is optional + * @param string $sql + * @param int $offset Row to start calculations from (1-based) + * @param int $nrows Number of rows to get + * @param array $inputarr Array of bind variables + * + * @return ADORecordSet The recordset ($rs->databaseType == 'array') */ function CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false) { if (!is_numeric($secs2cache)) { @@ -1995,11 +2440,12 @@ function _gencachename($sql,$createdir) { /** * Execute SQL, caching recordsets. * - * @param [secs2cache] seconds to cache data, set to 0 to force query. - * This is an optional parameter. - * @param sql SQL statement to execute - * @param [inputarr] holds the input data to bind to - * @return RecordSet or false + * @param int $secs2cache Seconds to cache data, set to 0 to force query. + * This is an optional parameter. + * @param string|bool $sql SQL statement to execute + * @param array|bool $inputarr Holds the input data to bind + * + * @return ADORecordSet RecordSet or false */ function CacheExecute($secs2cache,$sql=false,$inputarr=false) { global $ADODB_CACHE; @@ -2034,11 +2480,8 @@ function CacheExecute($secs2cache,$sql=false,$inputarr=false) { } if (!$rs) { - // no cached rs found + // no cached rs found if ($this->debug) { - if (get_magic_quotes_runtime() && !$this->memCache) { - ADOConnection::outp("Please disable magic_quotes_runtime - it corrupts cache files :("); - } if ($this->debug !== -1) { ADOConnection::outp( " $md5file cache failure: $err (this is a notice and not an error)"); } @@ -2109,13 +2552,32 @@ function CacheExecute($secs2cache,$sql=false,$inputarr=false) { /* - Similar to PEAR DB's autoExecute(), except that - $mode can be 'INSERT' or 'UPDATE' or DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - If $mode == 'UPDATE', then $where is compulsory as a safety measure. - $forceUpdate means that even if the data has not changed, perform update. + + $forceUpdate . + */ + /** + * Similar to PEAR DB's autoExecute(), except that $mode can be 'INSERT' + * or 'UPDATE' or DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE. + * If $mode == 'UPDATE', then $where is compulsory as a safety measure. + * + * @param $table + * @param $fields_values + * @param string $mode + * @param false $where + * @param bool $forceUpdate If true, perform update even if the data has not changed. + * @param bool $magic_quotes This param is not used since 5.21.0. + * It remains for backwards compatibility. + * + * @return bool + * + * @noinspection PhpUnusedParameterInspection */ - function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $forceUpdate = true, $magicq = false) { + function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $forceUpdate = true, $magic_quotes = false) { + if (empty($fields_values)) { + $this->outp_throw('AutoExecute: Empty fields array', 'AutoExecute'); + return false; + } if ($where === false && ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */) ) { $this->outp_throw('AutoExecute: Illegal mode=UPDATE with empty WHERE clause', 'AutoExecute'); return false; @@ -2136,11 +2598,11 @@ function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $ switch($mode) { case 'UPDATE': case DB_AUTOQUERY_UPDATE: - $sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate, $magicq); + $sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate); break; case 'INSERT': case DB_AUTOQUERY_INSERT: - $sql = $this->GetInsertSQL($rs, $fields_values, $magicq); + $sql = $this->GetInsertSQL($rs, $fields_values); break; default: $this->outp_throw("AutoExecute: Unknown mode=$mode", 'AutoExecute'); @@ -2152,16 +2614,27 @@ function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $ /** * Generates an Update Query based on an existing recordset. + * * $arrFields is an associative array of fields with the value * that should be assigned. * * Note: This function should only be used on a recordset - * that is run against a single table and sql should only - * be a simple select stmt with no groupby/orderby/limit + * that is run against a single table and sql should only + * be a simple select stmt with no groupby/orderby/limit + * @author "Jonathan Younger" + * + * @param $rs + * @param $arrFields + * @param bool $forceUpdate + * @param bool $magic_quotes This param is not used since 5.21.0. + * It remains for backwards compatibility. + * @param null $force + * + * @return false|string * - * "Jonathan Younger" + * @noinspection PhpUnusedParameterInspection */ - function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=null) { + function GetUpdateSQL(&$rs, $arrFields, $forceUpdate=false, $magic_quotes=false, $force=null) { global $ADODB_INCLUDED_LIB; // ******************************************************** @@ -2174,64 +2647,85 @@ function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=n // ******************************************************** if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } - return _adodb_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq,$force); + return _adodb_getupdatesql($this, $rs, $arrFields, $forceUpdate, $force); } /** * Generates an Insert Query based on an existing recordset. + * * $arrFields is an associative array of fields with the value * that should be assigned. * * Note: This function should only be used on a recordset * that is run against a single table. + * + * @param $rs + * @param $arrFields + * @param bool $magic_quotes This param is not used since 5.21.0. + * It remains for backwards compatibility. + * @param null $force + * + * @return false|string + * + * @noinspection PhpUnusedParameterInspection */ - function GetInsertSQL(&$rs, $arrFields,$magicq=false,$force=null) { + function GetInsertSQL(&$rs, $arrFields, $magic_quotes=false, $force=null) { global $ADODB_INCLUDED_LIB; if (!isset($force)) { global $ADODB_FORCE_TYPE; $force = $ADODB_FORCE_TYPE; } if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); - } - return _adodb_getinsertsql($this,$rs,$arrFields,$magicq,$force); - } - - - /** - * Update a blob column, given a where clause. There are more sophisticated - * blob handling functions that we could have implemented, but all require - * a very complex API. Instead we have chosen something that is extremely - * simple to understand and use. - * - * Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course. - * - * Usage to update a $blobvalue which has a primary key blob_id=1 into a - * field blobtable.blobcolumn: - * - * UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1'); - * - * Insert example: - * - * $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); - * $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); - */ - function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') { + include_once(ADODB_DIR.'/adodb-lib.inc.php'); + } + return _adodb_getinsertsql($this, $rs, $arrFields, $force); + } + + + /** + * Update a BLOB column, given a where clause. + * + * There are more sophisticated blob handling functions that we could have + * implemented, but all require a very complex API. Instead we have chosen + * something that is extremely simple to understand and use. + * + * Sample usage: + * - update a BLOB in field table.blob_col with value $blobValue, for a + * record having primary key id=1 + * $conn->updateBlob('table', 'blob_col', $blobValue, 'id=1'); + * - insert example: + * $conn->execute('INSERT INTO table (id, blob_col) VALUES (1, null)'); + * $conn->updateBlob('table', 'blob_col', $blobValue, 'id=1'); + * + * @param string $table + * @param string $column + * @param string $val Filename containing blob data + * @param mixed $where {@see updateBlob()} + * @param string $blobtype supports 'BLOB' (default) and 'CLOB' + * + * @return bool success + */ + function updateBlob($table, $column, $val, $where, $blobtype='BLOB') { return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; } /** - * Usage: - * UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1'); - * - * $blobtype supports 'BLOB' and 'CLOB' - * - * $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); - * $conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1'); - */ - function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') { + * Update a BLOB from a file. + * + * Usage example: + * $conn->updateBlobFile('table', 'blob_col', '/path/to/file', 'id=1'); + * + * @param string $table + * @param string $column + * @param string $path Filename containing blob data + * @param mixed $where {@see updateBlob()} + * @param string $blobtype supports 'BLOB' and 'CLOB' + * + * @return bool success + */ + function updateBlobFile($table, $column, $path, $where, $blobtype='BLOB') { $fd = fopen($path,'rb'); if ($fd === false) { return false; @@ -2249,11 +2743,26 @@ function BlobEncode($blob) { return $blob; } - function GetCharSet() { + /** + * Retrieve the client connection's current character set. + * + * @return string|false The character set, or false if it can't be determined. + */ + function getCharSet() { return $this->charSet; } - function SetCharSet($charset) { + /** + * Sets the client-side character set. + * + * This is only supported for some databases. + * @see https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:setcharset + * + * @param string $charset The character set to switch to. + * + * @return bool True if the character set was changed successfully, false otherwise. + */ + function setCharSet($charset) { $this->charSet = $charset; return true; } @@ -2280,12 +2789,12 @@ function LogSQL($enable=true) { } /** - * Usage: - * UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB'); - * - * $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)'); - * $conn->UpdateClob('clobtable','clobcol',$clob,'id=1'); - */ + * Usage: + * UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB'); + * + * $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)'); + * $conn->UpdateClob('clobtable','clobcol',$clob,'id=1'); + */ function UpdateClob($table,$column,$val,$where) { return $this->UpdateBlob($table,$column,$val,$where,'CLOB'); } @@ -2304,9 +2813,9 @@ function MetaType($t,$len=-1,$fieldobj=false) { /** - * Change the SQL connection locale to a specified locale. - * This is used to get the date formats written depending on the client locale. - */ + * Change the SQL connection locale to a specified locale. + * This is used to get the date formats written depending on the client locale. + */ function SetDateLocale($locale = 'En') { $this->locale = $locale; switch (strtoupper($locale)) @@ -2379,14 +2888,17 @@ function GetActiveRecords($table,$where=false,$bindarr=false,$primkeyArr=false) */ function Close() { $rez = $this->_close(); + $this->_queryID = false; $this->_connectionID = false; return $rez; } /** - * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans(). + * Begin a Transaction. * - * @return true if succeeded or false if database does not support transactions + * Must be followed by CommitTrans() or RollbackTrans(). + * + * @return bool true if succeeded or false if database does not support transactions */ function BeginTrans() { if ($this->debug) { @@ -2446,11 +2958,14 @@ function MetaTransaction($mode,$db) { } /** - * If database does not support transactions, always return true as data always commited + * Commits a transaction. + * + * If database does not support transactions, return true as data is + * always committed. * - * @param $ok set to false to rollback transaction, true to commit + * @param bool $ok True to commit, false to rollback the transaction. * - * @return true/false. + * @return bool true if successful */ function CommitTrans($ok=true) { return true; @@ -2458,9 +2973,12 @@ function CommitTrans($ok=true) { /** - * If database does not support transactions, rollbacks always fail, so return false + * Rolls back a transaction. * - * @return true/false. + * If database does not support transactions, return false as rollbacks + * always fail. + * + * @return bool true if successful */ function RollbackTrans() { return false; @@ -2471,7 +2989,7 @@ function RollbackTrans() { * return the databases that the driver can connect to. * Some databases will return an empty array. * - * @return an array of database names. + * @return array|bool an array of database names. */ function MetaDatabases() { global $ADODB_FETCH_MODE; @@ -2695,14 +3213,16 @@ function MetaColumnNames($table, $numIndexes=false,$useattnum=false /* only for } /** + * Concatenate strings. + * * Different SQL databases used different methods to combine strings together. * This function provides a wrapper. * - * param s variable number of string parameters - * * Usage: $db->Concat($str1,$str2); * - * @return concatenated string + * @param string $s Variable number of string parameters + * + * @return string concatenated string */ function Concat() { $arr = func_get_args(); @@ -2713,9 +3233,9 @@ function Concat() { /** * Converts a date "d" to a string that the database can understand. * - * @param d a date in Unix date time format. + * @param mixed $d a date in Unix date time format. * - * @return date string in database date format + * @return string date string in database date format */ function DBDate($d, $isfld=false) { if (empty($d) && $d !== 0) { @@ -2767,9 +3287,9 @@ function BindTimeStamp($d) { /** * Converts a timestamp "ts" to a string that the database can understand. * - * @param ts a timestamp in Unix date time format. + * @param int|object $ts A timestamp in Unix date time format. * - * @return timestamp string in database timestamp format + * @return string $timestamp string in database timestamp format */ function DBTimeStamp($ts,$isfld=false) { if (empty($ts) && $ts !== 0) { @@ -2800,9 +3320,9 @@ function DBTimeStamp($ts,$isfld=false) { /** * Also in ADORecordSet. - * @param $v is a date string in YYYY-MM-DD format + * @param mixed $v is a date string in YYYY-MM-DD format * - * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + * @return int|false Date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format */ static function UnixDate($v) { if (is_object($v)) { @@ -2829,9 +3349,9 @@ static function UnixDate($v) { /** * Also in ADORecordSet. - * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format + * @param string|object $v is a timestamp string in YYYY-MM-DD HH-NN-SS format * - * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + * @return int|false Date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format */ static function UnixTimeStamp($v) { if (is_object($v)) { @@ -2856,14 +3376,15 @@ static function UnixTimeStamp($v) { } /** - * Also in ADORecordSet. - * * Format database date based on user defined format. * - * @param v is the character date in YYYY-MM-DD format, returned by database - * @param fmt is the format to apply to it, using date() + * Also in ADORecordSet. + * + * @param mixed $v Date in YYYY-MM-DD format, returned by database + * @param string $fmt Format to apply, using date() + * @param bool $gmt * - * @return a date formated as user desires + * @return string Formatted date */ function UserDate($v,$fmt='Y-m-d',$gmt=false) { $tt = $this->UnixDate($v); @@ -2882,11 +3403,13 @@ function UserDate($v,$fmt='Y-m-d',$gmt=false) { } /** + * Format timestamp based on user defined format. * - * @param v is the character timestamp in YYYY-MM-DD hh:mm:ss format - * @param fmt is the format to apply to it, using date() + * @param mixed $v Date in YYYY-MM-DD hh:mm:ss format + * @param string $fmt Format to apply, using date() + * @param bool $gmt * - * @return a timestamp formated as user desires + * @return string Formatted timestamp */ function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) { if (!isset($v)) { @@ -2907,123 +3430,237 @@ function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) { return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt); } + /** + * Alias for addQ() + * @param string $s + * @param bool [$magic_quotes] + * @return mixed + * + * @deprecated 5.21.0 + * @noinspection PhpUnusedParameterInspection + */ function escape($s,$magic_quotes=false) { - return $this->addq($s,$magic_quotes); + return $this->addQ($s); } /** - * Quotes a string, without prefixing nor appending quotes. - */ - function addq($s,$magic_quotes=false) { - if (!$magic_quotes) { - if ($this->replaceQuote[0] == '\\') { - // only since php 4.0.5 - $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); - //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s)); - } - return str_replace("'",$this->replaceQuote,$s); - } - - // undo magic quotes for " - $s = str_replace('\\"','"',$s); - - if ($this->replaceQuote == "\\'" || ini_get('magic_quotes_sybase')) { - // ' already quoted, no need to change anything - return $s; - } else { - // change \' to '' for sybase/mssql - $s = str_replace('\\\\','\\',$s); - return str_replace("\\'",$this->replaceQuote,$s); + * Quotes a string, without prefixing nor appending quotes. + * + * @param string $s The string to quote + * @param bool $magic_quotes This param is not used since 5.21.0. + * It remains for backwards compatibility. + * + * @return string Quoted string + * + * @noinspection PhpUnusedParameterInspection + */ + function addQ($s, $magic_quotes=false) { + if ($this->replaceQuote[0] == '\\') { + $s = str_replace( + array('\\', "\0"), + array('\\\\', "\\\0"), + $s + ); } + return str_replace("'", $this->replaceQuote, $s); } /** - * Correctly quotes a string so that all strings are escaped. We prefix and append - * to the string single-quotes. - * An example is $db->qstr("Don't bother",magic_quotes_runtime()); + * Correctly quotes a string so that all strings are escaped. + * We prefix and append to the string single-quotes. + * An example is $db->qstr("Don't bother"); + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:qstr + * + * @param string $s The string to quote + * @param bool $magic_quotes This param is not used since 5.21.0. + * It remains for backwards compatibility. * - * @param s the string to quote - * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc(). - * This undoes the stupidity of magic quotes for GPC. + * @return string Quoted string to be sent back to database * - * @return quoted string to be sent back to database + * @noinspection PhpUnusedParameterInspection */ - function qstr($s,$magic_quotes=false) { - if (!$magic_quotes) { - if ($this->replaceQuote[0] == '\\'){ - // only since php 4.0.5 - $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); - //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s)); - } - return "'".str_replace("'",$this->replaceQuote,$s)."'"; - } + function qStr($s, $magic_quotes=false) { + return "'" . $this->addQ($s) . "'"; + } - // undo magic quotes for " - $s = str_replace('\\"','"',$s); - if ($this->replaceQuote == "\\'" || ini_get('magic_quotes_sybase')) { - // ' already quoted, no need to change anything - return "'$s'"; - } else { - // change \' to '' for sybase/mssql - $s = str_replace('\\\\','\\',$s); - return "'".str_replace("\\'",$this->replaceQuote,$s)."'"; - } - } - - - /** - * Will select the supplied $page number from a recordset, given that it is paginated in pages of - * $nrows rows per page. It also saves two boolean values saying if the given page is the first - * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination. - * - * See docs-adodb.htm#ex8 for an example of usage. - * - * @param sql - * @param nrows is the number of rows per page to get - * @param page is the page number to get (1-based) - * @param [inputarr] array of bind variables - * @param [secs2cache] is a private parameter only used by jlim - * @return the recordset ($rs->databaseType == 'array') - * - * NOTE: phpLens uses a different algorithm and does not use PageExecute(). - * - */ + /** + * Will select the supplied $page number from a recordset, given that it is paginated in pages of + * $nrows rows per page. It also saves two boolean values saying if the given page is the first + * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination. + * + * See docs-adodb.htm#ex8 for an example of usage. + * NOTE: phpLens uses a different algorithm and does not use PageExecute(). + * + * @param string $sql + * @param int $nrows Number of rows per page to get + * @param int $page Page number to get (1-based) + * @param mixed[]|bool $inputarr Array of bind variables + * @param int $secs2cache Private parameter only used by jlim + * + * @return mixed the recordset ($rs->databaseType == 'array') + */ function PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } if ($this->pageExecuteCountRows) { $rs = _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $secs2cache); } else { $rs = _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $secs2cache); } - return $rs; - } + return $rs; + } + + + /** + * Will select the supplied $page number from a recordset, given that it is paginated in pages of + * $nrows rows per page. It also saves two boolean values saying if the given page is the first + * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination. + * + * @param int $secs2cache seconds to cache data, set to 0 to force query + * @param string $sql + * @param int $nrows is the number of rows per page to get + * @param int $page is the page number to get (1-based) + * @param mixed[]|bool $inputarr array of bind variables + * @return mixed the recordset ($rs->databaseType == 'array') + */ + function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) { + /*switch($this->dataProvider) { + case 'postgres': + case 'mysql': + break; + default: $secs2cache = 0; break; + }*/ + return $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache); + } + + /** + * Returns the maximum size of a MetaType C field. If the method + * is not defined in the driver returns ADODB_STRINGMAX_NOTSET + * + * @return int + */ + function charMax() { + return ADODB_STRINGMAX_NOTSET; + } + + /** + * Returns the maximum size of a MetaType X field. If the method + * is not defined in the driver returns ADODB_STRINGMAX_NOTSET + * + * @return int + */ + function textMax() { + return ADODB_STRINGMAX_NOTSET; + } + + /** + * Returns a substring of a varchar type field + * + * Some databases have variations of the parameters, which is why + * we have an ADOdb function for it + * + * @param string $fld The field to sub-string + * @param int $start The start point + * @param int $length An optional length + * + * @return string The SQL text + */ + function substr($fld,$start,$length=0) { + $text = "{$this->substr}($fld,$start"; + if ($length > 0) + $text .= ",$length"; + $text .= ')'; + return $text; + } + + /* + * Formats the date into Month only format MM with leading zeroes + * + * @param string $fld The name of the date to format + * + * @return string The SQL text + */ + function month($fld) { + return $this->sqlDate('m',$fld); + } + + /* + * Formats the date into Day only format DD with leading zeroes + * + * @param string $fld The name of the date to format + * @return string The SQL text + */ + function day($fld) { + return $this->sqlDate('d',$fld); + } + + /* + * Formats the date into year only format YYYY + * + * @param string $fld The name of the date to format + * + * @return string The SQL text + */ + function year($fld) { + return $this->sqlDate('Y',$fld); + } + + /** + * Get the last error recorded by PHP and clear the message. + * + * By clearing the message, it becomes possible to detect whether a new error + * has occurred, even when it is the same error as before being repeated. + * + * @return mixed[]|null Array if an error has previously occurred. Null otherwise. + */ + protected function resetLastError() { + $error = error_get_last(); + + if (is_array($error)) { + $error['message'] = ''; + } + + return $error; + } + + /** + * Compare a previously stored error message with the last error recorded by PHP + * to determine whether a new error has occurred. + * + * @param mixed[]|null $old Optional. Previously stored return value of error_get_last(). + * + * @return string The error message if a new error has occurred + * or an empty string if no (new) errors have occurred.. + */ + protected function getChangedErrorMsg($old = null) { + $new = error_get_last(); + + if (is_null($new)) { + // No error has occurred yet at all. + return ''; + } + + if (is_null($old)) { + // First error recorded. + return $new['message']; + } + $changed = false; + foreach($new as $key => $value) { + if ($new[$key] !== $old[$key]) { + $changed = true; + break; + } + } - /** - * Will select the supplied $page number from a recordset, given that it is paginated in pages of - * $nrows rows per page. It also saves two boolean values saying if the given page is the first - * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination. - * - * @param secs2cache seconds to cache data, set to 0 to force query - * @param sql - * @param nrows is the number of rows per page to get - * @param page is the page number to get (1-based) - * @param [inputarr] array of bind variables - * @return the recordset ($rs->databaseType == 'array') - */ - function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) { - /*switch($this->dataProvider) { - case 'postgres': - case 'mysql': - break; - default: $secs2cache = 0; break; - }*/ - $rs = $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache); - return $rs; + if ($changed === true) { + return $new['message']; + } + + return ''; } } // end class ADOConnection @@ -3035,15 +3672,14 @@ function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) { //============================================================================================== /** - * Internal placeholder for record objects. Used by ADORecordSet->FetchObj(). - */ + * Internal placeholder for record objects. Used by ADORecordSet->FetchObj(). + */ class ADOFetchObj { }; - //============================================================================================== - // CLASS ADORecordSet_empty - //============================================================================================== - + /** + * Class ADODB_Iterator_empty + */ class ADODB_Iterator_empty implements Iterator { private $rs; @@ -3052,26 +3688,32 @@ function __construct($rs) { $this->rs = $rs; } + #[\ReturnTypeWillChange] function rewind() {} + #[\ReturnTypeWillChange] function valid() { return !$this->rs->EOF; } + #[\ReturnTypeWillChange] function key() { return false; } + #[\ReturnTypeWillChange] function current() { return false; } + #[\ReturnTypeWillChange] function next() {} function __call($func, $params) { return call_user_func_array(array($this->rs, $func), $params); } + #[\ReturnTypeWillChange] function hasMore() { return false; } @@ -3080,14 +3722,15 @@ function hasMore() { /** - * Lightweight recordset when there are no records to be returned - */ + * Lightweight recordset when there are no records to be returned + */ class ADORecordSet_empty implements IteratorAggregate { var $dataProvider = 'empty'; var $databaseType = false; var $EOF = true; var $_numOfRows = 0; + /** @var bool|array */ var $fields = false; var $connection = false; @@ -3117,6 +3760,7 @@ function FieldCount() { function Init() {} + #[\ReturnTypeWillChange] function getIterator() { return new ADODB_Iterator_empty($this); } @@ -3162,13 +3806,12 @@ function NumCols() { // DATE AND TIME FUNCTIONS //============================================================================================== if (!defined('ADODB_DATE_VERSION')) { - include(ADODB_DIR.'/adodb-time.inc.php'); + include_once(ADODB_DIR.'/adodb-time.inc.php'); } - //============================================================================================== - // CLASS ADORecordSet - //============================================================================================== - + /** + * Class ADODB_Iterator + */ class ADODB_Iterator implements Iterator { private $rs; @@ -3177,22 +3820,27 @@ function __construct($rs) { $this->rs = $rs; } + #[\ReturnTypeWillChange] function rewind() { $this->rs->MoveFirst(); } + #[\ReturnTypeWillChange] function valid() { return !$this->rs->EOF; } + #[\ReturnTypeWillChange] function key() { return $this->rs->_currentRow; } + #[\ReturnTypeWillChange] function current() { return $this->rs->fields; } + #[\ReturnTypeWillChange] function next() { $this->rs->MoveNext(); } @@ -3208,18 +3856,20 @@ function hasMore() { } - /** - * RecordSet class that represents the dataset returned by the database. - * To keep memory overhead low, this class holds only the current row in memory. - * No prefetching of data is done, so the RecordCount() can return -1 ( which - * means recordcount not known). - */ - class ADORecordSet implements IteratorAggregate { +/** + * RecordSet class that represents the dataset returned by the database. + * + * To keep memory overhead low, this class holds only the current row in memory. + * No prefetching of data is done, so the RecordCount() can return -1 (which + * means recordcount not known). + */ +class ADORecordSet implements IteratorAggregate { /** * public variables */ var $dataProvider = "native"; + /** @var bool|array */ var $fields = false; /// holds the current row data var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob /// in other words, we use a text area for editing. @@ -3234,14 +3884,15 @@ class ADORecordSet implements IteratorAggregate { var $bind = false; /// used by Fields() to hold array - should be private? var $fetchMode; /// default fetch mode - var $connection = false; /// the parent connection - + /** @var ADOConnection The parent connection */ + var $connection = false; /** * private variables */ var $_numOfRows = -1; /** number of rows, or -1 */ var $_numOfFields = -1; /** number of fields in recordset */ - var $_queryID = -1; /** This variable keeps the result link identifier. */ + /** @var resource result link identifier */ + var $_queryID = -1; var $_currentRow = -1; /** This variable keeps the current row in the Recordset. */ var $_closed = false; /** has recordset been closed */ var $_inited = false; /** Init() should only be called once */ @@ -3255,13 +3906,23 @@ class ADORecordSet implements IteratorAggregate { var $_maxRecordCount = 0; var $datetime = false; + public $customActualTypes; + public $customMetaTypes; + + + /** + * @var ADOFieldObject[] Field metadata cache + * @see fieldTypesArray() + */ + protected $fieldObjectsCache; + /** * Constructor * - * @param queryID this is the queryID returned by ADOConnection->_query() - * + * @param resource|int $queryID Query ID returned by ADOConnection->_query() + * @param int|bool $mode The ADODB_FETCH_MODE value */ - function __construct($queryID) { + function __construct($queryID,$mode=false) { $this->_queryID = $queryID; } @@ -3269,6 +3930,7 @@ function __destruct() { $this->Close(); } + #[\ReturnTypeWillChange] function getIterator() { return new ADODB_Iterator($this); } @@ -3285,7 +3947,7 @@ function Init() { } $this->_inited = true; if ($this->_queryID) { - @$this->_initrs(); + @$this->_initRS(); } else { $this->_numOfRows = 0; $this->_numOfFields = 0; @@ -3300,78 +3962,140 @@ function Init() { } } + /** + * Recordset initialization stub + */ + protected function _initRS() {} + + /** + * Row fetch stub + * @return bool + */ + protected function _fetch() {} /** - * Generate a SELECT tag string from a recordset, and return the string. - * If the recordset has 2 cols, we treat the 1st col as the containing - * the text to display to the user, and 2nd col as the return value. Default - * strings are compared with the FIRST column. + * Generate a SELECT tag from a recordset, and return the HTML markup. * - * @param name name of SELECT tag - * @param [defstr] the value to hilite. Use an array for multiple hilites for listbox. - * @param [blank1stItem] true to leave the 1st item in list empty - * @param [multiple] true for listbox, false for popup - * @param [size] #rows to show for listbox. not used by popup - * @param [selectAttr] additional attributes to defined for SELECT tag. - * useful for holding javascript onChange='...' handlers. - & @param [compareFields0] when we have 2 cols in recordset, we compare the defstr with - * column 0 (1st col) if this is true. This is not documented. + * If the recordset has 2 columns, we treat the first one as the text to + * display to the user, and the second as the return value. Extra columns + * are discarded. * - * @return HTML + * @param string $name Name of SELECT tag + * @param string|array $defstr The value to highlight. Use an array for multiple highlight values. + * @param bool|string $blank1stItem True to create an empty item (default), False not to add one; + * 'string' to set its label and 'value:string' to assign a value to it. + * @param bool $multiple True for multi-select list + * @param int $size Number of rows to show (applies to multi-select box only) + * @param string $selectAttr Additional attributes to defined for SELECT tag, + * useful for holding javascript onChange='...' handlers, CSS class, etc. + * @param bool $compareFirstCol When true (default), $defstr is compared against the value (column 2), + * while false will compare against the description (column 1). * - * changes by glen.davies@cce.ac.nz to support multiple hilited items + * @return string HTML */ - function GetMenu($name,$defstr='',$blank1stItem=true,$multiple=false, - $size=0, $selectAttr='',$compareFields0=true) + function getMenu($name, $defstr = '', $blank1stItem = true, $multiple = false, + $size = 0, $selectAttr = '', $compareFirstCol = true) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } - return _adodb_getmenu($this, $name,$defstr,$blank1stItem,$multiple, - $size, $selectAttr,$compareFields0); + return _adodb_getmenu($this, $name, $defstr, $blank1stItem, $multiple, + $size, $selectAttr, $compareFirstCol); } - - /** - * Generate a SELECT tag string from a recordset, and return the string. - * If the recordset has 2 cols, we treat the 1st col as the containing - * the text to display to the user, and 2nd col as the return value. Default - * strings are compared with the SECOND column. + * Generate a SELECT tag with groups from a recordset, and return the HTML markup. + * + * The recordset must have 3 columns and be ordered by the 3rd column. The + * first column contains the text to display to the user, the second is the + * return value and the third is the option group. Extra columns are discarded. + * Default strings are compared with the SECOND column. * + * @param string $name Name of SELECT tag + * @param string|array $defstr The value to highlight. Use an array for multiple highlight values. + * @param bool|string $blank1stItem True to create an empty item (default), False not to add one; + * 'string' to set its label and 'value:string' to assign a value to it. + * @param bool $multiple True for multi-select list + * @param int $size Number of rows to show (applies to multi-select box only) + * @param string $selectAttr Additional attributes to defined for SELECT tag, + * useful for holding javascript onChange='...' handlers, CSS class, etc. + * @param bool $compareFirstCol When true (default), $defstr is compared against the value (column 2), + * while false will compare against the description (column 1). + * + * @return string HTML */ - function GetMenu2($name,$defstr='',$blank1stItem=true,$multiple=false,$size=0, $selectAttr='') { - return $this->GetMenu($name,$defstr,$blank1stItem,$multiple, - $size, $selectAttr,false); - } - - /* - Grouped Menu - */ - function GetMenu3($name,$defstr='',$blank1stItem=true,$multiple=false, - $size=0, $selectAttr='') + function getMenuGrouped($name, $defstr = '', $blank1stItem = true, $multiple = false, + $size = 0, $selectAttr = '', $compareFirstCol = true) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } - return _adodb_getmenu_gp($this, $name,$defstr,$blank1stItem,$multiple, + return _adodb_getmenu_gp($this, $name, $defstr, $blank1stItem, $multiple, + $size, $selectAttr, $compareFirstCol); + } + + /** + * Generate a SELECT tag from a recordset, and return the HTML markup. + * + * Same as GetMenu(), except that default strings are compared with the + * FIRST column (the description). + * + * @param string $name Name of SELECT tag + * @param string|array $defstr The value to highlight. Use an array for multiple highlight values. + * @param bool|string $blank1stItem True to create an empty item (default), False not to add one; + * 'string' to set its label and 'value:string' to assign a value to it. + * @param bool $multiple True for multi-select list + * @param int $size Number of rows to show (applies to multi-select box only) + * @param string $selectAttr Additional attributes to defined for SELECT tag, + * useful for holding javascript onChange='...' handlers, CSS class, etc. + * + * @return string HTML + * + * @deprecated 5.21.0 Use getMenu() with $compareFirstCol = false instead. + */ + function getMenu2($name, $defstr = '', $blank1stItem = true, $multiple = false, + $size = 0, $selectAttr = '') + { + return $this->getMenu($name, $defstr, $blank1stItem, $multiple, $size, $selectAttr,false); } + /** + * Generate a SELECT tag with groups from a recordset, and return the HTML markup. + * + * Same as GetMenuGrouped(), except that default strings are compared with the + * FIRST column (the description). + * + * @param string $name Name of SELECT tag + * @param string|array $defstr The value to highlight. Use an array for multiple highlight values. + * @param bool|string $blank1stItem True to create an empty item (default), False not to add one; + * 'string' to set its label and 'value:string' to assign a value to it. + * @param bool $multiple True for multi-select list + * @param int $size Number of rows to show (applies to multi-select box only) + * @param string $selectAttr Additional attributes to defined for SELECT tag, + * useful for holding javascript onChange='...' handlers, CSS class, etc. + * + * @return string HTML + * + * @deprecated 5.21.0 Use getMenuGrouped() with $compareFirstCol = false instead. + */ + function getMenu3($name, $defstr = '', $blank1stItem = true, $multiple = false, + $size = 0, $selectAttr = '') + { + return $this->getMenuGrouped($name, $defstr, $blank1stItem, $multiple, + $size, $selectAttr, false); + } + /** * return recordset as a 2-dimensional array. * - * @param [nRows] is the number of rows to return. -1 means every row. + * @param int $nRows Number of rows to return. -1 means every row. * - * @return an array indexed by the rows (0-based) from the recordset + * @return array indexed by the rows (0-based) from the recordset */ function GetArray($nRows = -1) { - global $ADODB_EXTENSION; if ($ADODB_EXTENSION) { - $results = adodb_getall($this,$nRows); - return $results; - } $results = array(); $cnt = 0; while (!$this->EOF && $nRows != $cnt) { @@ -3383,31 +4107,33 @@ function GetArray($nRows = -1) { } function GetAll($nRows = -1) { - $arr = $this->GetArray($nRows); - return $arr; + return $this->GetArray($nRows); } - /* - * Some databases allow multiple recordsets to be returned. This function - * will return true if there is a next recordset, or false if no more. - */ + /** + * Checks if there is another available recordset. + * + * Some databases allow multiple recordsets to be returned. + * + * @return boolean true if there is a next recordset, or false if no more + */ function NextRecordSet() { return false; } /** - * return recordset as a 2-dimensional array. + * Return recordset as a 2-dimensional array. + * * Helper function for ADOConnection->SelectLimit() * - * @param offset is the row to start calculations from (1-based) - * @param [nrows] is the number of rows to return + * @param int $nrows Number of rows to return + * @param int $offset Starting row (1-based) * - * @return an array indexed by the rows (0-based) from the recordset + * @return array an array indexed by the rows (0-based) from the recordset */ - function GetArrayLimit($nrows,$offset=-1) { + function getArrayLimit($nrows, $offset=-1) { if ($offset <= 0) { - $arr = $this->GetArray($nrows); - return $arr; + return $this->GetArray($nrows); } $this->Move($offset); @@ -3426,139 +4152,165 @@ function GetArrayLimit($nrows,$offset=-1) { /** * Synonym for GetArray() for compatibility with ADO. * - * @param [nRows] is the number of rows to return. -1 means every row. + * @param int $nRows Number of rows to return. -1 means every row. * - * @return an array indexed by the rows (0-based) from the recordset + * @return array an array indexed by the rows (0-based) from the recordset */ - function GetRows($nRows = -1) { - $arr = $this->GetArray($nRows); - return $arr; + function getRows($nRows = -1) { + return $this->GetArray($nRows); } /** - * return whole recordset as a 2-dimensional associative array if there are more than 2 columns. - * The first column is treated as the key and is not included in the array. - * If there is only 2 columns, it will return a 1 dimensional array of key-value pairs unless - * $force_array == true. + * return whole recordset as a 2-dimensional associative array if + * there are more than 2 columns. The first column is treated as the + * key and is not included in the array. If there is only 2 columns, + * it will return a 1 dimensional array of key-value pairs unless + * $force_array == true. This recordset method is currently part of + * the API, but may not be in later versions of ADOdb. By preference, use + * ADOconnnection::getAssoc() + * + * @param bool $force_array (optional) Has only meaning if we have 2 data + * columns. If false, a 1 dimensional + * array is returned, otherwise a 2 dimensional + * array is returned. If this sounds confusing, + * read the source. * - * @param [force_array] has only meaning if we have 2 data columns. If false, a 1 dimensional - * array is returned, otherwise a 2 dimensional array is returned. If this sounds confusing, - * read the source. + * @param bool $first2cols (optional) Means if there are more than + * 2 cols, ignore the remaining cols and + * instead of returning + * array[col0] => array(remaining cols), + * return array[col0] => col1 * - * @param [first2cols] means if there are more than 2 cols, ignore the remaining cols and - * instead of returning array[col0] => array(remaining cols), return array[col0] => col1 + * @return mixed[]|false * - * @return an associative array indexed by the first column of the array, - * or false if the data has less than 2 cols. */ - function GetAssoc($force_array = false, $first2cols = false) { - global $ADODB_EXTENSION; - - $cols = $this->_numOfFields; - if ($cols < 2) { - return false; - } + function getAssoc($force_array = false, $first2cols = false) + { + /* + * Insufficient rows to show data + */ + if ($this->_numOfFields < 2) + return; - // Empty recordset + /* + * Empty recordset + */ if (!$this->fields) { return array(); } - // Determine whether the array is associative or 0-based numeric - $numIndex = array_keys($this->fields) == range(0, count($this->fields) - 1); + /* + * The number of fields is half the actual returned in BOTH mode + */ + $numberOfFields = $this->_numOfFields; - $results = array(); + /* + * Get the fetch mode when the call was executed, this may be + * different than ADODB_FETCH_MODE + */ + $fetchMode = $this->connection->fetchMode; + if ($fetchMode == ADODB_FETCH_BOTH) { + /* + * If we are using BOTH, we present the data as if it + * was in ASSOC mode. This could be enhanced by adding + * a BOTH_ASSOC_MODE class property + * We build a template of numeric keys. you could improve the + * speed by caching this, indexed by number of keys + */ + $testKeys = array_fill(0,$numberOfFields,0); + } - if (!$first2cols && ($cols > 2 || $force_array)) { - if ($ADODB_EXTENSION) { - if ($numIndex) { - while (!$this->EOF) { - $results[trim($this->fields[0])] = array_slice($this->fields, 1); - adodb_movenext($this); - } - } else { - while (!$this->EOF) { - // Fix for array_slice re-numbering numeric associative keys - $keys = array_slice(array_keys($this->fields), 1); - $sliced_array = array(); + $showArrayMethod = 0; - foreach($keys as $key) { - $sliced_array[$key] = $this->fields[$key]; - } + if ($numberOfFields == 2) + /* + * Key is always value of first element + * Value is always value of second element + */ + $showArrayMethod = 1; - $results[trim(reset($this->fields))] = $sliced_array; - adodb_movenext($this); - } - } - } else { - if ($numIndex) { - while (!$this->EOF) { - $results[trim($this->fields[0])] = array_slice($this->fields, 1); - $this->MoveNext(); - } - } else { - while (!$this->EOF) { - // Fix for array_slice re-numbering numeric associative keys - $keys = array_slice(array_keys($this->fields), 1); - $sliced_array = array(); + if ($force_array) + $showArrayMethod = 0; - foreach($keys as $key) { - $sliced_array[$key] = $this->fields[$key]; - } + if ($first2cols) + $showArrayMethod = 1; - $results[trim(reset($this->fields))] = $sliced_array; - $this->MoveNext(); - } - } + $results = array(); + + while (!$this->EOF){ + + $myFields = $this->fields; + + if ($fetchMode == ADODB_FETCH_BOTH) { + /* + * extract the associative keys + */ + $myFields = array_diff_key($myFields,$testKeys); } - } else { - if ($ADODB_EXTENSION) { - // return scalar values - if ($numIndex) { - while (!$this->EOF) { - // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string - $results[trim(($this->fields[0]))] = $this->fields[1]; - adodb_movenext($this); - } - } else { - while (!$this->EOF) { - // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string - $v1 = trim(reset($this->fields)); - $v2 = ''.next($this->fields); - $results[$v1] = $v2; - adodb_movenext($this); - } - } - } else { - if ($numIndex) { - while (!$this->EOF) { - // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string - $results[trim(($this->fields[0]))] = $this->fields[1]; - $this->MoveNext(); - } - } else { - while (!$this->EOF) { - // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string - $v1 = trim(reset($this->fields)); - $v2 = ''.next($this->fields); - $results[$v1] = $v2; - $this->MoveNext(); - } + + /* + * key is value of first element, rest is data, + * The key is not case processed + */ + $key = array_shift($myFields); + + switch ($showArrayMethod) { + case 0: + + if ($fetchMode == ADODB_FETCH_ASSOC + || $fetchMode == ADODB_FETCH_BOTH) + { + /* + * The driver should have already handled the key + * casing, but in case it did not. We will check and force + * this in later versions of ADOdb + */ + if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_UPPER) + $myFields = array_change_key_case($myFields,CASE_UPPER); + + elseif (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) + $myFields = array_change_key_case($myFields,CASE_LOWER); + + /* + * We have already shifted the key off + * the front, so the rest is the value + */ + $results[$key] = $myFields; + } + else + /* + * I want the values in a numeric array, + * nicely re-indexed from zero + */ + $results[$key] = array_values($myFields); + break; + + case 1: + + /* + * Don't care how long the array is, + * I just want value of second column, and it doesn't + * matter whether the array is associative or numeric + */ + $results[$key] = array_shift($myFields); + break; } - } - $ref = $results; # workaround accelerator incompat with PHP 4.4 :( - return $ref; + $this->MoveNext(); + } + /* + * Done + */ + return $results; } - /** * - * @param v is the character timestamp in YYYY-MM-DD hh:mm:ss format - * @param fmt is the format to apply to it, using date() + * @param mixed $v is the character timestamp in YYYY-MM-DD hh:mm:ss format + * @param string [$fmt] is the format to apply to it, using date() * - * @return a timestamp formated as user desires + * @return string a timestamp formated as user desires */ function UserTimeStamp($v,$fmt='Y-m-d H:i:s') { if (is_numeric($v) && strlen($v)<14) { @@ -3577,10 +4329,10 @@ function UserTimeStamp($v,$fmt='Y-m-d H:i:s') { /** - * @param v is the character date in YYYY-MM-DD format, returned by database - * @param fmt is the format to apply to it, using date() + * @param mixed $v is the character date in YYYY-MM-DD format, returned by database + * @param string $fmt is the format to apply to it, using date() * - * @return a date formated as user desires + * @return string a date formatted as user desires */ function UserDate($v,$fmt='Y-m-d') { $tt = $this->UnixDate($v); @@ -3597,9 +4349,9 @@ function UserDate($v,$fmt='Y-m-d') { /** - * @param $v is a date string in YYYY-MM-DD format + * @param mixed $v is a date string in YYYY-MM-DD format * - * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + * @return string date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format */ static function UnixDate($v) { return ADOConnection::UnixDate($v); @@ -3607,9 +4359,9 @@ static function UnixDate($v) { /** - * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format + * @param string|object $v is a timestamp string in YYYY-MM-DD HH-NN-SS format * - * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + * @return mixed date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format */ static function UnixTimeStamp($v) { return ADOConnection::UnixTimeStamp($v); @@ -3617,34 +4369,38 @@ static function UnixTimeStamp($v) { /** - * PEAR DB Compat - do not use internally - */ + * PEAR DB Compat - do not use internally + */ function Free() { return $this->Close(); } /** - * PEAR DB compat, number of rows - */ + * PEAR DB compat, number of rows + * + * @return int + */ function NumRows() { return $this->_numOfRows; } /** - * PEAR DB compat, number of cols - */ + * PEAR DB compat, number of cols + * + * @return int + */ function NumCols() { return $this->_numOfFields; } /** - * Fetch a row, returning false if no more rows. - * This is PEAR DB compat mode. - * - * @return false or array containing the current record - */ + * Fetch a row, returning false if no more rows. + * This is PEAR DB compat mode. + * + * @return mixed[]|false false or array containing the current record + */ function FetchRow() { if ($this->EOF) { return false; @@ -3659,11 +4415,13 @@ function FetchRow() { /** - * Fetch a row, returning PEAR_Error if no more rows. - * This is PEAR DB compat mode. - * - * @return DB_OK or error object - */ + * Fetch a row, returning PEAR_Error if no more rows. + * This is PEAR DB compat mode. + * + * @param mixed[]|false $arr + * + * @return mixed DB_OK or error object + */ function FetchInto(&$arr) { if ($this->EOF) { return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false; @@ -3677,7 +4435,7 @@ function FetchInto(&$arr) { /** * Move to the first row in the recordset. Many databases do NOT support this. * - * @return true or false + * @return bool true or false */ function MoveFirst() { if ($this->_currentRow == 0) { @@ -3690,7 +4448,7 @@ function MoveFirst() { /** * Move to the last row in the recordset. * - * @return true or false + * @return bool true or false */ function MoveLast() { if ($this->_numOfRows >= 0) { @@ -3712,7 +4470,7 @@ function MoveLast() { /** * Move to next record in the recordset. * - * @return true if there still rows available, or false if there are no more rows (EOF). + * @return bool true if there still rows available, or false if there are no more rows (EOF). */ function MoveNext() { if (!$this->EOF) { @@ -3737,9 +4495,9 @@ function MoveNext() { * Random access to a specific row in the recordset. Some databases do not support * access to previous rows in the databases (no scrolling backwards). * - * @param rowNumber is the row to move to (0-based) + * @param int $rowNumber is the row to move to (0-based) * - * @return true if there still rows available, or false if there are no more rows (EOF). + * @return bool true if there still rows available, or false if there are no more rows (EOF). */ function Move($rowNumber = 0) { $this->EOF = false; @@ -3771,18 +4529,11 @@ function Move($rowNumber = 0) { if ($rowNumber < $this->_currentRow) { return false; } - global $ADODB_EXTENSION; - if ($ADODB_EXTENSION) { - while (!$this->EOF && $this->_currentRow < $rowNumber) { - adodb_movenext($this); - } - } else { - while (! $this->EOF && $this->_currentRow < $rowNumber) { - $this->_currentRow++; + while (! $this->EOF && $this->_currentRow < $rowNumber) { + $this->_currentRow++; - if (!$this->_fetch()) { - $this->EOF = true; - } + if (!$this->_fetch()) { + $this->EOF = true; } } return !($this->EOF); @@ -3793,14 +4544,22 @@ function Move($rowNumber = 0) { return false; } + /** + * Adjusts the result pointer to an arbitrary row in the result. + * + * @param int $row The row to seek to. + * + * @return bool False if the recordset contains no rows, otherwise true. + */ + function _seek($row) {} /** * Get the value of a field in the current row by column name. * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM. * - * @param colname is the field to access + * @param string $colname is the field to access * - * @return the value of $colname column + * @return mixed the value of $colname column */ function Fields($colname) { return $this->fields[$colname]; @@ -3809,6 +4568,9 @@ function Fields($colname) { /** * Defines the function to use for table fields case conversion * depending on ADODB_ASSOC_CASE + * + * @param int [$case] + * * @return string strtolower/strtoupper or false if no conversion needed */ protected function AssocCaseConvertFunction($case = ADODB_ASSOC_CASE) { @@ -3826,7 +4588,7 @@ protected function AssocCaseConvertFunction($case = ADODB_ASSOC_CASE) { /** * Builds the bind array associating keys to recordset fields * - * @param int $upper Case for the array keys, defaults to uppercase + * @param int [$upper] Case for the array keys, defaults to uppercase * (see ADODB_ASSOC_CASE_xxx constants) */ function GetAssocKeys($upper = ADODB_ASSOC_CASE) { @@ -3865,6 +4627,7 @@ function GetAssocKeys($upper = ADODB_ASSOC_CASE) { * * @param int $upper Case for the array keys, defaults to uppercase * (see ADODB_ASSOC_CASE_xxx constants) + * @return array */ function GetRowAssoc($upper = ADODB_ASSOC_CASE) { $record = array(); @@ -3886,7 +4649,7 @@ function GetRowAssoc($upper = ADODB_ASSOC_CASE) { /** * Clean up recordset * - * @return true or false + * @return bool */ function Close() { // free connection object - this seems to globally free the object @@ -3900,40 +4663,47 @@ function Close() { } /** - * synonyms RecordCount and RowCount + * Number of rows in recordset. * - * @return the number of rows or -1 if this is not supported + * @return int Number of rows or -1 if this is not supported */ - function RecordCount() { + function recordCount() { return $this->_numOfRows; } - - /* - * If we are using PageExecute(), this will return the maximum possible rows - * that can be returned when paging a recordset. - */ + /** + * If we are using PageExecute(), this will return the maximum possible rows + * that can be returned when paging a recordset. + * + * @return int + */ function MaxRecordCount() { - return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount(); + return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->recordCount(); } /** - * synonyms RecordCount and RowCount + * Number of rows in recordset. + * Alias for {@see recordCount()} * - * @return the number of rows or -1 if this is not supported + * @return int Number of rows or -1 if this is not supported */ - function RowCount() { - return $this->_numOfRows; + function rowCount() { + return $this->recordCount(); } - - /** - * Portable RecordCount. Pablo Roca + /** + * Portable RecordCount. + * + * Be aware of possible problems in multiuser environments. + * For better speed the table must be indexed by the condition. + * Heavy test this before deploying. + * + * @param string $table + * @param string $condition * - * @return the number of records from a previous SELECT. All databases support this. + * @return int Number of records from a previous SELECT. All databases support this. * - * But aware possible problems in multiuser environments. For better speed the table - * must be indexed by the condition. Heavy test this before deploying. + * @author Pablo Roca */ function PO_RecordCount($table="", $condition="") { @@ -3978,51 +4748,53 @@ function FieldCount() { return $this->_numOfFields; } - /** - * Get the ADOFieldObject of a specific column. + * Get a Field's metadata from database. + * + * Must be defined by child class. * - * @param fieldoffset is the column position to access(0-based). + * @param int $fieldOffset * - * @return the ADOFieldObject for that column, or false. + * @return ADOFieldObject|false */ - function FetchField($fieldoffset = -1) { - // must be defined by child class - + function fetchField($fieldOffset) + { return false; } /** - * Get the ADOFieldObjects of all columns in an array. + * Get Field metadata for all the recordset's columns in an array. * + * @return ADOFieldObject[] */ - function FieldTypesArray() { - $arr = array(); - for ($i=0, $max=$this->_numOfFields; $i < $max; $i++) - $arr[] = $this->FetchField($i); - return $arr; + function fieldTypesArray() { + if (empty($this->fieldObjectsCache)) { + for ($i = 0; $i < $this->_numOfFields; $i++) { + $this->fieldObjectsCache[] = $this->fetchField($i); + } + } + return $this->fieldObjectsCache; } /** - * Return the fields array of the current row as an object for convenience. - * The default case is lowercase field names. - * - * @return the object with the properties set to the fields of the current row - */ + * Return the fields array of the current row as an object for convenience. + * The default case is lowercase field names. + * + * @return the object with the properties set to the fields of the current row + */ function FetchObj() { - $o = $this->FetchObject(false); - return $o; + return $this->FetchObject(false); } /** - * Return the fields array of the current row as an object for convenience. - * The default case is uppercase. - * - * @param $isupper to set the object property names to uppercase - * - * @return the object with the properties set to the fields of the current row - */ - function FetchObject($isupper=true) { + * Return the fields array of the current row as an object for convenience. + * The default case is uppercase. + * + * @param bool $isUpper to set the object property names to uppercase + * + * @return ADOFetchObj The object with properties set to the fields of the current row + */ + function FetchObject($isUpper=true) { if (empty($this->_obj)) { $this->_obj = new ADOFetchObj(); $this->_names = array(); @@ -4031,16 +4803,11 @@ function FetchObject($isupper=true) { $this->_names[] = $f->name; } } - $i = 0; - if (PHP_VERSION >= 5) { - $o = clone($this->_obj); - } else { - $o = $this->_obj; - } + $o = clone($this->_obj); for ($i=0; $i <$this->_numOfFields; $i++) { $name = $this->_names[$i]; - if ($isupper) { + if ($isUpper) { $n = strtoupper($name); } else { $n = $name; @@ -4052,35 +4819,34 @@ function FetchObject($isupper=true) { } /** - * Return the fields array of the current row as an object for convenience. - * The default is lower-case field names. - * - * @return the object with the properties set to the fields of the current row, - * or false if EOF - * - * Fixed bug reported by tim@orotech.net - */ + * Return the fields array of the current row as an object for convenience. + * The default is lower-case field names. + * + * @return ADOFetchObj|false The object with properties set to the fields of the current row + * or false if EOF. + * + * Fixed bug reported by tim@orotech.net + */ function FetchNextObj() { - $o = $this->FetchNextObject(false); - return $o; + return $this->FetchNextObject(false); } /** - * Return the fields array of the current row as an object for convenience. - * The default is upper case field names. - * - * @param $isupper to set the object property names to uppercase - * - * @return the object with the properties set to the fields of the current row, - * or false if EOF - * - * Fixed bug reported by tim@orotech.net - */ - function FetchNextObject($isupper=true) { + * Return the fields array of the current row as an object for convenience. + * The default is upper case field names. + * + * @param bool $isUpper to set the object property names to uppercase + * + * @return ADOFetchObj|false The object with properties set to the fields of the current row + * or false if EOF. + * + * Fixed bug reported by tim@orotech.net + */ + function FetchNextObject($isUpper=true) { $o = false; if ($this->_numOfRows != 0 && !$this->EOF) { - $o = $this->FetchObject($isupper); + $o = $this->FetchObject($isUpper); $this->_currentRow++; if ($this->_fetch()) { return $o; @@ -4091,34 +4857,27 @@ function FetchNextObject($isupper=true) { } /** - * Get the metatype of the column. This is used for formatting. This is because - * many databases use different names for the same type, so we transform the original - * type to our standardised version which uses 1 character codes: + * Get the ADOdb metatype. * - * @param t is the type passed in. Normally is ADOFieldObject->type. - * @param len is the maximum length of that field. This is because we treat character - * fields bigger than a certain size as a 'B' (blob). - * @param fieldobj is the field object returned by the database driver. Can hold - * additional info (eg. primary_key for mysql). + * Many databases use different names for the same type, so we transform + * the native type to our standardised one, which uses 1 character codes. + * @see https://adodb.org/dokuwiki/doku.php?id=v5:dictionary:dictionary_index#portable_data_types * - * @return the general type of the data: - * C for character < 250 chars - * X for teXt (>= 250 chars) - * B for Binary - * N for numeric or floating point - * D for date - * T for timestamp - * L for logical/Boolean - * I for integer - * R for autoincrement counter/integer + * @param string|ADOFieldObject $t Native type (usually ADOFieldObject->type) + * It is also possible to provide an + * ADOFieldObject here. + * @param int $len The field's maximum length. This is because we treat + * character fields bigger than a certain size as a 'B' (blob). + * @param ADOFieldObject $fieldObj Field object returned by the database driver; + * can hold additional info (eg. primary_key for mysql). * - * - */ - function MetaType($t,$len=-1,$fieldobj=false) { - if (is_object($t)) { - $fieldobj = $t; - $t = $fieldobj->type; - $len = $fieldobj->max_length; + * @return string The ADOdb Standard type + */ + function metaType($t, $len = -1, $fieldObj = false) { + if ($t instanceof ADOFieldObject) { + $fieldObj = $t; + $t = $fieldObj->type; + $len = $fieldObj->max_length; } // changed in 2.32 to hashing instead of switch stmt for speed... @@ -4227,9 +4986,8 @@ function MetaType($t,$len=-1,$fieldobj=false) { "SQLBOOL" => 'L' ); - $tmap = false; $t = strtoupper($t); - $tmap = (isset($typeMap[$t])) ? $typeMap[$t] : 'N'; + $tmap = (isset($typeMap[$t])) ? $typeMap[$t] : ADODB_DEFAULT_METATYPE; switch ($tmap) { case 'C': // is the char field is too long, return as text field... @@ -4243,7 +5001,7 @@ function MetaType($t,$len=-1,$fieldobj=false) { return 'C'; case 'I': - if (!empty($fieldobj->primary_key)) { + if (!empty($fieldObj->primary_key)) { return 'R'; } return 'I'; @@ -4252,8 +5010,8 @@ function MetaType($t,$len=-1,$fieldobj=false) { return 'N'; case 'B': - if (isset($fieldobj->binary)) { - return ($fieldobj->binary) ? 'B' : 'X'; + if (isset($fieldObj->binary)) { + return ($fieldObj->binary) ? 'B' : 'X'; } return 'B'; @@ -4304,8 +5062,10 @@ function _close() {} /** * set/returns the current recordset page when paginating + * @param int $page + * @return int */ - function AbsolutePage($page=-1) { + function absolutePage($page=-1) { if ($page != -1) { $this->_currentPage = $page; } @@ -4314,6 +5074,8 @@ function AbsolutePage($page=-1) { /** * set/returns the status of the atFirstPage flag when paginating + * @param bool $status + * @return bool */ function AtFirstPage($status=false) { if ($status != false) { @@ -4322,6 +5084,10 @@ function AtFirstPage($status=false) { return $this->_atFirstPage; } + /** + * @param bool $page + * @return bool + */ function LastPageNo($page = false) { if ($page != false) { $this->_lastPageNo = $page; @@ -4331,6 +5097,8 @@ function LastPageNo($page = false) { /** * set/returns the status of the atLastPage flag when paginating + * @param bool $status + * @return bool */ function AtLastPage($status=false) { if ($status != false) { @@ -4368,58 +5136,32 @@ class ADORecordSet_array extends ADORecordSet /** * Constructor + * + * The parameters passed to this recordset are always fake because + * this class does not use the queryID + * + * @param resource|int $queryID Ignored + * @param int|bool $mode The ADODB_FETCH_MODE value */ - function __construct($fakeid=1) { + function __construct($queryID, $mode=false) { global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH; // fetch() on EOF does not delete $this->fields $this->compat = !empty($ADODB_COMPAT_FETCH); - parent::__construct($fakeid); // fake queryID + parent::__construct($queryID); // fake queryID $this->fetchMode = $ADODB_FETCH_MODE; } - function _transpose($addfieldnames=true) { - global $ADODB_INCLUDED_LIB; - - if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); - } - $hdr = true; - - $fobjs = $addfieldnames ? $this->_fieldobjects : false; - adodb_transpose($this->_array, $newarr, $hdr, $fobjs); - //adodb_pr($newarr); - - $this->_skiprow1 = false; - $this->_array = $newarr; - $this->_colnames = $hdr; - - adodb_probetypes($newarr,$this->_types); - - $this->_fieldobjects = array(); - - foreach($hdr as $k => $name) { - $f = new ADOFieldObject(); - $f->name = $name; - $f->type = $this->_types[$k]; - $f->max_length = -1; - $this->_fieldobjects[] = $f; - } - $this->fields = reset($this->_array); - - $this->_initrs(); - - } /** * Setup the array. * * @param array is a 2-dimensional array holding the data. * The first row should hold the column names - * unless paramter $colnames is used. + * unless parameter $colnames is used. * @param typearr holds an array of types. These are the same types * used in MetaTypes (C,B,L,I,N). - * @param [colnames] array of column names. If set, then the first row of + * @param string[]|false [$colnames] array of column names. If set, then the first row of * $array should not hold the column names. */ function InitArray($array,$typearr,$colnames=false) { @@ -4437,10 +5179,10 @@ function InitArray($array,$typearr,$colnames=false) { /** * Setup the Array and datatype file objects * - * @param array is a 2-dimensional array holding the data. + * @param array $array 2-dimensional array holding the data * The first row should hold the column names - * unless paramter $colnames is used. - * @param fieldarr holds an array of ADOFieldObject's. + * unless parameter $colnames is used. + * @param array $fieldarr Array of ADOFieldObject's. */ function InitArrayFields(&$array,&$fieldarr) { $this->_array = $array; @@ -4451,12 +5193,15 @@ function InitArrayFields(&$array,&$fieldarr) { $this->Init(); } + /** + * @param int [$nRows] + * @return array + */ function GetArray($nRows=-1) { if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) { return $this->_array; } else { - $arr = ADORecordSet::GetArray($nRows); - return $arr; + return ADORecordSet::GetArray($nRows); } } @@ -4471,7 +5216,12 @@ function _initrs() { : sizeof($this->_types); } - /* Use associative array to get fields array */ + /** + * Use associative array to get fields array + * + * @param string $colname + * @return mixed + */ function Fields($colname) { $mode = isset($this->adodbFetchMode) ? $this->adodbFetchMode : $this->fetchMode; @@ -4491,6 +5241,11 @@ function Fields($colname) { return $this->fields[$this->bind[strtoupper($colname)]]; } + /** + * @param int [$fieldOffset] + * + * @return \ADOFieldObject + */ function FetchField($fieldOffset = -1) { if (isset($this->_fieldobjects)) { return $this->_fieldobjects[$fieldOffset]; @@ -4503,6 +5258,10 @@ function FetchField($fieldOffset = -1) { return $o; } + /** + * @param int $row + * @return bool + */ function _seek($row) { if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) { $this->_currentRow = $row; @@ -4515,6 +5274,9 @@ function _seek($row) { return false; } + /** + * @return bool + */ function MoveNext() { if (!$this->EOF) { $this->_currentRow++; @@ -4538,6 +5300,9 @@ function MoveNext() { return false; } + /** + * @return bool + */ function _fetch() { $pos = $this->_currentRow; @@ -4585,9 +5350,7 @@ function ADOLoadCode($dbType) { $db = strtolower($dbType); switch ($db) { case 'ado': - if (PHP_VERSION >= 5) { - $db = 'ado5'; - } + $db = 'ado5'; $class = 'ado'; break; @@ -4598,15 +5361,26 @@ function ADOLoadCode($dbType) { case 'pgsql': case 'postgres': - $class = $db = 'postgres8'; + $class = $db = 'postgres9'; + break; + + case 'mysql': + // mysql extension removed in PHP 7.0 - automatically switch to mysqli + $class = $db = 'mysqli'; break; default: - $class = $db; break; + if (substr($db, 0, 4) === 'pdo_') { + ADOConnection::outp("Invalid database type: $db"); + return false; + } + + $class = $db; + break; } - $file = ADODB_DIR."/drivers/adodb-".$db.".inc.php"; - @include_once($file); + $file = "drivers/adodb-$db.inc.php"; + @include_once(ADODB_DIR . '/' . $file); $ADODB_LASTDB = $class; if (class_exists("ADODB_" . $class)) { return $class; @@ -4622,20 +5396,24 @@ function ADOLoadCode($dbType) { } /** - * synonym for ADONewConnection for people like me who cannot remember the correct name + * Synonym for ADONewConnection for people like me who cannot remember the correct name + * + * @param string [$db] + * + * @return ADOConnection|false */ function NewADOConnection($db='') { - $tmp = ADONewConnection($db); - return $tmp; + return ADONewConnection($db); } /** * Instantiate a new Connection class for a specific database driver. * - * @param [db] is the database Connection object to create. If undefined, + * @param string $db Database Connection object to create. If undefined, * use the last database driver that was loaded by ADOLoadCode(). * - * @return the freshly created instance of the Connection class. + * @return ADOConnection|false The freshly created instance of the Connection class + * or false in case of error. */ function ADONewConnection($db='') { global $ADODB_NEWCONNECTION, $ADODB_LASTDB; @@ -4643,6 +5421,13 @@ function ADONewConnection($db='') { if (!defined('ADODB_ASSOC_CASE')) { define('ADODB_ASSOC_CASE', ADODB_ASSOC_CASE_NATIVE); } + + /* + * Are there special characters in the dsn password + * that disrupt parse_url + */ + $needsSpecialCharacterHandling = false; + $errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false; if (($at = strpos($db,'://')) !== FALSE) { $origdsn = $db; @@ -4664,8 +5449,27 @@ function ADONewConnection($db='') { $path = substr($path,0,$qmark); } $dsna['path'] = '/' . urlencode($path); - } else - $dsna = @parse_url($fakedsn); + } else { + /* + * Stop # character breaking parse_url + */ + $cFakedsn = str_replace('#','\035',$fakedsn); + if (strcmp($fakedsn,$cFakedsn) != 0) + { + /* + * There is a # in the string + */ + $needsSpecialCharacterHandling = true; + + /* + * This allows us to successfully parse the url + */ + $fakedsn = $cFakedsn; + + } + + $dsna = parse_url($fakedsn); + } if (!$dsna) { return false; @@ -4692,11 +5496,20 @@ function ADONewConnection($db='') { if (!$db) { return false; } + $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : ''; $dsna['user'] = isset($dsna['user']) ? rawurldecode($dsna['user']) : ''; $dsna['pass'] = isset($dsna['pass']) ? rawurldecode($dsna['pass']) : ''; $dsna['path'] = isset($dsna['path']) ? rawurldecode(substr($dsna['path'],1)) : ''; # strip off initial / + if ($needsSpecialCharacterHandling) + { + /* + * Revert back to the original string + */ + $dsna = str_replace('\035','#',$dsna); + } + if (isset($dsna['query'])) { $opt1 = explode('&',$dsna['query']); foreach($opt1 as $k => $v) { @@ -4706,6 +5519,7 @@ function ADONewConnection($db='') { } else { $opt = array(); } + } /* * phptype: Database backend used in PHP (mysql, odbc etc.) @@ -4890,12 +5704,19 @@ function NewPerfMonitor(&$conn) { if (!class_exists($class)) { return false; } - $perf = new $class($conn); - return $perf; + return new $class($conn); } - function NewDataDictionary(&$conn,$drivername=false) { + /** + * Get a new Data Dictionary object for the connection. + * + * @param ADOConnection $conn + * @param string $drivername + * + * @return ADODB_DataDict|false + */ + function newDataDictionary(&$conn, $drivername='') { if (!$drivername) { $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType); } @@ -4922,11 +5743,9 @@ function NewDataDictionary(&$conn,$drivername=false) { return $dict; } - - - /* - Perform a print_r, with pre tags for better formatting. - */ + /** + * Perform a print_r, with pre tags for better formatting. + */ function adodb_pr($var,$as_string=false) { if ($as_string) { ob_start(); @@ -4945,16 +5764,19 @@ function adodb_pr($var,$as_string=false) { } } - /* - Perform a stack-crawl and pretty print it. - - @param printOrArr Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then). - @param levels Number of levels to display - */ + /** + * Perform a stack-crawl and pretty print it. + * + * @param bool $printOrArr Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then). + * @param int $levels Number of levels to display + * @param mixed $ishtml + * + * @return string + */ function adodb_backtrace($printOrArr=true,$levels=9999,$ishtml=null) { global $ADODB_INCLUDED_LIB; if (empty($ADODB_INCLUDED_LIB)) { - include(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-lib.inc.php'); } return _adodb_backtrace($printOrArr,$levels,0,$ishtml); }