index : flyspray | |
Archlinux32 customized Flyspray installation | gitolite user |
summaryrefslogtreecommitdiff |
author | Andreas Baumann <mail@andreasbaumann.cc> | 2020-02-01 09:05:48 +0100 |
---|---|---|
committer | Andreas Baumann <mail@andreasbaumann.cc> | 2020-02-01 09:05:48 +0100 |
commit | 6854cb3f4d8219cf1829e32122eb2502a916eae9 (patch) | |
tree | 350feb504587d932e02837a1442b059759927646 /vendor/adodb/adodb-php/adodb.inc.php |
-rw-r--r-- | vendor/adodb/adodb-php/adodb.inc.php | 5051 |
diff --git a/vendor/adodb/adodb-php/adodb.inc.php b/vendor/adodb/adodb-php/adodb.inc.php new file mode 100644 index 0000000..99b533d --- /dev/null +++ b/vendor/adodb/adodb-php/adodb.inc.php @@ -0,0 +1,5051 @@ +<?php +/* + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://adodb.org/ + * + * This is the main include file for ADOdb. + * Database specific drivers are stored in the adodb/drivers/adodb-*.inc.php + * + * The ADOdb files are formatted so that doxygen can be used to generate documentation. + * Doxygen is a documentation generation tool and can be downloaded from http://doxygen.org/ + */ + +/** + \mainpage + + @version v5.20.14 06-Jan-2019 + @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved. + @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community + + Released under both BSD license and Lesser GPL library license. You can choose which license + you prefer. + + PHP's database access functions are not standardised. This creates a need for a database + class library to hide the differences between the different database API's (encapsulate + the differences) so we can easily switch databases. + + We currently support MySQL, Oracle, Microsoft SQL Server, Sybase, Sybase SQL Anywhere, DB2, + Informix, PostgreSQL, FrontBase, Interbase (Firebird and Borland variants), Foxpro, Access, + ADO, SAP DB, SQLite and ODBC. We have had successful reports of connecting to Progress and + other databases via ODBC. + */ + +if (!defined('_ADODB_LAYER')) { + define('_ADODB_LAYER',1); + + // The ADOdb extension is no longer maintained and effectively unsupported + // since v5.04. The library will not function properly if it is present. + if(defined('ADODB_EXTENSION')) { + $msg = "Unsupported ADOdb Extension (v" . ADODB_EXTENSION . ") detected! " + . "Disable it to use ADOdb"; + + $errorfn = defined('ADODB_ERROR_HANDLER') ? ADODB_ERROR_HANDLER : false; + if ($errorfn) { + $conn = false; + $errorfn('ADOdb', basename(__FILE__), -9999, $msg, null, null, $conn); + } else { + die($msg . PHP_EOL); + } + } + + //============================================================================================== + // CONSTANT DEFINITIONS + //============================================================================================== + + + /** + * Set ADODB_DIR to the directory where this file resides... + * This constant was formerly called $ADODB_RootPath + */ + if (!defined('ADODB_DIR')) { + define('ADODB_DIR',dirname(__FILE__)); + } + + //============================================================================================== + // GLOBAL VARIABLES + //============================================================================================== + + GLOBAL + $ADODB_vers, // database version + $ADODB_COUNTRECS, // count number of records returned - slows down query + $ADODB_CACHE_DIR, // directory to cache recordsets + $ADODB_CACHE, + $ADODB_CACHE_CLASS, + $ADODB_EXTENSION, // ADODB extension installed + $ADODB_COMPAT_FETCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF + $ADODB_FETCH_MODE, // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default... + $ADODB_GETONE_EOF, + $ADODB_QUOTE_FIELDNAMES; // Allows you to force quotes (backticks) around field names in queries generated by getinsertsql and getupdatesql. + + //============================================================================================== + // 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. + + define('ADODB_FORCE_IGNORE',0); + define('ADODB_FORCE_NULL',1); + define('ADODB_FORCE_EMPTY',2); + define('ADODB_FORCE_VALUE',3); + // ******************************************************** + + + if (!$ADODB_EXTENSION || ADODB_EXTENSION < 4.0) { + + define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>'); + + // allow [ ] @ ` " and . in table names + define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)'); + + // prefetching used by oracle + if (!defined('ADODB_PREFETCH_ROWS')) { + define('ADODB_PREFETCH_ROWS',10); + } + + + /** + * Fetch mode + * + * Set global variable $ADODB_FETCH_MODE to one of these constants or use + * the SetFetchMode() method to control how recordset fields are returned + * when fetching data. + * + * - NUM: array() + * - ASSOC: array('id' => 456, 'name' => 'john') + * - 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); + + /** + * Associative array case constants + * + * By defining the ADODB_ASSOC_CASE constant to one of these values, it is + * possible to control the case of field names (associative array's keys) + * when operating in ADODB_FETCH_ASSOC fetch mode. + * - LOWER: $rs->fields['orderid'] + * - UPPER: $rs->fields['ORDERID'] + * - NATIVE: $rs->fields['OrderID'] (or whatever the RDBMS will return) + * + * The default is to use native case-names. + * + * 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); + + + // 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); + } + + + /** + 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); + } + + $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 + $ADODB_vers, // database version + $ADODB_COUNTRECS, // count number of records returned - slows down query + $ADODB_CACHE_DIR, // directory to cache recordsets + $ADODB_FETCH_MODE, + $ADODB_CACHE, + $ADODB_CACHE_CLASS, + $ADODB_FORCE_TYPE, + $ADODB_GETONE_EOF, + $ADODB_QUOTE_FIELDNAMES; + + if (empty($ADODB_CACHE_CLASS)) { + $ADODB_CACHE_CLASS = 'ADODB_Cache_File' ; + } + $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT; + $ADODB_FORCE_TYPE = ADODB_FORCE_VALUE; + $ADODB_GETONE_EOF = null; + + if (!isset($ADODB_CACHE_DIR)) { + $ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp'; + } else { + // do not accept url based paths, eg. http:/ or ftp:/ + if (strpos($ADODB_CACHE_DIR,'://') !== false) { + die("Illegal path http:// or ftp://"); + } + } + + + // 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.14 06-Jan-2019'; + + /** + * Determines whether recordset->RecordCount() is used. + * Set to false for highest performance -- RecordCount() will always return -1 then + * for databases that provide "virtual" recordcounts... + */ + if (!isset($ADODB_COUNTRECS)) { + $ADODB_COUNTRECS = true; + } + } + + + //============================================================================================== + // CHANGE NOTHING BELOW UNLESS YOU ARE DESIGNING ADODB + //============================================================================================== + + ADODB_Setup(); + + //============================================================================================== + // CLASS ADOFieldObject + //============================================================================================== + /** + * Helper class for FetchFields -- holds info on a column + */ + class ADOFieldObject { + var $name = ''; + var $max_length=0; + var $type=""; +/* + // additional fields by dannym... (danny_milo@yahoo.com) + var $not_null = false; + // actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^ + // so we can as well make not_null standard (leaving it at "false" does not harm anyways) + + var $has_default = false; // this one I have done only in mysql and postgres for now ... + // others to come (dannym) + var $default_value; // default, if any, and supported. Check has_default first. +*/ + } + + + 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' + function _adodb_safedateq($s) { + $len = strlen($s); + if ($s[0] !== "'") { + $s2 = "'".$s[0]; + } else { + $s2 = "'"; + } + for($i=1; $i<$len; $i++) { + $ch = $s[$i]; + if ($ch === '\\') { + $s2 .= "'"; + break; + } elseif ($ch === "'") { + $s2 .= $ch; + break; + } + + $s2 .= $ch; + } + + return strlen($s2) == 0 ? 'null' : $s2; + } + + + // for transaction handling + + 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); + } + } + + //------------------ + // class for caching + class ADODB_Cache_File { + + var $createdir = true; // requires creation of temp dirs + + function __construct() { + global $ADODB_INCLUDED_CSV; + if (empty($ADODB_INCLUDED_CSV)) { + include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + } + } + + // write serialised recordset to cache item/file + function writecache($filename, $contents, $debug, $secs2cache) { + return adodb_write_file($filename, $contents,$debug); + } + + // load serialised recordset and unserialise it + function &readcache($filename, &$err, $secs2cache, $rsClass) { + $rs = csv2rs($filename,$err,$secs2cache,$rsClass); + return $rs; + } + + // flush all items in cache + function flushall($debug=false) { + global $ADODB_CACHE_DIR; + + $rez = false; + + if (strlen($ADODB_CACHE_DIR) > 1) { + $rez = $this->_dirFlush($ADODB_CACHE_DIR); + if ($debug) { + ADOConnection::outp( "flushall: $ADODB_CACHE_DIR<br><pre>\n". $rez."</pre>"); + } + } + return $rez; + } + + // flush one file in cache + function flushcache($f, $debug=false) { + if (!@unlink($f)) { + if ($debug) { + ADOConnection::outp( "flushcache: failed for $f"); + } + } + } + + function getdirname($hash) { + global $ADODB_CACHE_DIR; + if (!isset($this->notSafeMode)) { + $this->notSafeMode = !ini_get('safe_mode'); + } + return ($this->notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($hash,0,2) : $ADODB_CACHE_DIR; + } + + // create temp directories + function createdir($hash, $debug) { + global $ADODB_CACHE_PERMS; + + $dir = $this->getdirname($hash); + if ($this->notSafeMode && !file_exists($dir)) { + $oldu = umask(0); + if (!@mkdir($dir, empty($ADODB_CACHE_PERMS) ? 0771 : $ADODB_CACHE_PERMS)) { + if(!is_dir($dir) && $debug) { + ADOConnection::outp("Cannot create $dir"); + } + } + umask($oldu); + } + + return $dir; + } + + /** + * Private function to erase all of the files and subdirectories in a directory. + * + * Just specify the directory, and tell it if you want to delete the directory or just clear it out. + * Note: $kill_top_level is used internally in the function to flush subdirectories. + */ + function _dirFlush($dir, $kill_top_level = false) { + if(!$dh = @opendir($dir)) return; + + while (($obj = readdir($dh))) { + if($obj=='.' || $obj=='..') continue; + $f = $dir.'/'.$obj; + + if (strpos($obj,'.cache')) { + @unlink($f); + } + if (is_dir($f)) { + $this->_dirFlush($f, true); + } + } + if ($kill_top_level === true) { + @rmdir($dir); + } + return true; + } + } + + //============================================================================================== + // CLASS ADOConnection + //============================================================================================== + + /** + * Connection object. For connecting to databases, and executing queries. + */ + abstract class ADOConnection { + // + // PUBLIC VARS + // + var $dataProvider = 'native'; + var $databaseType = ''; /// RDBMS currently in use, eg. odbc, mysql, mssql + var $database = ''; /// Name of database to be used. + 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 + var $maxblobsize = 262144; /// maximum size of blobs or large text fields (262144 = 256K)-- some db's die otherwise like foxpro + var $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase + var $substr = 'substr'; /// substring operator + var $length = 'length'; /// string length ofperator + var $random = 'rand()'; /// random function + var $upperCase = 'upper'; /// uppercase function + var $fmtDate = "'Y-m-d'"; /// used by DBDate() as the default date format used by the database + var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /// used by DBTimeStamp as the default timestamp fmt. + var $true = '1'; /// string that represents TRUE for a database + 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 $charSet=false; /// character set to use - only for interbase, postgres and oci8 + var $metaDatabasesSQL = ''; + var $metaTablesSQL = ''; + var $uniqueOrderBy = false; /// All order by columns have to be unique + var $emptyDate = ' '; + var $emptyTimeStamp = ' '; + var $lastInsID = false; + //-- + var $hasInsertID = false; /// supports autoincrement ID? + var $hasAffectedRows = false; /// supports affected rows for update/delete? + var $hasTop = false; /// support mssql/access SELECT TOP 10 * FROM TABLE + var $hasLimit = false; /// support pgsql/mysql SELECT * FROM TABLE LIMIT 10 + var $readOnly = false; /// this is a readonly database - used by phpLens + var $hasMoveFirst = false; /// has ability to run MoveFirst(), scrolling backwards + var $hasGenID = false; /// can generate sequences using GenID(); + var $hasTransactions = true; /// has transactions + //-- + var $genID = 0; /// sequence id used by GenID(); + var $raiseErrorFn = false; /// error function to call + 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) + + var $sysDate = false; /// name of function that returns the current date + var $sysTimeStamp = false; /// name of function that returns the current timestamp + var $sysUTimeStamp = false; // name of function that returns the current timestamp accurate to the microsecond or nearest fraction + var $arrayClass = 'ADORecordSet_array'; /// name of class used to generate array recordsets, which are pre-downloaded recordsets + + var $noNullStrings = false; /// oracle specific stuff - if true ensures that '' is converted to ' ' + var $numCacheHits = 0; + var $numCacheMisses = 0; + var $pageExecuteCountRows = true; + var $uniqueSort = false; /// indicates that all fields in order by must be unique + var $leftOuter = false; /// operator to use for left outer join in WHERE clause + var $rightOuter = false; /// operator to use for right outer join in WHERE clause + var $ansiOuter = false; /// whether ansi outer join syntax supported + var $autoRollback = false; // autoRollback on PConnect(). + var $poorAffectedRows = false; // affectedRows not working or unreliable + + var $fnExecute = false; + var $fnCacheExecute = false; + var $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char + var $rsPrefix = "ADORecordSet_"; + + var $autoCommit = true; /// do not modify this yourself - actually private + var $transOff = 0; /// temporarily disable transactions + var $transCnt = 0; /// count of nested transactions + + var $fetchMode=false; + + var $null2null = 'null'; // in autoexecute/getinsertsql/getupdatesql, this value will be converted to a null + var $bulkBind = false; // enable 2D Execute array + // + // PRIVATE VARS + // + var $_oldRaiseFn = false; + var $_transOK = null; + var $_connectionID = false; /// The returned link identifier whenever a successful database connection is made. + 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 + var $_queryID = false; /// This variable keeps the last created result link identifier + + var $_isPersistentConnection = false; /// A boolean variable to state whether its a persistent connection or normal connection. */ + var $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters. + var $_evalAll = false; + var $_affected = false; + 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 + */ + protected $connectionParameters = 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) + { + + $this->connectionParameters[$parameter] = $value; + + } + + static function Version() { + global $ADODB_vers; + + // Semantic Version number matching regex + $regex = '^[vV]?(\d+\.\d+\.\d+' // Version number (X.Y.Z) with optional 'V' + . '(?:-(?:' // Optional preprod version: a '-' + . 'dev|' // followed by 'dev' + . '(?:(?:alpha|beta|rc)(?:\.\d+))' // or a preprod suffix and version number + . '))?)(?:\s|$)'; // Whitespace or end of string + + if (!preg_match("/$regex/", $ADODB_vers, $matches)) { + // This should normally not happen... Return whatever is between the start + // of the string and the first whitespace (or the end of the string). + self::outp("Invalid version number: '$ADODB_vers'", 'Version'); + $regex = '^[vV]?(.*?)(?:\s|$)'; + preg_match("/$regex/", $ADODB_vers, $matches); + } + return $matches[1]; + } + + /** + Get server version info... + + @returns An array with 2 elements: $arr['string'] is the description string, + and $arr[version] is the version (also a string). + */ + function ServerInfo() { + return array('description' => '', 'version' => ''); + } + + function IsConnected() { + return !empty($this->_connectionID); + } + + function _findvers($str) { + if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) { + return $arr[1]; + } else { + return ''; + } + } + + /** + * All error messages go through this bottleneck function. + * You can define your own handler by defining the function name in ADODB_OUTP. + */ + static function outp($msg,$newline=true) { + global $ADODB_FLUSH,$ADODB_OUTP; + + if (defined('ADODB_OUTP')) { + $fn = ADODB_OUTP; + $fn($msg,$newline); + return; + } else if (isset($ADODB_OUTP)) { + $fn = $ADODB_OUTP; + $fn($msg,$newline); + return; + } + + if ($newline) { + $msg .= "<br>\n"; + } + + if (isset($_SERVER['HTTP_USER_AGENT']) || !$newline) { + echo $msg; + } else { + echo strip_tags($msg); + } + + + if (!empty($ADODB_FLUSH) && ob_get_length() !== false) { + flush(); // do not flush if output buffering enabled - useless - thx to Jesse Mullan + } + + } + + function Time() { + $rs = $this->_Execute("select $this->sysTimeStamp"); + if ($rs && !$rs->EOF) { + return $this->UnixTimeStamp(reset($rs->fields)); + } + + return false; + } + + /** + * 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 + * + * @return true or false + */ + function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) { + if ($argHostname != "") { + $this->host = $argHostname; + } + // Overwrites $this->host and $this->port if a port is specified. + $this->parseHostNameAndPort(); + + if ($argUsername != "") { + $this->user = $argUsername; + } + if ($argPassword != "") { + $this->password = 'not stored'; // not stored for security reasons + } + if ($argDatabaseName != "") { + $this->database = $argDatabaseName; + } + + $this->_isPersistentConnection = false; + + if ($forceNew) { + if ($rez=$this->_nconnect($this->host, $this->user, $argPassword, $this->database)) { + return true; + } + } else { + if ($rez=$this->_connect($this->host, $this->user, $argPassword, $this->database)) { + return true; + } + } + if (isset($rez)) { + $err = $this->ErrorMsg(); + $errno = $this->ErrorNo(); + if (empty($err)) { + $err = "Connection error to server '$argHostname' with user '$argUsername'"; + } + } else { + $err = "Missing extension for ".$this->dataProvider; + $errno = 0; + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType, 'CONNECT', $errno, $err, $this->host, $this->database, $this); + } + + $this->_connectionID = false; + if ($this->debug) { + ADOConnection::outp( $this->host.': '.$err); + } + return false; + } + + 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 + * + * @param [argHostname] Host to connect to + * @param [argUsername] Userid to login + * @param [argPassword] Associated password + * @param [argDatabaseName] database + * + * @return true or false + */ + function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") { + return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true); + } + + /** + * Establish persistent connect to database + * + * @param [argHostname] Host to connect to + * @param [argUsername] Userid to login + * @param [argPassword] Associated password + * @param [argDatabaseName] database + * + * @return return true or false + */ + function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") { + + if (defined('ADODB_NEVER_PERSIST')) { + return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName); + } + + if ($argHostname != "") { + $this->host = $argHostname; + } + // Overwrites $this->host and $this->port if a port is specified. + $this->parseHostNameAndPort(); + + if ($argUsername != "") { + $this->user = $argUsername; + } + if ($argPassword != "") { + $this->password = 'not stored'; + } + if ($argDatabaseName != "") { + $this->database = $argDatabaseName; + } + + $this->_isPersistentConnection = true; + + if ($rez = $this->_pconnect($this->host, $this->user, $argPassword, $this->database)) { + return true; + } + if (isset($rez)) { + $err = $this->ErrorMsg(); + if (empty($err)) { + $err = "Connection error to server '$argHostname' with user '$argUsername'"; + } + $ret = false; + } else { + $err = "Missing extension for ".$this->dataProvider; + $ret = 0; + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this); + } + + $this->_connectionID = false; + if ($this->debug) { + ADOConnection::outp( $this->host.': '.$err); + } + return $ret; + } + + 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; + } + ADOConnection::outp($msg); + } + + // create cache class. Code is backward compat with old memcache implementation + function _CreateCache() { + global $ADODB_CACHE, $ADODB_CACHE_CLASS; + + if ($this->memCache) { + global $ADODB_INCLUDED_MEMCACHE; + + if (empty($ADODB_INCLUDED_MEMCACHE)) { + include_once(ADODB_DIR.'/adodb-memcache.lib.inc.php'); + } + $ADODB_CACHE = new ADODB_Cache_MemCache($this); + } else { + $ADODB_CACHE = new $ADODB_CACHE_CLASS($this); + } + } + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) { + if (!$col) { + $col = $this->sysDate; + } + return $col; // child class implement + } + + /** + * 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: + * + * $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'); + * + * @param sql SQL to send to database + * + * @return return FALSE, or the prepared statement, or the original sql if + * if the database does not support prepare. + * + */ + function Prepare($sql) { + return $sql; + } + + /** + * 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 + * + * @return return FALSE, or the prepared statement, or the original sql if + * if the database does not support prepare. + * + */ + function PrepareSP($sql,$param=true) { + return $this->Prepare($sql,$param); + } + + /** + * PEAR DB Compat + */ + function Quote($s) { + return $this->qstr($s,false); + } + + /** + * Requested by "Karsten Dambekalns" <k.dambekalns@fishfarm.de> + */ + 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); + } + + /** + * PEAR DB Compat - do not use internally. + */ + function ErrorNative() { + return $this->ErrorNo(); + } + + + /** + * PEAR DB Compat - do not use internally. + */ + 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 + * + * @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 + */ + function RowLock($table,$where,$col='1 as adodbignore') { + return false; + } + + function CommitLock($table) { + return $this->CommitTrans(); + } + + 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 + */ + function SetFetchMode($mode) { + $old = $this->fetchMode; + $this->fetchMode = $mode; + + if ($old === false) { + global $ADODB_FETCH_MODE; + return $ADODB_FETCH_MODE; + } + return $old; + } + + + /** + * PEAR DB Compat - do not use internally. + */ + function Query($sql, $inputarr=false) { + $rs = $this->Execute($sql, $inputarr); + if (!$rs && defined('ADODB_PEAR')) { + return ADODB_PEAR_Error(); + } + return $rs; + } + + + /** + * 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')) { + return ADODB_PEAR_Error(); + } + return $rs; + } + + + /** + * PEAR DB Compat - do not use internally + */ + function Disconnect() { + return $this->Close(); + } + + /** + * 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 string $type (unused) + * @return string query parameter placeholder + */ + function Param($name,$type='C') { + return '?'; + } + + /* + InParameter and OutParameter are self-documenting versions of Parameter(). + */ + function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) { + return $this->Parameter($stmt,$var,$name,false,$maxLen,$type); + } + + /* + */ + 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. + + */ + function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) { + return false; + } + + + function IgnoreErrors($saveErrs=false) { + if (!$saveErrs) { + $saveErrs = array($this->raiseErrorFn,$this->_transOK); + $this->raiseErrorFn = false; + return $saveErrs; + } else { + $this->raiseErrorFn = $saveErrs[0]; + $this->_transOK = $saveErrs[1]; + } + } + + /** + * Improved method of initiating a transaction. Used together with CompleteTrans(). + * Advantages include: + * + * a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans. + * Only the outermost block is treated as a transaction.<br> + * b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br> + * c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block + * are disabled, making it backward compatible. + */ + function StartTrans($errfn = 'ADODB_TransMonitor') { + if ($this->transOff > 0) { + $this->transOff += 1; + return true; + } + + $this->_oldRaiseFn = $this->raiseErrorFn; + $this->raiseErrorFn = $errfn; + $this->_transOK = true; + + if ($this->debug && $this->transCnt > 0) { + ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans"); + } + $ok = $this->BeginTrans(); + $this->transOff = 1; + return $ok; + } + + + /** + 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. + */ + function CompleteTrans($autoComplete = true) { + if ($this->transOff > 1) { + $this->transOff -= 1; + return true; + } + $this->raiseErrorFn = $this->_oldRaiseFn; + + $this->transOff = 0; + if ($this->_transOK && $autoComplete) { + if (!$this->CommitTrans()) { + $this->_transOK = false; + if ($this->debug) { + ADOConnection::outp("Smart Commit failed"); + } + } else { + if ($this->debug) { + ADOConnection::outp("Smart Commit occurred"); + } + } + } else { + $this->_transOK = false; + $this->RollbackTrans(); + if ($this->debug) { + ADOCOnnection::outp("Smart Rollback occurred"); + } + } + + return $this->_transOK; + } + + /* + At the end of a StartTrans/CompleteTrans block, perform a rollback. + */ + function FailTrans() { + if ($this->debug) + if ($this->transOff == 0) { + ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans"); + } else { + ADOConnection::outp("FailTrans was called"); + adodb_backtrace(); + } + $this->_transOK = false; + } + + /** + Check if transaction has failed, only for Smart Transactions. + */ + function HasFailedTrans() { + if ($this->transOff > 0) { + return $this->_transOK == false; + } + return false; + } + + /** + * 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 + */ + function Execute($sql,$inputarr=false) { + if ($this->fnExecute) { + $fn = $this->fnExecute; + $ret = $fn($this,$sql,$inputarr); + if (isset($ret)) { + return $ret; + } + } + if ($inputarr !== false) { + if (!is_array($inputarr)) { + $inputarr = array($inputarr); + } + + $element0 = reset($inputarr); + # is_object check because oci8 descriptors can be passed in + $array_2d = $this->bulkBind && is_array($element0) && !is_object(reset($element0)); + + //remove extra memory copy of input -mikefedyk + unset($element0); + + if (!is_array($sql) && !$this->_bindInputArray) { + // @TODO this would consider a '?' within a string as a parameter... + $sqlarr = explode('?',$sql); + $nparams = sizeof($sqlarr)-1; + + if (!$array_2d) { + // When not Bind Bulk - convert to array of arguments list + $inputarr = array($inputarr); + } else { + // Bulk bind - Make sure all list of params have the same number of elements + $countElements = array_map('count', $inputarr); + if (1 != count(array_unique($countElements))) { + $this->outp_throw( + "[bulk execute] Input array has different number of params [" . print_r($countElements, true) . "].", + 'Execute' + ); + return false; + } + unset($countElements); + } + // Make sure the number of parameters provided in the input + // array matches what the query expects + $element0 = reset($inputarr); + if ($nparams != count($element0)) { + $this->outp_throw( + "Input array has " . count($element0) . + " params, does not match query: '" . htmlspecialchars($sql) . "'", + 'Execute' + ); + return false; + } + + // clean memory + unset($element0); + + foreach($inputarr as $arr) { + $sql = ''; $i = 0; + foreach ($arr as $v) { + $sql .= $sqlarr[$i]; + // from Ron Baldwin <ron.baldwin#sourceprose.com> + // Only quote string types + $typ = gettype($v); + if ($typ == 'string') { + //New memory copy of input created here -mikefedyk + $sql .= $this->qstr($v); + } else if ($typ == 'double') { + $sql .= str_replace(',','.',$v); // locales fix so 1.1 does not get converted to 1,1 + } else if ($typ == 'boolean') { + $sql .= $v ? $this->true : $this->false; + } else if ($typ == 'object') { + if (method_exists($v, '__toString')) { + $sql .= $this->qstr($v->__toString()); + } else { + $sql .= $this->qstr((string) $v); + } + } else if ($v === null) { + $sql .= 'NULL'; + } else { + $sql .= $v; + } + $i += 1; + + if ($i == $nparams) { + break; + } + } // while + if (isset($sqlarr[$i])) { + $sql .= $sqlarr[$i]; + if ($i+1 != sizeof($sqlarr)) { + $this->outp_throw( "Input Array does not match ?: ".htmlspecialchars($sql),'Execute'); + } + } else if ($i != sizeof($sqlarr)) { + $this->outp_throw( "Input array does not match ?: ".htmlspecialchars($sql),'Execute'); + } + + $ret = $this->_Execute($sql); + if (!$ret) { + return $ret; + } + } + } else { + if ($array_2d) { + if (is_string($sql)) { + $stmt = $this->Prepare($sql); + } else { + $stmt = $sql; + } + + foreach($inputarr as $arr) { + $ret = $this->_Execute($stmt,$arr); + if (!$ret) { + return $ret; + } + } + } else { + $ret = $this->_Execute($sql,$inputarr); + } + } + } else { + $ret = $this->_Execute($sql,false); + } + + return $ret; + } + + function _Execute($sql,$inputarr=false) { + // ExecuteCursor() may send non-string queries (such as arrays), + // so we need to ignore those. + 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 ); + } + + if ($this->debug) { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) { + include(ADODB_DIR.'/adodb-lib.inc.php'); + } + $this->_queryID = _adodb_debug_execute($this, $sql,$inputarr); + } else { + $this->_queryID = @$this->_query($sql,$inputarr); + } + + // ************************ + // OK, query executed + // ************************ + + // error handling if query fails + if ($this->_queryID === false) { + if ($this->debug == 99) { + adodb_backtrace(true,5); + } + $fn = $this->raiseErrorFn; + if ($fn) { + $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this); + } + return false; + } + + // return simplified recordset for inserts/updates/deletes with lower overhead + if ($this->_queryID === true) { + $rsclass = $this->rsPrefix.'empty'; + $rs = (class_exists($rsclass)) ? new $rsclass(): new ADORecordSet_empty(); + + return $rs; + } + + // 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(); + if (is_array($sql)) { + $rs->sql = $sql[0]; + } else { + $rs->sql = $sql; + } + if ($rs->_numOfRows <= 0) { + global $ADODB_COUNTRECS; + if ($ADODB_COUNTRECS) { + if (!$rs->EOF) { + $rs = $this->_rs2rs($rs,-1,-1,!is_array($sql)); + $rs->_queryID = $this->_queryID; + } else + $rs->_numOfRows = 0; + } + } + return $rs; + } + + function CreateSequence($seqname='adodbseq',$startID=1) { + if (empty($this->_genSeqSQL)) { + return false; + } + return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID)); + } + + function DropSequence($seqname='adodbseq') { + if (empty($this->_dropSeqSQL)) { + return false; + } + return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + } + + /** + * 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 + */ + function GenID($seqname='adodbseq',$startID=1) { + if (!$this->hasGenID) { + return 0; // formerly returns false pre 1.60 + } + + $getnext = sprintf($this->_genIDSQL,$seqname); + + $holdtransOK = $this->_transOK; + + $save_handler = $this->raiseErrorFn; + $this->raiseErrorFn = ''; + @($rs = $this->Execute($getnext)); + $this->raiseErrorFn = $save_handler; + + if (!$rs) { + $this->_transOK = $holdtransOK; //if the status was ok before reset + $createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID)); + $rs = $this->Execute($getnext); + } + if ($rs && !$rs->EOF) { + $this->genID = reset($rs->fields); + } else { + $this->genID = 0; // false + } + + if ($rs) { + $rs->Close(); + } + + return $this->genID; + } + + /** + * @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. + */ + function Insert_ID($table='',$column='') { + if ($this->_logsql && $this->lastInsID) { + return $this->lastInsID; + } + if ($this->hasInsertID) { + return $this->_insertid($table,$column); + } + if ($this->debug) { + ADOConnection::outp( '<p>Insert_ID error</p>'); + adodb_backtrace(); + } + return false; + } + + + /** + * Portable Insert ID. Pablo Roca <pabloroca#mvps.org> + * + * @return the last inserted ID. All databases support this. But aware possible + * problems in multiuser environments. Heavy test this before deploying. + */ + function PO_Insert_ID($table="", $id="") { + if ($this->hasInsertID){ + return $this->Insert_ID($table,$id); + } else { + return $this->GetOne("SELECT MAX($id) FROM $table"); + } + } + + /** + * @return # rows affected by UPDATE/DELETE + */ + function Affected_Rows() { + if ($this->hasAffectedRows) { + if ($this->fnExecute === 'adodb_log_sql') { + if ($this->_logsql && $this->_affected !== false) { + return $this->_affected; + } + } + $val = $this->_affectedrows(); + return ($val < 0) ? false : $val; + } + + if ($this->debug) { + ADOConnection::outp( '<p>Affected_Rows error</p>',false); + } + return false; + } + + + /** + * @return the last error message + */ + function ErrorMsg() { + if ($this->_errorMsg) { + return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg; + } else { + return ''; + } + } + + + /** + * @return the last error number. Normally 0 means no error. + */ + function ErrorNo() { + return ($this->_errorMsg) ? -1 : 0; + } + + function MetaError($err=false) { + include_once(ADODB_DIR."/adodb-error.inc.php"); + if ($err === false) { + $err = $this->ErrorNo(); + } + return adodb_error($this->dataProvider,$this->databaseType,$err); + } + + function MetaErrorMsg($errno) { + include_once(ADODB_DIR."/adodb-error.inc.php"); + return adodb_errormsg($errno); + } + + /** + * @returns an array with the primary key columns in it. + */ + function MetaPrimaryKeys($table, $owner=false) { + // owner not used in base class - see oci8 + $p = array(); + $objs = $this->MetaColumns($table); + if ($objs) { + foreach($objs as $v) { + if (!empty($v->primary_key)) { + $p[] = $v->name; + } + } + } + if (sizeof($p)) { + return $p; + } + if (function_exists('ADODB_VIEW_PRIMARYKEYS')) { + return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner); + } + return false; + } + + /** + * @returns assoc array where keys are tables, and values are foreign keys + */ + function MetaForeignKeys($table, $owner=false, $upper=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 + */ + function SelectDB($dbName) {return false;} + + + /** + * 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 + * MySQL and PostgreSQL parameter ordering is the opposite of the other. + * eg. + * SelectLimit('select * from table',3); will return rows 1 to 3 (1-based) + * SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based) + * + * 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') + */ + 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 + $ismssql = (strpos($this->databaseType,'mssql') !== false); + if ($ismssql) { + $isaccess = false; + } else { + $isaccess = (strpos($this->databaseType,'access') !== false); + } + + if ($offset <= 0) { + // 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.' '.$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); + } else { + $sql = preg_replace( + '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql); + } + } + } + + // if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer rows + // 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS. + global $ADODB_COUNTRECS; + + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + + + if ($secs2cache != 0) { + $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); + } else { + $rs = $this->Execute($sql,$inputarr); + } + + $ADODB_COUNTRECS = $savec; + if ($rs && !$rs->EOF) { + $rs = $this->_rs2rs($rs,$nrows,$offset); + } + //print_r($rs); + return $rs; + } + + /** + * Create serializable recordset. Breaks rs link to connection. + * + * @param rs the recordset to serialize + */ + function SerializableRS(&$rs) { + $rs2 = $this->_rs2rs($rs); + $ignore = false; + $rs2->connection = $ignore; + + return $rs2; + } + + /** + * 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 + */ + function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) { + if (! $rs) { + return false; + } + $dbtype = $rs->databaseType; + if (!$dbtype) { + $rs = $rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ? + return $rs; + } + if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) { + $rs->MoveFirst(); + $rs = $rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ? + return $rs; + } + $flds = array(); + for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) { + $flds[] = $rs->FetchField($i); + } + + $arr = $rs->GetArrayLimit($nrows,$offset); + //print_r($arr); + if ($close) { + $rs->Close(); + } + + $arrayClass = $this->arrayClass; + + $rs2 = new $arrayClass(); + $rs2->connection = $this; + $rs2->sql = $rs->sql; + $rs2->dataProvider = $this->dataProvider; + $rs2->InitArrayFields($arr,$flds); + $rs2->fetchMode = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode; + return $rs2; + } + + /* + * Return all rows. Compat with PEAR DB + */ + function GetAll($sql, $inputarr=false) { + $arr = $this->GetArray($sql,$inputarr); + return $arr; + } + + function GetAssoc($sql, $inputarr=false,$force_array = false, $first2cols = false) { + $rs = $this->Execute($sql, $inputarr); + if (!$rs) { + return false; + } + $arr = $rs->GetAssoc($force_array,$first2cols); + return $arr; + } + + function CacheGetAssoc($secs2cache, $sql=false, $inputarr=false,$force_array = false, $first2cols = false) { + if (!is_numeric($secs2cache)) { + $first2cols = $force_array; + $force_array = $inputarr; + } + $rs = $this->CacheExecute($secs2cache, $sql, $inputarr); + if (!$rs) { + return false; + } + $arr = $rs->GetAssoc($force_array,$first2cols); + return $arr; + } + + /** + * 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) { + global $ADODB_COUNTRECS,$ADODB_GETONE_EOF; + + $crecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + + $ret = false; + $rs = $this->Execute($sql,$inputarr); + if ($rs) { + if ($rs->EOF) { + $ret = $ADODB_GETONE_EOF; + } else { + $ret = reset($rs->fields); + } + + $rs->Close(); + } + $ADODB_COUNTRECS = $crecs; + return $ret; + } + + // $where should include 'WHERE fld=value' + function GetMedian($table, $field,$where = '') { + $total = $this->GetOne("select count(*) from $table $where"); + if (!$total) { + return false; + } + + $midrow = (integer) ($total/2); + $rs = $this->SelectLimit("select $field from $table $where order by 1",1,$midrow); + if ($rs && !$rs->EOF) { + return reset($rs->fields); + } + return false; + } + + + function CacheGetOne($secs2cache,$sql=false,$inputarr=false) { + global $ADODB_GETONE_EOF; + + $ret = false; + $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); + if ($rs) { + if ($rs->EOF) { + $ret = $ADODB_GETONE_EOF; + } else { + $ret = reset($rs->fields); + } + $rs->Close(); + } + + return $ret; + } + + function GetCol($sql, $inputarr = false, $trim = false) { + + $rs = $this->Execute($sql, $inputarr); + if ($rs) { + $rv = array(); + if ($trim) { + while (!$rs->EOF) { + $rv[] = trim(reset($rs->fields)); + $rs->MoveNext(); + } + } else { + while (!$rs->EOF) { + $rv[] = reset($rs->fields); + $rs->MoveNext(); + } + } + $rs->Close(); + } else { + $rv = false; + } + return $rv; + } + + function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false) { + $rs = $this->CacheExecute($secs, $sql, $inputarr); + if ($rs) { + $rv = array(); + if ($trim) { + while (!$rs->EOF) { + $rv[] = trim(reset($rs->fields)); + $rs->MoveNext(); + } + } else { + while (!$rs->EOF) { + $rv[] = reset($rs->fields); + $rs->MoveNext(); + } + } + $rs->Close(); + } else + $rv = 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. + */ + function OffsetDate($dayFraction,$date=false) { + if (!$date) { + $date = $this->sysDate; + } + return '('.$date.'+'.$dayFraction.')'; + } + + + /** + * + * @param sql SQL statement + * @param [inputarr] input bind array + */ + function GetArray($sql,$inputarr=false) { + global $ADODB_COUNTRECS; + + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs = $this->Execute($sql,$inputarr); + $ADODB_COUNTRECS = $savec; + if (!$rs) + if (defined('ADODB_PEAR')) { + $cls = ADODB_PEAR_Error(); + return $cls; + } else { + return false; + } + $arr = $rs->GetArray(); + $rs->Close(); + return $arr; + } + + function CacheGetAll($secs2cache,$sql=false,$inputarr=false) { + $arr = $this->CacheGetArray($secs2cache,$sql,$inputarr); + return $arr; + } + + 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; + + if (!$rs) + if (defined('ADODB_PEAR')) { + $cls = ADODB_PEAR_Error(); + return $cls; + } else { + return false; + } + $arr = $rs->GetArray(); + $rs->Close(); + return $arr; + } + + function GetRandRow($sql, $arr= false) { + $rezarr = $this->GetAll($sql, $arr); + $sz = sizeof($rezarr); + return $rezarr[abs(rand()) % $sz]; + } + + /** + * 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 + */ + function GetRow($sql,$inputarr=false) { + global $ADODB_COUNTRECS; + + $crecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + + $rs = $this->Execute($sql,$inputarr); + + $ADODB_COUNTRECS = $crecs; + if ($rs) { + if (!$rs->EOF) { + $arr = $rs->fields; + } else { + $arr = array(); + } + $rs->Close(); + return $arr; + } + + return false; + } + + function CacheGetRow($secs2cache,$sql=false,$inputarr=false) { + $rs = $this->CacheExecute($secs2cache,$sql,$inputarr); + if ($rs) { + if (!$rs->EOF) { + $arr = $rs->fields; + } else { + $arr = array(); + } + + $rs->Close(); + return $arr; + } + return 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 + */ + + 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'); + } + + return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc); + } + + + /** + * 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 + * MySQL and PostgreSQL parameter ordering is the opposite of the other. + * eg. + * CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based) + * CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based) + * + * 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') + */ + function CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false) { + if (!is_numeric($secs2cache)) { + if ($sql === false) { + $sql = -1; + } + if ($offset == -1) { + $offset = false; + } + // sql, nrows, offset,inputarr + $rs = $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$this->cacheSecs); + } else { + if ($sql === false) { + $this->outp_throw("Warning: \$sql missing from CacheSelectLimit()",'CacheSelectLimit'); + } + $rs = $this->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + } + return $rs; + } + + /** + * Flush cached recordsets that match a particular $sql statement. + * If $sql == false, then we purge all files in the cache. + */ + function CacheFlush($sql=false,$inputarr=false) { + global $ADODB_CACHE_DIR, $ADODB_CACHE; + + # Create cache if it does not exist + if (empty($ADODB_CACHE)) { + $this->_CreateCache(); + } + + if (!$sql) { + $ADODB_CACHE->flushall($this->debug); + return; + } + + $f = $this->_gencachename($sql.serialize($inputarr),false); + return $ADODB_CACHE->flushcache($f, $this->debug); + } + + + /** + * Private function to generate filename for caching. + * Filename is generated based on: + * + * - sql statement + * - database type (oci8, ibase, ifx, etc) + * - database name + * - userid + * - setFetchMode (adodb 4.23) + * + * When not in safe mode, we create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR). + * Assuming that we can have 50,000 files per directory with good performance, + * then we can scale to 12.8 million unique cached recordsets. Wow! + */ + function _gencachename($sql,$createdir) { + global $ADODB_CACHE, $ADODB_CACHE_DIR; + + if ($this->fetchMode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } else { + $mode = $this->fetchMode; + } + $m = md5($sql.$this->databaseType.$this->database.$this->user.$mode); + if (!$ADODB_CACHE->createdir) { + return $m; + } + if (!$createdir) { + $dir = $ADODB_CACHE->getdirname($m); + } else { + $dir = $ADODB_CACHE->createdir($m, $this->debug); + } + + return $dir.'/adodb_'.$m.'.cache'; + } + + + /** + * 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 + */ + function CacheExecute($secs2cache,$sql=false,$inputarr=false) { + global $ADODB_CACHE; + + if (empty($ADODB_CACHE)) { + $this->_CreateCache(); + } + + if (!is_numeric($secs2cache)) { + $inputarr = $sql; + $sql = $secs2cache; + $secs2cache = $this->cacheSecs; + } + + if (is_array($sql)) { + $sqlparam = $sql; + $sql = $sql[0]; + } else + $sqlparam = $sql; + + + $md5file = $this->_gencachename($sql.serialize($inputarr),true); + $err = ''; + + if ($secs2cache > 0){ + $rs = $ADODB_CACHE->readcache($md5file,$err,$secs2cache,$this->arrayClass); + $this->numCacheHits += 1; + } else { + $err='Timeout 1'; + $rs = false; + $this->numCacheMisses += 1; + } + + if (!$rs) { + // 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)"); + } + } + + $rs = $this->Execute($sqlparam,$inputarr); + + if ($rs) { + $eof = $rs->EOF; + $rs = $this->_rs2rs($rs); // read entire recordset into memory immediately + $rs->timeCreated = time(); // used by caching + $txt = _rs2serialize($rs,false,$sql); // serialize + + $ok = $ADODB_CACHE->writecache($md5file,$txt,$this->debug, $secs2cache); + if (!$ok) { + if ($ok === false) { + $em = 'Cache write error'; + $en = -32000; + + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'CacheExecute', $en, $em, $md5file,$sql,$this); + } + } else { + $em = 'Cache file locked warning'; + $en = -32001; + // do not call error handling for just a warning + } + + if ($this->debug) { + ADOConnection::outp( " ".$em); + } + } + if ($rs->EOF && !$eof) { + $rs->MoveFirst(); + //$rs = csv2rs($md5file,$err); + $rs->connection = $this; // Pablo suggestion + } + + } else if (!$this->memCache) { + $ADODB_CACHE->flushcache($md5file); + } + } else { + $this->_errorMsg = ''; + $this->_errorCode = 0; + + if ($this->fnCacheExecute) { + $fn = $this->fnCacheExecute; + $fn($this, $secs2cache, $sql, $inputarr); + } + // ok, set cached object found + $rs->connection = $this; // Pablo suggestion + if ($this->debug){ + if ($this->debug == 99) { + adodb_backtrace(); + } + $inBrowser = isset($_SERVER['HTTP_USER_AGENT']); + $ttl = $rs->timeCreated + $secs2cache - time(); + $s = is_array($sql) ? $sql[0] : $sql; + if ($inBrowser) { + $s = '<i>'.htmlspecialchars($s).'</i>'; + } + + ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]"); + } + } + return $rs; + } + + + /* + 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. + */ + function AutoExecute($table, $fields_values, $mode = 'INSERT', $where = false, $forceUpdate = true, $magicq = 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; + } + + $sql = "SELECT * FROM $table"; + $rs = $this->SelectLimit($sql, 1); + if (!$rs) { + return false; // table does not exist + } + + $rs->tableName = $table; + if ($where !== false) { + $sql .= " WHERE $where"; + } + $rs->sql = $sql; + + switch($mode) { + case 'UPDATE': + case DB_AUTOQUERY_UPDATE: + $sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate, $magicq); + break; + case 'INSERT': + case DB_AUTOQUERY_INSERT: + $sql = $this->GetInsertSQL($rs, $fields_values, $magicq); + break; + default: + $this->outp_throw("AutoExecute: Unknown mode=$mode", 'AutoExecute'); + return false; + } + return $sql && $this->Execute($sql); + } + + + /** + * 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 + * + * "Jonathan Younger" <jyounger@unilab.com> + */ + function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=null) { + global $ADODB_INCLUDED_LIB; + + // ******************************************************** + // This is here to maintain compatibility + // with older adodb versions. Sets force type to force nulls if $forcenulls is set. + 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_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq,$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. + */ + function GetInsertSQL(&$rs, $arrFields,$magicq=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') { + 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') { + $fd = fopen($path,'rb'); + if ($fd === false) { + return false; + } + $val = fread($fd,filesize($path)); + fclose($fd); + return $this->UpdateBlob($table,$column,$val,$where,$blobtype); + } + + function BlobDecode($blob) { + return $blob; + } + + function BlobEncode($blob) { + return $blob; + } + + function GetCharSet() { + return $this->charSet; + } + + function SetCharSet($charset) { + $this->charSet = $charset; + return true; + } + + function IfNull( $field, $ifNull ) { + return " CASE WHEN $field is null THEN $ifNull ELSE $field END "; + } + + function LogSQL($enable=true) { + include_once(ADODB_DIR.'/adodb-perf.inc.php'); + + if ($enable) { + $this->fnExecute = 'adodb_log_sql'; + } else { + $this->fnExecute = false; + } + + $old = $this->_logsql; + $this->_logsql = $enable; + if ($enable && !$old) { + $this->_affected = false; + } + return $old; + } + + /** + * 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'); + } + + // not the fastest implementation - quick and dirty - jlim + // for best performance, use the actual $rs->MetaType(). + function MetaType($t,$len=-1,$fieldobj=false) { + + if (empty($this->_metars)) { + $rsclass = $this->rsPrefix.$this->databaseType; + $this->_metars = new $rsclass(false,$this->fetchMode); + $this->_metars->connection = $this; + } + return $this->_metars->MetaType($t,$len,$fieldobj); + } + + + /** + * 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)) + { + case 'EN': + $this->fmtDate="'Y-m-d'"; + $this->fmtTimeStamp = "'Y-m-d H:i:s'"; + break; + + case 'US': + $this->fmtDate = "'m-d-Y'"; + $this->fmtTimeStamp = "'m-d-Y H:i:s'"; + break; + + case 'PT_BR': + case 'NL': + case 'FR': + case 'RO': + case 'IT': + $this->fmtDate="'d-m-Y'"; + $this->fmtTimeStamp = "'d-m-Y H:i:s'"; + break; + + case 'GE': + $this->fmtDate="'d.m.Y'"; + $this->fmtTimeStamp = "'d.m.Y H:i:s'"; + break; + + default: + $this->fmtDate="'Y-m-d'"; + $this->fmtTimeStamp = "'Y-m-d H:i:s'"; + break; + } + } + + /** + * GetActiveRecordsClass Performs an 'ALL' query + * + * @param mixed $class This string represents the class of the current active record + * @param mixed $table Table used by the active record object + * @param mixed $whereOrderBy Where, order, by clauses + * @param mixed $bindarr + * @param mixed $primkeyArr + * @param array $extra Query extras: limit, offset... + * @param mixed $relations Associative array: table's foreign name, "hasMany", "belongsTo" + * @access public + * @return void + */ + function GetActiveRecordsClass( + $class, $table,$whereOrderBy=false,$bindarr=false, $primkeyArr=false, + $extra=array(), + $relations=array()) + { + global $_ADODB_ACTIVE_DBS; + ## reduce overhead of adodb.inc.php -- moved to adodb-active-record.inc.php + ## if adodb-active-recordx is loaded -- should be no issue as they will probably use Find() + if (!isset($_ADODB_ACTIVE_DBS)) { + include_once(ADODB_DIR.'/adodb-active-record.inc.php'); + } + return adodb_GetActiveRecordsClass($this, $class, $table, $whereOrderBy, $bindarr, $primkeyArr, $extra, $relations); + } + + function GetActiveRecords($table,$where=false,$bindarr=false,$primkeyArr=false) { + $arr = $this->GetActiveRecordsClass('ADODB_Active_Record', $table, $where, $bindarr, $primkeyArr); + return $arr; + } + + /** + * Close Connection + */ + function Close() { + $rez = $this->_close(); + $this->_queryID = false; + $this->_connectionID = false; + return $rez; + } + + /** + * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans(). + * + * @return true if succeeded or false if database does not support transactions + */ + function BeginTrans() { + if ($this->debug) { + ADOConnection::outp("BeginTrans: Transactions not supported for this driver"); + } + return false; + } + + /* set transaction mode */ + function SetTransactionMode( $transaction_mode ) { + $transaction_mode = $this->MetaTransaction($transaction_mode, $this->dataProvider); + $this->_transmode = $transaction_mode; + } +/* +http://msdn2.microsoft.com/en-US/ms173763.aspx +http://dev.mysql.com/doc/refman/5.0/en/innodb-transaction-isolation.html +http://www.postgresql.org/docs/8.1/interactive/sql-set-transaction.html +http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_10005.htm +*/ + function MetaTransaction($mode,$db) { + $mode = strtoupper($mode); + $mode = str_replace('ISOLATION LEVEL ','',$mode); + + switch($mode) { + + case 'READ UNCOMMITTED': + switch($db) { + case 'oci8': + case 'oracle': + return 'ISOLATION LEVEL READ COMMITTED'; + default: + return 'ISOLATION LEVEL READ UNCOMMITTED'; + } + break; + + case 'READ COMMITTED': + return 'ISOLATION LEVEL READ COMMITTED'; + break; + + case 'REPEATABLE READ': + switch($db) { + case 'oci8': + case 'oracle': + return 'ISOLATION LEVEL SERIALIZABLE'; + default: + return 'ISOLATION LEVEL REPEATABLE READ'; + } + break; + + case 'SERIALIZABLE': + return 'ISOLATION LEVEL SERIALIZABLE'; + break; + + default: + return $mode; + } + } + + /** + * If database does not support transactions, always return true as data always commited + * + * @param $ok set to false to rollback transaction, true to commit + * + * @return true/false. + */ + function CommitTrans($ok=true) { + return true; + } + + + /** + * If database does not support transactions, rollbacks always fail, so return false + * + * @return true/false. + */ + function RollbackTrans() { + return false; + } + + + /** + * return the databases that the driver can connect to. + * Some databases will return an empty array. + * + * @return an array of database names. + */ + function MetaDatabases() { + global $ADODB_FETCH_MODE; + + if ($this->metaDatabasesSQL) { + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) { + $savem = $this->SetFetchMode(false); + } + + $arr = $this->GetCol($this->metaDatabasesSQL); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + return $arr; + } + + return false; + } + + /** + * List procedures or functions in an array. + * @param procedureNamePattern a procedure name pattern; must match the procedure name as it is stored in the database + * @param catalog a catalog name; must match the catalog name as it is stored in the database; + * @param schemaPattern a schema name pattern; + * + * @return array of procedures on current database. + * + * Array( + * [name_of_procedure] => Array( + * [type] => PROCEDURE or FUNCTION + * [catalog] => Catalog_name + * [schema] => Schema_name + * [remarks] => explanatory comment on the procedure + * ) + * ) + */ + function MetaProcedures($procedureNamePattern = null, $catalog = null, $schemaPattern = null) { + return false; + } + + + /** + * @param ttype can either be 'VIEW' or 'TABLE' or false. + * If false, both views and tables are returned. + * "VIEW" returns only views + * "TABLE" returns only tables + * @param showSchema returns the schema/user with the table name, eg. USER.TABLE + * @param mask is the input mask - only supported by oci8 and postgresql + * + * @return array of tables for current database. + */ + function MetaTables($ttype=false,$showSchema=false,$mask=false) { + global $ADODB_FETCH_MODE; + + if ($mask) { + return false; + } + if ($this->metaTablesSQL) { + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) { + $savem = $this->SetFetchMode(false); + } + + $rs = $this->Execute($this->metaTablesSQL); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if ($rs === false) { + return false; + } + $arr = $rs->GetArray(); + $arr2 = array(); + + if ($hast = ($ttype && isset($arr[0][1]))) { + $showt = strncmp($ttype,'T',1); + } + + for ($i=0; $i < sizeof($arr); $i++) { + if ($hast) { + if ($showt == 0) { + if (strncmp($arr[$i][1],'T',1) == 0) { + $arr2[] = trim($arr[$i][0]); + } + } else { + if (strncmp($arr[$i][1],'V',1) == 0) { + $arr2[] = trim($arr[$i][0]); + } + } + } else + $arr2[] = trim($arr[$i][0]); + } + $rs->Close(); + return $arr2; + } + return false; + } + + + function _findschema(&$table,&$schema) { + if (!$schema && ($at = strpos($table,'.')) !== false) { + $schema = substr($table,0,$at); + $table = substr($table,$at+1); + } + } + + /** + * List columns in a database as an array of ADOFieldObjects. + * See top of file for definition of object. + * + * @param $table table name to query + * @param $normalize makes table name case-insensitive (required by some databases) + * @schema is optional database schema to use - not supported by all databases. + * + * @return array of ADOFieldObjects for current table. + */ + function MetaColumns($table,$normalize=true) { + global $ADODB_FETCH_MODE; + + if (!empty($this->metaColumnsSQL)) { + $schema = false; + $this->_findschema($table,$schema); + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) { + $savem = $this->SetFetchMode(false); + } + $rs = $this->Execute(sprintf($this->metaColumnsSQL,($normalize)?strtoupper($table):$table)); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + if ($rs === false || $rs->EOF) { + return false; + } + + $retarr = array(); + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + if (isset($rs->fields[3]) && $rs->fields[3]) { + if ($rs->fields[3]>0) { + $fld->max_length = $rs->fields[3]; + } + $fld->scale = $rs->fields[4]; + if ($fld->scale>0) { + $fld->max_length += 1; + } + } else { + $fld->max_length = $rs->fields[2]; + } + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) { + $retarr[] = $fld; + } else { + $retarr[strtoupper($fld->name)] = $fld; + } + $rs->MoveNext(); + } + $rs->Close(); + return $retarr; + } + return false; + } + + /** + * List indexes on a table as an array. + * @param table table name to query + * @param primary true to only show primary keys. Not actually used for most databases + * + * @return array of indexes on current table. Each element represents an index, and is itself an associative array. + * + * Array( + * [name_of_index] => Array( + * [unique] => true or false + * [columns] => Array( + * [0] => firstname + * [1] => lastname + * ) + * ) + * ) + */ + function MetaIndexes($table, $primary = false, $owner = false) { + return false; + } + + /** + * List columns names in a table as an array. + * @param table table name to query + * + * @return array of column names for current table. + */ + function MetaColumnNames($table, $numIndexes=false,$useattnum=false /* only for postgres */) { + $objarr = $this->MetaColumns($table); + if (!is_array($objarr)) { + return false; + } + $arr = array(); + if ($numIndexes) { + $i = 0; + if ($useattnum) { + foreach($objarr as $v) + $arr[$v->attnum] = $v->name; + + } else + foreach($objarr as $v) $arr[$i++] = $v->name; + } else + foreach($objarr as $v) $arr[strtoupper($v->name)] = $v->name; + + return $arr; + } + + /** + * 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 + */ + function Concat() { + $arr = func_get_args(); + return implode($this->concat_operator, $arr); + } + + + /** + * Converts a date "d" to a string that the database can understand. + * + * @param d a date in Unix date time format. + * + * @return date string in database date format + */ + function DBDate($d, $isfld=false) { + if (empty($d) && $d !== 0) { + return 'null'; + } + if ($isfld) { + return $d; + } + if (is_object($d)) { + return $d->format($this->fmtDate); + } + + if (is_string($d) && !is_numeric($d)) { + if ($d === 'null') { + return $d; + } + if (strncmp($d,"'",1) === 0) { + $d = _adodb_safedateq($d); + return $d; + } + if ($this->isoDates) { + return "'$d'"; + } + $d = ADOConnection::UnixDate($d); + } + + return adodb_date($this->fmtDate,$d); + } + + function BindDate($d) { + $d = $this->DBDate($d); + if (strncmp($d,"'",1)) { + return $d; + } + + return substr($d,1,strlen($d)-2); + } + + function BindTimeStamp($d) { + $d = $this->DBTimeStamp($d); + if (strncmp($d,"'",1)) { + return $d; + } + + return substr($d,1,strlen($d)-2); + } + + + /** + * Converts a timestamp "ts" to a string that the database can understand. + * + * @param ts a timestamp in Unix date time format. + * + * @return timestamp string in database timestamp format + */ + function DBTimeStamp($ts,$isfld=false) { + if (empty($ts) && $ts !== 0) { + return 'null'; + } + if ($isfld) { + return $ts; + } + if (is_object($ts)) { + return $ts->format($this->fmtTimeStamp); + } + + # strlen(14) allows YYYYMMDDHHMMSS format + if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14)) { + return adodb_date($this->fmtTimeStamp,$ts); + } + + if ($ts === 'null') { + return $ts; + } + if ($this->isoDates && strlen($ts) !== 14) { + $ts = _adodb_safedate($ts); + return "'$ts'"; + } + $ts = ADOConnection::UnixTimeStamp($ts); + return adodb_date($this->fmtTimeStamp,$ts); + } + + /** + * Also in ADORecordSet. + * @param $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 + */ + static function UnixDate($v) { + if (is_object($v)) { + // odbtp support + //( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 ) + return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year); + } + + if (is_numeric($v) && strlen($v) !== 8) { + return $v; + } + if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", $v, $rr)) { + return false; + } + + if ($rr[1] <= TIMESTAMP_FIRST_YEAR) { + return 0; + } + + // h-m-s-MM-DD-YY + return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]); + } + + + /** + * Also in ADORecordSet. + * @param $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 + */ + static function UnixTimeStamp($v) { + if (is_object($v)) { + // odbtp support + //( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 ) + return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year); + } + + if (!preg_match( + "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|", + ($v), $rr)) return false; + + if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) { + return 0; + } + + // h-m-s-MM-DD-YY + if (!isset($rr[5])) { + return adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]); + } + return @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]); + } + + /** + * 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() + * + * @return a date formated as user desires + */ + function UserDate($v,$fmt='Y-m-d',$gmt=false) { + $tt = $this->UnixDate($v); + + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) { + return $v; + } else if ($tt == 0) { + return $this->emptyDate; + } else if ($tt == -1) { + // pre-TIMESTAMP_FIRST_YEAR + } + + return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt); + + } + + /** + * + * @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() + * + * @return a timestamp formated as user desires + */ + function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) { + if (!isset($v)) { + return $this->emptyTimeStamp; + } + # strlen(14) allows YYYYMMDDHHMMSS format + if (is_numeric($v) && strlen($v)<14) { + return ($gmt) ? adodb_gmdate($fmt,$v) : adodb_date($fmt,$v); + } + $tt = $this->UnixTimeStamp($v); + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) { + return $v; + } + if ($tt == 0) { + return $this->emptyTimeStamp; + } + return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt); + } + + function escape($s,$magic_quotes=false) { + return $this->addq($s,$magic_quotes); + } + + /** + * 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); + } + } + + /** + * 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()); + * + * @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 quoted string to be sent back to database + */ + 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)."'"; + } + + // 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(). + * + */ + 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'); + } + 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; + } + + + /** + * 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; + } + + /** + * 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 array|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 occured. + * + * @param array|null $old Optional. Previously stored return value of error_get_last(). + * + * @return string The error message if a new error has occured + * or an empty string if no (new) errors have occured.. + */ + protected function getChangedErrorMsg($old = null) { + $new = error_get_last(); + + if (is_null($new)) { + // No error has occured 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; + } + } + + if ($changed === true) { + return $new['message']; + } + + return ''; + } + +} // end class ADOConnection + + + + //============================================================================================== + // CLASS ADOFetchObj + //============================================================================================== + + /** + * Internal placeholder for record objects. Used by ADORecordSet->FetchObj(). + */ + class ADOFetchObj { + }; + + //============================================================================================== + // CLASS ADORecordSet_empty + //============================================================================================== + + class ADODB_Iterator_empty implements Iterator { + + private $rs; + + function __construct($rs) { + $this->rs = $rs; + } + + function rewind() {} + + function valid() { + return !$this->rs->EOF; + } + + function key() { + return false; + } + + function current() { + return false; + } + + function next() {} + + function __call($func, $params) { + return call_user_func_array(array($this->rs, $func), $params); + } + + function hasMore() { + return false; + } + + } + + + /** + * 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 $fields = false; + var $connection = false; + + function RowCount() { + return 0; + } + + function RecordCount() { + return 0; + } + + function PO_RecordCount() { + return 0; + } + + function Close() { + return true; + } + + function FetchRow() { + return false; + } + + function FieldCount() { + return 0; + } + + function Init() {} + + function getIterator() { + return new ADODB_Iterator_empty($this); + } + + function GetAssoc() { + return array(); + } + + function GetArray() { + return array(); + } + + function GetAll() { + return array(); + } + + function GetArrayLimit() { + return array(); + } + + function GetRows() { + return array(); + } + + function GetRowAssoc() { + return array(); + } + + function MaxRecordCount() { + return 0; + } + + function NumRows() { + return 0; + } + + function NumCols() { + return 0; + } + } + + //============================================================================================== + // DATE AND TIME FUNCTIONS + //============================================================================================== + if (!defined('ADODB_DATE_VERSION')) { + include(ADODB_DIR.'/adodb-time.inc.php'); + } + + //============================================================================================== + // CLASS ADORecordSet + //============================================================================================== + + class ADODB_Iterator implements Iterator { + + private $rs; + + function __construct($rs) { + $this->rs = $rs; + } + + function rewind() { + $this->rs->MoveFirst(); + } + + function valid() { + return !$this->rs->EOF; + } + + function key() { + return $this->rs->_currentRow; + } + + function current() { + return $this->rs->fields; + } + + function next() { + $this->rs->MoveNext(); + } + + function __call($func, $params) { + return call_user_func_array(array($this->rs, $func), $params); + } + + function hasMore() { + return !$this->rs->EOF; + } + + } + + + /** + * 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 $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. + var $canSeek = false; /// indicates that seek is supported + var $sql; /// sql text + var $EOF = false; /// Indicates that the current record position is after the last record in a Recordset object. + + var $emptyTimeStamp = ' '; /// what to display when $time==0 + var $emptyDate = ' '; /// what to display when $time==0 + var $debug = false; + var $timeCreated=0; /// datetime in Unix format rs created -- for cached recordsets + + var $bind = false; /// used by Fields() to hold array - should be private? + var $fetchMode; /// default fetch mode + var $connection = false; /// the parent connection + + /** + * 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 $_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 */ + var $_obj; /** Used by FetchObj */ + var $_names; /** Used by FetchObj */ + + var $_currentPage = -1; /** Added by Iván Oliva to implement recordset pagination */ + var $_atFirstPage = false; /** Added by Iván Oliva to implement recordset pagination */ + var $_atLastPage = false; /** Added by Iván Oliva to implement recordset pagination */ + var $_lastPageNo = -1; + var $_maxRecordCount = 0; + var $datetime = false; + + /** + * Constructor + * + * @param queryID this is the queryID returned by ADOConnection->_query() + * + */ + function __construct($queryID) { + $this->_queryID = $queryID; + } + + function __destruct() { + $this->Close(); + } + + function getIterator() { + return new ADODB_Iterator($this); + } + + /* this is experimental - i don't really know what to return... */ + function __toString() { + include_once(ADODB_DIR.'/toexport.inc.php'); + return _adodb_export($this,',',',',false,true); + } + + function Init() { + if ($this->_inited) { + return; + } + $this->_inited = true; + if ($this->_queryID) { + @$this->_initrs(); + } else { + $this->_numOfRows = 0; + $this->_numOfFields = 0; + } + if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) { + $this->_currentRow = 0; + if ($this->EOF = ($this->_fetch() === false)) { + $this->_numOfRows = 0; // _numOfRows could be -1 + } + } else { + $this->EOF = true; + } + } + + + /** + * 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. + * + * @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. + * + * @return HTML + * + * changes by glen.davies@cce.ac.nz to support multiple hilited items + */ + function GetMenu($name,$defstr='',$blank1stItem=true,$multiple=false, + $size=0, $selectAttr='',$compareFields0=true) + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) { + include(ADODB_DIR.'/adodb-lib.inc.php'); + } + return _adodb_getmenu($this, $name,$defstr,$blank1stItem,$multiple, + $size, $selectAttr,$compareFields0); + } + + + + /** + * 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. + * + */ + 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='') + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) { + include(ADODB_DIR.'/adodb-lib.inc.php'); + } + return _adodb_getmenu_gp($this, $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. + * + * @return an 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) { + $results[] = $this->fields; + $this->MoveNext(); + $cnt++; + } + return $results; + } + + function GetAll($nRows = -1) { + $arr = $this->GetArray($nRows); + return $arr; + } + + /* + * Some databases allow multiple recordsets to be returned. This function + * will return true if there is a next recordset, or false if no more. + */ + function NextRecordSet() { + return false; + } + + /** + * 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 + * + * @return an array indexed by the rows (0-based) from the recordset + */ + function GetArrayLimit($nrows,$offset=-1) { + if ($offset <= 0) { + $arr = $this->GetArray($nrows); + return $arr; + } + + $this->Move($offset); + + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + + /** + * Synonym for GetArray() for compatibility with ADO. + * + * @param [nRows] is the number of rows to return. -1 means every row. + * + * @return an array indexed by the rows (0-based) from the recordset + */ + function GetRows($nRows = -1) { + $arr = $this->GetArray($nRows); + return $arr; + } + + /** + * 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. + * + * @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 [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 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; + } + + // 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); + + $results = array(); + + 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(); + + foreach($keys as $key) { + $sliced_array[$key] = $this->fields[$key]; + } + + $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(); + + foreach($keys as $key) { + $sliced_array[$key] = $this->fields[$key]; + } + + $results[trim(reset($this->fields))] = $sliced_array; + $this->MoveNext(); + } + } + } + } 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(); + } + } + } + } + + $ref = $results; # workaround accelerator incompat with PHP 4.4 :( + return $ref; + } + + + /** + * + * @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() + * + * @return a timestamp formated as user desires + */ + function UserTimeStamp($v,$fmt='Y-m-d H:i:s') { + if (is_numeric($v) && strlen($v)<14) { + return adodb_date($fmt,$v); + } + $tt = $this->UnixTimeStamp($v); + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) { + return $v; + } + if ($tt === 0) { + return $this->emptyTimeStamp; + } + return adodb_date($fmt,$tt); + } + + + /** + * @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() + * + * @return a date formated as user desires + */ + function UserDate($v,$fmt='Y-m-d') { + $tt = $this->UnixDate($v); + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) { + return $v; + } else if ($tt == 0) { + return $this->emptyDate; + } else if ($tt == -1) { + // pre-TIMESTAMP_FIRST_YEAR + } + return adodb_date($fmt,$tt); + } + + + /** + * @param $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 + */ + static function UnixDate($v) { + return ADOConnection::UnixDate($v); + } + + + /** + * @param $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 + */ + static function UnixTimeStamp($v) { + return ADOConnection::UnixTimeStamp($v); + } + + + /** + * PEAR DB Compat - do not use internally + */ + function Free() { + return $this->Close(); + } + + + /** + * PEAR DB compat, number of rows + */ + function NumRows() { + return $this->_numOfRows; + } + + + /** + * PEAR DB compat, number of cols + */ + 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 + */ + function FetchRow() { + if ($this->EOF) { + return false; + } + $arr = $this->fields; + $this->_currentRow++; + if (!$this->_fetch()) { + $this->EOF = true; + } + return $arr; + } + + + /** + * Fetch a row, returning PEAR_Error if no more rows. + * This is PEAR DB compat mode. + * + * @return DB_OK or error object + */ + function FetchInto(&$arr) { + if ($this->EOF) { + return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false; + } + $arr = $this->fields; + $this->MoveNext(); + return 1; // DB_OK + } + + + /** + * Move to the first row in the recordset. Many databases do NOT support this. + * + * @return true or false + */ + function MoveFirst() { + if ($this->_currentRow == 0) { + return true; + } + return $this->Move(0); + } + + + /** + * Move to the last row in the recordset. + * + * @return true or false + */ + function MoveLast() { + if ($this->_numOfRows >= 0) { + return $this->Move($this->_numOfRows-1); + } + if ($this->EOF) { + return false; + } + while (!$this->EOF) { + $f = $this->fields; + $this->MoveNext(); + } + $this->fields = $f; + $this->EOF = false; + return true; + } + + + /** + * Move to next record in the recordset. + * + * @return true if there still rows available, or false if there are no more rows (EOF). + */ + function MoveNext() { + if (!$this->EOF) { + $this->_currentRow++; + if ($this->_fetch()) { + return true; + } + } + $this->EOF = true; + /* -- tested error handling when scrolling cursor -- seems useless. + $conn = $this->connection; + if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) { + $fn = $conn->raiseErrorFn; + $fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database); + } + */ + return false; + } + + + /** + * 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) + * + * @return true if there still rows available, or false if there are no more rows (EOF). + */ + function Move($rowNumber = 0) { + $this->EOF = false; + if ($rowNumber == $this->_currentRow) { + return true; + } + if ($rowNumber >= $this->_numOfRows) { + if ($this->_numOfRows != -1) { + $rowNumber = $this->_numOfRows-2; + } + } + + if ($rowNumber < 0) { + $this->EOF = true; + return false; + } + + if ($this->canSeek) { + if ($this->_seek($rowNumber)) { + $this->_currentRow = $rowNumber; + if ($this->_fetch()) { + return true; + } + } else { + $this->EOF = true; + return false; + } + } else { + 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++; + + if (!$this->_fetch()) { + $this->EOF = true; + } + } + } + return !($this->EOF); + } + + $this->fields = false; + $this->EOF = true; + return false; + } + + + /** + * 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 + * + * @return the value of $colname column + */ + function Fields($colname) { + return $this->fields[$colname]; + } + + /** + * Defines the function to use for table fields case conversion + * depending on ADODB_ASSOC_CASE + * @return string strtolower/strtoupper or false if no conversion needed + */ + protected function AssocCaseConvertFunction($case = ADODB_ASSOC_CASE) { + switch($case) { + case ADODB_ASSOC_CASE_UPPER: + return 'strtoupper'; + case ADODB_ASSOC_CASE_LOWER: + return 'strtolower'; + case ADODB_ASSOC_CASE_NATIVE: + default: + return false; + } + } + + /** + * Builds the bind array associating keys to recordset fields + * + * @param int $upper Case for the array keys, defaults to uppercase + * (see ADODB_ASSOC_CASE_xxx constants) + */ + function GetAssocKeys($upper = ADODB_ASSOC_CASE) { + if ($this->bind) { + return; + } + $this->bind = array(); + + // Define case conversion function for ASSOC fetch mode + $fn_change_case = $this->AssocCaseConvertFunction($upper); + + // Build the bind array + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + + // Set the array's key + if(is_numeric($o->name)) { + // Just use the field ID + $key = $i; + } + elseif( $fn_change_case ) { + // Convert the key's case + $key = $fn_change_case($o->name); + } + else { + $key = $o->name; + } + + $this->bind[$key] = $i; + } + } + + /** + * Use associative array to get fields array for databases that do not support + * associative arrays. Submitted by Paolo S. Asioli paolo.asioli#libero.it + * + * @param int $upper Case for the array keys, defaults to uppercase + * (see ADODB_ASSOC_CASE_xxx constants) + */ + function GetRowAssoc($upper = ADODB_ASSOC_CASE) { + $record = array(); + $this->GetAssocKeys($upper); + + foreach($this->bind as $k => $v) { + if( array_key_exists( $v, $this->fields ) ) { + $record[$k] = $this->fields[$v]; + } elseif( array_key_exists( $k, $this->fields ) ) { + $record[$k] = $this->fields[$k]; + } else { + # This should not happen... trigger error ? + $record[$k] = null; + } + } + return $record; + } + + /** + * Clean up recordset + * + * @return true or false + */ + function Close() { + // free connection object - this seems to globally free the object + // and not merely the reference, so don't do this... + // $this->connection = false; + if (!$this->_closed) { + $this->_closed = true; + return $this->_close(); + } else + return true; + } + + /** + * synonyms RecordCount and RowCount + * + * @return the number of rows or -1 if this is not supported + */ + 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. + */ + function MaxRecordCount() { + return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount(); + } + + /** + * synonyms RecordCount and RowCount + * + * @return the number of rows or -1 if this is not supported + */ + function RowCount() { + return $this->_numOfRows; + } + + + /** + * Portable RecordCount. Pablo Roca <pabloroca@mvps.org> + * + * @return the 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. + */ + function PO_RecordCount($table="", $condition="") { + + $lnumrows = $this->_numOfRows; + // the database doesn't support native recordcount, so we do a workaround + if ($lnumrows == -1 && $this->connection) { + IF ($table) { + if ($condition) { + $condition = " WHERE " . $condition; + } + $resultrows = $this->connection->Execute("SELECT COUNT(*) FROM $table $condition"); + if ($resultrows) { + $lnumrows = reset($resultrows->fields); + } + } + } + return $lnumrows; + } + + + /** + * @return the current row in the recordset. If at EOF, will return the last row. 0-based. + */ + function CurrentRow() { + return $this->_currentRow; + } + + /** + * synonym for CurrentRow -- for ADO compat + * + * @return the current row in the recordset. If at EOF, will return the last row. 0-based. + */ + function AbsolutePosition() { + return $this->_currentRow; + } + + /** + * @return the number of columns in the recordset. Some databases will set this to 0 + * if no records are returned, others will return the number of columns in the query. + */ + function FieldCount() { + return $this->_numOfFields; + } + + + /** + * Get the ADOFieldObject of a specific column. + * + * @param fieldoffset is the column position to access(0-based). + * + * @return the ADOFieldObject for that column, or false. + */ + function FetchField($fieldoffset = -1) { + // must be defined by child class + + return false; + } + + /** + * Get the ADOFieldObjects of all columns in an array. + * + */ + function FieldTypesArray() { + $arr = array(); + for ($i=0, $max=$this->_numOfFields; $i < $max; $i++) + $arr[] = $this->FetchField($i); + return $arr; + } + + /** + * 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 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) { + if (empty($this->_obj)) { + $this->_obj = new ADOFetchObj(); + $this->_names = array(); + for ($i=0; $i <$this->_numOfFields; $i++) { + $f = $this->FetchField($i); + $this->_names[] = $f->name; + } + } + $i = 0; + if (PHP_VERSION >= 5) { + $o = clone($this->_obj); + } else { + $o = $this->_obj; + } + + for ($i=0; $i <$this->_numOfFields; $i++) { + $name = $this->_names[$i]; + if ($isupper) { + $n = strtoupper($name); + } else { + $n = $name; + } + + $o->$n = $this->Fields($name); + } + return $o; + } + + /** + * 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 + */ + function FetchNextObj() { + $o = $this->FetchNextObject(false); + return $o; + } + + + /** + * 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) { + $o = false; + if ($this->_numOfRows != 0 && !$this->EOF) { + $o = $this->FetchObject($isupper); + $this->_currentRow++; + if ($this->_fetch()) { + return $o; + } + } + $this->EOF = true; + return $o; + } + + /** + * 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: + * + * @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). + * + * @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 + * + * + */ + function MetaType($t,$len=-1,$fieldobj=false) { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + // changed in 2.32 to hashing instead of switch stmt for speed... + static $typeMap = array( + 'VARCHAR' => 'C', + 'VARCHAR2' => 'C', + 'CHAR' => 'C', + 'C' => 'C', + 'STRING' => 'C', + 'NCHAR' => 'C', + 'NVARCHAR' => 'C', + 'VARYING' => 'C', + 'BPCHAR' => 'C', + 'CHARACTER' => 'C', + 'INTERVAL' => 'C', # Postgres + 'MACADDR' => 'C', # postgres + 'VAR_STRING' => 'C', # mysql + ## + 'LONGCHAR' => 'X', + 'TEXT' => 'X', + 'NTEXT' => 'X', + 'M' => 'X', + 'X' => 'X', + 'CLOB' => 'X', + 'NCLOB' => 'X', + 'LVARCHAR' => 'X', + ## + 'BLOB' => 'B', + 'IMAGE' => 'B', + 'BINARY' => 'B', + 'VARBINARY' => 'B', + 'LONGBINARY' => 'B', + 'B' => 'B', + ## + 'YEAR' => 'D', // mysql + 'DATE' => 'D', + 'D' => 'D', + ## + 'UNIQUEIDENTIFIER' => 'C', # MS SQL Server + ## + 'SMALLDATETIME' => 'T', + 'TIME' => 'T', + 'TIMESTAMP' => 'T', + 'DATETIME' => 'T', + 'DATETIME2' => 'T', + 'TIMESTAMPTZ' => 'T', + 'T' => 'T', + 'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql + ## + 'BOOL' => 'L', + 'BOOLEAN' => 'L', + 'BIT' => 'L', + 'L' => 'L', + ## + 'COUNTER' => 'R', + 'R' => 'R', + 'SERIAL' => 'R', // ifx + 'INT IDENTITY' => 'R', + ## + 'INT' => 'I', + 'INT2' => 'I', + 'INT4' => 'I', + 'INT8' => 'I', + 'INTEGER' => 'I', + 'INTEGER UNSIGNED' => 'I', + 'SHORT' => 'I', + 'TINYINT' => 'I', + 'SMALLINT' => 'I', + 'I' => 'I', + ## + 'LONG' => 'N', // interbase is numeric, oci8 is blob + 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers + 'DECIMAL' => 'N', + 'DEC' => 'N', + 'REAL' => 'N', + 'DOUBLE' => 'N', + 'DOUBLE PRECISION' => 'N', + 'SMALLFLOAT' => 'N', + 'FLOAT' => 'N', + 'NUMBER' => 'N', + 'NUM' => 'N', + 'NUMERIC' => 'N', + 'MONEY' => 'N', + + ## informix 9.2 + 'SQLINT' => 'I', + 'SQLSERIAL' => 'I', + 'SQLSMINT' => 'I', + 'SQLSMFLOAT' => 'N', + 'SQLFLOAT' => 'N', + 'SQLMONEY' => 'N', + 'SQLDECIMAL' => 'N', + 'SQLDATE' => 'D', + 'SQLVCHAR' => 'C', + 'SQLCHAR' => 'C', + 'SQLDTIME' => 'T', + 'SQLINTERVAL' => 'N', + 'SQLBYTES' => 'B', + 'SQLTEXT' => 'X', + ## informix 10 + "SQLINT8" => 'I8', + "SQLSERIAL8" => 'I8', + "SQLNCHAR" => 'C', + "SQLNVCHAR" => 'C', + "SQLLVARCHAR" => 'X', + "SQLBOOL" => 'L' + ); + + $tmap = false; + $t = strtoupper($t); + $tmap = (isset($typeMap[$t])) ? $typeMap[$t] : 'N'; + switch ($tmap) { + case 'C': + // is the char field is too long, return as text field... + if ($this->blobSize >= 0) { + if ($len > $this->blobSize) { + return 'X'; + } + } else if ($len > 250) { + return 'X'; + } + return 'C'; + + case 'I': + if (!empty($fieldobj->primary_key)) { + return 'R'; + } + return 'I'; + + case false: + return 'N'; + + case 'B': + if (isset($fieldobj->binary)) { + return ($fieldobj->binary) ? 'B' : 'X'; + } + return 'B'; + + case 'D': + if (!empty($this->connection) && !empty($this->connection->datetime)) { + return 'T'; + } + return 'D'; + + default: + if ($t == 'LONG' && $this->dataProvider == 'oci8') { + return 'B'; + } + return $tmap; + } + } + + /** + * Convert case of field names associative array, if needed + * @return void + */ + protected function _updatefields() + { + if( empty($this->fields)) { + return; + } + + // Determine case conversion function + $fn_change_case = $this->AssocCaseConvertFunction(); + if(!$fn_change_case) { + // No conversion needed + return; + } + + $arr = array(); + + // Change the case + foreach($this->fields as $k => $v) { + if (!is_integer($k)) { + $k = $fn_change_case($k); + } + $arr[$k] = $v; + } + $this->fields = $arr; + } + + function _close() {} + + /** + * set/returns the current recordset page when paginating + */ + function AbsolutePage($page=-1) { + if ($page != -1) { + $this->_currentPage = $page; + } + return $this->_currentPage; + } + + /** + * set/returns the status of the atFirstPage flag when paginating + */ + function AtFirstPage($status=false) { + if ($status != false) { + $this->_atFirstPage = $status; + } + return $this->_atFirstPage; + } + + function LastPageNo($page = false) { + if ($page != false) { + $this->_lastPageNo = $page; + } + return $this->_lastPageNo; + } + + /** + * set/returns the status of the atLastPage flag when paginating + */ + function AtLastPage($status=false) { + if ($status != false) { + $this->_atLastPage = $status; + } + return $this->_atLastPage; + } + +} // end class ADORecordSet + + //============================================================================================== + // CLASS ADORecordSet_array + //============================================================================================== + + /** + * This class encapsulates the concept of a recordset created in memory + * as an array. This is useful for the creation of cached recordsets. + * + * Note that the constructor is different from the standard ADORecordSet + */ + class ADORecordSet_array extends ADORecordSet + { + var $databaseType = 'array'; + + var $_array; // holds the 2-dimensional data array + var $_types; // the array of types of each column (C B I L M) + var $_colnames; // names of each column in array + var $_skiprow1; // skip 1st row because it holds column names + var $_fieldobjects; // holds array of field objects + var $canSeek = true; + var $affectedrows = false; + var $insertid = false; + var $sql = ''; + var $compat = false; + + /** + * Constructor + */ + function __construct($fakeid=1) { + 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 + $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. + * @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 + * $array should not hold the column names. + */ + function InitArray($array,$typearr,$colnames=false) { + $this->_array = $array; + $this->_types = $typearr; + if ($colnames) { + $this->_skiprow1 = false; + $this->_colnames = $colnames; + } else { + $this->_skiprow1 = true; + $this->_colnames = $array[0]; + } + $this->Init(); + } + /** + * Setup the Array and datatype file objects + * + * @param array is a 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. + */ + function InitArrayFields(&$array,&$fieldarr) { + $this->_array = $array; + $this->_skiprow1= false; + if ($fieldarr) { + $this->_fieldobjects = $fieldarr; + } + $this->Init(); + } + + function GetArray($nRows=-1) { + if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) { + return $this->_array; + } else { + $arr = ADORecordSet::GetArray($nRows); + return $arr; + } + } + + function _initrs() { + $this->_numOfRows = sizeof($this->_array); + if ($this->_skiprow1) { + $this->_numOfRows -= 1; + } + + $this->_numOfFields = (isset($this->_fieldobjects)) + ? sizeof($this->_fieldobjects) + : sizeof($this->_types); + } + + /* Use associative array to get fields array */ + function Fields($colname) { + $mode = isset($this->adodbFetchMode) ? $this->adodbFetchMode : $this->fetchMode; + + if ($mode & ADODB_FETCH_ASSOC) { + if (!isset($this->fields[$colname]) && !is_null($this->fields[$colname])) { + $colname = strtolower($colname); + } + return $this->fields[$colname]; + } + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function FetchField($fieldOffset = -1) { + if (isset($this->_fieldobjects)) { + return $this->_fieldobjects[$fieldOffset]; + } + $o = new ADOFieldObject(); + $o->name = $this->_colnames[$fieldOffset]; + $o->type = $this->_types[$fieldOffset]; + $o->max_length = -1; // length not known + + return $o; + } + + function _seek($row) { + if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) { + $this->_currentRow = $row; + if ($this->_skiprow1) { + $row += 1; + } + $this->fields = $this->_array[$row]; + return true; + } + return false; + } + + function MoveNext() { + if (!$this->EOF) { + $this->_currentRow++; + + $pos = $this->_currentRow; + + if ($this->_numOfRows <= $pos) { + if (!$this->compat) { + $this->fields = false; + } + } else { + if ($this->_skiprow1) { + $pos += 1; + } + $this->fields = $this->_array[$pos]; + return true; + } + $this->EOF = true; + } + + return false; + } + + function _fetch() { + $pos = $this->_currentRow; + + if ($this->_numOfRows <= $pos) { + if (!$this->compat) { + $this->fields = false; + } + return false; + } + if ($this->_skiprow1) { + $pos += 1; + } + $this->fields = $this->_array[$pos]; + return true; + } + + function _close() { + return true; + } + + } // ADORecordSet_array + + //============================================================================================== + // HELPER FUNCTIONS + //============================================================================================== + + /** + * Synonym for ADOLoadCode. Private function. Do not use. + * + * @deprecated + */ + function ADOLoadDB($dbType) { + return ADOLoadCode($dbType); + } + + /** + * Load the code for a specific database driver. Private function. Do not use. + */ + function ADOLoadCode($dbType) { + global $ADODB_LASTDB; + + if (!$dbType) { + return false; + } + $db = strtolower($dbType); + switch ($db) { + case 'ado': + if (PHP_VERSION >= 5) { + $db = 'ado5'; + } + $class = 'ado'; + break; + + case 'ifx': + case 'maxsql': + $class = $db = 'mysqlt'; + break; + + case 'pgsql': + case 'postgres': + $class = $db = 'postgres8'; + break; + + default: + $class = $db; break; + } + + $file = "drivers/adodb-$db.inc.php"; + @include_once(ADODB_DIR . '/' . $file); + $ADODB_LASTDB = $class; + if (class_exists("ADODB_" . $class)) { + return $class; + } + + //ADOConnection::outp(adodb_pr(get_declared_classes(),true)); + if (!file_exists($file)) { + ADOConnection::outp("Missing file: $file"); + } else { + ADOConnection::outp("Syntax error in file: $file"); + } + return false; + } + + /** + * synonym for ADONewConnection for people like me who cannot remember the correct name + */ + function NewADOConnection($db='') { + $tmp = ADONewConnection($db); + return $tmp; + } + + /** + * Instantiate a new Connection class for a specific database driver. + * + * @param [db] is the 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. + */ + function ADONewConnection($db='') { + global $ADODB_NEWCONNECTION, $ADODB_LASTDB; + + if (!defined('ADODB_ASSOC_CASE')) { + define('ADODB_ASSOC_CASE', ADODB_ASSOC_CASE_NATIVE); + } + $errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false; + if (($at = strpos($db,'://')) !== FALSE) { + $origdsn = $db; + $fakedsn = 'fake'.substr($origdsn,$at); + if (($at2 = strpos($origdsn,'@/')) !== FALSE) { + // special handling of oracle, which might not have host + $fakedsn = str_replace('@/','@adodb-fakehost/',$fakedsn); + } + + if ((strpos($origdsn, 'sqlite')) !== FALSE && stripos($origdsn, '%2F') === FALSE) { + // special handling for SQLite, it only might have the path to the database file. + // If you try to connect to a SQLite database using a dsn + // like 'sqlite:///path/to/database', the 'parse_url' php function + // will throw you an exception with a message such as "unable to parse url" + list($scheme, $path) = explode('://', $origdsn); + $dsna['scheme'] = $scheme; + if ($qmark = strpos($path,'?')) { + $dsn['query'] = substr($path,$qmark+1); + $path = substr($path,0,$qmark); + } + $dsna['path'] = '/' . urlencode($path); + } else + $dsna = @parse_url($fakedsn); + + if (!$dsna) { + return false; + } + $dsna['scheme'] = substr($origdsn,0,$at); + if ($at2 !== FALSE) { + $dsna['host'] = ''; + } + + if (strncmp($origdsn,'pdo',3) == 0) { + $sch = explode('_',$dsna['scheme']); + if (sizeof($sch)>1) { + $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : ''; + if ($sch[1] == 'sqlite') { + $dsna['host'] = rawurlencode($sch[1].':'.rawurldecode($dsna['host'])); + } else { + $dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host'])); + } + $dsna['scheme'] = 'pdo'; + } + } + + $db = @$dsna['scheme']; + 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 (isset($dsna['query'])) { + $opt1 = explode('&',$dsna['query']); + foreach($opt1 as $k => $v) { + $arr = explode('=',$v); + $opt[$arr[0]] = isset($arr[1]) ? rawurldecode($arr[1]) : 1; + } + } else { + $opt = array(); + } + } + /* + * phptype: Database backend used in PHP (mysql, odbc etc.) + * dbsyntax: Database used with regards to SQL syntax etc. + * protocol: Communication protocol to use (tcp, unix etc.) + * hostspec: Host specification (hostname[:port]) + * database: Database to use on the DBMS server + * username: User name for login + * password: Password for login + */ + if (!empty($ADODB_NEWCONNECTION)) { + $obj = $ADODB_NEWCONNECTION($db); + + } + + if(empty($obj)) { + + if (!isset($ADODB_LASTDB)) { + $ADODB_LASTDB = ''; + } + if (empty($db)) { + $db = $ADODB_LASTDB; + } + if ($db != $ADODB_LASTDB) { + $db = ADOLoadCode($db); + } + + if (!$db) { + if (isset($origdsn)) { + $db = $origdsn; + } + if ($errorfn) { + // raise an error + $ignore = false; + $errorfn('ADONewConnection', 'ADONewConnection', -998, + "could not load the database driver for '$db'", + $db,false,$ignore); + } else { + ADOConnection::outp( "<p>ADONewConnection: Unable to load database driver '$db'</p>",false); + } + return false; + } + + $cls = 'ADODB_'.$db; + if (!class_exists($cls)) { + adodb_backtrace(); + return false; + } + + $obj = new $cls(); + } + + # constructor should not fail + if ($obj) { + if ($errorfn) { + $obj->raiseErrorFn = $errorfn; + } + if (isset($dsna)) { + if (isset($dsna['port'])) { + $obj->port = $dsna['port']; + } + foreach($opt as $k => $v) { + switch(strtolower($k)) { + case 'new': + $nconnect = true; $persist = true; break; + case 'persist': + case 'persistent': $persist = $v; break; + case 'debug': $obj->debug = (integer) $v; break; + #ibase + case 'role': $obj->role = $v; break; + case 'dialect': $obj->dialect = (integer) $v; break; + case 'charset': $obj->charset = $v; $obj->charSet=$v; break; + case 'buffers': $obj->buffers = $v; break; + case 'fetchmode': $obj->SetFetchMode($v); break; + #ado + case 'charpage': $obj->charPage = $v; break; + #mysql, mysqli + case 'clientflags': $obj->clientFlags = $v; break; + #mysql, mysqli, postgres + case 'port': $obj->port = $v; break; + #mysqli + case 'socket': $obj->socket = $v; break; + #oci8 + case 'nls_date_format': $obj->NLS_DATE_FORMAT = $v; break; + case 'cachesecs': $obj->cacheSecs = $v; break; + case 'memcache': + $varr = explode(':',$v); + $vlen = sizeof($varr); + if ($vlen == 0) { + break; + } + $obj->memCache = true; + $obj->memCacheHost = explode(',',$varr[0]); + if ($vlen == 1) { + break; + } + $obj->memCachePort = $varr[1]; + if ($vlen == 2) { + break; + } + $obj->memCacheCompress = $varr[2] ? true : false; + break; + } + } + if (empty($persist)) { + $ok = $obj->Connect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']); + } else if (empty($nconnect)) { + $ok = $obj->PConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']); + } else { + $ok = $obj->NConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']); + } + + if (!$ok) { + return false; + } + } + } + return $obj; + } + + + + // $perf == true means called by NewPerfMonitor(), otherwise for data dictionary + function _adodb_getdriver($provider,$drivername,$perf=false) { + switch ($provider) { + case 'odbtp': + if (strncmp('odbtp_',$drivername,6)==0) { + return substr($drivername,6); + } + case 'odbc' : + if (strncmp('odbc_',$drivername,5)==0) { + return substr($drivername,5); + } + case 'ado' : + if (strncmp('ado_',$drivername,4)==0) { + return substr($drivername,4); + } + case 'native': + break; + default: + return $provider; + } + + switch($drivername) { + case 'mysqlt': + case 'mysqli': + $drivername='mysql'; + break; + case 'postgres7': + case 'postgres8': + $drivername = 'postgres'; + break; + case 'firebird15': + $drivername = 'firebird'; + break; + case 'oracle': + $drivername = 'oci8'; + break; + case 'access': + if ($perf) { + $drivername = ''; + } + break; + case 'db2' : + case 'sapdb' : + break; + default: + $drivername = 'generic'; + break; + } + return $drivername; + } + + function NewPerfMonitor(&$conn) { + $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType,true); + if (!$drivername || $drivername == 'generic') { + return false; + } + include_once(ADODB_DIR.'/adodb-perf.inc.php'); + @include_once(ADODB_DIR."/perf/perf-$drivername.inc.php"); + $class = "Perf_$drivername"; + if (!class_exists($class)) { + return false; + } + $perf = new $class($conn); + + return $perf; + } + + function NewDataDictionary(&$conn,$drivername=false) { + if (!$drivername) { + $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType); + } + + include_once(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-datadict.inc.php'); + $path = ADODB_DIR."/datadict/datadict-$drivername.inc.php"; + + if (!file_exists($path)) { + ADOConnection::outp("Dictionary driver '$path' not available"); + return false; + } + include_once($path); + $class = "ADODB2_$drivername"; + $dict = new $class(); + $dict->dataProvider = $conn->dataProvider; + $dict->connection = $conn; + $dict->upperName = strtoupper($drivername); + $dict->quote = $conn->nameQuote; + if (!empty($conn->_connectionID)) { + $dict->serverInfo = $conn->ServerInfo(); + } + + return $dict; + } + + + + /* + Perform a print_r, with pre tags for better formatting. + */ + function adodb_pr($var,$as_string=false) { + if ($as_string) { + ob_start(); + } + + if (isset($_SERVER['HTTP_USER_AGENT'])) { + echo " <pre>\n";print_r($var);echo "</pre>\n"; + } else { + print_r($var); + } + + if ($as_string) { + $s = ob_get_contents(); + ob_end_clean(); + return $s; + } + } + + /* + 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 + */ + 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'); + } + return _adodb_backtrace($printOrArr,$levels,0,$ishtml); + } + +} |