Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
/**
3
 * ADOdb Library main include file.
4
 *
5
 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
6
 *
7
 * @package ADOdb
8
 * @link https://adodb.org Project's web site and documentation
9
 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
10
 *
11
 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
12
 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
13
 * any later version. This means you can use it in proprietary products.
14
 * See the LICENSE.md file distributed with this source code for details.
15
 * @license BSD-3-Clause
16
 * @license LGPL-2.1-or-later
17
 *
18
 * @copyright 2000-2013 John Lim
19
 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
20
 */
21
 
22
if (!defined('_ADODB_LAYER')) {
23
	define('_ADODB_LAYER',1);
24
 
25
	// The ADOdb extension is no longer maintained and effectively unsupported
26
	// since v5.04. The library will not function properly if it is present.
27
	if(defined('ADODB_EXTENSION')) {
28
		$msg = "Unsupported ADOdb Extension (v" . ADODB_EXTENSION . ") detected! "
29
			. "Disable it to use ADOdb";
30
 
31
		$errorfn = defined('ADODB_ERROR_HANDLER') ? ADODB_ERROR_HANDLER : false;
32
		if ($errorfn) {
33
			$conn = false;
34
			$errorfn('ADOdb', basename(__FILE__), -9999, $msg, null, null, $conn);
35
		} else {
36
			die($msg . PHP_EOL);
37
		}
38
	}
39
 
40
	//==============================================================================================
41
	// CONSTANT DEFINITIONS
42
	//==============================================================================================
43
 
44
 
45
	/**
46
	 * Set ADODB_DIR to the directory where this file resides...
47
	 * This constant was formerly called $ADODB_RootPath
48
	 */
49
	if (!defined('ADODB_DIR')) {
50
		define('ADODB_DIR',dirname(__FILE__));
51
	}
52
 
53
	//==============================================================================================
54
	// GLOBAL VARIABLES
55
	//==============================================================================================
56
 
57
	GLOBAL
58
		$ADODB_vers,		// database version
59
		$ADODB_COUNTRECS,	// count number of records returned - slows down query
60
		$ADODB_CACHE_DIR,	// directory to cache recordsets
61
		$ADODB_CACHE,
62
		$ADODB_CACHE_CLASS,
63
		$ADODB_COMPAT_FETCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF
64
		$ADODB_FETCH_MODE,	// DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default...
65
		$ADODB_GETONE_EOF,
66
		$ADODB_QUOTE_FIELDNAMES; // Allows you to force quotes (backticks) around field names in queries generated by getinsertsql and getupdatesql.
67
 
68
	//==============================================================================================
69
	// GLOBAL SETUP
70
	//==============================================================================================
71
 
72
	/*********************************************************
73
	 * Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3).
74
	 * Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi
75
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:adodb_force_type
76
	 *
77
	 * 0 = ignore empty fields. All empty fields in array are ignored.
78
	 * 1 = force null. All empty, php null and string 'null' fields are
79
	 *     changed to sql NULL values.
80
	 * 2 = force empty. All empty, php null and string 'null' fields are
81
	 *     changed to sql empty '' or 0 values.
82
	 * 3 = force value. Value is left as it is. Php null and string 'null'
83
	 *     are set to sql NULL values and empty fields '' are set to empty '' sql values.
84
	 * 4 = force value. Like 1 but numeric empty fields are set to zero.
85
	 */
86
		define('ADODB_FORCE_IGNORE',0);
87
		define('ADODB_FORCE_NULL',1);
88
		define('ADODB_FORCE_EMPTY',2);
89
		define('ADODB_FORCE_VALUE',3);
90
		define('ADODB_FORCE_NULL_AND_ZERO',4);
91
	// ********************************************************
92
 
93
 
94
	/**
95
	 * Constants for returned values from the charMax and textMax methods.
96
	 * If not specifically defined in the driver, methods return the NOTSET value.
97
	 */
98
	define ('ADODB_STRINGMAX_NOTSET', -1);
99
	define ('ADODB_STRINGMAX_NOLIMIT',-2);
100
 
101
	/*
102
	 * Defines the the default meta type returned
103
	 * when ADOdb encounters a type that it is not
104
	 * defined in the metaTypes.
105
	 */
106
	if (!defined('ADODB_DEFAULT_METATYPE'))
107
		define ('ADODB_DEFAULT_METATYPE','N');
108
 
109
	define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>');
110
 
111
	// allow [ ] @ ` " and . in table names
112
	define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)');
113
 
114
	// prefetching used by oracle
115
	if (!defined('ADODB_PREFETCH_ROWS')) {
116
		define('ADODB_PREFETCH_ROWS',10);
117
	}
118
 
119
 
120
	/**
121
	 * Fetch mode
122
	 *
123
	 * Set global variable $ADODB_FETCH_MODE to one of these constants or use
124
	 * the SetFetchMode() method to control how recordset fields are returned
125
	 * when fetching data.
126
	 *
127
	 *   - NUM:     array()
128
	 *   - ASSOC:   array('id' => 456, 'name' => 'john')
129
	 *   - BOTH:    array(0 => 456, 'id' => 456, 1 => 'john', 'name' => 'john')
130
	 *   - DEFAULT: driver-dependent
131
	 */
132
	define('ADODB_FETCH_DEFAULT', 0);
133
	define('ADODB_FETCH_NUM', 1);
134
	define('ADODB_FETCH_ASSOC', 2);
135
	define('ADODB_FETCH_BOTH', 3);
136
 
137
	/**
138
	 * Associative array case constants
139
	 *
140
	 * By defining the ADODB_ASSOC_CASE constant to one of these values, it is
141
	 * possible to control the case of field names (associative array's keys)
142
	 * when operating in ADODB_FETCH_ASSOC fetch mode.
143
	 *   - LOWER:  $rs->fields['orderid']
144
	 *   - UPPER:  $rs->fields['ORDERID']
145
	 *   - NATIVE: $rs->fields['OrderID'] (or whatever the RDBMS will return)
146
	 *
147
	 * The default is to use native case-names.
148
	 *
149
	 * NOTE: This functionality is not implemented everywhere, it currently
150
	 * works only with: mssql, odbc, oci8 and ibase derived drivers
151
	 */
152
	define('ADODB_ASSOC_CASE_LOWER', 0);
153
	define('ADODB_ASSOC_CASE_UPPER', 1);
154
	define('ADODB_ASSOC_CASE_NATIVE', 2);
155
 
156
 
157
	if (!defined('TIMESTAMP_FIRST_YEAR')) {
158
		define('TIMESTAMP_FIRST_YEAR',100);
159
	}
160
 
161
	/**
162
	 * AutoExecute constants
163
	 * (moved from adodb-pear.inc.php since they are only used in here)
164
	 */
165
	define('DB_AUTOQUERY_INSERT', 1);
166
	define('DB_AUTOQUERY_UPDATE', 2);
167
 
168
 
169
 
170
	function ADODB_Setup() {
171
	GLOBAL
172
		$ADODB_vers,		// database version
173
		$ADODB_COUNTRECS,	// count number of records returned - slows down query
174
		$ADODB_CACHE_DIR,	// directory to cache recordsets
175
		$ADODB_FETCH_MODE,
176
		$ADODB_CACHE,
177
		$ADODB_CACHE_CLASS,
178
		$ADODB_FORCE_TYPE,
179
		$ADODB_GETONE_EOF,
180
		$ADODB_QUOTE_FIELDNAMES;
181
 
182
		if (empty($ADODB_CACHE_CLASS)) {
183
			$ADODB_CACHE_CLASS =  'ADODB_Cache_File' ;
184
		}
185
		$ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT;
186
		$ADODB_FORCE_TYPE = ADODB_FORCE_VALUE;
187
		$ADODB_GETONE_EOF = null;
188
 
189
		if (!isset($ADODB_CACHE_DIR)) {
190
			$ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp';
191
		} else {
192
			// do not accept url based paths, eg. http:/ or ftp:/
193
			if (strpos($ADODB_CACHE_DIR,'://') !== false) {
194
				die("Illegal path http:// or ftp://");
195
			}
196
		}
197
 
198
		/**
199
		 * ADODB version as a string.
200
		 */
201
		$ADODB_vers = 'v5.22.7  2023-11-04';
202
 
203
		/**
204
		 * Determines whether recordset->RecordCount() is used.
205
		 * Set to false for highest performance -- RecordCount() will always return -1 then
206
		 * for databases that provide "virtual" recordcounts...
207
		 */
208
		if (!isset($ADODB_COUNTRECS)) {
209
			$ADODB_COUNTRECS = true;
210
		}
211
	}
212
 
213
 
214
	//==============================================================================================
215
	// CHANGE NOTHING BELOW UNLESS YOU ARE DESIGNING ADODB
216
	//==============================================================================================
217
 
218
	ADODB_Setup();
219
 
220
	//==============================================================================================
221
	// CLASS ADOFieldObject
222
	//==============================================================================================
223
	/**
224
	 * Helper class for FetchFields -- holds info on a column.
225
	 *
226
	 * Note: Dynamic properties are required here, as some drivers may require
227
	 * the object to hold database-specific field metadata.
228
	 */
229
	#[\AllowDynamicProperties]
230
	class ADOFieldObject {
231
		var $name = '';
232
		var $max_length=0;
233
		var $type="";
234
/*
235
		// additional fields by dannym... (danny_milo@yahoo.com)
236
		var $not_null = false;
237
		// actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^
238
		// so we can as well make not_null standard (leaving it at "false" does not harm anyways)
239
 
240
		var $has_default = false; // this one I have done only in mysql and postgres for now ...
241
			// others to come (dannym)
242
		var $default_value; // default, if any, and supported. Check has_default first.
243
*/
244
	}
245
 
246
 
247
	/**
248
	 * Parse date string to prevent injection attack.
249
	 *
250
	 * @param string $s
251
	 *
252
	 * @return string
253
	 */
254
	function _adodb_safedate($s) {
255
		return str_replace(array("'", '\\'), '', $s);
256
	}
257
 
258
	/**
259
	 * Parse date string to prevent injection attack.
260
	 * Date string will have one quote at beginning e.g. '3434343'
261
	 *
262
	 * @param string $s
263
	 *
264
	 * @return string
265
	 */
266
	function _adodb_safedateq($s) {
267
		$len = strlen($s);
268
		if ($s[0] !== "'") {
269
			$s2 = "'".$s[0];
270
		} else {
271
			$s2 = "'";
272
		}
273
		for($i=1; $i<$len; $i++) {
274
			$ch = $s[$i];
275
			if ($ch === '\\') {
276
				$s2 .= "'";
277
				break;
278
			} elseif ($ch === "'") {
279
				$s2 .= $ch;
280
				break;
281
			}
282
 
283
			$s2 .= $ch;
284
		}
285
 
286
		return strlen($s2) == 0 ? 'null' : $s2;
287
	}
288
 
289
	/**
290
	 * For transaction handling.
291
	 *
292
	 * @param $dbms
293
	 * @param $fn
294
	 * @param $errno
295
	 * @param $errmsg
296
	 * @param $p1
297
	 * @param $p2
298
	 * @param $thisConnection
299
	 */
300
	function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) {
301
		//print "Errorno ($fn errno=$errno m=$errmsg) ";
302
		$thisConnection->_transOK = false;
303
		if ($thisConnection->_oldRaiseFn) {
304
			$errfn = $thisConnection->_oldRaiseFn;
305
			$errfn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection);
306
		}
307
	}
308
 
309
	/**
310
	 * Class ADODB_Cache_File
311
	 */
312
	class ADODB_Cache_File {
313
 
314
		var $createdir = true; // requires creation of temp dirs
315
 
316
		function __construct() {
317
			global $ADODB_INCLUDED_CSV;
318
			if (empty($ADODB_INCLUDED_CSV)) {
319
				include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
320
			}
321
		}
322
 
323
		/**
324
		 * Write serialised RecordSet to cache item/file.
325
		 *
326
		 * @param $filename
327
		 * @param $contents
328
		 * @param $debug
329
		 * @param $secs2cache
330
		 *
331
		 * @return bool|int
332
		 */
333
		function writecache($filename, $contents, $debug, $secs2cache) {
334
			return adodb_write_file($filename, $contents,$debug);
335
		}
336
 
337
		/**
338
		 * load serialised RecordSet and unserialise it
339
		 *
340
		 * @param $filename
341
		 * @param $err
342
		 * @param $secs2cache
343
		 * @param $rsClass
344
		 *
345
		 * @return ADORecordSet
346
		 */
347
		function &readcache($filename, &$err, $secs2cache, $rsClass) {
348
			$rs = csv2rs($filename,$err,$secs2cache,$rsClass);
349
			return $rs;
350
		}
351
 
352
		/**
353
		 * Flush all items in cache.
354
		 *
355
		 * @param bool $debug
356
		 *
357
		 * @return bool|void
358
		 */
359
		function flushall($debug=false) {
360
			global $ADODB_CACHE_DIR;
361
 
362
			$rez = false;
363
 
364
			if (strlen($ADODB_CACHE_DIR) > 1) {
365
				$rez = $this->_dirFlush($ADODB_CACHE_DIR);
366
				if ($debug) {
367
					ADOConnection::outp( "flushall: $ADODB_CACHE_DIR<br><pre>\n". $rez."</pre>");
368
				}
369
			}
370
			return $rez;
371
		}
372
 
373
		/**
374
		 * Flush one file in cache.
375
		 *
376
		 * @param string $f
377
		 * @param bool   $debug
378
		 */
379
		function flushcache($f, $debug=false) {
380
			if (!@unlink($f)) {
381
				if ($debug) {
382
					ADOConnection::outp( "flushcache: failed for $f");
383
				}
384
			}
385
		}
386
 
387
		/**
388
		 * @param string $hash
389
		 *
390
		 * @return string
391
		 */
392
		function getdirname($hash) {
393
			global $ADODB_CACHE_DIR;
394
			return $ADODB_CACHE_DIR . '/' . substr($hash, 0, 2);
395
		}
396
 
397
		/**
398
		 * Create temp directories.
399
		 *
400
		 * @param string $hash
401
		 * @param bool   $debug
402
		 *
403
		 * @return string
404
		 */
405
		function createdir($hash, $debug) {
406
			global $ADODB_CACHE_PERMS;
407
 
408
			$dir = $this->getdirname($hash);
409
			if (!file_exists($dir)) {
410
				$oldu = umask(0);
411
				if (!@mkdir($dir, empty($ADODB_CACHE_PERMS) ? 0771 : $ADODB_CACHE_PERMS)) {
412
					if(!is_dir($dir) && $debug) {
413
						ADOConnection::outp("Cannot create $dir");
414
					}
415
				}
416
				umask($oldu);
417
			}
418
 
419
			return $dir;
420
		}
421
 
422
		/**
423
		* Private function to erase all of the files and subdirectories in a directory.
424
		*
425
		* Just specify the directory, and tell it if you want to delete the directory or just clear it out.
426
		* Note: $kill_top_level is used internally in the function to flush subdirectories.
427
		*/
428
		function _dirFlush($dir, $kill_top_level = false) {
429
			if(!$dh = @opendir($dir)) return;
430
 
431
			while (($obj = readdir($dh))) {
432
				if($obj=='.' || $obj=='..') continue;
433
				$f = $dir.'/'.$obj;
434
 
435
				if (strpos($obj,'.cache')) {
436
					@unlink($f);
437
				}
438
				if (is_dir($f)) {
439
					$this->_dirFlush($f, true);
440
				}
441
			}
442
			if ($kill_top_level === true) {
443
				@rmdir($dir);
444
			}
445
			return true;
446
		}
447
	}
448
 
449
	//==============================================================================================
450
	// CLASS ADOConnection
451
	//==============================================================================================
452
 
453
	/**
454
	 * Connection object. For connecting to databases, and executing queries.
455
	 */
456
	abstract class ADOConnection {
457
	//
458
	// PUBLIC VARS
459
	//
460
	var $dataProvider = 'native';
461
	var $databaseType = '';		/// RDBMS currently in use, eg. odbc, mysql, mssql
462
 
463
	/**
464
	 * @var string Current database name.
465
	 *
466
	 * This used to be stored in the $databaseName property, which was marked
467
	 * as deprecated in 4.66 and removed in 5.22.5.
468
	 */
469
	public $database = '';
470
 
471
	/**
472
	 * @var string If the driver is PDO, then the dsnType is e.g. sqlsrv, otherwise empty
473
	 */
474
	public $dsnType = '';
475
 
476
	var $host = '';				/// The hostname of the database server
477
	var $port = '';				/// The port of the database server
478
	var $user = '';				/// The username which is used to connect to the database server.
479
	var $password = '';			/// Password for the username. For security, we no longer store it.
480
	var $debug = false;			/// if set to true will output sql statements
481
	var $maxblobsize = 262144;	/// maximum size of blobs or large text fields (262144 = 256K)-- some db's die otherwise like foxpro
482
	var $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase
483
	var $substr = 'substr';		/// substring operator
484
	var $length = 'length';		/// string length ofperator
485
	var $random = 'rand()';		/// random function
486
	var $upperCase = 'upper';		/// uppercase function
487
	var $fmtDate = "'Y-m-d'";	/// used by DBDate() as the default date format used by the database
488
	var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /// used by DBTimeStamp as the default timestamp fmt.
489
	var $true = '1';			/// string that represents TRUE for a database
490
	var $false = '0';			/// string that represents FALSE for a database
491
	var $replaceQuote = "\\'";	/// string to use to replace quotes
492
	var $nameQuote = '"';		/// string to use to quote identifiers and names
493
	var $leftBracket = '[';		/// left square bracked for t-sql styled column names
494
	var $rightBracket = ']';	/// right square bracked for t-sql styled column names
495
	var $charSet=false;			/// character set to use - only for interbase, postgres and oci8
496
 
497
	/** @var string SQL statement to get databases */
498
	var $metaDatabasesSQL = '';
499
 
500
	/** @var string SQL statement to get database tables */
501
	var $metaTablesSQL = '';
502
 
503
	/** @var string SQL statement to get table columns. */
504
	var $metaColumnsSQL;
505
 
506
	/**
507
	 * SQL statement to get the last IDENTITY value inserted into an IDENTITY
508
	 * column in the same scope.
509
	 * @see https://learn.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql
510
	 * @var string
511
	 */
512
	var $identitySQL;
513
 
514
	/** @var string SQL statement to create a Sequence . */
515
	var $_genSeqSQL;
516
 
517
	/** @var string SQL statement to drop a Sequence. */
518
	var $_dropSeqSQL;
519
 
520
	/** @var string SQL statement to generate a Sequence ID. */
521
	var $_genIDSQL;
522
 
523
	var $uniqueOrderBy = false; /// All order by columns have to be unique
524
	var $emptyDate = '&nbsp;';
525
	var $emptyTimeStamp = '&nbsp;';
526
	var $lastInsID = false;
527
	//--
528
	var $hasInsertID = false;		/// supports autoincrement ID?
529
	var $hasAffectedRows = false;	/// supports affected rows for update/delete?
530
	var $hasTop = false;			/// support mssql/access SELECT TOP 10 * FROM TABLE
531
	var $hasLimit = false;			/// support pgsql/mysql SELECT * FROM TABLE LIMIT 10
532
	var $readOnly = false;			/// this is a readonly database - used by phpLens
533
	var $hasMoveFirst = false;		/// has ability to run MoveFirst(), scrolling backwards
534
	var $hasGenID = false;			/// can generate sequences using GenID();
535
	var $hasTransactions = true;	/// has transactions
536
	//--
537
	var $genID = 0;					/// sequence id used by GenID();
538
 
539
	/** @var bool|callable Error function to call */
540
	var $raiseErrorFn = false;
541
 
542
	var $isoDates = false;			/// accepts dates in ISO format
543
	var $cacheSecs = 3600;			/// cache for 1 hour
544
 
545
	/*****************************************
546
	* memcached server options
547
	******************************************/
548
 
549
	/**
550
	 * Use memCache library instead of caching in files.
551
	 * @var bool $memCache
552
	 */
553
	public $memCache = false;
554
 
555
	/**
556
	 * The memcache server(s) to connect to. Can be defined as:
557
	 * - a single host name/ip address
558
	 * - a list of hosts/ip addresses
559
	 * - an array of server connection data (weighted server groups).
560
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:userguide:memcached
561
	 * @var string|array $memCacheHost
562
	 */
563
	public $memCacheHost;
564
 
565
	/**
566
	 * Default port number.
567
	 * The default port can be overridden if memcache server connection data
568
	 * is provided as an array {@see $memCacheHost}.
569
	 * @var int $memCachePort
570
	 */
571
	public $memCachePort = 11211;
572
 
573
	/**
574
	 * Enable compression of stored items.
575
	 * @var bool $memCacheCompress
576
	 */
577
	public $memCacheCompress = false;
578
 
579
	/**
580
	 * An array of memcached options.
581
	 * Only used with memcached; memcache ignores this setting.
582
	 * @link https://www.php.net/manual/en/memcached.constants.php
583
	 * @var array $memCacheOptions
584
	 */
585
	public $memCacheOptions = array();
586
 
587
	var $sysDate = false; /// name of function that returns the current date
588
	var $sysTimeStamp = false; /// name of function that returns the current timestamp
589
	var $sysUTimeStamp = false; // name of function that returns the current timestamp accurate to the microsecond or nearest fraction
590
	var $arrayClass = 'ADORecordSet_array'; /// name of class used to generate array recordsets, which are pre-downloaded recordsets
591
 
592
	var $noNullStrings = false; /// oracle specific stuff - if true ensures that '' is converted to ' '
593
	var $numCacheHits = 0;
594
	var $numCacheMisses = 0;
595
	var $pageExecuteCountRows = true;
596
	var $uniqueSort = false; /// indicates that all fields in order by must be unique
597
	var $leftOuter = false; /// operator to use for left outer join in WHERE clause
598
	var $rightOuter = false; /// operator to use for right outer join in WHERE clause
599
	var $ansiOuter = false; /// whether ansi outer join syntax supported
600
	var $autoRollback = false; // autoRollback on PConnect().
601
	var $poorAffectedRows = false; // affectedRows not working or unreliable
602
 
603
	/** @var bool|callable Execute function to call */
604
	var $fnExecute = false;
605
 
606
	/** @var bool|callable Cache execution function to call */
607
	var $fnCacheExecute = false;
608
 
609
	var $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char
610
	var $rsPrefix = "ADORecordSet_";
611
 
612
	var $autoCommit = true;		/// do not modify this yourself - actually private
613
	var $transOff = 0;			/// temporarily disable transactions
614
	var $transCnt = 0;			/// count of nested transactions
615
 
616
	var $fetchMode=false;
617
 
618
	var $null2null = 'null'; // in autoexecute/getinsertsql/getupdatesql, this value will be converted to a null
619
	var $bulkBind = false; // enable 2D Execute array
620
 
621
	/** @var string SQL statement executed by some drivers after successful connection. */
622
	public $connectStmt = '';
623
 
624
	//
625
	// PRIVATE VARS
626
	//
627
	var $_oldRaiseFn =  false;
628
	var $_transOK = null;
629
	/** @var resource Identifier for the native database connection */
630
	var $_connectionID = false;
631
 
632
	/**
633
	 * Stores the last returned error message.
634
	 * @see ADOConnection::errorMsg()
635
	 * @var string|false
636
	 */
637
	var $_errorMsg = false;
638
 
639
	/**
640
	 * Stores the last returned error code.
641
	 * Not guaranteed to be used. Only some drivers actually populate it.
642
	 * @var int|false
643
	 */
644
	var $_errorCode = false;
645
 
646
	var $_queryID = false;		/// This variable keeps the last created result link identifier
647
 
648
	var $_isPersistentConnection = false;	/// A boolean variable to state whether its a persistent connection or normal connection.	*/
649
	var $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters.
650
 
651
	/**
652
	 * Eval string used to filter data.
653
	 * Only used in the deprecated Text driver.
654
	 * @see https://adodb.org/dokuwiki/doku.php?id=v5:database:text#workaround
655
	 * @var string
656
	 */
657
	var $evalAll = false;
658
 
659
	var $_affected = false;
660
	var $_logsql = false;
661
	var $_transmode = ''; // transaction mode
662
 
663
	/**
664
	 * Additional parameters that may be passed to drivers in the connect string.
665
	 *
666
	 * Data is stored as an array of arrays and not a simple associative array,
667
	 * because some drivers (e.g. mysql) allow multiple parameters with the same
668
	 * key to be set.
669
	 * @link https://github.com/ADOdb/ADOdb/issues/187
670
	 *
671
	 * @see setConnectionParameter()
672
	 *
673
	 * @var array $connectionParameters Set of ParameterName => Value pairs
674
	 */
675
	protected $connectionParameters = array();
676
 
677
	/*
678
	 * A simple associative array of user-defined custom actual/meta types
679
	 */
680
	public $customActualTypes = array();
681
 
682
	/*
683
	 * An array of user-defined custom meta/actual types.
684
	 * $this->customMetaTypes[$meta] = array(
685
	 *     'actual'=>'',
686
	 *     'dictionary'=>'',
687
	 *     'handler'=>'',
688
	 *     'callback'=>''
689
	 * );
690
	 */
691
	public $customMetaTypes = array();
692
 
693
	/** @var ADORecordSet Recordset used to retrieve MetaType information */
694
	var $_metars;
695
 
696
	/** @var string a specified locale. */
697
	var $locale;
698
 
699
 
700
	/**
701
	 * Default Constructor.
702
	 * We define it even though it does not actually do anything. This avoids
703
	 * getting a PHP Fatal error:  Cannot call constructor if a subclass tries
704
	 * to call its parent constructor.
705
	 */
706
	public function __construct()
707
	{
708
	}
709
 
710
	/**
711
	 * Adds a parameter to the connection string.
712
	 *
713
	 * Parameters must be added before the connection is established;
714
	 * they are then passed on to the connect statement, which will.
715
	 * process them if the driver supports this feature.
716
	 *
717
	 * Example usage:
718
	 * - mssqlnative: setConnectionParameter('CharacterSet','UTF-8');
719
	 * - mysqli: setConnectionParameter(MYSQLI_SET_CHARSET_NAME,'utf8mb4');
720
	 *
721
	 * If used in a portable environment, parameters set in this manner should
722
	 * be predicated on the database provider, as unexpected results may occur
723
	 * if applied to the wrong database.
724
	 *
725
	 * @param string $parameter The name of the parameter to set
726
	 * @param string $value     The value of the parameter
727
	 *
728
	 * @return bool True if success, false otherwise (e.g. parameter is not valid)
729
	 */
730
	public function setConnectionParameter($parameter, $value) {
731
		$this->connectionParameters[] = array($parameter=>$value);
732
		return true;
733
	}
734
 
735
	/**
736
	 * ADOdb version.
737
	 *
738
	 * @return string
739
	 */
740
	static function Version() {
741
		global $ADODB_vers;
742
 
743
		// Semantic Version number matching regex
744
		$regex = '^[vV]?(\d+\.\d+\.\d+'         // Version number (X.Y.Z) with optional 'V'
745
			. '(?:-(?:'                         // Optional preprod version: a '-'
746
			. 'dev|'                            // followed by 'dev'
747
			. '(?:(?:alpha|beta|rc)(?:\.\d+))'  // or a preprod suffix and version number
748
			. '))?)(?:\s|$)';                   // Whitespace or end of string
749
 
750
		if (!preg_match("/$regex/", $ADODB_vers, $matches)) {
751
			// This should normally not happen... Return whatever is between the start
752
			// of the string and the first whitespace (or the end of the string).
753
			self::outp("Invalid version number: '$ADODB_vers'", 'Version');
754
			$regex = '^[vV]?(.*?)(?:\s|$)';
755
			preg_match("/$regex/", $ADODB_vers, $matches);
756
		}
757
		return $matches[1];
758
	}
759
 
760
	/**
761
	 * Set a custom meta type with a corresponding actual
762
	 *
763
	 * @param	string	$metaType	The Custom ADOdb metatype
764
	 * @param	string	$dictionaryType	The database dictionary type
765
	 * @param	string	$actualType	The database actual type
766
	 * @param	bool	$handleAsType handle like an existing Metatype
767
	 * @param	mixed	$callBack A pre-processing function
768
	 *
769
	 * @return bool success if the actual exists
770
	 */
771
	final public function setCustomMetaType(
772
		$metaType,
773
		$dictionaryType,
774
		$actualType,
775
		$handleAsType=false,
776
		$callback=false){
777
 
778
		$this->customMetaTypes[strtoupper($metaType)] = array(
779
			'actual'=>$actualType,
780
			'dictionary'=>strtoupper($dictionaryType),
781
			'handler'=>$handleAsType,
782
			'callback'=>$callback
783
			);
784
 
785
		/*
786
		* Create a reverse lookup for the actualType
787
		*/
788
		$this->customActualTypes[$actualType] = $metaType;
789
 
790
		return true;
791
	}
792
 
793
	/**
794
	 * Get a list of custom meta types.
795
	 *
796
	 * @return string[]
797
	 */
798
	final public function getCustomMetaTypes()
799
	{
800
		return $this->customMetaTypes;
801
	}
802
 
803
 
804
	/**
805
	 * Get server version info.
806
	 *
807
	 * @return string[] Array with 2 string elements: version and description
808
	 */
809
	function ServerInfo() {
810
		return array('description' => '', 'version' => '');
811
	}
812
 
813
	/**
814
	 * Return true if connected to the database.
815
	 *
816
	 * @return bool
817
	 */
818
	function IsConnected() {
819
		return !empty($this->_connectionID);
820
	}
821
 
822
	/**
823
	 * Find version string.
824
	 *
825
	 * @param string $str
826
	 *
827
	 * @return string
828
	 */
829
	function _findvers($str) {
830
		if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) {
831
			return $arr[1];
832
		} else {
833
			return '';
834
		}
835
	}
836
 
837
	/**
838
	 * All error messages go through this bottleneck function.
839
	 *
840
	 * You can define your own handler by defining the function name in ADODB_OUTP.
841
	 *
842
	 * @param string $msg     Message to print
843
	 * @param bool   $newline True to add a newline after printing $msg
844
	 */
845
	static function outp($msg,$newline=true) {
846
		global $ADODB_FLUSH,$ADODB_OUTP;
847
 
848
		if (defined('ADODB_OUTP')) {
849
			$fn = ADODB_OUTP;
850
			$fn($msg,$newline);
851
			return;
852
		} else if (isset($ADODB_OUTP)) {
853
			call_user_func($ADODB_OUTP,$msg,$newline);
854
			return;
855
		}
856
 
857
		if ($newline) {
858
			$msg .= "<br>\n";
859
		}
860
 
861
		if (isset($_SERVER['HTTP_USER_AGENT']) || !$newline) {
862
			echo $msg;
863
		} else {
864
			echo strip_tags($msg);
865
		}
866
 
867
 
868
		if (!empty($ADODB_FLUSH) && ob_get_length() !== false) {
869
			flush(); //  do not flush if output buffering enabled - useless - thx to Jesse Mullan
870
		}
871
 
872
	}
873
 
874
	/**
875
	 * Return the database server's current date and time.
876
	 * @return int|false
877
	 */
878
	function Time() {
879
		$rs = $this->_Execute("select $this->sysTimeStamp");
880
		if ($rs && !$rs->EOF) {
881
			return $this->UnixTimeStamp(reset($rs->fields));
882
		}
883
 
884
		return false;
885
	}
886
 
887
	/**
888
	 * Parses the hostname to extract the port.
889
	 * Overwrites $this->host and $this->port, only if a port is specified.
890
	 * The Hostname can be fully or partially qualified,
891
	 * ie: "db.mydomain.com:5432" or "ldaps://ldap.mydomain.com:636"
892
	 * Any specified scheme such as ldap:// or ldaps:// is maintained.
893
	 */
894
	protected function parseHostNameAndPort() {
895
		$parsed_url = parse_url($this->host);
896
		if (is_array($parsed_url) && isset($parsed_url['host']) && isset($parsed_url['port'])) {
897
			if ( isset($parsed_url['scheme']) ) {
898
				// If scheme is specified (ie: ldap:// or ldaps://, make sure we retain that.
899
				$this->host = $parsed_url['scheme'] . "://" . $parsed_url['host'];
900
			} else {
901
				$this->host = $parsed_url['host'];
902
			}
903
			$this->port = $parsed_url['port'];
904
		}
905
	}
906
 
907
	/**
908
	 * Connect to database.
909
	 *
910
	 * @param string $argHostname     Host to connect to
911
	 * @param string $argUsername     Userid to login
912
	 * @param string $argPassword     Associated password
913
	 * @param string $argDatabaseName Database name
914
	 * @param bool   $forceNew        Force new connection
915
	 *
916
	 * @return bool
917
	 */
918
	function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) {
919
		if ($argHostname != "") {
920
			$this->host = $argHostname;
921
		}
922
		// Overwrites $this->host and $this->port if a port is specified.
923
		$this->parseHostNameAndPort();
924
 
925
		if ($argUsername != "") {
926
			$this->user = $argUsername;
927
		}
928
		if ($argPassword != "") {
929
			$this->password = 'not stored'; // not stored for security reasons
930
		}
931
		if ($argDatabaseName != "") {
932
			$this->database = $argDatabaseName;
933
		}
934
 
935
		$this->_isPersistentConnection = false;
936
 
937
		if ($forceNew) {
938
			if ($rez=$this->_nconnect($this->host, $this->user, $argPassword, $this->database)) {
939
				return true;
940
			}
941
		} else {
942
			if ($rez=$this->_connect($this->host, $this->user, $argPassword, $this->database)) {
943
				return true;
944
			}
945
		}
946
		if (isset($rez)) {
947
			$err = $this->ErrorMsg();
948
			$errno = $this->ErrorNo();
949
			if (empty($err)) {
950
				$err = "Connection error to server '$argHostname' with user '$argUsername'";
951
			}
952
		} else {
953
			$err = "Missing extension for ".$this->dataProvider;
954
			$errno = 0;
955
		}
956
		if ($fn = $this->raiseErrorFn) {
957
			$fn($this->databaseType, 'CONNECT', $errno, $err, $this->host, $this->database, $this);
958
		}
959
 
960
		$this->_connectionID = false;
961
		if ($this->debug) {
962
			ADOConnection::outp( $this->host.': '.$err);
963
		}
964
		return false;
965
	}
966
 
967
	/**
968
	 * Always force a new connection to database.
969
	 *
970
	 * @param string $argHostname     Host to connect to
971
	 * @param string $argUsername     Userid to login
972
	 * @param string $argPassword     Associated password
973
	 * @param string $argDatabaseName Database name
974
	 *
975
	 * @return bool
976
	 */
977
	function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) {
978
		return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
979
	}
980
 
981
	/**
982
	 * Always force a new connection to database.
983
	 *
984
	 * Currently this only works with Oracle.
985
	 *
986
	 * @param string $argHostname     Host to connect to
987
	 * @param string $argUsername     Userid to login
988
	 * @param string $argPassword     Associated password
989
	 * @param string $argDatabaseName Database name
990
	 *
991
	 * @return bool
992
	 */
993
	function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
994
		return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true);
995
	}
996
 
997
	/**
998
	 * Establish persistent connection to database.
999
	 *
1000
	 * @param string $argHostname     Host to connect to
1001
	 * @param string $argUsername     Userid to login
1002
	 * @param string $argPassword     Associated password
1003
	 * @param string $argDatabaseName Database name
1004
	 *
1005
	 * @return bool
1006
	 */
1007
	function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") {
1008
 
1009
		if (defined('ADODB_NEVER_PERSIST')) {
1010
			return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName);
1011
		}
1012
 
1013
		if ($argHostname != "") {
1014
			$this->host = $argHostname;
1015
		}
1016
		// Overwrites $this->host and $this->port if a port is specified.
1017
		$this->parseHostNameAndPort();
1018
 
1019
		if ($argUsername != "") {
1020
			$this->user = $argUsername;
1021
		}
1022
		if ($argPassword != "") {
1023
			$this->password = 'not stored';
1024
		}
1025
		if ($argDatabaseName != "") {
1026
			$this->database = $argDatabaseName;
1027
		}
1028
 
1029
		$this->_isPersistentConnection = true;
1030
 
1031
		if ($rez = $this->_pconnect($this->host, $this->user, $argPassword, $this->database)) {
1032
			return true;
1033
		}
1034
		if (isset($rez)) {
1035
			$err = $this->ErrorMsg();
1036
			if (empty($err)) {
1037
				$err = "Connection error to server '$argHostname' with user '$argUsername'";
1038
			}
1039
			$ret = false;
1040
		} else {
1041
			$err = "Missing extension for ".$this->dataProvider;
1042
			$ret = false;
1043
		}
1044
		if ($fn = $this->raiseErrorFn) {
1045
			$fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
1046
		}
1047
 
1048
		$this->_connectionID = false;
1049
		if ($this->debug) {
1050
			ADOConnection::outp( $this->host.': '.$err);
1051
		}
1052
		return $ret;
1053
	}
1054
 
1055
	/**
1056
	 * Throw an exception if the handler is defined or prints the message if not.
1057
	 * @param string $msg Message
1058
	 * @param string $src the name of the calling function (in uppercase)
1059
	 * @param string $sql Optional offending SQL statement
1060
	 */
1061
	function outp_throw($msg, $src='WARN', $sql='') {
1062
		if (defined('ADODB_ERROR_HANDLER') &&  ADODB_ERROR_HANDLER == 'adodb_throw') {
1063
			adodb_throw($this->databaseType,$src,-9999,$msg,$sql,false,$this);
1064
			return;
1065
		}
1066
		ADOConnection::outp($msg);
1067
	}
1068
 
1069
	/**
1070
	 * Create cache class.
1071
	 *
1072
	 * Code is backwards-compatible with old memcache implementation.
1073
	 */
1074
	function _CreateCache() {
1075
		global $ADODB_CACHE, $ADODB_CACHE_CLASS;
1076
 
1077
		if ($this->memCache) {
1078
			global $ADODB_INCLUDED_MEMCACHE;
1079
 
1080
			if (empty($ADODB_INCLUDED_MEMCACHE)) {
1081
				include_once(ADODB_DIR.'/adodb-memcache.lib.inc.php');
1082
			}
1083
			$ADODB_CACHE = new ADODB_Cache_MemCache($this);
1084
		} else {
1085
			$ADODB_CACHE = new $ADODB_CACHE_CLASS($this);
1086
		}
1087
	}
1088
 
1089
	/**
1090
	 * Format date column in sql string.
1091
	 *
1092
	 * See https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:sqldate
1093
	 * for documentation on supported formats.
1094
	 *
1095
	 * @param string $fmt Format string
1096
	 * @param string $col Date column; use system date if not specified.
1097
	 *
1098
	 * @return string
1099
	 */
1100
	function SQLDate($fmt, $col = '') {
1101
		if (!$col) {
1102
			$col = $this->sysDate;
1103
		}
1104
		return $col; // child class implement
1105
	}
1106
 
1107
	/**
1108
	 * Prepare an SQL statement and return the statement resource.
1109
	 *
1110
	 * For databases that do not support prepared statements, we return the
1111
	 * provided SQL statement as-is, to ensure compatibility:
1112
	 *
1113
	 *   $stmt = $db->prepare("insert into table (id, name) values (?,?)");
1114
	 *   $db->execute($stmt, array(1,'Jill')) or die('insert failed');
1115
	 *   $db->execute($stmt, array(2,'Joe')) or die('insert failed');
1116
	 *
1117
	 * @param string $sql SQL to send to database
1118
	 *
1119
	 * @return mixed|false The prepared statement, or the original sql if the
1120
	 *                     database does not support prepare.
1121
	 */
1122
	function Prepare($sql) {
1123
		return $sql;
1124
	}
1125
 
1126
	/**
1127
	 * Releases a previously prepared statement.
1128
	 *
1129
	 * @param mixed $stmt Statement resource, as returned by {@see prepare()}
1130
	 *
1131
	 * @return bool
1132
	 */
1133
	function releaseStatement(&$stmt) {
1134
		return true;
1135
	}
1136
 
1137
	/**
1138
	 * Prepare a Stored Procedure and return the statement resource.
1139
	 *
1140
	 * Some databases, eg. mssql require a different function for preparing
1141
	 * stored procedures. So we cannot use Prepare().
1142
	 *
1143
	 * For databases that do not support this, we return the $sql.
1144
	 *
1145
	 * @param string $sql   SQL to send to database
1146
	 * @param bool   $param
1147
	 *
1148
	 * @return mixed|false The prepared statement, or the original sql if the
1149
	 *                     database does not support prepare.
1150
	 */
1151
	function PrepareSP($sql,$param=true) {
1152
		return $this->Prepare($sql,$param);
1153
	}
1154
 
1155
	/**
1156
	 * PEAR DB Compat - alias for qStr.
1157
	 * @param $s
1158
	 * @return string
1159
	 */
1160
	function Quote($s) {
1161
		return $this->qstr($s);
1162
	}
1163
 
1164
	/**
1165
	 * Quotes a string so that all strings are escaped.
1166
	 * Wrapper for qstr with magic_quotes = false.
1167
	 *
1168
	 * @param string &$s
1169
	 */
1170
	function q(&$s) {
1171
		//if (!empty($this->qNull && $s == 'null') {
1172
		//	return $s;
1173
		//}
1174
		$s = $this->qstr($s);
1175
	}
1176
 
1177
	/**
1178
	 * PEAR DB Compat - do not use internally.
1179
	 * @return int
1180
	 */
1181
	function ErrorNative() {
1182
		return $this->ErrorNo();
1183
	}
1184
 
1185
 
1186
	/**
1187
	 * PEAR DB Compat - do not use internally.
1188
	 * @param string $seq_name
1189
	 * @return int
1190
	 */
1191
	function nextId($seq_name) {
1192
		return $this->GenID($seq_name);
1193
	}
1194
 
1195
	/**
1196
	 * Lock a row.
1197
	 * Will escalate and lock the table if row locking is not supported.
1198
	 * Will normally free the lock at the end of the transaction.
1199
	 *
1200
	 * @param string $table name of table to lock
1201
	 * @param string $where where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
1202
	 * @param string $col
1203
	 *
1204
	 * @return bool
1205
	 */
1206
	function RowLock($table,$where,$col='1 as adodbignore') {
1207
		return false;
1208
	}
1209
 
1210
	/**
1211
	 * @param string $table
1212
	 * @return true
1213
	 */
1214
	function CommitLock($table) {
1215
		return $this->CommitTrans();
1216
	}
1217
 
1218
	/**
1219
	 * @param string $table
1220
	 * @return true
1221
	 */
1222
	function RollbackLock($table) {
1223
		return $this->RollbackTrans();
1224
	}
1225
 
1226
	/**
1227
	 * PEAR DB Compat - do not use internally.
1228
	 *
1229
	 * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
1230
	 * for easy porting :-)
1231
	 *
1232
	 * @param int $mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
1233
	 *
1234
	 * @return int Previous fetch mode
1235
	 */
1236
	function SetFetchMode($mode) {
1237
		$old = $this->fetchMode;
1238
		$this->fetchMode = $mode;
1239
 
1240
		if ($old === false) {
1241
			global $ADODB_FETCH_MODE;
1242
			return $ADODB_FETCH_MODE;
1243
		}
1244
		return $old;
1245
	}
1246
 
1247
	/**
1248
	 * PEAR DB Compat - do not use internally.
1249
	 *
1250
	 * @param string     $sql
1251
	 * @param array|bool $inputarr
1252
	 *
1253
	 * @return ADORecordSet|bool
1254
	 */
1255
	function Query($sql, $inputarr=false) {
1256
		$rs = $this->Execute($sql, $inputarr);
1257
		if (!$rs && defined('ADODB_PEAR')) {
1258
			return ADODB_PEAR_Error();
1259
		}
1260
		return $rs;
1261
	}
1262
 
1263
	/**
1264
	 * PEAR DB Compat - do not use internally
1265
	 */
1266
	function LimitQuery($sql, $offset, $count, $params=false) {
1267
		$rs = $this->SelectLimit($sql, $count, $offset, $params);
1268
		if (!$rs && defined('ADODB_PEAR')) {
1269
			return ADODB_PEAR_Error();
1270
		}
1271
		return $rs;
1272
	}
1273
 
1274
 
1275
	/**
1276
	 * PEAR DB Compat - do not use internally
1277
	 */
1278
	function Disconnect() {
1279
		return $this->Close();
1280
	}
1281
 
1282
	/**
1283
	 * Returns a placeholder for query parameters.
1284
	 *
1285
	 * e.g. $DB->Param('a') will return
1286
	 * - '?' for most databases
1287
	 * - ':a' for Oracle
1288
	 * - '$1', '$2', etc. for PostgreSQL
1289
	 *
1290
	 * @param mixed $name parameter's name.
1291
	 *                    For databases that require positioned params (e.g. PostgreSQL),
1292
	 *                    a "falsy" value can be used to force resetting the placeholder
1293
	 *                    count; using boolean 'false' will reset it without actually
1294
	 *                    returning a placeholder. ADOdb will also automatically reset
1295
	 *                    the count when executing a query.
1296
	 * @param string $type (unused)
1297
	 * @return string query parameter placeholder
1298
	 */
1299
	function Param($name,$type='C') {
1300
		return '?';
1301
	}
1302
 
1303
	/**
1304
	 * Self-documenting version of Parameter().
1305
	 *
1306
	 * @param $stmt
1307
	 * @param &$var
1308
	 * @param $name
1309
	 * @param int $maxLen
1310
	 * @param bool $type
1311
	 *
1312
	 * @return bool
1313
	 */
1314
	function InParameter(&$stmt, &$var, $name, $maxLen=4000, $type=false) {
1315
		return $this->Parameter($stmt,$var,$name,false,$maxLen,$type);
1316
	}
1317
 
1318
	/**
1319
	 * Self-documenting version of Parameter().
1320
	 *
1321
	 * @param $stmt
1322
	 * @param $var
1323
	 * @param $name
1324
	 * @param int $maxLen
1325
	 * @param bool $type
1326
	 *
1327
	 * @return bool
1328
	 */
1329
	function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) {
1330
		return $this->Parameter($stmt,$var,$name,true,$maxLen,$type);
1331
 
1332
	}
1333
 
1334
	/**
1335
	 *
1336
	 * Usage in oracle
1337
	 *   $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
1338
	 *   $db->Parameter($stmt,$id,'myid');
1339
	 *   $db->Parameter($stmt,$group,'group',64);
1340
	 *   $db->Execute();
1341
	 *
1342
	 * @param mixed &$stmt Statement returned by Prepare() or PrepareSP().
1343
	 * @param mixed &$var PHP variable to bind to
1344
	 * @param string $name Name of stored procedure variable name to bind to.
1345
	 * @param int|bool $isOutput Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
1346
	 * @param int $maxLen Holds an maximum length of the variable.
1347
	 * @param mixed $type The data type of $var. Legal values depend on driver.
1348
	 *
1349
	 * @return bool
1350
	 */
1351
	function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) {
1352
		return false;
1353
	}
1354
 
1355
 
1356
	function IgnoreErrors($saveErrs=false) {
1357
		if (!$saveErrs) {
1358
			$saveErrs = array($this->raiseErrorFn,$this->_transOK);
1359
			$this->raiseErrorFn = false;
1360
			return $saveErrs;
1361
		} else {
1362
			$this->raiseErrorFn = $saveErrs[0];
1363
			$this->_transOK = $saveErrs[1];
1364
		}
1365
	}
1366
 
1367
	/**
1368
	 * Improved method of initiating a transaction. Used together with CompleteTrans().
1369
	 * Advantages include:
1370
     *
1371
	 * a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans.
1372
	 *    Only the outermost block is treated as a transaction.<br>
1373
	 * b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br>
1374
	 * c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block
1375
	 *    are disabled, making it backward compatible.
1376
	 */
1377
	function StartTrans($errfn = 'ADODB_TransMonitor') {
1378
		if ($this->transOff > 0) {
1379
			$this->transOff += 1;
1380
			return true;
1381
		}
1382
 
1383
		$this->_oldRaiseFn = $this->raiseErrorFn;
1384
		$this->raiseErrorFn = $errfn;
1385
		$this->_transOK = true;
1386
 
1387
		if ($this->debug && $this->transCnt > 0) {
1388
			ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans");
1389
		}
1390
		$ok = $this->BeginTrans();
1391
		$this->transOff = 1;
1392
		return $ok;
1393
	}
1394
 
1395
 
1396
	/**
1397
	 * Complete a transation.
1398
	 *
1399
	 * Used together with StartTrans() to end a transaction. Monitors connection
1400
	 * for sql errors, and will commit or rollback as appropriate.
1401
	 *
1402
	 * @param bool autoComplete if true, monitor sql errors and commit and
1403
	 *                          rollback as appropriate, and if set to false
1404
	 *                          force rollback even if no SQL error detected.
1405
	 * @returns true on commit, false on rollback.
1406
	 */
1407
	function CompleteTrans($autoComplete = true) {
1408
		if ($this->transOff > 1) {
1409
			$this->transOff -= 1;
1410
			return true;
1411
		}
1412
		$this->raiseErrorFn = $this->_oldRaiseFn;
1413
 
1414
		$this->transOff = 0;
1415
		if ($this->_transOK && $autoComplete) {
1416
			if (!$this->CommitTrans()) {
1417
				$this->_transOK = false;
1418
				if ($this->debug) {
1419
					ADOConnection::outp("Smart Commit failed");
1420
				}
1421
			} else {
1422
				if ($this->debug) {
1423
					ADOConnection::outp("Smart Commit occurred");
1424
				}
1425
			}
1426
		} else {
1427
			$this->_transOK = false;
1428
			$this->RollbackTrans();
1429
			if ($this->debug) {
1430
				ADOConnection::outp("Smart Rollback occurred");
1431
			}
1432
		}
1433
 
1434
		return $this->_transOK;
1435
	}
1436
 
1437
	/**
1438
	 * At the end of a StartTrans/CompleteTrans block, perform a rollback.
1439
	 */
1440
	function FailTrans() {
1441
		if ($this->debug)
1442
			if ($this->transOff == 0) {
1443
				ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans");
1444
			} else {
1445
				ADOConnection::outp("FailTrans was called");
1446
				adodb_backtrace();
1447
			}
1448
		$this->_transOK = false;
1449
	}
1450
 
1451
	/**
1452
	 * Check if transaction has failed, only for Smart Transactions.
1453
	 */
1454
	function HasFailedTrans() {
1455
		if ($this->transOff > 0) {
1456
			return $this->_transOK == false;
1457
		}
1458
		return false;
1459
	}
1460
 
1461
	/**
1462
	 * Execute SQL
1463
	 *
1464
	 * @param string     $sql      SQL statement to execute, or possibly an array
1465
	 *                             holding prepared statement ($sql[0] will hold sql text)
1466
	 * @param array|bool $inputarr holds the input data to bind to.
1467
	 *                             Null elements will be set to null.
1468
	 *
1469
	 * @return ADORecordSet|false
1470
	 */
1471
	public function Execute($sql, $inputarr = false) {
1472
		if ($this->fnExecute) {
1473
			$fn = $this->fnExecute;
1474
			$ret = $fn($this,$sql,$inputarr);
1475
			if (isset($ret)) {
1476
				return $ret;
1477
			}
1478
		}
1479
		if ($inputarr !== false) {
1480
			if (!is_array($inputarr)) {
1481
				$inputarr = array($inputarr);
1482
			}
1483
 
1484
			$element0 = reset($inputarr);
1485
			# is_object check because oci8 descriptors can be passed in
1486
			$array_2d = $this->bulkBind && is_array($element0) && !is_object(reset($element0));
1487
 
1488
			//remove extra memory copy of input -mikefedyk
1489
			unset($element0);
1490
 
1491
			if (!is_array($sql) && !$this->_bindInputArray) {
1492
				// @TODO this would consider a '?' within a string as a parameter...
1493
				$sqlarr = explode('?',$sql);
1494
				$nparams = sizeof($sqlarr)-1;
1495
 
1496
				if (!$array_2d) {
1497
					// When not Bind Bulk - convert to array of arguments list
1498
					$inputarr = array($inputarr);
1499
				} else {
1500
					// Bulk bind - Make sure all list of params have the same number of elements
1501
					$countElements = array_map('count', $inputarr);
1502
					if (1 != count(array_unique($countElements))) {
1503
						$this->outp_throw(
1504
							"[bulk execute] Input array has different number of params  [" . print_r($countElements, true) .  "].",
1505
							'Execute'
1506
						);
1507
						return false;
1508
					}
1509
					unset($countElements);
1510
				}
1511
				// Make sure the number of parameters provided in the input
1512
				// array matches what the query expects
1513
				$element0 = reset($inputarr);
1514
				if ($nparams != count($element0)) {
1515
					$this->outp_throw(
1516
						"Input array has " . count($element0) .
1517
						" params, does not match query: '" . htmlspecialchars($sql) . "'",
1518
						'Execute'
1519
					);
1520
					return false;
1521
				}
1522
 
1523
				// clean memory
1524
				unset($element0);
1525
 
1526
				foreach($inputarr as $arr) {
1527
					$sql = ''; $i = 0;
1528
					foreach ($arr as $v) {
1529
						$sql .= $sqlarr[$i];
1530
						// from Ron Baldwin <ron.baldwin#sourceprose.com>
1531
						// Only quote string types
1532
						$typ = gettype($v);
1533
						if ($typ == 'string') {
1534
							//New memory copy of input created here -mikefedyk
1535
							$sql .= $this->qstr($v);
1536
						} else if ($typ == 'double') {
1537
							$sql .= str_replace(',','.',$v); // locales fix so 1.1 does not get converted to 1,1
1538
						} else if ($typ == 'boolean') {
1539
							$sql .= $v ? $this->true : $this->false;
1540
						} else if ($typ == 'object') {
1541
							if (method_exists($v, '__toString')) {
1542
								$sql .= $this->qstr($v->__toString());
1543
							} else {
1544
								$sql .= $this->qstr((string) $v);
1545
							}
1546
						} else if ($v === null) {
1547
							$sql .= 'NULL';
1548
						} else {
1549
							$sql .= $v;
1550
						}
1551
						$i += 1;
1552
 
1553
						if ($i == $nparams) {
1554
							break;
1555
						}
1556
					} // while
1557
					if (isset($sqlarr[$i])) {
1558
						$sql .= $sqlarr[$i];
1559
						if ($i+1 != sizeof($sqlarr)) {
1560
							$this->outp_throw( "Input Array does not match ?: ".htmlspecialchars($sql),'Execute');
1561
						}
1562
					} else if ($i != sizeof($sqlarr)) {
1563
						$this->outp_throw( "Input array does not match ?: ".htmlspecialchars($sql),'Execute');
1564
					}
1565
 
1566
					$ret = $this->_Execute($sql);
1567
					if (!$ret) {
1568
						return $ret;
1569
					}
1570
				}
1571
			} else {
1572
				if ($array_2d) {
1573
					if (is_string($sql)) {
1574
						$stmt = $this->Prepare($sql);
1575
					} else {
1576
						$stmt = $sql;
1577
					}
1578
 
1579
					foreach($inputarr as $arr) {
1580
						$ret = $this->_Execute($stmt,$arr);
1581
						if (!$ret) {
1582
							return $ret;
1583
						}
1584
					}
1585
				} else {
1586
					$ret = $this->_Execute($sql,$inputarr);
1587
				}
1588
			}
1589
		} else {
1590
			$ret = $this->_Execute($sql,false);
1591
		}
1592
 
1593
		return $ret;
1594
	}
1595
 
1596
	function _Execute($sql,$inputarr=false) {
1597
		// ExecuteCursor() may send non-string queries (such as arrays),
1598
		// so we need to ignore those.
1599
		if( is_string($sql) ) {
1600
			// Strips keyword used to help generate SELECT COUNT(*) queries
1601
			// from SQL if it exists.
1602
			// TODO: obsoleted by #715 - kept for backwards-compatibility
1603
			$sql = str_replace( '_ADODB_COUNT', '', $sql );
1604
		}
1605
 
1606
		if ($this->debug) {
1607
			global $ADODB_INCLUDED_LIB;
1608
			if (empty($ADODB_INCLUDED_LIB)) {
1609
				include_once(ADODB_DIR.'/adodb-lib.inc.php');
1610
			}
1611
			$this->_queryID = _adodb_debug_execute($this, $sql,$inputarr);
1612
		} else {
1613
			$this->_queryID = @$this->_query($sql,$inputarr);
1614
		}
1615
 
1616
		// ************************
1617
		// OK, query executed
1618
		// ************************
1619
 
1620
		// error handling if query fails
1621
		if ($this->_queryID === false) {
1622
			$fn = $this->raiseErrorFn;
1623
			if ($fn) {
1624
				$fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this);
1625
			}
1626
			return false;
1627
		}
1628
 
1629
		// return simplified recordset for inserts/updates/deletes with lower overhead
1630
		if ($this->_queryID === true) {
1631
			$rsclass = $this->rsPrefix.'empty';
1632
			$rs = (class_exists($rsclass)) ? new $rsclass():  new ADORecordSet_empty();
1633
 
1634
			return $rs;
1635
		}
1636
 
1637
		if ($this->dataProvider == 'pdo' && $this->databaseType != 'pdo') {
1638
			// PDO uses a slightly different naming convention for the
1639
			// recordset class if the database type is changed, so we must
1640
			// treat it specifically. The mysql driver leaves the
1641
			// databaseType as pdo
1642
			$rsclass = $this->rsPrefix . 'pdo_' . $this->databaseType;
1643
		} else {
1644
			$rsclass = $this->rsPrefix . $this->databaseType;
1645
		}
1646
 
1647
		// return real recordset from select statement
1648
		$rs = new $rsclass($this->_queryID,$this->fetchMode);
1649
		$rs->connection = $this; // Pablo suggestion
1650
		$rs->Init();
1651
		if (is_array($sql)) {
1652
			$rs->sql = $sql[0];
1653
		} else {
1654
			$rs->sql = $sql;
1655
		}
1656
		if ($rs->_numOfRows <= 0) {
1657
			global $ADODB_COUNTRECS;
1658
			if ($ADODB_COUNTRECS) {
1659
				if (!$rs->EOF) {
1660
					$rs = $this->_rs2rs($rs,-1,-1,!is_array($sql));
1661
					$rs->_queryID = $this->_queryID;
1662
				} else
1663
					$rs->_numOfRows = 0;
1664
			}
1665
		}
1666
		return $rs;
1667
	}
1668
 
1669
	/**
1670
	 * Execute a query.
1671
	 *
1672
	 * @param string|array $sql        Query to execute.
1673
	 * @param array        $inputarr   An optional array of parameters.
1674
	 *
1675
	 * @return mixed|bool Query identifier or true if execution successful, false if failed.
1676
	 */
1677
	function _query($sql, $inputarr = false) {
1678
		return false;
1679
	}
1680
 
1681
	function CreateSequence($seqname='adodbseq',$startID=1) {
1682
		if (empty($this->_genSeqSQL)) {
1683
			return false;
1684
		}
1685
		return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
1686
	}
1687
 
1688
	function DropSequence($seqname='adodbseq') {
1689
		if (empty($this->_dropSeqSQL)) {
1690
			return false;
1691
		}
1692
		return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
1693
	}
1694
 
1695
	/**
1696
	 * Generates a sequence id and stores it in $this->genID.
1697
	 *
1698
	 * GenID is only available if $this->hasGenID = true;
1699
	 *
1700
	 * @param string $seqname Name of sequence to use
1701
	 * @param int    $startID If sequence does not exist, start at this ID
1702
	 *
1703
	 * @return int Sequence id, 0 if not supported
1704
	 */
1705
	function GenID($seqname='adodbseq',$startID=1) {
1706
		if (!$this->hasGenID) {
1707
			return 0; // formerly returns false pre 1.60
1708
		}
1709
 
1710
		$getnext = sprintf($this->_genIDSQL,$seqname);
1711
 
1712
		$holdtransOK = $this->_transOK;
1713
 
1714
		$save_handler = $this->raiseErrorFn;
1715
		$this->raiseErrorFn = '';
1716
		@($rs = $this->Execute($getnext));
1717
		$this->raiseErrorFn = $save_handler;
1718
 
1719
		if (!$rs) {
1720
			$this->_transOK = $holdtransOK; //if the status was ok before reset
1721
			$createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
1722
			$rs = $this->Execute($getnext);
1723
		}
1724
		if ($rs && !$rs->EOF) {
1725
			$this->genID = reset($rs->fields);
1726
		} else {
1727
			$this->genID = 0; // false
1728
		}
1729
 
1730
		if ($rs) {
1731
			$rs->Close();
1732
		}
1733
 
1734
		return $this->genID;
1735
	}
1736
 
1737
	/**
1738
	 * Returns the last inserted ID.
1739
	 *
1740
	 * Not all databases support this feature. Some do not require to specify
1741
	 * table or column name (e.g. MySQL).
1742
	 *
1743
	 * @param string $table  Table name, default ''
1744
	 * @param string $column Column name, default ''
1745
	 *
1746
	 * @return int The last inserted ID.
1747
	 */
1748
	function Insert_ID($table='',$column='') {
1749
		if ($this->_logsql && $this->lastInsID) {
1750
			return $this->lastInsID;
1751
		}
1752
		if ($this->hasInsertID) {
1753
			return $this->_insertID($table,$column);
1754
		}
1755
		if ($this->debug) {
1756
			ADOConnection::outp( '<p>Insert_ID error</p>');
1757
			adodb_backtrace();
1758
		}
1759
		return false;
1760
	}
1761
 
1762
	/**
1763
	 * Enable or disable the Last Insert Id functionality.
1764
	 *
1765
	 * If the Driver supports it, this function allows setting {@see $hasInsertID}.
1766
	 *
1767
	 * @param bool $enable False to disable
1768
	 */
1769
	public function enableLastInsertID($enable = true) {}
1770
 
1771
	/**
1772
	 * Return the id of the last row that has been inserted in a table.
1773
	 *
1774
	 * @param string $table
1775
	 * @param string $column
1776
	 *
1777
	 * @return int|false
1778
	 */
1779
	protected function _insertID($table = '', $column = '')
1780
	{
1781
		return false;
1782
	}
1783
 
1784
	/**
1785
	 * Portable Insert ID. Pablo Roca <pabloroca#mvps.org>
1786
	 *
1787
	 * @param string $table
1788
	 * @param string $id
1789
 
1790
	 * @return mixed The last inserted ID. All databases support this, but be
1791
	 *               aware of possible problems in multiuser environments.
1792
	 *               Heavily test this before deploying.
1793
	 */
1794
	function PO_Insert_ID($table="", $id="") {
1795
		if ($this->hasInsertID){
1796
			return $this->Insert_ID($table,$id);
1797
		} else {
1798
			return $this->GetOne("SELECT MAX($id) FROM $table");
1799
		}
1800
	}
1801
 
1802
	/**
1803
	 * @return int|false Number of rows affected by UPDATE/DELETE
1804
	 */
1805
	function Affected_Rows() {
1806
		if ($this->hasAffectedRows) {
1807
			if ($this->fnExecute === 'adodb_log_sql') {
1808
				if ($this->_logsql && $this->_affected !== false) {
1809
					return $this->_affected;
1810
				}
1811
			}
1812
			$val = $this->_affectedrows();
1813
			return ($val < 0) ? false : $val;
1814
		}
1815
 
1816
		if ($this->debug) {
1817
			ADOConnection::outp( '<p>Affected_Rows error</p>',false);
1818
		}
1819
		return false;
1820
	}
1821
 
1822
 
1823
	/**
1824
	 * @return string the last error message
1825
	 */
1826
	function ErrorMsg() {
1827
		if ($this->_errorMsg) {
1828
			return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg;
1829
		} else {
1830
			return '';
1831
		}
1832
	}
1833
 
1834
 
1835
	/**
1836
	 * @return int the last error number. Normally 0 means no error.
1837
	 */
1838
	function ErrorNo() {
1839
		return ($this->_errorMsg) ? -1 : 0;
1840
	}
1841
 
1842
	function MetaError($err=false) {
1843
		include_once(ADODB_DIR."/adodb-error.inc.php");
1844
		if ($err === false) {
1845
			$err = $this->ErrorNo();
1846
		}
1847
		return adodb_error($this->dataProvider,$this->databaseType,$err);
1848
	}
1849
 
1850
	function MetaErrorMsg($errno) {
1851
		include_once(ADODB_DIR."/adodb-error.inc.php");
1852
		return adodb_errormsg($errno);
1853
	}
1854
 
1855
	/**
1856
	 * @returns an array with the primary key columns in it.
1857
	 */
1858
	function MetaPrimaryKeys($table, $owner=false) {
1859
	// owner not used in base class - see oci8
1860
		$p = array();
1861
		$objs = $this->MetaColumns($table);
1862
		if ($objs) {
1863
			foreach($objs as $v) {
1864
				if (!empty($v->primary_key)) {
1865
					$p[] = $v->name;
1866
				}
1867
			}
1868
		}
1869
		if (sizeof($p)) {
1870
			return $p;
1871
		}
1872
		if (function_exists('ADODB_VIEW_PRIMARYKEYS')) {
1873
			return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner);
1874
		}
1875
		return false;
1876
	}
1877
 
1878
	/**
1879
	 * Returns a list of Foreign Keys associated with a specific table.
1880
	 *
1881
	 * If there are no foreign keys then the function returns false.
1882
	 *
1883
	 * @param string $table       The name of the table to get the foreign keys for.
1884
	 * @param string $owner       Table owner/schema.
1885
	 * @param bool   $upper       If true, only matches the table with the uppercase name.
1886
	 * @param bool   $associative Returns the result in associative mode;
1887
	 *                            if ADODB_FETCH_MODE is already associative, then
1888
	 *                            this parameter is discarded.
1889
	 *
1890
	 * @return string[]|false An array where keys are tables, and values are foreign keys;
1891
	 *                        false if no foreign keys could be found.
1892
	 */
1893
	function metaForeignKeys($table, $owner = '', $upper = false, $associative = false) {
1894
		return false;
1895
	}
1896
 
1897
	/**
1898
	 * Choose a database to connect to. Many databases do not support this.
1899
	 *
1900
	 * @param string $dbName the name of the database to select
1901
	 * @return bool
1902
	 */
1903
	function SelectDB($dbName) {return false;}
1904
 
1905
 
1906
	/**
1907
	 * Select a limited number of rows.
1908
	 *
1909
	 * Will select, getting rows from $offset (1-based), for $nrows.
1910
	 * This simulates the MySQL "select * from table limit $offset,$nrows" , and
1911
	 * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
1912
	 * MySQL and PostgreSQL parameter ordering is the opposite of the other.
1913
	 * eg.
1914
	 *  SelectLimit('select * from table',3); will return rows 1 to 3 (1-based)
1915
	 *  SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based)
1916
	 *
1917
	 * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set)
1918
	 * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set
1919
	 *
1920
	 * @param string     $sql
1921
	 * @param int        $offset     Row to start calculations from (1-based)
1922
	 * @param int        $nrows      Number of rows to get
1923
	 * @param array|bool $inputarr   Array of bind variables
1924
	 * @param int        $secs2cache Private parameter only used by jlim
1925
	 *
1926
	 * @return ADORecordSet The recordset ($rs->databaseType == 'array')
1927
	 */
1928
	function SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) {
1929
		$nrows = (int)$nrows;
1930
		$offset = (int)$offset;
1931
 
1932
		if ($this->hasTop && $nrows > 0) {
1933
			// suggested by Reinhard Balling. Access requires top after distinct
1934
			// Informix requires first before distinct - F Riosa
1935
			$ismssql = (strpos($this->databaseType,'mssql') !== false);
1936
			if ($ismssql) {
1937
				$isaccess = false;
1938
			} else {
1939
				$isaccess = (strpos($this->databaseType,'access') !== false);
1940
			}
1941
 
1942
			if ($offset <= 0) {
1943
				// access includes ties in result
1944
				if ($isaccess) {
1945
					$sql = preg_replace(
1946
						'/(^\s*select\s+(distinctrow|distinct)?)/i',
1947
						'\\1 '.$this->hasTop.' '.$nrows.' ',
1948
						$sql
1949
					);
1950
 
1951
					if ($secs2cache != 0) {
1952
						$ret = $this->CacheExecute($secs2cache, $sql,$inputarr);
1953
					} else {
1954
						$ret = $this->Execute($sql,$inputarr);
1955
					}
1956
					return $ret; // PHP5 fix
1957
				} else if ($ismssql){
1958
					$sql = preg_replace(
1959
						'/(^\s*select\s+(distinctrow|distinct)?)/i',
1960
						'\\1 '.$this->hasTop.' '.$nrows.' ',
1961
						$sql
1962
					);
1963
				} else {
1964
					$sql = preg_replace(
1965
						'/(^\s*select\s)/i',
1966
						'\\1 '.$this->hasTop.' '.$nrows.' ',
1967
						$sql
1968
					);
1969
				}
1970
			} else {
1971
				$nn = $nrows + $offset;
1972
				if ($isaccess || $ismssql) {
1973
					$sql = preg_replace(
1974
						'/(^\s*select\s+(distinctrow|distinct)?)/i',
1975
						'\\1 '.$this->hasTop.' '.$nn.' ',
1976
						$sql
1977
					);
1978
				} else {
1979
					$sql = preg_replace(
1980
						'/(^\s*select\s)/i',
1981
						'\\1 '.$this->hasTop.' '.$nn.' ',
1982
						$sql
1983
					);
1984
				}
1985
			}
1986
		}
1987
 
1988
		// if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer  rows
1989
		// 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS.
1990
		global $ADODB_COUNTRECS;
1991
 
1992
		try {
1993
			$savec = $ADODB_COUNTRECS;
1994
			$ADODB_COUNTRECS = false;
1995
 
1996
			if ($secs2cache != 0) {
1997
				$rs = $this->CacheExecute($secs2cache, $sql, $inputarr);
1998
			} else {
1999
				$rs = $this->Execute($sql, $inputarr);
2000
			}
2001
		} finally {
2002
			$ADODB_COUNTRECS = $savec;
2003
		}
2004
 
2005
		if ($rs && !$rs->EOF) {
2006
			$rs = $this->_rs2rs($rs,$nrows,$offset);
2007
		}
2008
		//print_r($rs);
2009
		return $rs;
2010
	}
2011
 
2012
	/**
2013
	 * Create serializable recordset. Breaks rs link to connection.
2014
	 *
2015
	 * @param ADORecordSet $rs the recordset to serialize
2016
	 *
2017
	 * @return ADORecordSet_array|bool the new recordset
2018
	 */
2019
	function SerializableRS(&$rs) {
2020
		$rs2 = $this->_rs2rs($rs);
2021
		$ignore = false;
2022
		$rs2->connection = $ignore;
2023
 
2024
		return $rs2;
2025
	}
2026
 
2027
	/**
2028
	 * Convert a database recordset to an array recordset.
2029
	 *
2030
	 * Input recordset's cursor should be at beginning, and old $rs will be closed.
2031
	 *
2032
	 * @param ADORecordSet $rs     the recordset to copy
2033
	 * @param int          $nrows  number of rows to retrieve (optional)
2034
	 * @param int          $offset offset by number of rows (optional)
2035
	 * @param bool         $close
2036
	 *
2037
	 * @return ADORecordSet_array|ADORecordSet|bool the new recordset
2038
	 */
2039
	function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) {
2040
		if (! $rs) {
2041
			$ret = false;
2042
			return $ret;
2043
		}
2044
		$dbtype = $rs->databaseType;
2045
		if (!$dbtype) {
2046
			$rs = $rs;  // required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ?
2047
			return $rs;
2048
		}
2049
		if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) {
2050
			$rs->MoveFirst();
2051
			$rs = $rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ?
2052
			return $rs;
2053
		}
2054
		$flds = array();
2055
		for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) {
2056
			$flds[] = $rs->FetchField($i);
2057
		}
2058
 
2059
		$arr = $rs->GetArrayLimit($nrows,$offset);
2060
		//print_r($arr);
2061
		if ($close) {
2062
			$rs->Close();
2063
		}
2064
 
2065
		$arrayClass = $this->arrayClass;
2066
 
2067
		$rs2 = new $arrayClass($fakeQueryId=1);
2068
		$rs2->connection = $this;
2069
		$rs2->sql = $rs->sql;
2070
		$rs2->dataProvider = $this->dataProvider;
2071
		$rs2->InitArrayFields($arr,$flds);
2072
		$rs2->fetchMode = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode;
2073
		return $rs2;
2074
	}
2075
 
2076
	/**
2077
	 * Return all rows.
2078
	 *
2079
	 * Compat with PEAR DB.
2080
	 *
2081
	 * @param string     $sql      SQL statement
2082
	 * @param array|bool $inputarr Input bind array
2083
	 *
2084
	 * @return array|false
2085
	 */
2086
	function GetAll($sql, $inputarr=false) {
2087
		return $this->GetArray($sql,$inputarr);
2088
	}
2089
 
2090
	/**
2091
	 * Execute statement and return rows in an array.
2092
	 *
2093
	 * The function executes a statement and returns all of the returned rows in
2094
	 * an array, or false if the statement execution fails or if only 1 column
2095
	 * is requested in the SQL statement.
2096
	 * If no records match the provided SQL statement, an empty array is returned.
2097
	 *
2098
	 * @param string     $sql         SQL statement
2099
	 * @param array|bool $inputarr    input bind array
2100
	 * @param bool       $force_array
2101
	 * @param bool       $first2cols
2102
	 *
2103
	 * @return array|bool
2104
	 */
2105
	public function GetAssoc($sql, $inputarr = false, $force_array = false, $first2cols = false) {
2106
		$rs = $this->Execute($sql, $inputarr);
2107
 
2108
		if (!$rs) {
2109
			/*
2110
			* Execution failure
2111
			*/
2112
			return false;
2113
		}
2114
		return $rs->GetAssoc($force_array, $first2cols);
2115
	}
2116
 
2117
	/**
2118
	 * Search for the results of an executed query in the cache.
2119
	 *
2120
	 * @param int $secs2cache
2121
	 * @param string|bool $sql         SQL statement
2122
	 * @param array|bool  $inputarr    input bind array
2123
	 * @param bool        $force_array
2124
	 * @param bool        $first2cols
2125
	 *
2126
	 * @return false|array
2127
	 */
2128
	public function CacheGetAssoc($secs2cache, $sql = false, $inputarr = false,$force_array = false, $first2cols = false) {
2129
		if (!is_numeric($secs2cache)) {
2130
			$first2cols = $force_array;
2131
			$force_array = $inputarr;
2132
		}
2133
		$rs = $this->CacheExecute($secs2cache, $sql, $inputarr);
2134
		if (!$rs) {
2135
			return false;
2136
		}
2137
		return $rs->GetAssoc($force_array, $first2cols);
2138
	}
2139
 
2140
	/**
2141
	 * Return first element of first row of sql statement. Recordset is disposed
2142
	 * for you.
2143
	 *
2144
	 * @param string		$sql		SQL statement
2145
	 * @param array|bool	$inputarr	input bind array
2146
	 * @return mixed
2147
	 */
2148
	public function GetOne($sql, $inputarr=false) {
2149
		global $ADODB_COUNTRECS,$ADODB_GETONE_EOF;
2150
 
2151
		try {
2152
			$crecs = $ADODB_COUNTRECS;
2153
			$ADODB_COUNTRECS = false;
2154
			$rs = $this->Execute($sql, $inputarr);
2155
		} finally {
2156
			$ADODB_COUNTRECS = $crecs;
2157
		}
2158
 
2159
		$ret = false;
2160
		if ($rs) {
2161
			if ($rs->EOF) {
2162
				$ret = $ADODB_GETONE_EOF;
2163
			} else {
2164
				$ret = reset($rs->fields);
2165
			}
2166
 
2167
			$rs->Close();
2168
		}
2169
		return $ret;
2170
	}
2171
 
2172
	// $where should include 'WHERE fld=value'
2173
	function GetMedian($table, $field,$where = '') {
2174
		$total = $this->GetOne("select count(*) from $table $where");
2175
		if (!$total) {
2176
			return false;
2177
		}
2178
 
2179
		$midrow = (integer) ($total/2);
2180
		$rs = $this->SelectLimit("select $field from $table $where order by 1",1,$midrow);
2181
		if ($rs && !$rs->EOF) {
2182
			return reset($rs->fields);
2183
		}
2184
		return false;
2185
	}
2186
 
2187
 
2188
	function CacheGetOne($secs2cache,$sql=false,$inputarr=false) {
2189
		global $ADODB_GETONE_EOF;
2190
 
2191
		$ret = false;
2192
		$rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
2193
		if ($rs) {
2194
			if ($rs->EOF) {
2195
				$ret = $ADODB_GETONE_EOF;
2196
			} else {
2197
				$ret = reset($rs->fields);
2198
			}
2199
			$rs->Close();
2200
		}
2201
 
2202
		return $ret;
2203
	}
2204
 
2205
	/**
2206
	 * Executes a statement and returns each row's first column in an array.
2207
	 *
2208
	 * @param string     $sql      SQL statement
2209
	 * @param array|bool $inputarr input bind array
2210
	 * @param bool       $trim     enables space trimming of the returned value.
2211
	 *                             This is only relevant if the returned string
2212
	 *                             is coming from a CHAR type field.
2213
	 *
2214
	 * @return array|bool 1D array containning the first row of the query
2215
	 */
2216
	function GetCol($sql, $inputarr = false, $trim = false) {
2217
 
2218
		$rs = $this->Execute($sql, $inputarr);
2219
		if ($rs) {
2220
			$rv = array();
2221
			if ($trim) {
2222
				while (!$rs->EOF) {
2223
					$rv[] = trim(reset($rs->fields));
2224
					$rs->MoveNext();
2225
				}
2226
			} else {
2227
				while (!$rs->EOF) {
2228
					$rv[] = reset($rs->fields);
2229
					$rs->MoveNext();
2230
				}
2231
			}
2232
			$rs->Close();
2233
		} else {
2234
			$rv = false;
2235
		}
2236
		return $rv;
2237
	}
2238
 
2239
	function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false) {
2240
		$rs = $this->CacheExecute($secs, $sql, $inputarr);
2241
		if ($rs) {
2242
			$rv = array();
2243
			if ($trim) {
2244
				while (!$rs->EOF) {
2245
					$rv[] = trim(reset($rs->fields));
2246
					$rs->MoveNext();
2247
				}
2248
			} else {
2249
				while (!$rs->EOF) {
2250
					$rv[] = reset($rs->fields);
2251
					$rs->MoveNext();
2252
				}
2253
			}
2254
			$rs->Close();
2255
		} else
2256
			$rv = false;
2257
 
2258
		return $rv;
2259
	}
2260
 
2261
	/**
2262
	 * Calculate the offset of a date for a particular database
2263
	 * and generate appropriate SQL.
2264
	 *
2265
	 * Useful for calculating future/past dates and storing in a database.
2266
	 *
2267
	 * @param double       $dayFraction 1.5 means 1.5 days from now, 1.0/24 for 1 hour
2268
	 * @param string|false $date        Reference date, false for system time
2269
	 *
2270
	 * @return string
2271
	 */
2272
	function OffsetDate($dayFraction,$date=false) {
2273
		if (!$date) {
2274
			$date = $this->sysDate;
2275
		}
2276
		return  '('.$date.'+'.$dayFraction.')';
2277
	}
2278
 
2279
 
2280
	/**
2281
	 * Executes a statement and returns a the entire recordset in an array.
2282
	 *
2283
	 * @param string     $sql      SQL statement
2284
	 * @param array|bool $inputarr input bind array
2285
	 *
2286
	 * @return array|false
2287
	 */
2288
	function GetArray($sql,$inputarr=false) {
2289
		global $ADODB_COUNTRECS;
2290
 
2291
		try {
2292
			$savec = $ADODB_COUNTRECS;
2293
			$ADODB_COUNTRECS = false;
2294
			$rs = $this->Execute($sql, $inputarr);
2295
		} finally {
2296
			$ADODB_COUNTRECS = $savec;
2297
		}
2298
 
2299
		if (!$rs)
2300
			if (defined('ADODB_PEAR')) {
2301
				return ADODB_PEAR_Error();
2302
			} else {
2303
				return false;
2304
			}
2305
		$arr = $rs->GetArray();
2306
		$rs->Close();
2307
		return $arr;
2308
	}
2309
 
2310
	function CacheGetAll($secs2cache,$sql=false,$inputarr=false) {
2311
		return $this->CacheGetArray($secs2cache,$sql,$inputarr);
2312
	}
2313
 
2314
	function CacheGetArray($secs2cache,$sql=false,$inputarr=false) {
2315
		global $ADODB_COUNTRECS;
2316
 
2317
		try {
2318
			$savec = $ADODB_COUNTRECS;
2319
			$ADODB_COUNTRECS = false;
2320
			$rs = $this->CacheExecute($secs2cache, $sql, $inputarr);
2321
		} finally {
2322
			$ADODB_COUNTRECS = $savec;
2323
		}
2324
 
2325
		if (!$rs)
2326
			if (defined('ADODB_PEAR')) {
2327
				return ADODB_PEAR_Error();
2328
			} else {
2329
				return false;
2330
			}
2331
		$arr = $rs->GetArray();
2332
		$rs->Close();
2333
		return $arr;
2334
	}
2335
 
2336
	function GetRandRow($sql, $arr= false) {
2337
		$rezarr = $this->GetAll($sql, $arr);
2338
		$sz = sizeof($rezarr);
2339
		return $rezarr[abs(rand()) % $sz];
2340
	}
2341
 
2342
	/**
2343
	 * Return one row of sql statement. Recordset is disposed for you.
2344
	 * Note that SelectLimit should not be called.
2345
	 *
2346
	 * @param string     $sql      SQL statement
2347
	 * @param array|bool $inputarr input bind array
2348
	 *
2349
	 * @return array|false Array containing the first row of the query
2350
	 */
2351
	function GetRow($sql,$inputarr=false) {
2352
		global $ADODB_COUNTRECS;
2353
 
2354
		try {
2355
			$crecs = $ADODB_COUNTRECS;
2356
			$ADODB_COUNTRECS = false;
2357
			$rs = $this->Execute($sql, $inputarr);
2358
		} finally {
2359
			$ADODB_COUNTRECS = $crecs;
2360
		}
2361
 
2362
		if ($rs) {
2363
			if (!$rs->EOF) {
2364
				$arr = $rs->fields;
2365
			} else {
2366
				$arr = array();
2367
			}
2368
			$rs->Close();
2369
			return $arr;
2370
		}
2371
 
2372
		return false;
2373
	}
2374
 
2375
	/**
2376
	 * @param int $secs2cache
2377
	 * @param string|false $sql
2378
	 * @param mixed[]|bool $inputarr
2379
	 * @return mixed[]|bool
2380
	 */
2381
	function CacheGetRow($secs2cache,$sql=false,$inputarr=false) {
2382
		$rs = $this->CacheExecute($secs2cache,$sql,$inputarr);
2383
		if ($rs) {
2384
			if (!$rs->EOF) {
2385
				$arr = $rs->fields;
2386
			} else {
2387
				$arr = array();
2388
			}
2389
 
2390
			$rs->Close();
2391
			return $arr;
2392
		}
2393
		return false;
2394
	}
2395
 
2396
	/**
2397
	 * Insert or replace a single record. Note: this is not the same as MySQL's replace.
2398
	 * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
2399
	 * Also note that no table locking is done currently, so it is possible that the
2400
	 * record be inserted twice by two programs...
2401
	 *
2402
	 * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
2403
	 *
2404
	 * $table		table name
2405
	 * $fieldArray	associative array of data (you must quote strings yourself).
2406
	 * $keyCol		the primary key field name or if compound key, array of field names
2407
	 * autoQuote		set to true to use a heuristic to quote strings. Works with nulls and numbers
2408
	 *					but does not work with dates nor SQL functions.
2409
	 * has_autoinc	the primary key is an auto-inc field, so skip in insert.
2410
	 *
2411
	 * Currently blob replace not supported
2412
	 *
2413
	 * returns 0 = fail, 1 = update, 2 = insert
2414
	 */
2415
 
2416
	function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false) {
2417
		global $ADODB_INCLUDED_LIB;
2418
		if (empty($ADODB_INCLUDED_LIB)) {
2419
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
2420
		}
2421
 
2422
		return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc);
2423
	}
2424
 
2425
 
2426
	/**
2427
	 * Will select, getting rows from $offset (1-based), for $nrows.
2428
	 * This simulates the MySQL "select * from table limit $offset,$nrows" , and
2429
	 * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
2430
	 * MySQL and PostgreSQL parameter ordering is the opposite of the other.
2431
	 * eg.
2432
	 *  CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based)
2433
	 *  CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based)
2434
	 *
2435
	 * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set
2436
	 *
2437
	 * @param int    $secs2cache Seconds to cache data, set to 0 to force query. This is optional
2438
	 * @param string $sql
2439
	 * @param int    $offset     Row to start calculations from (1-based)
2440
	 * @param int    $nrows      Number of rows to get
2441
	 * @param array $inputarr    Array of bind variables
2442
	 *
2443
	 * @return ADORecordSet The recordset ($rs->databaseType == 'array')
2444
	 */
2445
	function CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false) {
2446
		if (!is_numeric($secs2cache)) {
2447
			if ($sql === false) {
2448
				$sql = -1;
2449
			}
2450
			if ($offset == -1) {
2451
				$offset = false;
2452
			}
2453
												// sql,	nrows, offset,inputarr
2454
			$rs = $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$this->cacheSecs);
2455
		} else {
2456
			if ($sql === false) {
2457
				$this->outp_throw("Warning: \$sql missing from CacheSelectLimit()",'CacheSelectLimit');
2458
			}
2459
			$rs = $this->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
2460
		}
2461
		return $rs;
2462
	}
2463
 
2464
	/**
2465
	 * Flush cached recordsets that match a particular $sql statement.
2466
	 * If $sql == false, then we purge all files in the cache.
2467
	 */
2468
	function CacheFlush($sql=false,$inputarr=false) {
2469
		global $ADODB_CACHE_DIR, $ADODB_CACHE;
2470
 
2471
		# Create cache if it does not exist
2472
		if (empty($ADODB_CACHE)) {
2473
			$this->_CreateCache();
2474
		}
2475
 
2476
		if (!$sql) {
2477
			$ADODB_CACHE->flushall($this->debug);
2478
			return;
2479
		}
2480
 
2481
		$f = $this->_gencachename($sql.serialize($inputarr),false);
2482
		return $ADODB_CACHE->flushcache($f, $this->debug);
2483
	}
2484
 
2485
 
2486
	/**
2487
	 * Private function to generate filename for caching.
2488
	 * Filename is generated based on:
2489
	 *
2490
	 *  - sql statement
2491
	 *  - database type (oci8, ibase, ifx, etc)
2492
	 *  - database name
2493
	 *  - userid
2494
	 *  - setFetchMode (adodb 4.23)
2495
	 *
2496
	 * We create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR).
2497
	 * Assuming that we can have 50,000 files per directory with good performance,
2498
	 * then we can scale to 12.8 million unique cached recordsets. Wow!
2499
	 */
2500
	function _gencachename($sql,$createdir) {
2501
		global $ADODB_CACHE, $ADODB_CACHE_DIR;
2502
 
2503
		if ($this->fetchMode === false) {
2504
			global $ADODB_FETCH_MODE;
2505
			$mode = $ADODB_FETCH_MODE;
2506
		} else {
2507
			$mode = $this->fetchMode;
2508
		}
2509
		$m = md5($sql.$this->databaseType.$this->database.$this->user.$mode);
2510
		if (!$ADODB_CACHE->createdir) {
2511
			return $m;
2512
		}
2513
		if (!$createdir) {
2514
			$dir = $ADODB_CACHE->getdirname($m);
2515
		} else {
2516
			$dir = $ADODB_CACHE->createdir($m, $this->debug);
2517
		}
2518
 
2519
		return $dir.'/adodb_'.$m.'.cache';
2520
	}
2521
 
2522
 
2523
	/**
2524
	 * Execute SQL, caching recordsets.
2525
	 *
2526
	 * @param int         $secs2cache Seconds to cache data, set to 0 to force query.
2527
	 *                                This is an optional parameter.
2528
	 * @param string|bool $sql        SQL statement to execute
2529
	 * @param array|bool  $inputarr   Holds the input data to bind
2530
	 *
2531
	 * @return ADORecordSet RecordSet or false
2532
	 */
2533
	function CacheExecute($secs2cache,$sql=false,$inputarr=false) {
2534
		global $ADODB_CACHE;
2535
 
2536
		if (empty($ADODB_CACHE)) {
2537
			$this->_CreateCache();
2538
		}
2539
 
2540
		if (!is_numeric($secs2cache)) {
2541
			$inputarr = $sql;
2542
			$sql = $secs2cache;
2543
			$secs2cache = $this->cacheSecs;
2544
		}
2545
 
2546
		if (is_array($sql)) {
2547
			$sqlparam = $sql;
2548
			$sql = $sql[0];
2549
		} else
2550
			$sqlparam = $sql;
2551
 
2552
 
2553
		$md5file = $this->_gencachename($sql.serialize($inputarr),true);
2554
		$err = '';
2555
 
2556
		if ($secs2cache > 0){
2557
			$rs = $ADODB_CACHE->readcache($md5file,$err,$secs2cache,$this->arrayClass);
2558
			$this->numCacheHits += 1;
2559
		} else {
2560
			$err='Timeout 1';
2561
			$rs = false;
2562
			$this->numCacheMisses += 1;
2563
		}
2564
 
2565
		if (!$rs) {
2566
			// no cached rs found
2567
			if ($this->debug) {
2568
				if ($this->debug !== -1) {
2569
					ADOConnection::outp( " $md5file cache failure: $err (this is a notice and not an error)");
2570
				}
2571
			}
2572
 
2573
			$rs = $this->Execute($sqlparam,$inputarr);
2574
 
2575
			if ($rs) {
2576
				$eof = $rs->EOF;
2577
				$rs = $this->_rs2rs($rs); // read entire recordset into memory immediately
2578
				$rs->timeCreated = time(); // used by caching
2579
				$txt = _rs2serialize($rs,false,$sql); // serialize
2580
 
2581
				$ok = $ADODB_CACHE->writecache($md5file,$txt,$this->debug, $secs2cache);
2582
				if (!$ok) {
2583
					if ($ok === false) {
2584
						$em = 'Cache write error';
2585
						$en = -32000;
2586
 
2587
						if ($fn = $this->raiseErrorFn) {
2588
							$fn($this->databaseType,'CacheExecute', $en, $em, $md5file,$sql,$this);
2589
						}
2590
					} else {
2591
						$em = 'Cache file locked warning';
2592
						$en = -32001;
2593
						// do not call error handling for just a warning
2594
					}
2595
 
2596
					if ($this->debug) {
2597
						ADOConnection::outp( " ".$em);
2598
					}
2599
				}
2600
				if ($rs->EOF && !$eof) {
2601
					$rs->MoveFirst();
2602
					//$rs = csv2rs($md5file,$err);
2603
					$rs->connection = $this; // Pablo suggestion
2604
				}
2605
 
2606
			} else if (!$this->memCache) {
2607
				$ADODB_CACHE->flushcache($md5file);
2608
			}
2609
		} else {
2610
			$this->_errorMsg = '';
2611
			$this->_errorCode = 0;
2612
 
2613
			if ($this->fnCacheExecute) {
2614
				$fn = $this->fnCacheExecute;
2615
				$fn($this, $secs2cache, $sql, $inputarr);
2616
			}
2617
			// ok, set cached object found
2618
			$rs->connection = $this; // Pablo suggestion
2619
			if ($this->debug){
2620
				if ($this->debug == 99) {
2621
					adodb_backtrace();
2622
				}
2623
				$inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
2624
				$ttl = $rs->timeCreated + $secs2cache - time();
2625
				$s = is_array($sql) ? $sql[0] : $sql;
2626
				if ($inBrowser) {
2627
					$s = '<i>'.htmlspecialchars($s).'</i>';
2628
				}
2629
 
2630
				ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]");
2631
			}
2632
		}
2633
		return $rs;
2634
	}
2635
 
2636
 
2637
	/**
2638
	 * Simple interface to insert and update records.
2639
	 *
2640
	 * Automatically generate and execute INSERT and UPDATE statements
2641
	 * on a given table, similar to PEAR DB's autoExecute().
2642
	 *
2643
	 * @param string $table        Name of the table to process.
2644
	 * @param array $fields_values Associative array of field names => values.
2645
	 * @param string|int $mode     Execution mode: 'INSERT' (default), 'UPDATE' or
2646
	 *                             one of the DB_AUTOQUERY_xx constants.
2647
	 * @param string $where        SQL where clause (mandatory in UPDATE mode as a safety measure)
2648
	 * @param bool $forceUpdate    If true, update all provided fields, even if they have not changed;
2649
	 * 							   otherwise only modified fields are updated.
2650
	 * @param bool $magic_quotes   This param is not used since 5.21.0.
2651
	 *                             It remains for backwards compatibility.
2652
	 *
2653
	 * @return bool
2654
	 *
2655
	 * @noinspection PhpUnusedParameterInspection
2656
	 */
2657
	function autoExecute($table, $fields_values, $mode = 'INSERT', $where = '', $forceUpdate = true, $magic_quotes = false) {
2658
		if (empty($fields_values)) {
2659
			$this->outp_throw('AutoExecute: Empty fields array', 'AutoExecute');
2660
			return false;
2661
		}
2662
		if (empty($where) && ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */)) {
2663
			$this->outp_throw('AutoExecute: Illegal mode=UPDATE with empty WHERE clause', 'AutoExecute');
2664
			return false;
2665
		}
2666
 
2667
		$sql = "SELECT * FROM $table";
2668
		$rs = $this->SelectLimit($sql, 1);
2669
		if (!$rs) {
2670
			return false; // table does not exist
2671
		}
2672
 
2673
		$rs->tableName = $table;
2674
		if (!empty($where)) {
2675
			$sql .= " WHERE $where";
2676
		}
2677
		$rs->sql = $sql;
2678
 
2679
		switch($mode) {
2680
			case 'UPDATE':
2681
			case DB_AUTOQUERY_UPDATE:
2682
				$sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate);
2683
				break;
2684
			case 'INSERT':
2685
			case DB_AUTOQUERY_INSERT:
2686
				$sql = $this->GetInsertSQL($rs, $fields_values);
2687
				break;
2688
			default:
2689
				$this->outp_throw("AutoExecute: Unknown mode=$mode", 'AutoExecute');
2690
				return false;
2691
		}
2692
		return $sql && $this->Execute($sql);
2693
	}
2694
 
2695
 
2696
	/**
2697
	 * Generates an Update Query based on an existing recordset.
2698
	 *
2699
	 * $arrFields is an associative array of fields with the value
2700
	 * that should be assigned.
2701
	 *
2702
	 * Note: This function should only be used on a recordset
2703
	 *       that is run against a single table and sql should only
2704
	 *       be a simple select stmt with no groupby/orderby/limit
2705
	 * @author "Jonathan Younger" <jyounger@unilab.com>
2706
	 *
2707
	 * @param $rs
2708
	 * @param $arrFields
2709
	 * @param bool $forceUpdate
2710
	 * @param bool $magic_quotes This param is not used since 5.21.0.
2711
	 *                           It remains for backwards compatibility.
2712
	 * @param null $force
2713
	 *
2714
	 * @return false|string
2715
	 *
2716
	 * @noinspection PhpUnusedParameterInspection
2717
	 */
2718
	function GetUpdateSQL(&$rs, $arrFields, $forceUpdate=false, $magic_quotes=false, $force=null) {
2719
		global $ADODB_INCLUDED_LIB;
2720
 
2721
		// ********************************************************
2722
		// This is here to maintain compatibility
2723
		// with older adodb versions. Sets force type to force nulls if $forcenulls is set.
2724
		if (!isset($force)) {
2725
			global $ADODB_FORCE_TYPE;
2726
			$force = $ADODB_FORCE_TYPE;
2727
		}
2728
		// ********************************************************
2729
 
2730
		if (empty($ADODB_INCLUDED_LIB)) {
2731
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
2732
		}
2733
		return _adodb_getupdatesql($this, $rs, $arrFields, $forceUpdate, $force);
2734
	}
2735
 
2736
	/**
2737
	 * Generates an Insert Query based on an existing recordset.
2738
	 *
2739
	 * $arrFields is an associative array of fields with the value
2740
	 * that should be assigned.
2741
	 *
2742
	 * Note: This function should only be used on a recordset
2743
	 *       that is run against a single table.
2744
	 *
2745
	 * @param $rs
2746
	 * @param $arrFields
2747
	 * @param bool $magic_quotes This param is not used since 5.21.0.
2748
	 *                           It remains for backwards compatibility.
2749
	 * @param null $force
2750
	 *
2751
	 * @return false|string
2752
	 *
2753
	 * @noinspection PhpUnusedParameterInspection
2754
	 */
2755
	function GetInsertSQL(&$rs, $arrFields, $magic_quotes=false, $force=null) {
2756
		global $ADODB_INCLUDED_LIB;
2757
		if (!isset($force)) {
2758
			global $ADODB_FORCE_TYPE;
2759
			$force = $ADODB_FORCE_TYPE;
2760
		}
2761
		if (empty($ADODB_INCLUDED_LIB)) {
2762
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
2763
		}
2764
		return _adodb_getinsertsql($this, $rs, $arrFields, $force);
2765
	}
2766
 
2767
 
2768
	/**
2769
	 * Update a BLOB column, given a where clause.
2770
	 *
2771
	 * There are more sophisticated blob handling functions that we could have
2772
	 * implemented, but all require a very complex API. Instead we have chosen
2773
	 * something that is extremely simple to understand and use.
2774
	 *
2775
	 * Sample usage:
2776
	 * - update a BLOB in field table.blob_col with value $blobValue, for a
2777
	 *   record having primary key id=1
2778
	 *   $conn->updateBlob('table', 'blob_col', $blobValue, 'id=1');
2779
	 * - insert example:
2780
	 *   $conn->execute('INSERT INTO table (id, blob_col) VALUES (1, null)');
2781
	 *   $conn->updateBlob('table', 'blob_col', $blobValue, 'id=1');
2782
	 *
2783
	 * @param string $table
2784
	 * @param string $column
2785
	 * @param string $val      Filename containing blob data
2786
	 * @param mixed  $where    {@see updateBlob()}
2787
	 * @param string $blobtype supports 'BLOB' (default) and 'CLOB'
2788
	 *
2789
	 * @return bool success
2790
	 */
2791
	function updateBlob($table, $column, $val, $where, $blobtype='BLOB') {
2792
		return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
2793
	}
2794
 
2795
	/**
2796
	 * Update a BLOB from a file.
2797
	 *
2798
	 * Usage example:
2799
	 * $conn->updateBlobFile('table', 'blob_col', '/path/to/file', 'id=1');
2800
	 *
2801
	 * @param string $table
2802
	 * @param string $column
2803
	 * @param string $path     Filename containing blob data
2804
	 * @param mixed  $where    {@see updateBlob()}
2805
	 * @param string $blobtype supports 'BLOB' and 'CLOB'
2806
	 *
2807
	 * @return bool success
2808
	 */
2809
	function updateBlobFile($table, $column, $path, $where, $blobtype='BLOB') {
2810
		$fd = fopen($path,'rb');
2811
		if ($fd === false) {
2812
			return false;
2813
		}
2814
		$val = fread($fd,filesize($path));
2815
		fclose($fd);
2816
		return $this->UpdateBlob($table,$column,$val,$where,$blobtype);
2817
	}
2818
 
2819
	function BlobDecode($blob) {
2820
		return $blob;
2821
	}
2822
 
2823
	function BlobEncode($blob) {
2824
		return $blob;
2825
	}
2826
 
2827
	/**
2828
	 * Retrieve the client connection's current character set.
2829
	 *
2830
	 * @return string|false The character set, or false if it can't be determined.
2831
	 */
2832
	function getCharSet() {
2833
		return $this->charSet;
2834
	}
2835
 
2836
	/**
2837
	 * Sets the client-side character set.
2838
	 *
2839
	 * This is only supported for some databases.
2840
	 * @see https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:setcharset
2841
	 *
2842
	 * @param string $charset The character set to switch to.
2843
	 *
2844
	 * @return bool True if the character set was changed successfully, false otherwise.
2845
	 */
2846
	function setCharSet($charset) {
2847
		$this->charSet = $charset;
2848
		return true;
2849
	}
2850
 
2851
	function IfNull( $field, $ifNull ) {
2852
		return " CASE WHEN $field is null THEN $ifNull ELSE $field END ";
2853
	}
2854
 
2855
	function LogSQL($enable=true) {
2856
		include_once(ADODB_DIR.'/adodb-perf.inc.php');
2857
 
2858
		if ($enable) {
2859
			$this->fnExecute = 'adodb_log_sql';
2860
		} else {
2861
			$this->fnExecute = false;
2862
		}
2863
 
2864
		$old = $this->_logsql;
2865
		$this->_logsql = $enable;
2866
		if ($enable && !$old) {
2867
			$this->_affected = false;
2868
		}
2869
		return $old;
2870
	}
2871
 
2872
	/**
2873
	 * Usage:
2874
	 *	UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
2875
	 *
2876
	 *	$conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
2877
	 *	$conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
2878
	 */
2879
	function UpdateClob($table,$column,$val,$where) {
2880
		return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
2881
	}
2882
 
2883
	// not the fastest implementation - quick and dirty - jlim
2884
	// for best performance, use the actual $rs->MetaType().
2885
	function MetaType($t,$len=-1,$fieldobj=false) {
2886
 
2887
		if (empty($this->_metars)) {
2888
			$rsclass = $this->rsPrefix.$this->databaseType;
2889
			$this->_metars = new $rsclass(false,$this->fetchMode);
2890
			$this->_metars->connection = $this;
2891
		}
2892
		return $this->_metars->MetaType($t,$len,$fieldobj);
2893
	}
2894
 
2895
 
2896
	/**
2897
	 *  Change the SQL connection locale to a specified locale.
2898
	 *  This is used to get the date formats written depending on the client locale.
2899
	 */
2900
	function SetDateLocale($locale = 'En') {
2901
		$this->locale = $locale;
2902
		switch (strtoupper($locale))
2903
		{
2904
			case 'EN':
2905
				$this->fmtDate="'Y-m-d'";
2906
				$this->fmtTimeStamp = "'Y-m-d H:i:s'";
2907
				break;
2908
 
2909
			case 'US':
2910
				$this->fmtDate = "'m-d-Y'";
2911
				$this->fmtTimeStamp = "'m-d-Y H:i:s'";
2912
				break;
2913
 
2914
			case 'PT_BR':
2915
			case 'NL':
2916
			case 'FR':
2917
			case 'RO':
2918
			case 'IT':
2919
				$this->fmtDate="'d-m-Y'";
2920
				$this->fmtTimeStamp = "'d-m-Y H:i:s'";
2921
				break;
2922
 
2923
			case 'GE':
2924
				$this->fmtDate="'d.m.Y'";
2925
				$this->fmtTimeStamp = "'d.m.Y H:i:s'";
2926
				break;
2927
 
2928
			default:
2929
				$this->fmtDate="'Y-m-d'";
2930
				$this->fmtTimeStamp = "'Y-m-d H:i:s'";
2931
				break;
2932
		}
2933
	}
2934
 
2935
	/**
2936
	 * GetActiveRecordsClass Performs an 'ALL' query
2937
	 *
2938
	 * @param mixed $class This string represents the class of the current active record
2939
	 * @param mixed $table Table used by the active record object
2940
	 * @param mixed $whereOrderBy Where, order, by clauses
2941
	 * @param mixed $bindarr
2942
	 * @param mixed $primkeyArr
2943
	 * @param array $extra Query extras: limit, offset...
2944
	 * @param mixed $relations Associative array: table's foreign name, "hasMany", "belongsTo"
2945
	 * @access public
2946
	 * @return void
2947
	 */
2948
	function GetActiveRecordsClass(
2949
			$class, $table,$whereOrderBy=false,$bindarr=false, $primkeyArr=false,
2950
			$extra=array(),
2951
			$relations=array())
2952
	{
2953
		global $_ADODB_ACTIVE_DBS;
2954
		## reduce overhead of adodb.inc.php -- moved to adodb-active-record.inc.php
2955
		## if adodb-active-recordx is loaded -- should be no issue as they will probably use Find()
2956
		if (!isset($_ADODB_ACTIVE_DBS)) {
2957
			include_once(ADODB_DIR.'/adodb-active-record.inc.php');
2958
		}
2959
		return adodb_GetActiveRecordsClass($this, $class, $table, $whereOrderBy, $bindarr, $primkeyArr, $extra, $relations);
2960
	}
2961
 
2962
	function GetActiveRecords($table,$where=false,$bindarr=false,$primkeyArr=false) {
2963
		$arr = $this->GetActiveRecordsClass('ADODB_Active_Record', $table, $where, $bindarr, $primkeyArr);
2964
		return $arr;
2965
	}
2966
 
2967
	/**
2968
	 * Close Connection
2969
	 */
2970
	function Close() {
2971
		$rez = $this->_close();
2972
		$this->_queryID = false;
2973
		$this->_connectionID = false;
2974
		return $rez;
2975
	}
2976
 
2977
	/**
2978
	 * Begin a Transaction.
2979
	 *
2980
	 * Must be followed by CommitTrans() or RollbackTrans().
2981
	 *
2982
	 * @return bool true if succeeded or false if database does not support transactions
2983
	 */
2984
	function BeginTrans() {
2985
		if ($this->debug) {
2986
			ADOConnection::outp("BeginTrans: Transactions not supported for this driver");
2987
		}
2988
		return false;
2989
	}
2990
 
2991
	/* set transaction mode */
2992
	function SetTransactionMode( $transaction_mode ) {
2993
		$transaction_mode = $this->MetaTransaction($transaction_mode, $this->dataProvider);
2994
		$this->_transmode  = $transaction_mode;
2995
	}
2996
/*
2997
http://msdn2.microsoft.com/en-US/ms173763.aspx
2998
http://dev.mysql.com/doc/refman/5.0/en/innodb-transaction-isolation.html
2999
http://www.postgresql.org/docs/8.1/interactive/sql-set-transaction.html
3000
http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_10005.htm
3001
*/
3002
	function MetaTransaction($mode,$db) {
3003
		$mode = strtoupper($mode);
3004
		$mode = str_replace('ISOLATION LEVEL ','',$mode);
3005
 
3006
		switch($mode) {
3007
 
3008
		case 'READ UNCOMMITTED':
3009
			switch($db) {
3010
			case 'oci8':
3011
			case 'oracle':
3012
				return 'ISOLATION LEVEL READ COMMITTED';
3013
			default:
3014
				return 'ISOLATION LEVEL READ UNCOMMITTED';
3015
			}
3016
			break;
3017
 
3018
		case 'READ COMMITTED':
3019
				return 'ISOLATION LEVEL READ COMMITTED';
3020
			break;
3021
 
3022
		case 'REPEATABLE READ':
3023
			switch($db) {
3024
			case 'oci8':
3025
			case 'oracle':
3026
				return 'ISOLATION LEVEL SERIALIZABLE';
3027
			default:
3028
				return 'ISOLATION LEVEL REPEATABLE READ';
3029
			}
3030
			break;
3031
 
3032
		case 'SERIALIZABLE':
3033
				return 'ISOLATION LEVEL SERIALIZABLE';
3034
			break;
3035
 
3036
		default:
3037
			return $mode;
3038
		}
3039
	}
3040
 
3041
	/**
3042
	 * Commits a transaction.
3043
	 *
3044
	 * If database does not support transactions, return true as data is
3045
	 * always committed.
3046
	 *
3047
	 * @param bool $ok True to commit, false to rollback the transaction.
3048
	 *
3049
	 * @return bool true if successful
3050
	 */
3051
	function CommitTrans($ok=true) {
3052
		return true;
3053
	}
3054
 
3055
 
3056
	/**
3057
	 * Rolls back a transaction.
3058
	 *
3059
	 * If database does not support transactions, return false as rollbacks
3060
	 * always fail.
3061
	 *
3062
	 * @return bool true if successful
3063
	 */
3064
	function RollbackTrans() {
3065
		return false;
3066
	}
3067
 
3068
 
3069
	/**
3070
	 * return the databases that the driver can connect to.
3071
	 * Some databases will return an empty array.
3072
	 *
3073
	 * @return array|bool an array of database names.
3074
	 */
3075
	function MetaDatabases() {
3076
		global $ADODB_FETCH_MODE;
3077
 
3078
		if ($this->metaDatabasesSQL) {
3079
			$save = $ADODB_FETCH_MODE;
3080
			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
3081
 
3082
			if ($this->fetchMode !== false) {
3083
				$savem = $this->SetFetchMode(false);
3084
			}
3085
 
3086
			$arr = $this->GetCol($this->metaDatabasesSQL);
3087
			if (isset($savem)) {
3088
				$this->SetFetchMode($savem);
3089
			}
3090
			$ADODB_FETCH_MODE = $save;
3091
 
3092
			return $arr;
3093
		}
3094
 
3095
		return false;
3096
	}
3097
 
3098
	/**
3099
	 * List procedures or functions in an array.
3100
	 * @param procedureNamePattern  a procedure name pattern; must match the procedure name as it is stored in the database
3101
	 * @param catalog a catalog name; must match the catalog name as it is stored in the database;
3102
	 * @param schemaPattern a schema name pattern;
3103
	 *
3104
	 * @return array of procedures on current database.
3105
	 *
3106
	 * Array(
3107
	 *   [name_of_procedure] => Array(
3108
	 *     [type] => PROCEDURE or FUNCTION
3109
	 *     [catalog] => Catalog_name
3110
	 *     [schema] => Schema_name
3111
	 *     [remarks] => explanatory comment on the procedure
3112
	 *   )
3113
	 * )
3114
	 */
3115
	function MetaProcedures($procedureNamePattern = null, $catalog  = null, $schemaPattern  = null) {
3116
		return false;
3117
	}
3118
 
3119
 
3120
	/**
3121
	 * @param ttype can either be 'VIEW' or 'TABLE' or false.
3122
	 *		If false, both views and tables are returned.
3123
	 *		"VIEW" returns only views
3124
	 *		"TABLE" returns only tables
3125
	 * @param showSchema returns the schema/user with the table name, eg. USER.TABLE
3126
	 * @param mask  is the input mask - only supported by oci8 and postgresql
3127
	 *
3128
	 * @return  array of tables for current database.
3129
	 */
3130
	function MetaTables($ttype=false,$showSchema=false,$mask=false) {
3131
		global $ADODB_FETCH_MODE;
3132
 
3133
		if ($mask) {
3134
			return false;
3135
		}
3136
		if ($this->metaTablesSQL) {
3137
			$save = $ADODB_FETCH_MODE;
3138
			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
3139
 
3140
			if ($this->fetchMode !== false) {
3141
				$savem = $this->SetFetchMode(false);
3142
			}
3143
 
3144
			$rs = $this->Execute($this->metaTablesSQL);
3145
			if (isset($savem)) {
3146
				$this->SetFetchMode($savem);
3147
			}
3148
			$ADODB_FETCH_MODE = $save;
3149
 
3150
			if ($rs === false) {
3151
				return false;
3152
			}
3153
			$arr = $rs->GetArray();
3154
			$arr2 = array();
3155
 
3156
			if ($hast = ($ttype && isset($arr[0][1]))) {
3157
				$showt = strncmp($ttype,'T',1);
3158
			}
3159
 
3160
			for ($i=0; $i < sizeof($arr); $i++) {
3161
				if ($hast) {
3162
					if ($showt == 0) {
3163
						if (strncmp($arr[$i][1],'T',1) == 0) {
3164
							$arr2[] = trim($arr[$i][0]);
3165
						}
3166
					} else {
3167
						if (strncmp($arr[$i][1],'V',1) == 0) {
3168
							$arr2[] = trim($arr[$i][0]);
3169
						}
3170
					}
3171
				} else
3172
					$arr2[] = trim($arr[$i][0]);
3173
			}
3174
			$rs->Close();
3175
			return $arr2;
3176
		}
3177
		return false;
3178
	}
3179
 
3180
 
3181
	function _findschema(&$table,&$schema) {
3182
		if (!$schema && ($at = strpos($table,'.')) !== false) {
3183
			$schema = substr($table,0,$at);
3184
			$table = substr($table,$at+1);
3185
		}
3186
	}
3187
 
3188
	/**
3189
	 * List columns in a database as an array of ADOFieldObjects.
3190
	 * See top of file for definition of object.
3191
	 *
3192
	 * @param $table	table name to query
3193
	 * @param $normalize	makes table name case-insensitive (required by some databases)
3194
	 * @schema is optional database schema to use - not supported by all databases.
3195
	 *
3196
	 * @return  array of ADOFieldObjects for current table.
3197
	 */
3198
	function MetaColumns($table,$normalize=true) {
3199
		global $ADODB_FETCH_MODE;
3200
 
3201
		if (!empty($this->metaColumnsSQL)) {
3202
			$schema = false;
3203
			$this->_findschema($table,$schema);
3204
 
3205
			$save = $ADODB_FETCH_MODE;
3206
			$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
3207
			if ($this->fetchMode !== false) {
3208
				$savem = $this->SetFetchMode(false);
3209
			}
3210
			$rs = $this->Execute(sprintf($this->metaColumnsSQL,($normalize)?strtoupper($table):$table));
3211
			if (isset($savem)) {
3212
				$this->SetFetchMode($savem);
3213
			}
3214
			$ADODB_FETCH_MODE = $save;
3215
			if ($rs === false || $rs->EOF) {
3216
				return false;
3217
			}
3218
 
3219
			$retarr = array();
3220
			while (!$rs->EOF) { //print_r($rs->fields);
3221
				$fld = new ADOFieldObject();
3222
				$fld->name = $rs->fields[0];
3223
				$fld->type = $rs->fields[1];
3224
				if (isset($rs->fields[3]) && $rs->fields[3]) {
3225
					if ($rs->fields[3]>0) {
3226
						$fld->max_length = $rs->fields[3];
3227
					}
3228
					$fld->scale = $rs->fields[4];
3229
					if ($fld->scale>0) {
3230
						$fld->max_length += 1;
3231
					}
3232
				} else {
3233
					$fld->max_length = $rs->fields[2];
3234
				}
3235
 
3236
				if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) {
3237
					$retarr[] = $fld;
3238
				} else {
3239
					$retarr[strtoupper($fld->name)] = $fld;
3240
				}
3241
				$rs->MoveNext();
3242
			}
3243
			$rs->Close();
3244
			return $retarr;
3245
		}
3246
		return false;
3247
	}
3248
 
3249
	/**
3250
	 * List indexes on a table as an array.
3251
	 * @param table  table name to query
3252
	 * @param primary true to only show primary keys. Not actually used for most databases
3253
	 *
3254
	 * @return array of indexes on current table. Each element represents an index, and is itself an associative array.
3255
	 *
3256
	 * Array(
3257
	 *   [name_of_index] => Array(
3258
	 *     [unique] => true or false
3259
	 *     [columns] => Array(
3260
	 *       [0] => firstname
3261
	 *       [1] => lastname
3262
	 *     )
3263
	 *   )
3264
	 * )
3265
	 */
3266
	function MetaIndexes($table, $primary = false, $owner = false) {
3267
		return false;
3268
	}
3269
 
3270
	/**
3271
	 * List columns names in a table as an array.
3272
	 * @param table	table name to query
3273
	 *
3274
	 * @return  array of column names for current table.
3275
	 */
3276
	function MetaColumnNames($table, $numIndexes=false,$useattnum=false /* only for postgres */) {
3277
		$objarr = $this->MetaColumns($table);
3278
		if (!is_array($objarr)) {
3279
			return false;
3280
		}
3281
		$arr = array();
3282
		if ($numIndexes) {
3283
			$i = 0;
3284
			if ($useattnum) {
3285
				foreach($objarr as $v)
3286
					$arr[$v->attnum] = $v->name;
3287
 
3288
			} else
3289
				foreach($objarr as $v) $arr[$i++] = $v->name;
3290
		} else
3291
			foreach($objarr as $v) $arr[strtoupper($v->name)] = $v->name;
3292
 
3293
		return $arr;
3294
	}
3295
 
3296
	/**
3297
	 * Concatenate strings.
3298
	 *
3299
	 * Different SQL databases used different methods to combine strings together.
3300
	 * This function provides a wrapper.
3301
	 *
3302
	 * Usage: $db->Concat($str1,$str2);
3303
	 *
3304
	 * @param string $s Variable number of string parameters
3305
	 *
3306
	 * @return string concatenated string
3307
	 */
3308
	function Concat() {
3309
		$arr = func_get_args();
3310
		return implode($this->concat_operator, $arr);
3311
	}
3312
 
3313
 
3314
	/**
3315
	 * Converts a date "d" to a string that the database can understand.
3316
	 *
3317
	 * @param mixed $d a date in Unix date time format.
3318
	 *
3319
	 * @return string date string in database date format
3320
	 */
3321
	function DBDate($d, $isfld=false) {
3322
		if (empty($d) && $d !== 0) {
3323
			return 'null';
3324
		}
3325
		if ($isfld) {
3326
			return $d;
3327
		}
3328
		if (is_object($d)) {
3329
			return $d->format($this->fmtDate);
3330
		}
3331
 
3332
		if (is_string($d) && !is_numeric($d)) {
3333
			if ($d === 'null') {
3334
				return $d;
3335
			}
3336
			if (strncmp($d,"'",1) === 0) {
3337
				$d = _adodb_safedateq($d);
3338
				return $d;
3339
			}
3340
			if ($this->isoDates) {
3341
				return "'$d'";
3342
			}
3343
			$d = ADOConnection::UnixDate($d);
3344
		}
3345
 
3346
		return adodb_date($this->fmtDate,$d);
3347
	}
3348
 
3349
	function BindDate($d) {
3350
		$d = $this->DBDate($d);
3351
		if (strncmp($d,"'",1)) {
3352
			return $d;
3353
		}
3354
 
3355
		return substr($d,1,strlen($d)-2);
3356
	}
3357
 
3358
	function BindTimeStamp($d) {
3359
		$d = $this->DBTimeStamp($d);
3360
		if (strncmp($d,"'",1)) {
3361
			return $d;
3362
		}
3363
 
3364
		return substr($d,1,strlen($d)-2);
3365
	}
3366
 
3367
 
3368
	/**
3369
	 * Converts a timestamp "ts" to a string that the database can understand.
3370
	 *
3371
	 * @param int|object $ts A timestamp in Unix date time format.
3372
	 *
3373
	 * @return string $timestamp string in database timestamp format
3374
	 */
3375
	function DBTimeStamp($ts,$isfld=false) {
3376
		if (empty($ts) && $ts !== 0) {
3377
			return 'null';
3378
		}
3379
		if ($isfld) {
3380
			return $ts;
3381
		}
3382
		if (is_object($ts)) {
3383
			return $ts->format($this->fmtTimeStamp);
3384
		}
3385
 
3386
		# strlen(14) allows YYYYMMDDHHMMSS format
3387
		if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14)) {
3388
			return adodb_date($this->fmtTimeStamp,$ts);
3389
		}
3390
 
3391
		if ($ts === 'null') {
3392
			return $ts;
3393
		}
3394
		if ($this->isoDates && strlen($ts) !== 14) {
3395
			$ts = _adodb_safedate($ts);
3396
			return "'$ts'";
3397
		}
3398
		$ts = ADOConnection::UnixTimeStamp($ts);
3399
		return adodb_date($this->fmtTimeStamp,$ts);
3400
	}
3401
 
3402
	/**
3403
	 * Also in ADORecordSet.
3404
	 * @param mixed $v is a date string in YYYY-MM-DD format
3405
	 *
3406
	 * @return int|false Date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
3407
	 */
3408
	static function UnixDate($v) {
3409
		if (is_object($v)) {
3410
		// odbtp support
3411
		//( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 )
3412
			return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year);
3413
		}
3414
 
3415
		if (is_numeric($v) && strlen($v) !== 8) {
3416
			return $v;
3417
		}
3418
		if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", $v, $rr)) {
3419
			return false;
3420
		}
3421
 
3422
		if ($rr[1] <= TIMESTAMP_FIRST_YEAR) {
3423
			return 0;
3424
		}
3425
 
3426
		// h-m-s-MM-DD-YY
3427
		return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
3428
	}
3429
 
3430
 
3431
	/**
3432
	 * Also in ADORecordSet.
3433
	 * @param string|object $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
3434
	 *
3435
	 * @return int|false Date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
3436
	 */
3437
	static function UnixTimeStamp($v) {
3438
		if (is_object($v)) {
3439
		// odbtp support
3440
		//( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 )
3441
			return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year);
3442
		}
3443
 
3444
		if (!preg_match(
3445
			"|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
3446
			($v), $rr)) return false;
3447
 
3448
		if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) {
3449
			return 0;
3450
		}
3451
 
3452
		// h-m-s-MM-DD-YY
3453
		if (!isset($rr[5])) {
3454
			return  adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
3455
		}
3456
		return @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]);
3457
	}
3458
 
3459
	/**
3460
	 * Format database date based on user defined format.
3461
	 *
3462
	 * Also in ADORecordSet.
3463
	 *
3464
	 * @param mixed  $v    Date in YYYY-MM-DD format, returned by database
3465
	 * @param string $fmt  Format to apply, using date()
3466
	 * @param bool   $gmt
3467
	 *
3468
	 * @return string Formatted date
3469
	 */
3470
	function UserDate($v,$fmt='Y-m-d',$gmt=false) {
3471
		$tt = $this->UnixDate($v);
3472
 
3473
		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
3474
		if (($tt === false || $tt == -1) && $v != false) {
3475
			return $v;
3476
		} else if ($tt == 0) {
3477
			return $this->emptyDate;
3478
		} else if ($tt == -1) {
3479
			// pre-TIMESTAMP_FIRST_YEAR
3480
		}
3481
 
3482
		return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt);
3483
 
3484
	}
3485
 
3486
	/**
3487
	 * Format timestamp based on user defined format.
3488
	 *
3489
	 * @param mixed  $v    Date in YYYY-MM-DD hh:mm:ss format
3490
	 * @param string $fmt  Format to apply, using date()
3491
	 * @param bool   $gmt
3492
	 *
3493
	 * @return string Formatted timestamp
3494
	 */
3495
	function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) {
3496
		if (!isset($v)) {
3497
			return $this->emptyTimeStamp;
3498
		}
3499
		# strlen(14) allows YYYYMMDDHHMMSS format
3500
		if (is_numeric($v) && strlen($v)<14) {
3501
			return ($gmt) ? adodb_gmdate($fmt,$v) : adodb_date($fmt,$v);
3502
		}
3503
		$tt = $this->UnixTimeStamp($v);
3504
		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
3505
		if (($tt === false || $tt == -1) && $v != false) {
3506
			return $v;
3507
		}
3508
		if ($tt == 0) {
3509
			return $this->emptyTimeStamp;
3510
		}
3511
		return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt);
3512
	}
3513
 
3514
	/**
3515
	 * Alias for addQ()
3516
	 * @param string $s
3517
	 * @param bool [$magic_quotes]
3518
	 * @return mixed
3519
	 *
3520
	 * @deprecated 5.21.0
3521
	 * @noinspection PhpUnusedParameterInspection
3522
	 */
3523
	function escape($s,$magic_quotes=false) {
3524
		return $this->addQ($s);
3525
	}
3526
 
3527
	/**
3528
	 * Quotes a string, without prefixing nor appending quotes.
3529
	 *
3530
	 * @param string $s            The string to quote
3531
	 * @param bool   $magic_quotes This param is not used since 5.21.0.
3532
	 *                             It remains for backwards compatibility.
3533
	 *
3534
	 * @return string Quoted string
3535
	 *
3536
	 * @noinspection PhpUnusedParameterInspection
3537
	 */
3538
	function addQ($s, $magic_quotes=false) {
3539
		if ($this->replaceQuote[0] == '\\') {
3540
			$s = str_replace(
3541
				array('\\', "\0"),
3542
				array('\\\\', "\\\0"),
3543
				$s
3544
			);
3545
		}
3546
		return str_replace("'", $this->replaceQuote, $s);
3547
	}
3548
 
3549
	/**
3550
	 * Correctly quotes a string so that all strings are escaped.
3551
	 * We prefix and append to the string single-quotes.
3552
	 * An example is  $db->qstr("Don't bother");
3553
	 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:qstr
3554
	 *
3555
	 * @param string $s            The string to quote
3556
	 * @param bool   $magic_quotes This param is not used since 5.21.0.
3557
	 *                             It remains for backwards compatibility.
3558
	 *
3559
	 * @return string Quoted string to be sent back to database
3560
	 *
3561
	 * @noinspection PhpUnusedParameterInspection
3562
	 */
3563
	function qStr($s, $magic_quotes=false) {
3564
		return  "'" . $this->addQ($s) . "'";
3565
	}
3566
 
3567
 
3568
	/**
3569
	 * Execute query with pagination.
3570
	 *
3571
	 * Will select the supplied $page number from a recordset, divided in
3572
	 * pages of $nrows rows each. It also saves two boolean values saying
3573
	 * if the given page is the first and/or last one of the recordset.
3574
	 *
3575
	 * @param string     $sql        Query to execute
3576
	 * @param int        $nrows      Number of rows per page
3577
	 * @param int        $page       Page number to retrieve (1-based)
3578
	 * @param array|bool $inputarr   Array of bind variables
3579
	 * @param int        $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
3580
	 *
3581
	 * @return ADORecordSet|bool the recordset ($rs->databaseType == 'array')
3582
	 *
3583
	 * @author Iván Oliva
3584
	 */
3585
	function PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0) {
3586
		global $ADODB_INCLUDED_LIB;
3587
		if (empty($ADODB_INCLUDED_LIB)) {
3588
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
3589
		}
3590
		if ($this->pageExecuteCountRows) {
3591
			$rs = _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $secs2cache);
3592
		} else {
3593
			$rs = _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $secs2cache);
3594
		}
3595
		return $rs;
3596
	}
3597
 
3598
 
3599
	/**
3600
	 * Will select the supplied $page number from a recordset, given that it is paginated in pages of
3601
	 * $nrows rows per page. It also saves two boolean values saying if the given page is the first
3602
	 * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
3603
	 *
3604
	 * @param int $secs2cache	seconds to cache data, set to 0 to force query
3605
	 * @param string $sql
3606
	 * @param int $nrows		is the number of rows per page to get
3607
	 * @param int $page		is the page number to get (1-based)
3608
	 * @param mixed[]|bool $inputarr	array of bind variables
3609
	 * @return mixed	the recordset ($rs->databaseType == 'array')
3610
	 */
3611
	function CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) {
3612
		/*switch($this->dataProvider) {
3613
		case 'postgres':
3614
		case 'mysql':
3615
			break;
3616
		default: $secs2cache = 0; break;
3617
		}*/
3618
		return $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache);
3619
	}
3620
 
3621
	/**
3622
	 * Returns the maximum size of a MetaType C field. If the method
3623
	 * is not defined in the driver returns ADODB_STRINGMAX_NOTSET
3624
	 *
3625
	 * @return int
3626
	 */
3627
	function charMax() {
3628
		return ADODB_STRINGMAX_NOTSET;
3629
	}
3630
 
3631
	/**
3632
	 * Returns the maximum size of a MetaType X field. If the method
3633
	 * is not defined in the driver returns ADODB_STRINGMAX_NOTSET
3634
	 *
3635
	 * @return int
3636
	 */
3637
	function textMax() {
3638
		return ADODB_STRINGMAX_NOTSET;
3639
	}
3640
 
3641
	/**
3642
	 * Returns a substring of a varchar type field
3643
	 *
3644
	 * Some databases have variations of the parameters, which is why
3645
	 * we have an ADOdb function for it
3646
	 *
3647
	 * @param	string	$fld	The field to sub-string
3648
	 * @param	int		$start	The start point
3649
	 * @param	int		$length	An optional length
3650
	 *
3651
	 * @return string	The SQL text
3652
	 */
3653
	function substr($fld,$start,$length=0) {
3654
		$text = "{$this->substr}($fld,$start";
3655
		if ($length > 0)
3656
			$text .= ",$length";
3657
		$text .= ')';
3658
		return $text;
3659
	}
3660
 
3661
	/*
3662
	 * Formats the date into Month only format MM with leading zeroes
3663
	 *
3664
	 * @param	string		$fld	The name of the date to format
3665
	 *
3666
	 * @return	string				The SQL text
3667
	 */
3668
	function month($fld) {
3669
		return $this->sqlDate('m',$fld);
3670
	}
3671
 
3672
	/*
3673
	 * Formats the date into Day only format DD with leading zeroes
3674
	 *
3675
	 * @param	string		$fld	The name of the date to format
3676
	 * @return	string		The SQL text
3677
	 */
3678
	function day($fld) {
3679
		return $this->sqlDate('d',$fld);
3680
	}
3681
 
3682
	/*
3683
	 * Formats the date into year only format YYYY
3684
	 *
3685
	 * @param	string		$fld The name of the date to format
3686
	 *
3687
	 * @return	string		The SQL text
3688
	 */
3689
	function year($fld) {
3690
		return $this->sqlDate('Y',$fld);
3691
	}
3692
 
3693
	/**
3694
	 * Get the last error recorded by PHP and clear the message.
3695
	 *
3696
	 * By clearing the message, it becomes possible to detect whether a new error
3697
	 * has occurred, even when it is the same error as before being repeated.
3698
	 *
3699
	 * @return mixed[]|null Array if an error has previously occurred. Null otherwise.
3700
	 */
3701
	protected function resetLastError() {
3702
		$error = error_get_last();
3703
 
3704
		if (is_array($error)) {
3705
			$error['message'] = '';
3706
		}
3707
 
3708
		return $error;
3709
	}
3710
 
3711
	/**
3712
	 * Compare a previously stored error message with the last error recorded by PHP
3713
	 * to determine whether a new error has occurred.
3714
	 *
3715
	 * @param mixed[]|null $old Optional. Previously stored return value of error_get_last().
3716
	 *
3717
	 * @return string The error message if a new error has occurred
3718
	 *                or an empty string if no (new) errors have occurred..
3719
	 */
3720
	protected function getChangedErrorMsg($old = null) {
3721
		$new = error_get_last();
3722
 
3723
		if (is_null($new)) {
3724
			// No error has occurred yet at all.
3725
			return '';
3726
		}
3727
 
3728
		if (is_null($old)) {
3729
			// First error recorded.
3730
			return $new['message'];
3731
		}
3732
 
3733
		$changed = false;
3734
		foreach($new as $key => $value) {
3735
			if ($new[$key] !== $old[$key]) {
3736
				$changed = true;
3737
				break;
3738
			}
3739
		}
3740
 
3741
		if ($changed === true) {
3742
			return $new['message'];
3743
		}
3744
 
3745
		return '';
3746
	}
3747
 
3748
} // end class ADOConnection
3749
 
3750
 
3751
 
3752
	//==============================================================================================
3753
	// CLASS ADOFetchObj
3754
	//==============================================================================================
3755
 
3756
	/**
3757
	 * Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
3758
	 */
3759
	#[\AllowDynamicProperties]
3760
	class ADOFetchObj {
3761
	};
3762
 
3763
	/**
3764
	 * Class ADODB_Iterator_empty
3765
	 */
3766
	class ADODB_Iterator_empty implements Iterator {
3767
 
3768
		private $rs;
3769
 
3770
		function __construct($rs) {
3771
			$this->rs = $rs;
3772
		}
3773
 
3774
		#[\ReturnTypeWillChange]
3775
		function rewind() {}
3776
 
3777
		#[\ReturnTypeWillChange]
3778
		function valid() {
3779
			return !$this->rs->EOF;
3780
		}
3781
 
3782
		#[\ReturnTypeWillChange]
3783
		function key() {
3784
			return false;
3785
		}
3786
 
3787
		#[\ReturnTypeWillChange]
3788
		function current() {
3789
			return false;
3790
		}
3791
 
3792
		#[\ReturnTypeWillChange]
3793
		function next() {}
3794
 
3795
		function __call($func, $params) {
3796
			return call_user_func_array(array($this->rs, $func), $params);
3797
		}
3798
 
3799
		#[\ReturnTypeWillChange]
3800
		function hasMore() {
3801
			return false;
3802
		}
3803
 
3804
	}
3805
 
3806
 
3807
	/**
3808
	 * Lightweight recordset when there are no records to be returned
3809
	 */
3810
	class ADORecordSet_empty implements IteratorAggregate
3811
	{
3812
		var $dataProvider = 'empty';
3813
		var $databaseType = false;
3814
		var $EOF = true;
3815
		var $_numOfRows = 0;
3816
		/** @var bool|array  */
3817
		var $fields = false;
3818
		var $connection = false;
3819
 
3820
		function RowCount() {
3821
			return 0;
3822
		}
3823
 
3824
		function RecordCount() {
3825
			return 0;
3826
		}
3827
 
3828
		function PO_RecordCount() {
3829
			return 0;
3830
		}
3831
 
3832
		function Close() {
3833
			return true;
3834
		}
3835
 
3836
		function FetchRow() {
3837
			return false;
3838
		}
3839
 
3840
		function FieldCount() {
3841
			return 0;
3842
		}
3843
 
3844
		function Init() {}
3845
 
3846
		#[\ReturnTypeWillChange]
3847
		function getIterator() {
3848
			return new ADODB_Iterator_empty($this);
3849
		}
3850
 
3851
		function GetAssoc() {
3852
			return array();
3853
		}
3854
 
3855
		function GetArray() {
3856
			return array();
3857
		}
3858
 
3859
		function GetAll() {
3860
			return array();
3861
		}
3862
 
3863
		function GetArrayLimit() {
3864
			return array();
3865
		}
3866
 
3867
		function GetRows() {
3868
			return array();
3869
		}
3870
 
3871
		function GetRowAssoc() {
3872
			return array();
3873
		}
3874
 
3875
		function MaxRecordCount() {
3876
			return 0;
3877
		}
3878
 
3879
		function NumRows() {
3880
			return 0;
3881
		}
3882
 
3883
		function NumCols() {
3884
			return 0;
3885
		}
3886
	}
3887
 
3888
	//==============================================================================================
3889
	// DATE AND TIME FUNCTIONS
3890
	//==============================================================================================
3891
	if (!defined('ADODB_DATE_VERSION')) {
3892
		include_once(ADODB_DIR.'/adodb-time.inc.php');
3893
	}
3894
 
3895
	/**
3896
	 * Class ADODB_Iterator
3897
	 */
3898
	class ADODB_Iterator implements Iterator {
3899
 
3900
		private $rs;
3901
 
3902
		function __construct($rs) {
3903
			$this->rs = $rs;
3904
		}
3905
 
3906
		#[\ReturnTypeWillChange]
3907
		function rewind() {
3908
			$this->rs->MoveFirst();
3909
		}
3910
 
3911
		#[\ReturnTypeWillChange]
3912
		function valid() {
3913
			return !$this->rs->EOF;
3914
		}
3915
 
3916
		#[\ReturnTypeWillChange]
3917
		function key() {
3918
			return $this->rs->_currentRow;
3919
		}
3920
 
3921
		#[\ReturnTypeWillChange]
3922
		function current() {
3923
			return $this->rs->fields;
3924
		}
3925
 
3926
		#[\ReturnTypeWillChange]
3927
		function next() {
3928
			$this->rs->MoveNext();
3929
		}
3930
 
3931
		function __call($func, $params) {
3932
			return call_user_func_array(array($this->rs, $func), $params);
3933
		}
3934
 
3935
		function hasMore() {
3936
			return !$this->rs->EOF;
3937
		}
3938
 
3939
	}
3940
 
3941
 
3942
/**
3943
 * RecordSet class that represents the dataset returned by the database.
3944
 *
3945
 * To keep memory overhead low, this class holds only the current row in memory.
3946
 * No prefetching of data is done, so the RecordCount() can return -1 (which
3947
 * means recordcount not known).
3948
 */
3949
class ADORecordSet implements IteratorAggregate {
3950
	/**
3951
	 * Used for cases when a recordset object is not created by executing a query.
3952
	 */
3953
	const DUMMY_QUERY_ID = -1;
3954
 
3955
	/**
3956
	 * public variables
3957
	 */
3958
	var $dataProvider = "native";
3959
 
3960
	/**
3961
	 * @var string Table name (used in _adodb_getupdatesql() and _adodb_getinsertsql())-
3962
	 */
3963
	public $tableName = '';
3964
 
3965
	/** @var bool|array  */
3966
	var $fields = false;	/// holds the current row data
3967
	var $blobSize = 100;	/// any varchar/char field this size or greater is treated as a blob
3968
							/// in other words, we use a text area for editing.
3969
	var $canSeek = false;	/// indicates that seek is supported
3970
	var $sql;				/// sql text
3971
	var $EOF = false;		/// Indicates that the current record position is after the last record in a Recordset object.
3972
 
3973
	var $emptyTimeStamp = '&nbsp;'; /// what to display when $time==0
3974
	var $emptyDate = '&nbsp;'; /// what to display when $time==0
3975
	var $debug = false;
3976
	var $timeCreated=0;		/// datetime in Unix format rs created -- for cached recordsets
3977
 
3978
	var $bind = false;		/// used by Fields() to hold array - should be private?
3979
	var $fetchMode;			/// default fetch mode
3980
	/** @var ADOConnection The parent connection */
3981
	var $connection = false;
3982
	/**
3983
	 *	private variables
3984
	 */
3985
	var $_numOfRows = -1;	/** number of rows, or -1 */
3986
	var $_numOfFields = -1;	/** number of fields in recordset */
3987
 
3988
	/**
3989
	 * @var resource|int|false result link identifier
3990
	 */
3991
	var $_queryID = self::DUMMY_QUERY_ID;
3992
 
3993
	var $_currentRow = -1;	/** This variable keeps the current row in the Recordset.	*/
3994
	var $_closed = false;	/** has recordset been closed */
3995
	var $_inited = false;	/** Init() should only be called once */
3996
	var $_obj;				/** Used by FetchObj */
3997
	var $_names;			/** Used by FetchObj */
3998
 
3999
	// Recordset pagination
4000
	/** @var int Number of rows per page */
4001
	var $rowsPerPage;
4002
	/** @var int Current page number */
4003
	var $_currentPage = -1;
4004
	/** @var bool True if current page is the first page */
4005
	var $_atFirstPage = false;
4006
	/** @var bool True if current page is the last page */
4007
	var $_atLastPage = false;
4008
	/** @var int Last page number */
4009
	var $_lastPageNo = -1;
4010
	/** @var int Total number of rows in recordset */
4011
	var $_maxRecordCount = 0;
4012
 
4013
	var $datetime = false;
4014
 
4015
	public $customActualTypes;
4016
	public $customMetaTypes;
4017
 
4018
	/** @var int Only used in _adodb_getinsertsql() */
4019
	public $insertSig;
4020
 
4021
	/**
4022
	 * @var ADOFieldObject[] Field metadata cache
4023
	 * @see fieldTypesArray()
4024
	 */
4025
	protected $fieldObjectsCache;
4026
 
4027
	/**
4028
	 * @var int Defines the Fetch Mode for a recordset
4029
	 * See the ADODB_FETCH_* constants
4030
	 */
4031
	public $adodbFetchMode;
4032
 
4033
	/**
4034
	 * Constructor
4035
	 *
4036
	 * @param resource|int $queryID Query ID returned by ADOConnection->_query()
4037
	 * @param int|bool     $mode    The ADODB_FETCH_MODE value
4038
	 */
4039
	function __construct($queryID,$mode=false) {
4040
		$this->_queryID = $queryID;
4041
	}
4042
 
4043
	function __destruct() {
4044
		$this->Close();
4045
	}
4046
 
4047
	#[\ReturnTypeWillChange]
4048
	function getIterator() {
4049
		return new ADODB_Iterator($this);
4050
	}
4051
 
4052
	/* this is experimental - i don't really know what to return... */
4053
	function __toString() {
4054
		include_once(ADODB_DIR.'/toexport.inc.php');
4055
		return _adodb_export($this,',',',',false,true);
4056
	}
4057
 
4058
	function Init() {
4059
		if ($this->_inited) {
4060
			return;
4061
		}
4062
		$this->_inited = true;
4063
		if ($this->_queryID) {
4064
			@$this->_initRS();
4065
		} else {
4066
			$this->_numOfRows = 0;
4067
			$this->_numOfFields = 0;
4068
		}
4069
		if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) {
4070
			$this->_currentRow = 0;
4071
			if ($this->EOF = ($this->_fetch() === false)) {
4072
				$this->_numOfRows = 0; // _numOfRows could be -1
4073
			}
4074
		} else {
4075
			$this->EOF = true;
4076
		}
4077
	}
4078
 
4079
	/**
4080
	 * Recordset initialization stub
4081
	 */
4082
	protected function _initRS() {}
4083
 
4084
	/**
4085
	 * Row fetch stub
4086
	 * @return bool
4087
	 */
4088
	protected function _fetch() {}
4089
 
4090
	/**
4091
	 * Generate a SELECT tag from a recordset, and return the HTML markup.
4092
	 *
4093
	 * If the recordset has 2 columns, we treat the first one as the text to
4094
	 * display to the user, and the second as the return value. Extra columns
4095
	 * are discarded.
4096
	 *
4097
	 * @param string       $name            Name of SELECT tag
4098
	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
4099
	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
4100
	 *                                      'string' to set its label and 'value:string' to assign a value to it.
4101
	 * @param bool         $multiple        True for multi-select list
4102
	 * @param int          $size            Number of rows to show (applies to multi-select box only)
4103
	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
4104
	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
4105
	 * @param bool         $compareFirstCol When true (default), $defstr is compared against the value (column 2),
4106
	 *                                      while false will compare against the description (column 1).
4107
	 *
4108
	 * @return string HTML
4109
	 */
4110
	function getMenu($name, $defstr = '', $blank1stItem = true, $multiple = false,
4111
					 $size = 0, $selectAttr = '', $compareFirstCol = true)
4112
	{
4113
		global $ADODB_INCLUDED_LIB;
4114
		if (empty($ADODB_INCLUDED_LIB)) {
4115
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
4116
		}
4117
		return _adodb_getmenu($this, $name, $defstr, $blank1stItem, $multiple,
4118
			$size, $selectAttr, $compareFirstCol);
4119
	}
4120
 
4121
	/**
4122
	 * Generate a SELECT tag with groups from a recordset, and return the HTML markup.
4123
	 *
4124
	 * The recordset must have 3 columns and be ordered by the 3rd column. The
4125
	 * first column contains the text to display to the user, the second is the
4126
	 * return value and the third is the option group. Extra columns are discarded.
4127
	 * Default strings are compared with the SECOND column.
4128
	 *
4129
	 * @param string       $name            Name of SELECT tag
4130
	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
4131
	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
4132
	 *                                      'string' to set its label and 'value:string' to assign a value to it.
4133
	 * @param bool         $multiple        True for multi-select list
4134
	 * @param int          $size            Number of rows to show (applies to multi-select box only)
4135
	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
4136
	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
4137
	 * @param bool         $compareFirstCol When true (default), $defstr is compared against the value (column 2),
4138
	 *                                      while false will compare against the description (column 1).
4139
	 *
4140
	 * @return string HTML
4141
	 */
4142
	function getMenuGrouped($name, $defstr = '', $blank1stItem = true, $multiple = false,
4143
							$size = 0, $selectAttr = '', $compareFirstCol = true)
4144
	{
4145
		global $ADODB_INCLUDED_LIB;
4146
		if (empty($ADODB_INCLUDED_LIB)) {
4147
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
4148
		}
4149
		return _adodb_getmenu_gp($this, $name, $defstr, $blank1stItem, $multiple,
4150
			$size, $selectAttr, $compareFirstCol);
4151
	}
4152
 
4153
	/**
4154
	 * Generate a SELECT tag from a recordset, and return the HTML markup.
4155
	 *
4156
	 * Same as GetMenu(), except that default strings are compared with the
4157
	 * FIRST column (the description).
4158
	 *
4159
	 * @param string       $name            Name of SELECT tag
4160
	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
4161
	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
4162
	 *                                      'string' to set its label and 'value:string' to assign a value to it.
4163
	 * @param bool         $multiple        True for multi-select list
4164
	 * @param int          $size            Number of rows to show (applies to multi-select box only)
4165
	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
4166
	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
4167
	 *
4168
	 * @return string HTML
4169
	 *
4170
	 * @deprecated 5.21.0 Use getMenu() with $compareFirstCol = false instead.
4171
	 */
4172
	function getMenu2($name, $defstr = '', $blank1stItem = true, $multiple = false,
4173
					  $size = 0, $selectAttr = '')
4174
	{
4175
		return $this->getMenu($name, $defstr, $blank1stItem, $multiple,
4176
			$size, $selectAttr,false);
4177
	}
4178
 
4179
	/**
4180
	 * Generate a SELECT tag with groups from a recordset, and return the HTML markup.
4181
	 *
4182
	 * Same as GetMenuGrouped(), except that default strings are compared with the
4183
	 * FIRST column (the description).
4184
	 *
4185
	 * @param string       $name            Name of SELECT tag
4186
	 * @param string|array $defstr          The value to highlight. Use an array for multiple highlight values.
4187
	 * @param bool|string $blank1stItem     True to create an empty item (default), False not to add one;
4188
	 *                                      'string' to set its label and 'value:string' to assign a value to it.
4189
	 * @param bool         $multiple        True for multi-select list
4190
	 * @param int          $size            Number of rows to show (applies to multi-select box only)
4191
	 * @param string       $selectAttr      Additional attributes to defined for SELECT tag,
4192
	 *                                      useful for holding javascript onChange='...' handlers, CSS class, etc.
4193
	 *
4194
	 * @return string HTML
4195
	 *
4196
	 * @deprecated 5.21.0 Use getMenuGrouped() with $compareFirstCol = false instead.
4197
	 */
4198
	function getMenu3($name, $defstr = '', $blank1stItem = true, $multiple = false,
4199
					  $size = 0, $selectAttr = '')
4200
	{
4201
		return $this->getMenuGrouped($name, $defstr, $blank1stItem, $multiple,
4202
			$size, $selectAttr, false);
4203
	}
4204
 
4205
	/**
4206
	 * return recordset as a 2-dimensional array.
4207
	 *
4208
	 * @param int $nRows  Number of rows to return. -1 means every row.
4209
	 *
4210
	 * @return array indexed by the rows (0-based) from the recordset
4211
	 */
4212
	function GetArray($nRows = -1) {
4213
		$results = array();
4214
		$cnt = 0;
4215
		while (!$this->EOF && $nRows != $cnt) {
4216
			$results[] = $this->fields;
4217
			$this->MoveNext();
4218
			$cnt++;
4219
		}
4220
		return $results;
4221
	}
4222
 
4223
	function GetAll($nRows = -1) {
4224
		return $this->GetArray($nRows);
4225
	}
4226
 
4227
	/**
4228
	 * Checks if there is another available recordset.
4229
	 *
4230
	 * Some databases allow multiple recordsets to be returned.
4231
	 *
4232
	 * @return boolean true if there is a next recordset, or false if no more
4233
	 */
4234
	function NextRecordSet() {
4235
		return false;
4236
	}
4237
 
4238
	/**
4239
	 * Return recordset as a 2-dimensional array.
4240
	 *
4241
	 * Helper function for ADOConnection->SelectLimit()
4242
	 *
4243
	 * @param int $nrows  Number of rows to return
4244
	 * @param int $offset Starting row (1-based)
4245
	 *
4246
	 * @return array an array indexed by the rows (0-based) from the recordset
4247
	 */
4248
	function getArrayLimit($nrows, $offset=-1) {
4249
		if ($offset <= 0) {
4250
			return $this->GetArray($nrows);
4251
		}
4252
 
4253
		$this->Move($offset);
4254
 
4255
		$results = array();
4256
		$cnt = 0;
4257
		while (!$this->EOF && $nrows != $cnt) {
4258
			$results[$cnt++] = $this->fields;
4259
			$this->MoveNext();
4260
		}
4261
 
4262
		return $results;
4263
	}
4264
 
4265
 
4266
	/**
4267
	 * Synonym for GetArray() for compatibility with ADO.
4268
	 *
4269
	 * @param int $nRows Number of rows to return. -1 means every row.
4270
	 *
4271
	 * @return array an array indexed by the rows (0-based) from the recordset
4272
	 */
4273
	function getRows($nRows = -1) {
4274
		return $this->GetArray($nRows);
4275
	}
4276
 
4277
	/**
4278
	 * return whole recordset as a 2-dimensional associative array if
4279
	 * there are more than 2 columns. The first column is treated as the
4280
	 * key and is not included in the array. If there is only 2 columns,
4281
	 * it will return a 1 dimensional array of key-value pairs unless
4282
	 * $force_array == true. This recordset method is currently part of
4283
	 * the API, but may not be in later versions of ADOdb. By preference, use
4284
	 * ADOconnnection::getAssoc()
4285
	 *
4286
	 * @param bool	$force_array	(optional) Has only meaning if we have 2 data
4287
	 *								columns. If false, a 1 dimensional
4288
	 * 								array is returned, otherwise a 2 dimensional
4289
	 *								array is returned. If this sounds confusing,
4290
	 * 								read the source.
4291
	 *
4292
	 * @param bool	$first2cols 	(optional) Means if there are more than
4293
	 *								2 cols, ignore the remaining cols and
4294
	 * 								instead of returning
4295
	 *								array[col0] => array(remaining cols),
4296
	 *								return array[col0] => col1
4297
	 *
4298
	 * @return mixed[]|false
4299
	 *
4300
	 */
4301
	function getAssoc($force_array = false, $first2cols = false)
4302
	{
4303
		/*
4304
		* Insufficient rows to show data
4305
		*/
4306
		if ($this->_numOfFields < 2)
4307
			  return;
4308
 
4309
		/*
4310
		* Empty recordset
4311
		*/
4312
		if (!$this->fields) {
4313
			return array();
4314
		}
4315
 
4316
		/*
4317
		* The number of fields is half the actual returned in BOTH mode
4318
		*/
4319
		$numberOfFields = $this->_numOfFields;
4320
 
4321
		/*
4322
		* Get the fetch mode when the call was executed, this may be
4323
		* different than ADODB_FETCH_MODE
4324
		*/
4325
		$fetchMode = $this->connection->fetchMode;
4326
		if ($fetchMode == ADODB_FETCH_BOTH) {
4327
			/*
4328
			* If we are using BOTH, we present the data as if it
4329
			* was in ASSOC mode. This could be enhanced by adding
4330
			* a BOTH_ASSOC_MODE class property
4331
			* We build a template of numeric keys. you could improve the
4332
			* speed by caching this, indexed by number of keys
4333
			*/
4334
			$testKeys = array_fill(0,$numberOfFields,0);
4335
		}
4336
 
4337
		$showArrayMethod = 0;
4338
 
4339
		if ($numberOfFields == 2)
4340
			/*
4341
			* Key is always value of first element
4342
			* Value is always value of second element
4343
			*/
4344
			$showArrayMethod = 1;
4345
 
4346
		if ($force_array)
4347
			$showArrayMethod = 0;
4348
 
4349
		if ($first2cols)
4350
			$showArrayMethod = 1;
4351
 
4352
		$results  = array();
4353
 
4354
		while (!$this->EOF){
4355
 
4356
			$myFields = $this->fields;
4357
 
4358
			if ($fetchMode == ADODB_FETCH_BOTH) {
4359
				/*
4360
				* extract the associative keys
4361
				*/
4362
				$myFields = array_diff_key($myFields,$testKeys);
4363
			}
4364
 
4365
			/*
4366
			* key is value of first element, rest is data,
4367
			* The key is not case processed
4368
			*/
4369
			$key = array_shift($myFields);
4370
 
4371
			switch ($showArrayMethod) {
4372
			case 0:
4373
 
4374
				if ($fetchMode != ADODB_FETCH_NUM) {
4375
					/*
4376
					* The driver should have already handled the key
4377
					* casing, but in case it did not. We will check and force
4378
					* this in later versions of ADOdb
4379
					*/
4380
					if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_UPPER)
4381
						$myFields = array_change_key_case($myFields,CASE_UPPER);
4382
 
4383
					elseif (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER)
4384
						$myFields = array_change_key_case($myFields,CASE_LOWER);
4385
 
4386
					/*
4387
					* We have already shifted the key off
4388
					* the front, so the rest is the value
4389
					*/
4390
					$results[$key] = $myFields;
4391
 
4392
				}
4393
				else
4394
					/*
4395
					 * I want the values in a numeric array,
4396
					 * nicely re-indexed from zero
4397
					 */
4398
					$results[$key] = array_values($myFields);
4399
				break;
4400
 
4401
			case 1:
4402
 
4403
				/*
4404
				 * Don't care how long the array is,
4405
				 * I just want value of second column, and it doesn't
4406
				 * matter whether the array is associative or numeric
4407
				 */
4408
				$results[$key] = array_shift($myFields);
4409
				break;
4410
			}
4411
 
4412
			$this->MoveNext();
4413
		}
4414
		/*
4415
		 * Done
4416
		 */
4417
		return $results;
4418
	}
4419
 
4420
	/**
4421
	 *
4422
	 * @param mixed $v		is the character timestamp in YYYY-MM-DD hh:mm:ss format
4423
	 * @param string [$fmt]	is the format to apply to it, using date()
4424
	 *
4425
	 * @return string a timestamp formated as user desires
4426
	 */
4427
	function UserTimeStamp($v,$fmt='Y-m-d H:i:s') {
4428
		if (is_numeric($v) && strlen($v)<14) {
4429
			return adodb_date($fmt,$v);
4430
		}
4431
		$tt = $this->UnixTimeStamp($v);
4432
		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
4433
		if (($tt === false || $tt == -1) && $v != false) {
4434
			return $v;
4435
		}
4436
		if ($tt === 0) {
4437
			return $this->emptyTimeStamp;
4438
		}
4439
		return adodb_date($fmt,$tt);
4440
	}
4441
 
4442
 
4443
	/**
4444
	 * @param mixed $v		is the character date in YYYY-MM-DD format, returned by database
4445
	 * @param string $fmt	is the format to apply to it, using date()
4446
	 *
4447
	 * @return string a date formatted as user desires
4448
	 */
4449
	function UserDate($v,$fmt='Y-m-d') {
4450
		$tt = $this->UnixDate($v);
4451
		// $tt == -1 if pre TIMESTAMP_FIRST_YEAR
4452
		if (($tt === false || $tt == -1) && $v != false) {
4453
			return $v;
4454
		} else if ($tt == 0) {
4455
			return $this->emptyDate;
4456
		} else if ($tt == -1) {
4457
			// pre-TIMESTAMP_FIRST_YEAR
4458
		}
4459
		return adodb_date($fmt,$tt);
4460
	}
4461
 
4462
 
4463
	/**
4464
	 * @param mixed $v is a date string in YYYY-MM-DD format
4465
	 *
4466
	 * @return string date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
4467
	 */
4468
	static function UnixDate($v) {
4469
		return ADOConnection::UnixDate($v);
4470
	}
4471
 
4472
 
4473
	/**
4474
	 * @param string|object $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
4475
	 *
4476
	 * @return mixed date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
4477
	 */
4478
	static function UnixTimeStamp($v) {
4479
		return ADOConnection::UnixTimeStamp($v);
4480
	}
4481
 
4482
 
4483
	/**
4484
	 * PEAR DB Compat - do not use internally
4485
	 */
4486
	function Free() {
4487
		return $this->Close();
4488
	}
4489
 
4490
 
4491
	/**
4492
	 * PEAR DB compat, number of rows
4493
	 *
4494
	 * @return int
4495
	 */
4496
	function NumRows() {
4497
		return $this->_numOfRows;
4498
	}
4499
 
4500
 
4501
	/**
4502
	 * PEAR DB compat, number of cols
4503
	 *
4504
	 * @return int
4505
	 */
4506
	function NumCols() {
4507
		return $this->_numOfFields;
4508
	}
4509
 
4510
	/**
4511
	 * Fetch a row, returning false if no more rows.
4512
	 * This is PEAR DB compat mode.
4513
	 *
4514
	 * @return mixed[]|false false or array containing the current record
4515
	 */
4516
	function FetchRow() {
4517
		if ($this->EOF) {
4518
			return false;
4519
		}
4520
		$arr = $this->fields;
4521
		$this->_currentRow++;
4522
		if (!$this->_fetch()) {
4523
			$this->EOF = true;
4524
		}
4525
		return $arr;
4526
	}
4527
 
4528
 
4529
	/**
4530
	 * Fetch a row, returning PEAR_Error if no more rows.
4531
	 * This is PEAR DB compat mode.
4532
	 *
4533
	 * @param mixed[]|false $arr
4534
	 *
4535
	 * @return mixed DB_OK or error object
4536
	 */
4537
	function FetchInto(&$arr) {
4538
		if ($this->EOF) {
4539
			return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false;
4540
		}
4541
		$arr = $this->fields;
4542
		$this->MoveNext();
4543
		return 1; // DB_OK
4544
	}
4545
 
4546
 
4547
	/**
4548
	 * Move to the first row in the recordset. Many databases do NOT support this.
4549
	 *
4550
	 * @return bool true or false
4551
	 */
4552
	function MoveFirst() {
4553
		if ($this->_currentRow == 0) {
4554
			return true;
4555
		}
4556
		return $this->Move(0);
4557
	}
4558
 
4559
 
4560
	/**
4561
	 * Move to the last row in the recordset.
4562
	 *
4563
	 * @return bool true or false
4564
	 */
4565
	function MoveLast() {
4566
		if ($this->_numOfRows >= 0) {
4567
			return $this->Move($this->_numOfRows-1);
4568
		}
4569
		if ($this->EOF) {
4570
			return false;
4571
		}
4572
		while (!$this->EOF) {
4573
			$f = $this->fields;
4574
			$this->MoveNext();
4575
		}
4576
		$this->fields = $f;
4577
		$this->EOF = false;
4578
		return true;
4579
	}
4580
 
4581
 
4582
	/**
4583
	 * Move to next record in the recordset.
4584
	 *
4585
	 * @return bool true if there still rows available, or false if there are no more rows (EOF).
4586
	 */
4587
	function MoveNext() {
4588
		if (!$this->EOF) {
4589
			$this->_currentRow++;
4590
			if ($this->_fetch()) {
4591
				return true;
4592
			}
4593
		}
4594
		$this->EOF = true;
4595
		/* -- tested error handling when scrolling cursor -- seems useless.
4596
		$conn = $this->connection;
4597
		if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) {
4598
			$fn = $conn->raiseErrorFn;
4599
			$fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database);
4600
		}
4601
		*/
4602
		return false;
4603
	}
4604
 
4605
 
4606
	/**
4607
	 * Random access to a specific row in the recordset. Some databases do not support
4608
	 * access to previous rows in the databases (no scrolling backwards).
4609
	 *
4610
	 * @param int $rowNumber is the row to move to (0-based)
4611
	 *
4612
	 * @return bool true if there still rows available, or false if there are no more rows (EOF).
4613
	 */
4614
	function Move($rowNumber = 0) {
4615
		$this->EOF = false;
4616
		if ($rowNumber == $this->_currentRow) {
4617
			return true;
4618
		}
4619
		if ($rowNumber >= $this->_numOfRows) {
4620
			if ($this->_numOfRows != -1) {
4621
				$rowNumber = $this->_numOfRows-2;
4622
			}
4623
		}
4624
 
4625
		if ($rowNumber < 0) {
4626
			$this->EOF = true;
4627
			return false;
4628
		}
4629
 
4630
		if ($this->canSeek) {
4631
			if ($this->_seek($rowNumber)) {
4632
				$this->_currentRow = $rowNumber;
4633
				if ($this->_fetch()) {
4634
					return true;
4635
				}
4636
			} else {
4637
				$this->EOF = true;
4638
				return false;
4639
			}
4640
		} else {
4641
			if ($rowNumber < $this->_currentRow) {
4642
				return false;
4643
			}
4644
			while (! $this->EOF && $this->_currentRow < $rowNumber) {
4645
				$this->_currentRow++;
4646
 
4647
				if (!$this->_fetch()) {
4648
					$this->EOF = true;
4649
				}
4650
			}
4651
			return !($this->EOF);
4652
		}
4653
 
4654
		$this->fields = false;
4655
		$this->EOF = true;
4656
		return false;
4657
	}
4658
 
4659
	/**
4660
	 * Adjusts the result pointer to an arbitrary row in the result.
4661
	 *
4662
	 * @param int $row The row to seek to.
4663
	 *
4664
	 * @return bool False if the recordset contains no rows, otherwise true.
4665
	 */
4666
	function _seek($row) {}
4667
 
4668
	/**
4669
	 * Get the value of a field in the current row by column name.
4670
	 * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM.
4671
	 *
4672
	 * @param string $colname is the field to access
4673
	 *
4674
	 * @return mixed the value of $colname column
4675
	 */
4676
	function Fields($colname) {
4677
		return $this->fields[$colname];
4678
	}
4679
 
4680
	/**
4681
	 * Defines the function to use for table fields case conversion
4682
	 * depending on ADODB_ASSOC_CASE
4683
	 *
4684
	 * @param int [$case]
4685
	 *
4686
	 * @return string strtolower/strtoupper or false if no conversion needed
4687
	 */
4688
	protected function AssocCaseConvertFunction($case = ADODB_ASSOC_CASE) {
4689
		switch($case) {
4690
			case ADODB_ASSOC_CASE_UPPER:
4691
				return 'strtoupper';
4692
			case ADODB_ASSOC_CASE_LOWER:
4693
				return 'strtolower';
4694
			case ADODB_ASSOC_CASE_NATIVE:
4695
			default:
4696
				return false;
4697
		}
4698
	}
4699
 
4700
	/**
4701
	 * Builds the bind array associating keys to recordset fields
4702
	 *
4703
	 * @param int [$upper] Case for the array keys, defaults to uppercase
4704
	 *                   (see ADODB_ASSOC_CASE_xxx constants)
4705
	 */
4706
	function GetAssocKeys($upper = ADODB_ASSOC_CASE) {
4707
		if ($this->bind) {
4708
			return;
4709
		}
4710
		$this->bind = array();
4711
 
4712
		// Define case conversion function for ASSOC fetch mode
4713
		$fn_change_case = $this->AssocCaseConvertFunction($upper);
4714
 
4715
		// Build the bind array
4716
		for ($i=0; $i < $this->_numOfFields; $i++) {
4717
			$o = $this->FetchField($i);
4718
 
4719
			// Set the array's key
4720
			if(is_numeric($o->name)) {
4721
				// Just use the field ID
4722
				$key = $i;
4723
			}
4724
			elseif( $fn_change_case ) {
4725
				// Convert the key's case
4726
				$key = $fn_change_case($o->name);
4727
			}
4728
			else {
4729
				$key = $o->name;
4730
			}
4731
 
4732
			$this->bind[$key] = $i;
4733
		}
4734
	}
4735
 
4736
	/**
4737
	 * Use associative array to get fields array for databases that do not support
4738
	 * associative arrays. Submitted by Paolo S. Asioli paolo.asioli#libero.it
4739
	 *
4740
	 * @param int $upper Case for the array keys, defaults to uppercase
4741
	 *                   (see ADODB_ASSOC_CASE_xxx constants)
4742
	 * @return array
4743
	 */
4744
	function GetRowAssoc($upper = ADODB_ASSOC_CASE) {
4745
		$record = array();
4746
		$this->GetAssocKeys($upper);
4747
 
4748
		foreach($this->bind as $k => $v) {
4749
			if( array_key_exists( $v, $this->fields ) ) {
4750
				$record[$k] = $this->fields[$v];
4751
			} elseif( array_key_exists( $k, $this->fields ) ) {
4752
				$record[$k] = $this->fields[$k];
4753
			} else {
4754
				# This should not happen... trigger error ?
4755
				$record[$k] = null;
4756
			}
4757
		}
4758
		return $record;
4759
	}
4760
 
4761
	/**
4762
	 * Clean up recordset
4763
	 *
4764
	 * @return bool
4765
	 */
4766
	function Close() {
4767
		// free connection object - this seems to globally free the object
4768
		// and not merely the reference, so don't do this...
4769
		// $this->connection = false;
4770
		if (!$this->_closed) {
4771
			$this->_closed = true;
4772
			return $this->_close();
4773
		} else
4774
			return true;
4775
	}
4776
 
4777
	/**
4778
	 * Number of rows in recordset.
4779
	 *
4780
	 * @return int Number of rows or -1 if this is not supported
4781
	 */
4782
	function recordCount() {
4783
		return $this->_numOfRows;
4784
	}
4785
 
4786
	/**
4787
	 * If we are using PageExecute(), this will return the maximum possible rows
4788
	 * that can be returned when paging a recordset.
4789
	 *
4790
	 * @return int
4791
	 */
4792
	function MaxRecordCount() {
4793
		return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->recordCount();
4794
	}
4795
 
4796
	/**
4797
	 * Number of rows in recordset.
4798
	 * Alias for {@see recordCount()}
4799
	 *
4800
	 * @return int Number of rows or -1 if this is not supported
4801
	 */
4802
	function rowCount() {
4803
		return $this->recordCount();
4804
	}
4805
 
4806
	/**
4807
	 * Portable RecordCount.
4808
	 *
4809
	 * Be aware of possible problems in multiuser environments.
4810
	 * For better speed the table must be indexed by the condition.
4811
	 * Heavy test this before deploying.
4812
	 *
4813
	 * @param string $table
4814
	 * @param string $condition
4815
	 *
4816
	 * @return int Number of records from a previous SELECT. All databases support this.
4817
	 *
4818
	 * @author Pablo Roca <pabloroca@mvps.org>
4819
	 */
4820
	function PO_RecordCount($table="", $condition="") {
4821
 
4822
		$lnumrows = $this->_numOfRows;
4823
		// the database doesn't support native recordcount, so we do a workaround
4824
		if ($lnumrows == -1 && $this->connection) {
4825
			IF ($table) {
4826
				if ($condition) {
4827
					$condition = " WHERE " . $condition;
4828
				}
4829
				$resultrows = $this->connection->Execute("SELECT COUNT(*) FROM $table $condition");
4830
				if ($resultrows) {
4831
					$lnumrows = reset($resultrows->fields);
4832
				}
4833
			}
4834
		}
4835
		return $lnumrows;
4836
	}
4837
 
4838
 
4839
	/**
4840
	 * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
4841
	 */
4842
	function CurrentRow() {
4843
		return $this->_currentRow;
4844
	}
4845
 
4846
	/**
4847
	 * synonym for CurrentRow -- for ADO compat
4848
	 *
4849
	 * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
4850
	 */
4851
	function AbsolutePosition() {
4852
		return $this->_currentRow;
4853
	}
4854
 
4855
	/**
4856
	 * @return the number of columns in the recordset. Some databases will set this to 0
4857
	 * if no records are returned, others will return the number of columns in the query.
4858
	 */
4859
	function FieldCount() {
4860
		return $this->_numOfFields;
4861
	}
4862
 
4863
	/**
4864
	 * Get a Field's metadata from database.
4865
	 *
4866
	 * Must be defined by child class.
4867
	 *
4868
	 * @param int $fieldOffset
4869
	 *
4870
	 * @return ADOFieldObject|false
4871
	 */
4872
	function fetchField($fieldOffset)
4873
	{
4874
		return false;
4875
	}
4876
 
4877
	/**
4878
	 * Get Field metadata for all the recordset's columns in an array.
4879
	 *
4880
	 * @return ADOFieldObject[]
4881
	 */
4882
	function fieldTypesArray() {
4883
		if (empty($this->fieldObjectsCache)) {
4884
			for ($i = 0; $i < $this->_numOfFields; $i++) {
4885
				$this->fieldObjectsCache[] = $this->fetchField($i);
4886
			}
4887
		}
4888
		return $this->fieldObjectsCache;
4889
	}
4890
 
4891
	/**
4892
	 * Return the fields array of the current row as an object for convenience.
4893
	 * The default case is lowercase field names.
4894
	 *
4895
	 * @return the object with the properties set to the fields of the current row
4896
	 */
4897
	function FetchObj() {
4898
		return $this->FetchObject(false);
4899
	}
4900
 
4901
	/**
4902
	 * Return the fields array of the current row as an object for convenience.
4903
	 * The default case is uppercase.
4904
	 *
4905
	 * @param bool $isUpper to set the object property names to uppercase
4906
	 *
4907
	 * @return ADOFetchObj The object with properties set to the fields of the current row
4908
	 */
4909
	function FetchObject($isUpper=true) {
4910
		if (empty($this->_obj)) {
4911
			$this->_obj = new ADOFetchObj();
4912
			$this->_names = array();
4913
			for ($i=0; $i <$this->_numOfFields; $i++) {
4914
				$f = $this->FetchField($i);
4915
				$this->_names[] = $f->name;
4916
			}
4917
		}
4918
		$o = clone($this->_obj);
4919
 
4920
		for ($i=0; $i <$this->_numOfFields; $i++) {
4921
			$name = $this->_names[$i];
4922
			if ($isUpper) {
4923
				$n = strtoupper($name);
4924
			} else {
4925
				$n = $name;
4926
			}
4927
 
4928
			$o->$n = $this->Fields($name);
4929
		}
4930
		return $o;
4931
	}
4932
 
4933
	/**
4934
	 * Return the fields array of the current row as an object for convenience.
4935
	 * The default is lower-case field names.
4936
	 *
4937
	 * @return ADOFetchObj|false The object with properties set to the fields of the current row
4938
	 *                           or false if EOF.
4939
	 *
4940
	 * Fixed bug reported by tim@orotech.net
4941
	 */
4942
	function FetchNextObj() {
4943
		return $this->FetchNextObject(false);
4944
	}
4945
 
4946
 
4947
	/**
4948
	 * Return the fields array of the current row as an object for convenience.
4949
	 * The default is upper case field names.
4950
	 *
4951
	 * @param bool $isUpper to set the object property names to uppercase
4952
	 *
4953
	 * @return ADOFetchObj|false The object with properties set to the fields of the current row
4954
	 *                           or false if EOF.
4955
	 *
4956
	 * Fixed bug reported by tim@orotech.net
4957
	 */
4958
	function FetchNextObject($isUpper=true) {
4959
		$o = false;
4960
		if ($this->_numOfRows != 0 && !$this->EOF) {
4961
			$o = $this->FetchObject($isUpper);
4962
			$this->_currentRow++;
4963
			if ($this->_fetch()) {
4964
				return $o;
4965
			}
4966
		}
4967
		$this->EOF = true;
4968
		return $o;
4969
	}
4970
 
4971
	/**
4972
	 * Get the ADOdb metatype.
4973
	 *
4974
	 * Many databases use different names for the same type, so we transform
4975
	 * the native type to our standardised one, which uses 1 character codes.
4976
	 * @see https://adodb.org/dokuwiki/doku.php?id=v5:dictionary:dictionary_index#portable_data_types
4977
	 *
4978
	 * @param string|ADOFieldObject $t  Native type (usually ADOFieldObject->type)
4979
	 *                                  It is also possible to provide an
4980
	 *                                  ADOFieldObject here.
4981
	 * @param int $len The field's maximum length. This is because we treat
4982
	 *                 character fields bigger than a certain size as a 'B' (blob).
4983
	 * @param ADOFieldObject $fieldObj Field object returned by the database driver;
4984
	 *                                 can hold additional info (eg. primary_key for mysql).
4985
	 *
4986
	 * @return string The ADOdb Standard type
4987
	 */
4988
	function metaType($t, $len = -1, $fieldObj = false) {
4989
		if ($t instanceof ADOFieldObject) {
4990
			$fieldObj = $t;
4991
			$t = $fieldObj->type;
4992
			$len = $fieldObj->max_length;
4993
		}
4994
 
4995
		// changed in 2.32 to hashing instead of switch stmt for speed...
4996
		static $typeMap = array(
4997
			'VARCHAR' => 'C',
4998
			'VARCHAR2' => 'C',
4999
			'CHAR' => 'C',
5000
			'C' => 'C',
5001
			'STRING' => 'C',
5002
			'NCHAR' => 'C',
5003
			'NVARCHAR' => 'C',
5004
			'VARYING' => 'C',
5005
			'BPCHAR' => 'C',
5006
			'CHARACTER' => 'C',
5007
			'INTERVAL' => 'C',  # Postgres
5008
			'MACADDR' => 'C', # postgres
5009
			'VAR_STRING' => 'C', # mysql
5010
			##
5011
			'LONGCHAR' => 'X',
5012
			'TEXT' => 'X',
5013
			'NTEXT' => 'X',
5014
			'M' => 'X',
5015
			'X' => 'X',
5016
			'CLOB' => 'X',
5017
			'NCLOB' => 'X',
5018
			'LVARCHAR' => 'X',
5019
			##
5020
			'BLOB' => 'B',
5021
			'IMAGE' => 'B',
5022
			'BINARY' => 'B',
5023
			'VARBINARY' => 'B',
5024
			'LONGBINARY' => 'B',
5025
			'B' => 'B',
5026
			##
5027
			'YEAR' => 'D', // mysql
5028
			'DATE' => 'D',
5029
			'D' => 'D',
5030
			##
5031
			'UNIQUEIDENTIFIER' => 'C', # MS SQL Server
5032
			##
5033
			'SMALLDATETIME' => 'T',
5034
			'TIME' => 'T',
5035
			'TIMESTAMP' => 'T',
5036
			'DATETIME' => 'T',
5037
			'DATETIME2' => 'T',
5038
			'TIMESTAMPTZ' => 'T',
5039
			'T' => 'T',
5040
			'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql
5041
			##
5042
			'BOOL' => 'L',
5043
			'BOOLEAN' => 'L',
5044
			'BIT' => 'L',
5045
			'L' => 'L',
5046
			##
5047
			'COUNTER' => 'R',
5048
			'R' => 'R',
5049
			'SERIAL' => 'R', // ifx
5050
			'INT IDENTITY' => 'R',
5051
			##
5052
			'INT' => 'I',
5053
			'INT2' => 'I',
5054
			'INT4' => 'I',
5055
			'INT8' => 'I',
5056
			'INTEGER' => 'I',
5057
			'INTEGER UNSIGNED' => 'I',
5058
			'SHORT' => 'I',
5059
			'TINYINT' => 'I',
5060
			'SMALLINT' => 'I',
5061
			'I' => 'I',
5062
			##
5063
			'LONG' => 'N', // interbase is numeric, oci8 is blob
5064
			'BIGINT' => 'N', // this is bigger than PHP 32-bit integers
5065
			'DECIMAL' => 'N',
5066
			'DEC' => 'N',
5067
			'REAL' => 'N',
5068
			'DOUBLE' => 'N',
5069
			'DOUBLE PRECISION' => 'N',
5070
			'SMALLFLOAT' => 'N',
5071
			'FLOAT' => 'N',
5072
			'NUMBER' => 'N',
5073
			'NUM' => 'N',
5074
			'NUMERIC' => 'N',
5075
			'MONEY' => 'N',
5076
 
5077
			## informix 9.2
5078
			'SQLINT' => 'I',
5079
			'SQLSERIAL' => 'I',
5080
			'SQLSMINT' => 'I',
5081
			'SQLSMFLOAT' => 'N',
5082
			'SQLFLOAT' => 'N',
5083
			'SQLMONEY' => 'N',
5084
			'SQLDECIMAL' => 'N',
5085
			'SQLDATE' => 'D',
5086
			'SQLVCHAR' => 'C',
5087
			'SQLCHAR' => 'C',
5088
			'SQLDTIME' => 'T',
5089
			'SQLINTERVAL' => 'N',
5090
			'SQLBYTES' => 'B',
5091
			'SQLTEXT' => 'X',
5092
			## informix 10
5093
			"SQLINT8" => 'I8',
5094
			"SQLSERIAL8" => 'I8',
5095
			"SQLNCHAR" => 'C',
5096
			"SQLNVCHAR" => 'C',
5097
			"SQLLVARCHAR" => 'X',
5098
			"SQLBOOL" => 'L'
5099
		);
5100
 
5101
		$t = strtoupper($t);
5102
		$tmap = (isset($typeMap[$t])) ? $typeMap[$t] : ADODB_DEFAULT_METATYPE;
5103
		switch ($tmap) {
5104
			case 'C':
5105
				// is the char field is too long, return as text field...
5106
				if ($this->blobSize >= 0) {
5107
					if ($len > $this->blobSize) {
5108
						return 'X';
5109
					}
5110
				} else if ($len > 250) {
5111
					return 'X';
5112
				}
5113
				return 'C';
5114
 
5115
			case 'I':
5116
				if (!empty($fieldObj->primary_key)) {
5117
					return 'R';
5118
				}
5119
				return 'I';
5120
 
5121
			case false:
5122
				return 'N';
5123
 
5124
			case 'B':
5125
				if (isset($fieldObj->binary)) {
5126
					return ($fieldObj->binary) ? 'B' : 'X';
5127
				}
5128
				return 'B';
5129
 
5130
			case 'D':
5131
				if (!empty($this->connection) && !empty($this->connection->datetime)) {
5132
					return 'T';
5133
				}
5134
				return 'D';
5135
 
5136
			default:
5137
				if ($t == 'LONG' && $this->dataProvider == 'oci8') {
5138
					return 'B';
5139
				}
5140
				return $tmap;
5141
		}
5142
	}
5143
 
5144
	/**
5145
	 * Convert case of field names associative array, if needed
5146
	 * @return void
5147
	 */
5148
	protected function _updatefields()
5149
	{
5150
		if( empty($this->fields)) {
5151
			return;
5152
		}
5153
 
5154
		// Determine case conversion function
5155
		$fn_change_case = $this->AssocCaseConvertFunction();
5156
		if(!$fn_change_case) {
5157
			// No conversion needed
5158
			return;
5159
		}
5160
 
5161
		$arr = array();
5162
 
5163
		// Change the case
5164
		foreach($this->fields as $k => $v) {
5165
			if (!is_integer($k)) {
5166
				$k = $fn_change_case($k);
5167
			}
5168
			$arr[$k] = $v;
5169
		}
5170
		$this->fields = $arr;
5171
	}
5172
 
5173
	function _close() {}
5174
 
5175
	/**
5176
	 * set/returns the current recordset page when paginating
5177
	 * @param int $page
5178
	 * @return int
5179
	 */
5180
	function absolutePage($page=-1) {
5181
		if ($page != -1) {
5182
			$this->_currentPage = $page;
5183
		}
5184
		return $this->_currentPage;
5185
	}
5186
 
5187
	/**
5188
	 * set/returns the status of the atFirstPage flag when paginating
5189
	 * @param bool $status
5190
	 * @return bool
5191
	 */
5192
	function AtFirstPage($status=false) {
5193
		if ($status != false) {
5194
			$this->_atFirstPage = $status;
5195
		}
5196
		return $this->_atFirstPage;
5197
	}
5198
 
5199
	/**
5200
	 * @param bool $page
5201
	 * @return bool
5202
	 */
5203
	function LastPageNo($page = false) {
5204
		if ($page != false) {
5205
			$this->_lastPageNo = $page;
5206
		}
5207
		return $this->_lastPageNo;
5208
	}
5209
 
5210
	/**
5211
	 * set/returns the status of the atLastPage flag when paginating
5212
	 * @param bool $status
5213
	 * @return bool
5214
	 */
5215
	function AtLastPage($status=false) {
5216
		if ($status != false) {
5217
			$this->_atLastPage = $status;
5218
		}
5219
		return $this->_atLastPage;
5220
	}
5221
 
5222
} // end class ADORecordSet
5223
 
5224
	//==============================================================================================
5225
	// CLASS ADORecordSet_array
5226
	//==============================================================================================
5227
 
5228
	/**
5229
	 * This class encapsulates the concept of a recordset created in memory
5230
	 * as an array. This is useful for the creation of cached recordsets.
5231
	 *
5232
	 * Note that the constructor is different from the standard ADORecordSet
5233
	 */
5234
	class ADORecordSet_array extends ADORecordSet
5235
	{
5236
		var $databaseType = 'array';
5237
 
5238
		var $_array;	// holds the 2-dimensional data array
5239
		var $_types;	// the array of types of each column (C B I L M)
5240
		var $_colnames;	// names of each column in array
5241
		var $_skiprow1;	// skip 1st row because it holds column names
5242
		var $_fieldobjects; // holds array of field objects
5243
		var $canSeek = true;
5244
		var $affectedrows = false;
5245
		var $insertid = false;
5246
		var $sql = '';
5247
		var $compat = false;
5248
 
5249
		/**
5250
		 * Constructor
5251
		 *
5252
		 * The parameters passed to this recordset are always fake because
5253
		 * this class does not use the queryID
5254
		 *
5255
		 * @param resource|int $queryID Ignored
5256
		 * @param int|bool     $mode    The ADODB_FETCH_MODE value
5257
		 */
5258
		function __construct($queryID, $mode=false) {
5259
			global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH;
5260
 
5261
			// fetch() on EOF does not delete $this->fields
5262
			$this->compat = !empty($ADODB_COMPAT_FETCH);
5263
			parent::__construct($queryID); // fake queryID
5264
			$this->fetchMode = $ADODB_FETCH_MODE;
5265
		}
5266
 
5267
 
5268
		/**
5269
		 * Setup the array.
5270
		 *
5271
		 * @param array		is a 2-dimensional array holding the data.
5272
		 *			The first row should hold the column names
5273
		 *			unless parameter $colnames is used.
5274
		 * @param typearr	holds an array of types. These are the same types
5275
		 *			used in MetaTypes (C,B,L,I,N).
5276
		 * @param string[]|false [$colnames]	array of column names. If set, then the first row of
5277
		 *			$array should not hold the column names.
5278
		 */
5279
		function InitArray($array,$typearr,$colnames=false) {
5280
			$this->_array = $array;
5281
			$this->_types = $typearr;
5282
			if ($colnames) {
5283
				$this->_skiprow1 = false;
5284
				$this->_colnames = $colnames;
5285
			} else {
5286
				$this->_skiprow1 = true;
5287
				$this->_colnames = $array[0];
5288
			}
5289
			$this->Init();
5290
		}
5291
		/**
5292
		 * Setup the Array and datatype file objects
5293
		 *
5294
		 * @param array $array    2-dimensional array holding the data
5295
		 *			The first row should hold the column names
5296
		 *			unless parameter $colnames is used.
5297
		 * @param array $fieldarr Array of ADOFieldObject's.
5298
		 */
5299
		function InitArrayFields(&$array,&$fieldarr) {
5300
			$this->_array = $array;
5301
			$this->_skiprow1= false;
5302
			if ($fieldarr) {
5303
				$this->_fieldobjects = $fieldarr;
5304
			}
5305
			$this->Init();
5306
		}
5307
 
5308
		/**
5309
		 * @param int [$nRows]
5310
		 * @return array
5311
		 */
5312
		function GetArray($nRows=-1) {
5313
			if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) {
5314
				return $this->_array;
5315
			} else {
5316
				return ADORecordSet::GetArray($nRows);
5317
			}
5318
		}
5319
 
5320
		function _initrs() {
5321
			$this->_numOfRows =  sizeof($this->_array);
5322
			if ($this->_skiprow1) {
5323
				$this->_numOfRows -= 1;
5324
			}
5325
 
5326
			$this->_numOfFields = (isset($this->_fieldobjects))
5327
				? sizeof($this->_fieldobjects)
5328
				: sizeof($this->_types);
5329
		}
5330
 
5331
		/**
5332
		 * Use associative array to get fields array
5333
		 *
5334
		 * @param string $colname
5335
		 * @return mixed
5336
		 */
5337
		function Fields($colname) {
5338
			$mode = isset($this->adodbFetchMode) ? $this->adodbFetchMode : $this->fetchMode;
5339
 
5340
			if ($mode & ADODB_FETCH_ASSOC) {
5341
				if (!isset($this->fields[$colname]) && !is_null($this->fields[$colname])) {
5342
					$colname = strtolower($colname);
5343
				}
5344
				return $this->fields[$colname];
5345
			}
5346
			if (!$this->bind) {
5347
				$this->bind = array();
5348
				for ($i=0; $i < $this->_numOfFields; $i++) {
5349
					$o = $this->FetchField($i);
5350
					$this->bind[strtoupper($o->name)] = $i;
5351
				}
5352
			}
5353
			return $this->fields[$this->bind[strtoupper($colname)]];
5354
		}
5355
 
5356
		/**
5357
		 * @param int [$fieldOffset]
5358
		 *
5359
		 * @return \ADOFieldObject
5360
		 */
5361
		function FetchField($fieldOffset = -1) {
5362
			if (isset($this->_fieldobjects)) {
5363
				return $this->_fieldobjects[$fieldOffset];
5364
			}
5365
			$o =  new ADOFieldObject();
5366
			$o->name = $this->_colnames[$fieldOffset];
5367
			$o->type =  $this->_types[$fieldOffset];
5368
			$o->max_length = -1; // length not known
5369
 
5370
			return $o;
5371
		}
5372
 
5373
		/**
5374
		 * @param int $row
5375
		 * @return bool
5376
		 */
5377
		function _seek($row) {
5378
			if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) {
5379
				$this->_currentRow = $row;
5380
				if ($this->_skiprow1) {
5381
					$row += 1;
5382
				}
5383
				$this->fields = $this->_array[$row];
5384
				return true;
5385
			}
5386
			return false;
5387
		}
5388
 
5389
		/**
5390
		 * @return bool
5391
		 */
5392
		function MoveNext() {
5393
			if (!$this->EOF) {
5394
				$this->_currentRow++;
5395
 
5396
				$pos = $this->_currentRow;
5397
 
5398
				if ($this->_numOfRows <= $pos) {
5399
					if (!$this->compat) {
5400
						$this->fields = false;
5401
					}
5402
				} else {
5403
					if ($this->_skiprow1) {
5404
						$pos += 1;
5405
					}
5406
					$this->fields = $this->_array[$pos];
5407
					return true;
5408
				}
5409
				$this->EOF = true;
5410
			}
5411
 
5412
			return false;
5413
		}
5414
 
5415
		/**
5416
		 * @return bool
5417
		 */
5418
		function _fetch() {
5419
			$pos = $this->_currentRow;
5420
 
5421
			if ($this->_numOfRows <= $pos) {
5422
				if (!$this->compat) {
5423
					$this->fields = false;
5424
				}
5425
				return false;
5426
			}
5427
			if ($this->_skiprow1) {
5428
				$pos += 1;
5429
			}
5430
			$this->fields = $this->_array[$pos];
5431
			return true;
5432
		}
5433
 
5434
		function _close() {
5435
			return true;
5436
		}
5437
 
5438
	} // ADORecordSet_array
5439
 
5440
	//==============================================================================================
5441
	// HELPER FUNCTIONS
5442
	//==============================================================================================
5443
 
5444
	/**
5445
	 * Synonym for ADOLoadCode. Private function. Do not use.
5446
	 *
5447
	 * @deprecated
5448
	 */
5449
	function ADOLoadDB($dbType) {
5450
		return ADOLoadCode($dbType);
5451
	}
5452
 
5453
	/**
5454
	 * Load the code for a specific database driver. Private function. Do not use.
5455
	 */
5456
	function ADOLoadCode($dbType) {
5457
		global $ADODB_LASTDB;
5458
 
5459
		if (!$dbType) {
5460
			return false;
5461
		}
5462
		$db = strtolower($dbType);
5463
		switch ($db) {
5464
			case 'ado':
5465
				$db = 'ado5';
5466
				$class = 'ado';
5467
				break;
5468
 
5469
			case 'ifx':
5470
			case 'maxsql':
5471
				$class = $db = 'mysqlt';
5472
				break;
5473
 
5474
			case 'pgsql':
5475
			case 'postgres':
5476
				$class = $db = 'postgres9';
5477
				break;
5478
 
5479
			case 'mysql':
5480
				// mysql extension removed in PHP 7.0 - automatically switch to mysqli
5481
				$class = $db = 'mysqli';
5482
				break;
5483
 
5484
			default:
5485
				if (substr($db, 0, 4) === 'pdo_') {
5486
					ADOConnection::outp("Invalid database type: $db");
5487
					return false;
5488
				}
5489
 
5490
				$class = $db;
5491
				break;
5492
		}
5493
 
5494
		$file = "drivers/adodb-$db.inc.php";
5495
		@include_once(ADODB_DIR . '/' . $file);
5496
		$ADODB_LASTDB = $class;
5497
		if (class_exists("ADODB_" . $class)) {
5498
			return $class;
5499
		}
5500
 
5501
		//ADOConnection::outp(adodb_pr(get_declared_classes(),true));
5502
		if (!file_exists($file)) {
5503
			ADOConnection::outp("Missing file: $file");
5504
		} else {
5505
			ADOConnection::outp("Syntax error in file: $file");
5506
		}
5507
		return false;
5508
	}
5509
 
5510
	/**
5511
	 * Synonym for ADONewConnection for people like me who cannot remember the correct name
5512
	 *
5513
	 * @param string [$db]
5514
	 *
5515
	 * @return ADOConnection|false
5516
	 */
5517
	function NewADOConnection($db='') {
5518
		return ADONewConnection($db);
5519
	}
5520
 
5521
	/**
5522
	 * Instantiate a new Connection class for a specific database driver.
5523
	 *
5524
	 * @param string $db Database Connection object to create. If undefined,
5525
	 *	use the last database driver that was loaded by ADOLoadCode().
5526
	 *
5527
	 * @return ADOConnection|false The freshly created instance of the Connection class
5528
	 *                             or false in case of error.
5529
	 */
5530
	function ADONewConnection($db='') {
5531
		global $ADODB_NEWCONNECTION, $ADODB_LASTDB;
5532
 
5533
		if (!defined('ADODB_ASSOC_CASE')) {
5534
			define('ADODB_ASSOC_CASE', ADODB_ASSOC_CASE_NATIVE);
5535
		}
5536
 
5537
		/*
5538
		* Are there special characters in the dsn password
5539
		* that disrupt parse_url
5540
		*/
5541
		$needsSpecialCharacterHandling = false;
5542
 
5543
		$errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false;
5544
		if (($at = strpos($db,'://')) !== FALSE) {
5545
			$origdsn = $db;
5546
			$fakedsn = 'fake'.substr($origdsn,$at);
5547
			if (($at2 = strpos($origdsn,'@/')) !== FALSE) {
5548
				// special handling of oracle, which might not have host
5549
				$fakedsn = str_replace('@/','@adodb-fakehost/',$fakedsn);
5550
			}
5551
 
5552
			if ((strpos($origdsn, 'sqlite')) !== FALSE && stripos($origdsn, '%2F') === FALSE) {
5553
				// special handling for SQLite, it only might have the path to the database file.
5554
				// If you try to connect to a SQLite database using a dsn
5555
				// like 'sqlite:///path/to/database', the 'parse_url' php function
5556
				// will throw you an exception with a message such as "unable to parse url"
5557
				list($scheme, $path) = explode('://', $origdsn);
5558
				$dsna['scheme'] = $scheme;
5559
				if ($qmark = strpos($path,'?')) {
5560
					$dsn['query'] = substr($path,$qmark+1);
5561
					$path = substr($path,0,$qmark);
5562
				}
5563
				$dsna['path'] = '/' . urlencode($path);
5564
			} else {
5565
				/*
5566
				* Stop # character breaking parse_url
5567
				*/
5568
				$cFakedsn = str_replace('#','\035',$fakedsn);
5569
				if (strcmp($fakedsn,$cFakedsn) != 0)
5570
				{
5571
					/*
5572
					* There is a # in the string
5573
					*/
5574
					$needsSpecialCharacterHandling = true;
5575
 
5576
					/*
5577
					* This allows us to successfully parse the url
5578
					*/
5579
					$fakedsn = $cFakedsn;
5580
 
5581
				}
5582
 
5583
				$dsna = parse_url($fakedsn);
5584
			}
5585
 
5586
			if (!$dsna) {
5587
				return false;
5588
			}
5589
			$dsna['scheme'] = substr($origdsn,0,$at);
5590
			if ($at2 !== FALSE) {
5591
				$dsna['host'] = '';
5592
			}
5593
 
5594
			if (strncmp($origdsn,'pdo',3) == 0) {
5595
				$sch = explode('_',$dsna['scheme']);
5596
				if (sizeof($sch)>1) {
5597
					$dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : '';
5598
					if ($sch[1] == 'sqlite') {
5599
						$dsna['host'] = rawurlencode($sch[1].':'.rawurldecode($dsna['host']));
5600
					} else {
5601
						$dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host']));
5602
					}
5603
					$dsna['scheme'] = 'pdo';
5604
				}
5605
			}
5606
 
5607
			$db = @$dsna['scheme'];
5608
			if (!$db) {
5609
				return false;
5610
			}
5611
 
5612
			$dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : '';
5613
			$dsna['user'] = isset($dsna['user']) ? rawurldecode($dsna['user']) : '';
5614
			$dsna['pass'] = isset($dsna['pass']) ? rawurldecode($dsna['pass']) : '';
5615
			$dsna['path'] = isset($dsna['path']) ? rawurldecode(substr($dsna['path'],1)) : ''; # strip off initial /
5616
 
5617
			if ($needsSpecialCharacterHandling)
5618
			{
5619
				/*
5620
				* Revert back to the original string
5621
				*/
5622
				$dsna = str_replace('\035','#',$dsna);
5623
			}
5624
 
5625
			if (isset($dsna['query'])) {
5626
				$opt1 = explode('&',$dsna['query']);
5627
				foreach($opt1 as $k => $v) {
5628
					$arr = explode('=',$v);
5629
					$opt[$arr[0]] = isset($arr[1]) ? rawurldecode($arr[1]) : 1;
5630
				}
5631
			} else {
5632
				$opt = array();
5633
			}
5634
 
5635
		}
5636
	/*
5637
	 *  phptype: Database backend used in PHP (mysql, odbc etc.)
5638
	 *  dbsyntax: Database used with regards to SQL syntax etc.
5639
	 *  protocol: Communication protocol to use (tcp, unix etc.)
5640
	 *  hostspec: Host specification (hostname[:port])
5641
	 *  database: Database to use on the DBMS server
5642
	 *  username: User name for login
5643
	 *  password: Password for login
5644
	 */
5645
		if (!empty($ADODB_NEWCONNECTION)) {
5646
			$obj = $ADODB_NEWCONNECTION($db);
5647
 
5648
		}
5649
 
5650
		if(empty($obj)) {
5651
 
5652
			if (!isset($ADODB_LASTDB)) {
5653
				$ADODB_LASTDB = '';
5654
			}
5655
			if (empty($db)) {
5656
				$db = $ADODB_LASTDB;
5657
			}
5658
			if ($db != $ADODB_LASTDB) {
5659
				$db = ADOLoadCode($db);
5660
			}
5661
 
5662
			if (!$db) {
5663
				if (isset($origdsn)) {
5664
					$db = $origdsn;
5665
				}
5666
				if ($errorfn) {
5667
					// raise an error
5668
					$ignore = false;
5669
					$errorfn('ADONewConnection', 'ADONewConnection', -998,
5670
							"could not load the database driver for '$db'",
5671
							$db,false,$ignore);
5672
				} else {
5673
					ADOConnection::outp( "<p>ADONewConnection: Unable to load database driver '$db'</p>",false);
5674
				}
5675
				return false;
5676
			}
5677
 
5678
			$cls = 'ADODB_'.$db;
5679
			if (!class_exists($cls)) {
5680
				adodb_backtrace();
5681
				return false;
5682
			}
5683
 
5684
			$obj = new $cls();
5685
		}
5686
 
5687
		# constructor should not fail
5688
		if ($obj) {
5689
			if ($errorfn) {
5690
				$obj->raiseErrorFn = $errorfn;
5691
			}
5692
			if (isset($dsna)) {
5693
				if (isset($dsna['port'])) {
5694
					$obj->port = $dsna['port'];
5695
				}
5696
				foreach($opt as $k => $v) {
5697
					switch(strtolower($k)) {
5698
					case 'new':
5699
										$nconnect = true; $persist = true; break;
5700
					case 'persist':
5701
					case 'persistent':	$persist = $v; break;
5702
					case 'debug':		$obj->debug = (integer) $v; break;
5703
					#ibase
5704
					case 'role':		$obj->role = $v; break;
5705
					case 'dialect':	$obj->dialect = (integer) $v; break;
5706
					case 'charset':		$obj->charset = $v; $obj->charSet=$v; break;
5707
					case 'buffers':		$obj->buffers = $v; break;
5708
					case 'fetchmode':   $obj->SetFetchMode($v); break;
5709
					#ado
5710
					case 'charpage':	$obj->charPage = $v; break;
5711
					#mysql, mysqli
5712
					case 'clientflags': $obj->clientFlags = $v; break;
5713
					#mysql, mysqli, postgres
5714
					case 'port': $obj->port = $v; break;
5715
					#mysqli
5716
					case 'socket': $obj->socket = $v; break;
5717
					#oci8
5718
					case 'nls_date_format': $obj->NLS_DATE_FORMAT = $v; break;
5719
					case 'cachesecs': $obj->cacheSecs = $v; break;
5720
					case 'memcache':
5721
						$varr = explode(':',$v);
5722
						$vlen = sizeof($varr);
5723
						if ($vlen == 0) {
5724
							break;
5725
						}
5726
						$obj->memCache = true;
5727
						$obj->memCacheHost = explode(',',$varr[0]);
5728
						if ($vlen == 1) {
5729
							break;
5730
						}
5731
						$obj->memCachePort = $varr[1];
5732
						if ($vlen == 2) {
5733
							break;
5734
						}
5735
						$obj->memCacheCompress = $varr[2] ?  true : false;
5736
						break;
5737
					}
5738
				}
5739
				if (empty($persist)) {
5740
					$ok = $obj->Connect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
5741
				} else if (empty($nconnect)) {
5742
					$ok = $obj->PConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
5743
				} else {
5744
					$ok = $obj->NConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']);
5745
				}
5746
 
5747
				if (!$ok) {
5748
					return false;
5749
				}
5750
			}
5751
		}
5752
		return $obj;
5753
	}
5754
 
5755
 
5756
 
5757
	// $perf == true means called by NewPerfMonitor(), otherwise for data dictionary
5758
	function _adodb_getdriver($provider,$drivername,$perf=false) {
5759
		switch ($provider) {
5760
			case 'odbtp':
5761
				if (strncmp('odbtp_',$drivername,6)==0) {
5762
					return substr($drivername,6);
5763
				}
5764
			case 'odbc' :
5765
				if (strncmp('odbc_',$drivername,5)==0) {
5766
					return substr($drivername,5);
5767
				}
5768
			case 'ado'  :
5769
				if (strncmp('ado_',$drivername,4)==0) {
5770
					return substr($drivername,4);
5771
				}
5772
			case 'native':
5773
				break;
5774
			default:
5775
				return $provider;
5776
		}
5777
 
5778
		switch($drivername) {
5779
			case 'mysqlt':
5780
			case 'mysqli':
5781
				$drivername='mysql';
5782
				break;
5783
			case 'postgres7':
5784
			case 'postgres8':
5785
				$drivername = 'postgres';
5786
				break;
5787
			case 'firebird15':
5788
				$drivername = 'firebird';
5789
				break;
5790
			case 'oracle':
5791
				$drivername = 'oci8';
5792
				break;
5793
			case 'access':
5794
				if ($perf) {
5795
					$drivername = '';
5796
				}
5797
				break;
5798
			case 'db2'   :
5799
			case 'sapdb' :
5800
				break;
5801
			default:
5802
				$drivername = 'generic';
5803
				break;
5804
		}
5805
		return $drivername;
5806
	}
5807
 
5808
	function NewPerfMonitor(&$conn) {
5809
		$drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType,true);
5810
		if (!$drivername || $drivername == 'generic') {
5811
			return false;
5812
		}
5813
		include_once(ADODB_DIR.'/adodb-perf.inc.php');
5814
		@include_once(ADODB_DIR."/perf/perf-$drivername.inc.php");
5815
		$class = "Perf_$drivername";
5816
		if (!class_exists($class)) {
5817
			return false;
5818
		}
5819
 
5820
		return new $class($conn);
5821
	}
5822
 
5823
	/**
5824
	 * Get a new Data Dictionary object for the connection.
5825
	 *
5826
	 * @param ADOConnection $conn
5827
	 * @param string        $drivername
5828
	 *
5829
	 * @return ADODB_DataDict|false
5830
	 */
5831
	function newDataDictionary(&$conn, $drivername='') {
5832
		if (!$drivername) {
5833
			$drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType);
5834
		}
5835
 
5836
		include_once(ADODB_DIR.'/adodb-lib.inc.php');
5837
		include_once(ADODB_DIR.'/adodb-datadict.inc.php');
5838
		$path = ADODB_DIR."/datadict/datadict-$drivername.inc.php";
5839
 
5840
		if (!file_exists($path)) {
5841
			ADOConnection::outp("Dictionary driver '$path' not available");
5842
			return false;
5843
		}
5844
		include_once($path);
5845
		$class = "ADODB2_$drivername";
5846
		/** @var ADODB_DataDict $dict */
5847
		$dict = new $class();
5848
		$dict->dataProvider = $conn->dataProvider;
5849
		$dict->connection = $conn;
5850
		$dict->upperName = strtoupper($drivername);
5851
		$dict->quote = $conn->nameQuote;
5852
		if (!empty($conn->_connectionID)) {
5853
			$dict->serverInfo = $conn->ServerInfo();
5854
		}
5855
 
5856
		return $dict;
5857
	}
5858
 
5859
	/**
5860
	 * Perform a print_r, with pre tags for better formatting.
5861
	 */
5862
	function adodb_pr($var,$as_string=false) {
5863
		if ($as_string) {
5864
			ob_start();
5865
		}
5866
 
5867
		if (isset($_SERVER['HTTP_USER_AGENT'])) {
5868
			echo " <pre>\n";print_r($var);echo "</pre>\n";
5869
		} else {
5870
			print_r($var);
5871
		}
5872
 
5873
		if ($as_string) {
5874
			$s = ob_get_contents();
5875
			ob_end_clean();
5876
			return $s;
5877
		}
5878
	}
5879
 
5880
	/**
5881
	 * Perform a stack-crawl and pretty print it.
5882
	 *
5883
	 * @param bool  $printOrArr Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then).
5884
	 * @param int   $levels     Number of levels to display
5885
	 * @param mixed $ishtml
5886
	 *
5887
	 * @return string
5888
	 */
5889
	function adodb_backtrace($printOrArr=true,$levels=9999,$ishtml=null) {
5890
		global $ADODB_INCLUDED_LIB;
5891
		if (empty($ADODB_INCLUDED_LIB)) {
5892
			include_once(ADODB_DIR.'/adodb-lib.inc.php');
5893
		}
5894
		return _adodb_backtrace($printOrArr,$levels,0,$ishtml);
5895
	}
5896
 
5897
}