Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
/**
3
 * Helper functions.
4
 *
5
 * Less commonly used functions are placed here to reduce size of adodb.inc.php.
6
 *
7
 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
8
 *
9
 * @package ADOdb
10
 * @link https://adodb.org Project's web site and documentation
11
 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
12
 *
13
 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
14
 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
15
 * any later version. This means you can use it in proprietary products.
16
 * See the LICENSE.md file distributed with this source code for details.
17
 * @license BSD-3-Clause
18
 * @license LGPL-2.1-or-later
19
 *
20
 * @copyright 2000-2013 John Lim
21
 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
22
 */
23
 
24
// security - hide paths
25
if (!defined('ADODB_DIR')) die();
26
 
27
global $ADODB_INCLUDED_LIB;
28
$ADODB_INCLUDED_LIB = 1;
29
 
30
/**
31
 * Strip the ORDER BY clause from the outer SELECT.
32
 *
33
 * @param string $sql
34
 *
35
 * @return string
36
 */
37
function adodb_strip_order_by($sql)
38
{
39
	$num = preg_match_all('/(\sORDER\s+BY\s(?:[^)](?!LIMIT))*)/is', $sql, $matches, PREG_OFFSET_CAPTURE);
40
	if ($num) {
41
		// Get the last match
42
		list($last_order_by, $offset) = array_pop($matches[1]);
43
 
44
		// If we find a ')' after the last order by, then it belongs to a
45
		// sub-query, not the outer SQL statement and should not be stripped
46
		if (strpos($sql, ')', $offset) === false) {
47
			$sql = str_replace($last_order_by, '', $sql);
48
		}
49
	}
50
	return $sql;
51
}
52
 
53
function adodb_probetypes($array,&$types,$probe=8)
54
{
55
// probe and guess the type
56
	$types = array();
57
	if ($probe > sizeof($array)) $max = sizeof($array);
58
	else $max = $probe;
59
 
60
 
61
	for ($j=0;$j < $max; $j++) {
62
		$row = $array[$j];
63
		if (!$row) break;
64
		$i = -1;
65
		foreach($row as $v) {
66
			$i += 1;
67
 
68
			if (isset($types[$i]) && $types[$i]=='C') continue;
69
 
70
			//print " ($i ".$types[$i]. "$v) ";
71
			$v = trim($v);
72
 
73
			if (!preg_match('/^[+-]{0,1}[0-9\.]+$/',$v)) {
74
				$types[$i] = 'C'; // once C, always C
75
 
76
				continue;
77
			}
78
			if ($j == 0) {
79
			// If empty string, we presume is character
80
			// test for integer for 1st row only
81
			// after that it is up to testing other rows to prove
82
			// that it is not an integer
83
				if (strlen($v) == 0) $types[$i] = 'C';
84
				if (strpos($v,'.') !== false) $types[$i] = 'N';
85
				else $types[$i] = 'I';
86
				continue;
87
			}
88
 
89
			if (strpos($v,'.') !== false) $types[$i] = 'N';
90
 
91
		}
92
	}
93
 
94
}
95
 
96
function adodb_transpose(&$arr, &$newarr, &$hdr, $fobjs)
97
{
98
	$oldX = sizeof(reset($arr));
99
	$oldY = sizeof($arr);
100
 
101
	if ($hdr) {
102
		$startx = 1;
103
		$hdr = array('Fields');
104
		for ($y = 0; $y < $oldY; $y++) {
105
			$hdr[] = $arr[$y][0];
106
		}
107
	} else
108
		$startx = 0;
109
 
110
	for ($x = $startx; $x < $oldX; $x++) {
111
		if ($fobjs) {
112
			$o = $fobjs[$x];
113
			$newarr[] = array($o->name);
114
		} else
115
			$newarr[] = array();
116
 
117
		for ($y = 0; $y < $oldY; $y++) {
118
			$newarr[$x-$startx][] = $arr[$y][$x];
119
		}
120
	}
121
}
122
 
123
 
124
function _adodb_replace($zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc)
125
{
126
	// Add Quote around table name to support use of spaces / reserved keywords
127
	$table=sprintf('%s%s%s', $zthis->nameQuote,$table,$zthis->nameQuote);
128
 
129
	if (count($fieldArray) == 0) return 0;
130
 
131
	if (!is_array($keyCol)) {
132
		$keyCol = array($keyCol);
133
	}
134
	$uSet = '';
135
	foreach($fieldArray as $k => $v) {
136
		if ($v === null) {
137
			$v = 'NULL';
138
			$fieldArray[$k] = $v;
139
		} else if ($autoQuote && /*!is_numeric($v) /*and strncmp($v,"'",1) !== 0 -- sql injection risk*/ strcasecmp($v,$zthis->null2null)!=0) {
140
			$v = $zthis->qstr($v);
141
			$fieldArray[$k] = $v;
142
		}
143
		if (in_array($k,$keyCol)) continue; // skip UPDATE if is key
144
 
145
		// Add Quote around column name to support use of spaces / reserved keywords
146
		$uSet .= sprintf(',%s%s%s=%s',$zthis->nameQuote,$k,$zthis->nameQuote,$v);
147
	}
148
	$uSet = ltrim($uSet, ',');
149
 
150
	// Add Quote around column name in where clause
151
	$where = '';
152
	foreach ($keyCol as $v) {
153
		if (isset($fieldArray[$v])) {
154
			$where .= sprintf(' and %s%s%s=%s ', $zthis->nameQuote,$v,$zthis->nameQuote,$fieldArray[$v]);
155
		}
156
	}
157
	if ($where) {
158
		$where = substr($where, 5);
159
	}
160
 
161
	if ($uSet && $where) {
162
		$update = "UPDATE $table SET $uSet WHERE $where";
163
		$rs = $zthis->Execute($update);
164
 
165
		if ($rs) {
166
			if ($zthis->poorAffectedRows) {
167
				// The Select count(*) wipes out any errors that the update would have returned.
168
				// PHPLens Issue No: 5696
169
				if ($zthis->ErrorNo()<>0) return 0;
170
 
171
				// affected_rows == 0 if update field values identical to old values
172
				// for mysql - which is silly.
173
				$cnt = $zthis->GetOne("select count(*) from $table where $where");
174
				if ($cnt > 0) return 1; // record already exists
175
			} else {
176
				if (($zthis->Affected_Rows()>0)) return 1;
177
			}
178
		} else
179
			return 0;
180
	}
181
 
182
	$iCols = $iVals = '';
183
	foreach($fieldArray as $k => $v) {
184
		if ($has_autoinc && in_array($k,$keyCol)) continue; // skip autoinc col
185
 
186
		// Add Quote around Column Name
187
		$iCols .= sprintf(',%s%s%s',$zthis->nameQuote,$k,$zthis->nameQuote);
188
		$iVals .= ",$v";
189
	}
190
	$iCols = ltrim($iCols, ',');
191
	$iVals = ltrim($iVals, ',');
192
 
193
	$insert = "INSERT INTO $table ($iCols) VALUES ($iVals)";
194
	$rs = $zthis->Execute($insert);
195
	return ($rs) ? 2 : 0;
196
}
197
 
198
function _adodb_getmenu($zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
199
			$size=0, $selectAttr='',$compareFields0=true)
200
{
201
	global $ADODB_FETCH_MODE;
202
 
203
	$s = _adodb_getmenu_select($name, $defstr, $blank1stItem, $multiple, $size, $selectAttr);
204
 
205
	$hasvalue = $zthis->FieldCount() > 1;
206
	if (!$hasvalue) {
207
		$compareFields0 = true;
208
	}
209
 
210
	$value = '';
211
	while(!$zthis->EOF) {
212
		$zval = rtrim(reset($zthis->fields));
213
 
214
		if ($blank1stItem && $zval == "") {
215
			$zthis->MoveNext();
216
			continue;
217
		}
218
 
219
		if ($hasvalue) {
220
			if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC) {
221
				// Get 2nd field's value regardless of its name
222
				$zval2 = current(array_slice($zthis->fields, 1, 1));
223
			} else {
224
				// With NUM or BOTH fetch modes, we have a numeric index
225
				$zval2 = $zthis->fields[1];
226
			}
227
			$zval2 = trim($zval2);
228
			$value = 'value="' . htmlspecialchars($zval2) . '"';
229
		}
230
 
231
		/** @noinspection PhpUndefinedVariableInspection */
232
		$s .= _adodb_getmenu_option($defstr, $compareFields0 ? $zval : $zval2, $value, $zval);
233
 
234
		$zthis->MoveNext();
235
	} // while
236
 
237
	return $s ."\n</select>\n";
238
}
239
 
240
function _adodb_getmenu_gp($zthis, $name,$defstr='',$blank1stItem=true,$multiple=false,
241
			$size=0, $selectAttr='',$compareFields0=true)
242
{
243
	global $ADODB_FETCH_MODE;
244
 
245
	$s = _adodb_getmenu_select($name, $defstr, $blank1stItem, $multiple, $size, $selectAttr);
246
 
247
	$hasvalue = $zthis->FieldCount() > 1;
248
	$hasgroup = $zthis->FieldCount() > 2;
249
	if (!$hasvalue) {
250
		$compareFields0 = true;
251
	}
252
 
253
	$value = '';
254
	$optgroup = null;
255
	$firstgroup = true;
256
	while(!$zthis->EOF) {
257
		$zval = rtrim(reset($zthis->fields));
258
		$group = '';
259
 
260
		if ($blank1stItem && $zval=="") {
261
			$zthis->MoveNext();
262
			continue;
263
		}
264
 
265
		if ($hasvalue) {
266
			if ($ADODB_FETCH_MODE == ADODB_FETCH_ASSOC) {
267
				// Get 2nd field's value regardless of its name
268
				$fields = array_slice($zthis->fields, 1);
269
				$zval2 = current($fields);
270
				if ($hasgroup) {
271
					$group = trim(next($fields));
272
				}
273
			} else {
274
				// With NUM or BOTH fetch modes, we have a numeric index
275
				$zval2 = $zthis->fields[1];
276
				if ($hasgroup) {
277
					$group = trim($zthis->fields[2]);
278
				}
279
			}
280
			$zval2 = trim($zval2);
281
			$value = "value='".htmlspecialchars($zval2)."'";
282
		}
283
 
284
		if ($optgroup != $group) {
285
			$optgroup = $group;
286
			if ($firstgroup) {
287
				$firstgroup = false;
288
			} else {
289
				$s .="\n</optgroup>";
290
			}
291
			$s .="\n<optgroup label='". htmlspecialchars($group) ."'>";
292
		}
293
 
294
		/** @noinspection PhpUndefinedVariableInspection */
295
		$s .= _adodb_getmenu_option($defstr, $compareFields0 ? $zval : $zval2, $value, $zval);
296
 
297
		$zthis->MoveNext();
298
	} // while
299
 
300
	// closing last optgroup
301
	if($optgroup != null) {
302
		$s .= "\n</optgroup>";
303
	}
304
	return $s ."\n</select>\n";
305
}
306
 
307
/**
308
 * Generate the opening SELECT tag for getmenu functions.
309
 *
310
 * ADOdb internal function, used by _adodb_getmenu() and _adodb_getmenu_gp().
311
 *
312
 * @param string $name
313
 * @param string $defstr
314
 * @param bool   $blank1stItem
315
 * @param bool   $multiple
316
 * @param int    $size
317
 * @param string $selectAttr
318
 *
319
 * @return string HTML
320
 */
321
function _adodb_getmenu_select($name, $defstr = '', $blank1stItem = true,
322
							   $multiple = false, $size = 0, $selectAttr = '')
323
{
324
	if ($multiple || is_array($defstr)) {
325
		if ($size == 0 ) {
326
			$size = 5;
327
		}
328
		$attr = ' multiple size="' . $size . '"';
329
		if (!strpos($name,'[]')) {
330
			$name .= '[]';
331
		}
332
	} elseif ($size) {
333
		$attr = ' size="' . $size . '"';
334
	} else {
335
		$attr = '';
336
	}
337
 
338
	$html = '<select name="' . $name . '"' . $attr . ' ' . $selectAttr . '>';
339
	if ($blank1stItem) {
340
		if (is_string($blank1stItem)) {
341
			$barr = explode(':',$blank1stItem);
342
			if (sizeof($barr) == 1) {
343
				$barr[] = '';
344
			}
345
			$html .= "\n<option value=\"" . $barr[0] . "\">" . $barr[1] . "</option>";
346
		} else {
347
			$html .= "\n<option></option>";
348
		}
349
	}
350
 
351
	return $html;
352
}
353
 
354
/**
355
 * Print the OPTION tags for getmenu functions.
356
 *
357
 * ADOdb internal function, used by _adodb_getmenu() and _adodb_getmenu_gp().
358
 *
359
 * @param string $defstr  Default values
360
 * @param string $compare Value to compare against defaults
361
 * @param string $value   Ready-to-print `value="xxx"` (or empty) string
362
 * @param string $display Display value
363
 *
364
 * @return string HTML
365
 */
366
function _adodb_getmenu_option($defstr, $compare, $value, $display)
367
{
368
	if (   is_array($defstr) && in_array($compare, $defstr)
369
		|| !is_array($defstr) && strcasecmp($compare, $defstr) == 0
370
	) {
371
		$selected = ' selected="selected"';
372
	} else {
373
		$selected = '';
374
	}
375
 
376
	return "\n<option $value$selected>" . htmlspecialchars($display) . '</option>';
377
}
378
 
379
/*
380
	Count the number of records this sql statement will return by using
381
	query rewriting heuristics...
382
 
383
	Does not work with UNIONs, except with postgresql and oracle.
384
 
385
	Usage:
386
 
387
	$conn->Connect(...);
388
	$cnt = _adodb_getcount($conn, $sql);
389
 
390
*/
391
function _adodb_getcount($zthis, $sql,$inputarr=false,$secs2cache=0)
392
{
393
	$qryRecs = 0;
394
 
395
	/*
396
	* These databases require a "SELECT * FROM (SELECT" type
397
	* statement to have an alias for the result
398
	*/
399
	$requiresAlias = '';
400
	$requiresAliasArray = array('postgres9','postgres','mysql','mysqli','mssql','mssqlnative','sqlsrv');
401
	if (in_array($zthis->databaseType,$requiresAliasArray)
402
		|| in_array($zthis->dsnType,$requiresAliasArray)
403
	) {
404
		$requiresAlias = '_ADODB_ALIAS_';
405
	}
406
 
407
	if (!empty($zthis->_nestedSQL)
408
		|| preg_match("/^\s*SELECT\s+DISTINCT/is", $sql)
409
		|| preg_match('/\s+GROUP\s+BY\s+/is',$sql)
410
		|| preg_match('/\s+UNION\s+/is',$sql)
411
	) {
412
		$rewritesql = adodb_strip_order_by($sql);
413
 
414
		// ok, has SELECT DISTINCT or GROUP BY so see if we can use a table alias
415
		// but this is only supported by oracle and postgresql...
416
		if ($zthis->dataProvider == 'oci8') {
417
			// Allow Oracle hints to be used for query optimization, Chris Wrye
418
			if (preg_match('#/\\*+.*?\\*\\/#', $sql, $hint)) {
419
				$rewritesql = "SELECT ".$hint[0]." COUNT(*) FROM (".$rewritesql.")";
420
			} else
421
				$rewritesql = "SELECT COUNT(*) FROM (".$rewritesql.")";
422
		} else {
423
			$rewritesql = "SELECT COUNT(*) FROM ($rewritesql) $requiresAlias";
424
		}
425
 
426
	} else {
427
		// Replace 'SELECT ... FROM' with 'SELECT COUNT(*) FROM'
428
		// Parse the query one char at a time starting after the SELECT
429
		// to find the FROM clause's position, ignoring any sub-queries.
430
		$start = stripos($sql, 'SELECT') + 7;
431
		if ($start === false) {
432
			// Not a SELECT statement - probably should trigger an exception here
433
			return 0;
434
		}
435
		$len = strlen($sql);
436
		$numParentheses = 0;
437
		for ($pos = $start; $pos < $len; $pos++) {
438
			switch ($sql[$pos]) {
439
				case '(': $numParentheses++; continue 2;
440
				case ')': $numParentheses--; continue 2;
441
			}
442
			// Ignore whatever is between parentheses (sub-queries)
443
			if ($numParentheses > 0) {
444
				continue;
445
			}
446
			// Exit loop if 'FROM' keyword was found
447
			if (strtoupper(substr($sql, $pos, 4)) == 'FROM') {
448
				break;
449
			}
450
		}
451
		$rewritesql = 'SELECT COUNT(*) ' . substr($sql, $pos);
452
 
453
		// fix by alexander zhukov, alex#unipack.ru, because count(*) and 'order by' fails
454
		// with mssql, access and postgresql. Also a good speedup optimization - skips sorting!
455
		// also see PHPLens Issue No: 12752
456
		$rewritesql = adodb_strip_order_by($rewritesql);
457
	}
458
 
459
	if (isset($rewritesql) && $rewritesql != $sql) {
460
		if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) {
461
			$rewritesql .= $limitarr[0];
462
		}
463
 
464
		if ($secs2cache) {
465
			// we only use half the time of secs2cache because the count can quickly
466
			// become inaccurate if new records are added
467
			$qryRecs = $zthis->CacheGetOne($secs2cache/2,$rewritesql,$inputarr);
468
 
469
		} else {
470
			$qryRecs = $zthis->GetOne($rewritesql,$inputarr);
471
		}
472
		if ($qryRecs !== false) return $qryRecs;
473
	}
474
 
475
	//--------------------------------------------
476
	// query rewrite failed - so try slower way...
477
 
478
	// strip off unneeded ORDER BY if no UNION
479
	if (preg_match('/\s*UNION\s*/is', $sql)) {
480
		$rewritesql = $sql;
481
	} else {
482
		$rewritesql = adodb_strip_order_by($sql);
483
	}
484
 
485
	if (preg_match('/\sLIMIT\s+[0-9]+/i',$sql,$limitarr)) {
486
		$rewritesql .= $limitarr[0];
487
	}
488
 
489
	if ($secs2cache) {
490
		$rstest = $zthis->CacheExecute($secs2cache,$rewritesql,$inputarr);
491
		if (!$rstest) $rstest = $zthis->CacheExecute($secs2cache,$sql,$inputarr);
492
	} else {
493
		$rstest = $zthis->Execute($rewritesql,$inputarr);
494
		if (!$rstest) $rstest = $zthis->Execute($sql,$inputarr);
495
	}
496
	if ($rstest) {
497
		$qryRecs = $rstest->RecordCount();
498
		if ($qryRecs == -1) {
499
			// some databases will return -1 on MoveLast() - change to MoveNext()
500
			while(!$rstest->EOF) {
501
				$rstest->MoveNext();
502
			}
503
			$qryRecs = $rstest->_currentRow;
504
		}
505
		$rstest->Close();
506
		if ($qryRecs == -1) return 0;
507
	}
508
	return $qryRecs;
509
}
510
 
511
/**
512
 * Execute query with pagination including record count.
513
 *
514
 * This code might not work with SQL that has UNION in it.
515
 * Also if you are using cachePageExecute(), there is a strong possibility that
516
 * data will get out of sync. cachePageExecute() should only be used with
517
 * tables that rarely change.
518
 *
519
 * @param ADOConnection $zthis      Connection
520
 * @param string        $sql        Query to execute
521
 * @param int           $nrows      Number of rows per page
522
 * @param int           $page       Page number to retrieve (1-based)
523
 * @param array         $inputarr   Array of bind variables
524
 * @param int           $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
525
 *
526
 * @return ADORecordSet|bool
527
 *
528
 * @author Cornel G <conyg@fx.ro>
529
 */
530
function _adodb_pageexecute_all_rows($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
531
{
532
	$atfirstpage = false;
533
	$atlastpage = false;
534
 
535
	// If an invalid nrows is supplied, assume a default value of 10 rows per page
536
	if (!isset($nrows) || $nrows <= 0) $nrows = 10;
537
 
538
	$qryRecs = _adodb_getcount($zthis,$sql,$inputarr,$secs2cache);
539
	$lastpageno = (int) ceil($qryRecs / $nrows);
540
 
541
	// Check whether $page is the last page or if we are trying to retrieve
542
	// a page number greater than the last one.
543
	if ($page >= $lastpageno) {
544
		$page = $lastpageno;
545
		$atlastpage = true;
546
	}
547
 
548
	// If page number <= 1, then we are at the first page
549
	if (empty($page) || $page <= 1) {
550
		$page = 1;
551
		$atfirstpage = true;
552
	}
553
 
554
	// We get the data we want
555
	$offset = $nrows * ($page-1);
556
	if ($secs2cache > 0)
557
		$rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
558
	else
559
		$rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
560
 
561
 
562
	// Before returning the RecordSet, we set the pagination properties we need
563
	if ($rsreturn) {
564
		$rsreturn->_maxRecordCount = $qryRecs;
565
		$rsreturn->rowsPerPage = $nrows;
566
		$rsreturn->AbsolutePage($page);
567
		$rsreturn->AtFirstPage($atfirstpage);
568
		$rsreturn->AtLastPage($atlastpage);
569
		$rsreturn->LastPageNo($lastpageno);
570
	}
571
	return $rsreturn;
572
}
573
 
574
/**
575
 * Execute query with pagination without last page information.
576
 *
577
 * This code might not work with SQL that has UNION in it.
578
 * Also if you are using cachePageExecute(), there is a strong possibility that
579
 * data will get out of sync. cachePageExecute() should only be used with
580
 * tables that rarely change.
581
 *
582
 * @param ADOConnection $zthis      Connection
583
 * @param string        $sql        Query to execute
584
 * @param int           $nrows      Number of rows per page
585
 * @param int           $page       Page number to retrieve (1-based)
586
 * @param array         $inputarr   Array of bind variables
587
 * @param int           $secs2cache Time-to-live of the cache (in seconds), 0 to force query execution
588
 *
589
 * @return ADORecordSet|bool
590
 *
591
 * @author Iván Oliva
592
 */
593
function _adodb_pageexecute_no_last_page($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
594
{
595
	$atfirstpage = false;
596
	$atlastpage = false;
597
 
598
	if (!isset($page) || $page <= 1) {
599
		// If page number <= 1, then we are at the first page
600
		$page = 1;
601
		$atfirstpage = true;
602
	}
603
	if ($nrows <= 0) {
604
		// If an invalid nrows is supplied, we assume a default value of 10 rows per page
605
		$nrows = 10;
606
	}
607
 
608
	$pagecounteroffset = ($page * $nrows) - $nrows;
609
 
610
	// To find out if there are more pages of rows, simply increase the limit or
611
	// nrows by 1 and see if that number of records was returned. If it was,
612
	// then we know there is at least one more page left, otherwise we are on
613
	// the last page. Therefore allow non-Count() paging with single queries
614
	// rather than three queries as was done before.
615
	$test_nrows = $nrows + 1;
616
	if ($secs2cache > 0) {
617
		$rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
618
	} else {
619
		$rsreturn = $zthis->SelectLimit($sql, $test_nrows, $pagecounteroffset, $inputarr, $secs2cache);
620
	}
621
 
622
	// Now check to see if the number of rows returned was the higher value we asked for or not.
623
	if ( $rsreturn->_numOfRows == $test_nrows ) {
624
		// Still at least 1 more row, so we are not on last page yet...
625
		// Remove the last row from the RS.
626
		$rsreturn->_numOfRows = ( $rsreturn->_numOfRows - 1 );
627
	} elseif ( $rsreturn->_numOfRows == 0 && $page > 1 ) {
628
		// Likely requested a page that doesn't exist, so need to find the last
629
		// page and return it. Revert to original method and loop through pages
630
		// until we find some data...
631
		$pagecounter = $page + 1;
632
 
633
		$rstest = $rsreturn;
634
		if ($rstest) {
635
			while ($rstest && $rstest->EOF && $pagecounter > 0) {
636
				$atlastpage = true;
637
				$pagecounter--;
638
				$pagecounteroffset = $nrows * ($pagecounter - 1);
639
				$rstest->Close();
640
				if ($secs2cache>0) {
641
					$rstest = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
642
				}
643
				else {
644
					$rstest = $zthis->SelectLimit($sql, $nrows, $pagecounteroffset, $inputarr, $secs2cache);
645
				}
646
			}
647
			if ($rstest) $rstest->Close();
648
		}
649
		if ($atlastpage) {
650
			// If we are at the last page or beyond it, we are going to retrieve it
651
			$page = $pagecounter;
652
			if ($page == 1) {
653
				// We have to do this again in case the last page is the same as
654
				// the first page, that is, the recordset has only 1 page.
655
				$atfirstpage = true;
656
			}
657
		}
658
		// We get the data we want
659
		$offset = $nrows * ($page-1);
660
		if ($secs2cache > 0) {
661
			$rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
662
		}
663
		else {
664
			$rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
665
		}
666
	} elseif ( $rsreturn->_numOfRows < $test_nrows ) {
667
		// Rows is less than what we asked for, so must be at the last page.
668
		$atlastpage = true;
669
	}
670
 
671
	// Before returning the RecordSet, we set the pagination properties we need
672
	if ($rsreturn) {
673
		$rsreturn->rowsPerPage = $nrows;
674
		$rsreturn->AbsolutePage($page);
675
		$rsreturn->AtFirstPage($atfirstpage);
676
		$rsreturn->AtLastPage($atlastpage);
677
	}
678
	return $rsreturn;
679
}
680
 
681
/**
682
 * Performs case conversion and quoting of the given field name.
683
 *
684
 * See Global variable $ADODB_QUOTE_FIELDNAMES.
685
 *
686
 * @param ADOConnection $zthis
687
 * @param string $fieldName
688
 *
689
 * @return string Quoted field name
690
 */
691
function _adodb_quote_fieldname($zthis, $fieldName)
692
{
693
	global $ADODB_QUOTE_FIELDNAMES;
694
 
695
	// Case conversion - defaults to UPPER
696
	$case = is_bool($ADODB_QUOTE_FIELDNAMES) ? 'UPPER' : $ADODB_QUOTE_FIELDNAMES;
697
	switch ($case) {
698
		case 'LOWER':
699
			$fieldName = strtolower($fieldName);
700
			break;
701
		case 'NATIVE':
702
			// Do nothing
703
			break;
704
		case 'UPPER':
705
		case 'BRACKETS':
706
		default:
707
			$fieldName = strtoupper($fieldName);
708
			break;
709
	}
710
 
711
	// Quote field if requested, or necessary (field contains space)
712
	if ($ADODB_QUOTE_FIELDNAMES || strpos($fieldName, ' ') !== false ) {
713
		if ($ADODB_QUOTE_FIELDNAMES === 'BRACKETS') {
714
			return $zthis->leftBracket . $fieldName . $zthis->rightBracket;
715
		} else {
716
			return $zthis->nameQuote . $fieldName . $zthis->nameQuote;
717
		}
718
	} else {
719
		return $fieldName;
720
	}
721
}
722
 
723
function _adodb_getupdatesql(&$zthis, $rs, $arrFields, $forceUpdate=false, $force=2)
724
{
725
	if (!$rs) {
726
		printf(ADODB_BAD_RS,'GetUpdateSQL');
727
		return false;
728
	}
729
 
730
	$fieldUpdatedCount = 0;
731
	if (is_array($arrFields))
732
		$arrFields = array_change_key_case($arrFields,CASE_UPPER);
733
 
734
	$hasnumeric = isset($rs->fields[0]);
735
	$setFields = '';
736
 
737
	// Loop through all of the fields in the recordset
738
	for ($i=0, $max=$rs->fieldCount(); $i < $max; $i++) {
739
		// Get the field from the recordset
740
		$field = $rs->fetchField($i);
741
 
742
		// If the recordset field is one
743
		// of the fields passed in then process.
744
		$upperfname = strtoupper($field->name);
745
		if (adodb_key_exists($upperfname, $arrFields, $force)) {
746
 
747
			// If the existing field value in the recordset
748
			// is different from the value passed in then
749
			// go ahead and append the field name and new value to
750
			// the update query.
751
 
752
			if ($hasnumeric) $val = $rs->fields[$i];
753
			else if (isset($rs->fields[$upperfname])) $val = $rs->fields[$upperfname];
754
			else if (isset($rs->fields[$field->name])) $val = $rs->fields[$field->name];
755
			else if (isset($rs->fields[strtolower($upperfname)])) $val = $rs->fields[strtolower($upperfname)];
756
			else $val = '';
757
 
758
			if ($forceUpdate || $val !== $arrFields[$upperfname]) {
759
				// Set the counter for the number of fields that will be updated.
760
				$fieldUpdatedCount++;
761
 
762
				// Based on the datatype of the field
763
				// Format the value properly for the database
764
				$type = $rs->metaType($field->type);
765
 
766
				if ($type == 'null') {
767
					$type = 'C';
768
				}
769
 
770
				$fnameq = _adodb_quote_fieldname($zthis, $field->name);
771
 
772
				//********************************************************//
773
				if (is_null($arrFields[$upperfname])
774
					|| (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
775
					|| $arrFields[$upperfname] === $zthis->null2null
776
					) {
777
 
778
					switch ($force) {
779
 
780
						//case 0:
781
						//	// Ignore empty values. This is already handled in "adodb_key_exists" function.
782
						//	break;
783
 
784
						case 1:
785
							// set null
786
							$setFields .= $fnameq . " = null, ";
787
							break;
788
 
789
						case 2:
790
							// set empty
791
							$arrFields[$upperfname] = "";
792
							$setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
793
							break;
794
 
795
						default:
796
						case 3:
797
							// set the value that was given in array, so you can give both null and empty values
798
							if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
799
								$setFields .= $fnameq . " = null, ";
800
							} else {
801
								$setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
802
							}
803
							break;
804
 
805
						case ADODB_FORCE_NULL_AND_ZERO:
806
 
807
							switch ($type) {
808
								case 'N':
809
								case 'I':
810
								case 'L':
811
									$setFields .= $fnameq . ' = 0, ';
812
									break;
813
								default:
814
									$setFields .= $fnameq . ' = null, ';
815
									break;
816
							}
817
							break;
818
 
819
					}
820
				//********************************************************//
821
				} else {
822
					// we do this so each driver can customize the sql for
823
					// DB specific column types.
824
					// Oracle needs BLOB types to be handled with a returning clause
825
					// postgres has special needs as well
826
					$setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
827
				}
828
			}
829
		}
830
	}
831
 
832
	// If there were any modified fields then build the rest of the update query.
833
	if ($fieldUpdatedCount > 0 || $forceUpdate) {
834
		// Get the table name from the existing query.
835
		if (!empty($rs->tableName)) {
836
			$tableName = $rs->tableName;
837
		} else {
838
			preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName);
839
			$tableName = $tableName[1];
840
		}
841
 
842
		// Get the full where clause excluding the word "WHERE" from the existing query.
843
		preg_match('/\sWHERE\s(.*)/is', $rs->sql, $whereClause);
844
 
845
		$discard = false;
846
		// not a good hack, improvements?
847
		if ($whereClause) {
848
			if (preg_match('/\s(ORDER\s.*)/is', $whereClause[1], $discard));
849
			else if (preg_match('/\s(LIMIT\s.*)/is', $whereClause[1], $discard));
850
			else if (preg_match('/\s(FOR UPDATE.*)/is', $whereClause[1], $discard));
851
			else preg_match('/\s.*(\) WHERE .*)/is', $whereClause[1], $discard); # see https://sourceforge.net/p/adodb/bugs/37/
852
		} else {
853
			$whereClause = array(false, false);
854
		}
855
 
856
		if ($discard) {
857
			$whereClause[1] = substr($whereClause[1], 0, strlen($whereClause[1]) - strlen($discard[1]));
858
		}
859
 
860
		$sql = 'UPDATE '.$tableName.' SET '.substr($setFields, 0, -2);
861
		if (strlen($whereClause[1]) > 0) {
862
			$sql .= ' WHERE '.$whereClause[1];
863
		}
864
		return $sql;
865
	} else {
866
		return false;
867
	}
868
}
869
 
870
function adodb_key_exists($key, $arr,$force=2)
871
{
872
	if ($force<=0) {
873
		// the following is the old behaviour where null or empty fields are ignored
874
		return (!empty($arr[$key])) || (isset($arr[$key]) && strlen($arr[$key])>0);
875
	}
876
 
877
	if (isset($arr[$key]))
878
		return true;
879
	## null check below
880
	return array_key_exists($key,$arr);
881
}
882
 
883
/**
884
 * There is a special case of this function for the oci8 driver.
885
 * The proper way to handle an insert w/ a blob in oracle requires
886
 * a returning clause with bind variables and a descriptor blob.
887
 *
888
 *
889
 */
890
function _adodb_getinsertsql(&$zthis, $rs, $arrFields, $force=2)
891
{
892
static $cacheRS = false;
893
static $cacheSig = 0;
894
static $cacheCols;
895
 
896
	$tableName = '';
897
	$values = '';
898
	$fields = '';
899
	if (is_array($arrFields))
900
		$arrFields = array_change_key_case($arrFields,CASE_UPPER);
901
	$fieldInsertedCount = 0;
902
 
903
	if (is_string($rs)) {
904
		//ok we have a table name
905
		//try and get the column info ourself.
906
		$tableName = $rs;
907
 
908
		//we need an object for the recordSet
909
		//because we have to call MetaType.
910
		//php can't do a $rsclass::MetaType()
911
		$rsclass = $zthis->rsPrefix.$zthis->databaseType;
912
		$recordSet = new $rsclass(ADORecordSet::DUMMY_QUERY_ID, $zthis->fetchMode);
913
		$recordSet->connection = $zthis;
914
 
915
		if (is_string($cacheRS) && $cacheRS == $rs) {
916
			$columns = $cacheCols;
917
		} else {
918
			$columns = $zthis->MetaColumns( $tableName );
919
			$cacheRS = $tableName;
920
			$cacheCols = $columns;
921
		}
922
	} else if (is_subclass_of($rs, 'adorecordset')) {
923
		if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) {
924
			$columns = $cacheCols;
925
		} else {
926
			$columns = [];
927
			for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)
928
				$columns[] = $rs->FetchField($i);
929
			$cacheRS = $cacheSig;
930
			$cacheCols = $columns;
931
			$rs->insertSig = $cacheSig++;
932
		}
933
		$recordSet = $rs;
934
 
935
	} else {
936
		printf(ADODB_BAD_RS,'GetInsertSQL');
937
		return false;
938
	}
939
 
940
	// Loop through all of the fields in the recordset
941
	foreach( $columns as $field ) {
942
		$upperfname = strtoupper($field->name);
943
		if (adodb_key_exists($upperfname, $arrFields, $force)) {
944
			$bad = false;
945
			$fnameq = _adodb_quote_fieldname($zthis, $field->name);
946
			$type = $recordSet->MetaType($field->type);
947
 
948
			/********************************************************/
949
			if (is_null($arrFields[$upperfname])
950
				|| (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
951
				|| $arrFields[$upperfname] === $zthis->null2null
952
			) {
953
				switch ($force) {
954
 
955
					case ADODB_FORCE_IGNORE: // we must always set null if missing
956
						$bad = true;
957
						break;
958
 
959
					case ADODB_FORCE_NULL:
960
						$values .= "null, ";
961
						break;
962
 
963
					case ADODB_FORCE_EMPTY:
964
						//Set empty
965
						$arrFields[$upperfname] = "";
966
						$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
967
						break;
968
 
969
					default:
970
					case ADODB_FORCE_VALUE:
971
						//Set the value that was given in array, so you can give both null and empty values
972
						if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
973
							$values .= "null, ";
974
						} else {
975
							$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
976
						}
977
						break;
978
 
979
					case ADODB_FORCE_NULL_AND_ZERO:
980
						switch ($type) {
981
							case 'N':
982
							case 'I':
983
							case 'L':
984
								$values .= '0, ';
985
								break;
986
							default:
987
								$values .= "null, ";
988
								break;
989
						}
990
						break;
991
 
992
				} // switch
993
 
994
				/*********************************************************/
995
			} else {
996
				//we do this so each driver can customize the sql for
997
				//DB specific column types.
998
				//Oracle needs BLOB types to be handled with a returning clause
999
				//postgres has special needs as well
1000
				$values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
1001
			}
1002
 
1003
			if ($bad) {
1004
				continue;
1005
			}
1006
			// Set the counter for the number of fields that will be inserted.
1007
			$fieldInsertedCount++;
1008
 
1009
			// Get the name of the fields to insert
1010
			$fields .= $fnameq . ", ";
1011
		}
1012
	}
1013
 
1014
 
1015
	// If there were any inserted fields then build the rest of the insert query.
1016
	if ($fieldInsertedCount <= 0) return false;
1017
 
1018
	// Get the table name from the existing query.
1019
	if (!$tableName) {
1020
		if (!empty($rs->tableName)) $tableName = $rs->tableName;
1021
		else if (preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName))
1022
			$tableName = $tableName[1];
1023
		else
1024
			return false;
1025
	}
1026
 
1027
	// Strip off the comma and space on the end of both the fields
1028
	// and their values.
1029
	$fields = substr($fields, 0, -2);
1030
	$values = substr($values, 0, -2);
1031
 
1032
	// Append the fields and their values to the insert query.
1033
	return 'INSERT INTO '.$tableName.' ( '.$fields.' ) VALUES ( '.$values.' )';
1034
}
1035
 
1036
 
1037
/**
1038
 * This private method is used to help construct
1039
 * the update/sql which is generated by GetInsertSQL and GetUpdateSQL.
1040
 * It handles the string construction of 1 column -> sql string based on
1041
 * the column type.  We want to do 'safe' handling of BLOBs
1042
 *
1043
 * @param string the type of sql we are trying to create
1044
 *                'I' or 'U'.
1045
 * @param string column data type from the db::MetaType() method
1046
 * @param string the column name
1047
 * @param array the column value
1048
 *
1049
 * @return string
1050
 *
1051
 */
1052
function _adodb_column_sql_oci8(&$zthis,$action, $type, $fname, $fnameq, $arrFields)
1053
{
1054
	// Based on the datatype of the field
1055
	// Format the value properly for the database
1056
	switch ($type) {
1057
		case 'B':
1058
			//in order to handle Blobs correctly, we need
1059
			//to do some magic for Oracle
1060
 
1061
			//we need to create a new descriptor to handle
1062
			//this properly
1063
			if (!empty($zthis->hasReturningInto)) {
1064
				if ($action == 'I') {
1065
					$sql = 'empty_blob(), ';
1066
				} else {
1067
					$sql = $fnameq . '=empty_blob(), ';
1068
				}
1069
				//add the variable to the returning clause array
1070
				//so the user can build this later in
1071
				//case they want to add more to it
1072
				$zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
1073
			} else {
1074
				if (empty($arrFields[$fname])) {
1075
					if ($action == 'I') {
1076
						$sql = 'empty_blob(), ';
1077
					} else {
1078
						$sql = $fnameq . '=empty_blob(), ';
1079
					}
1080
				} else {
1081
					//this is to maintain compatibility
1082
					//with older adodb versions.
1083
					$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1084
				}
1085
			}
1086
			break;
1087
 
1088
		case "X":
1089
			//we need to do some more magic here for long variables
1090
			//to handle these correctly in oracle.
1091
 
1092
			//create a safe bind var name
1093
			//to avoid conflicts w/ dupes.
1094
			if (!empty($zthis->hasReturningInto)) {
1095
				if ($action == 'I') {
1096
					$sql = ':xx' . $fname . 'xx, ';
1097
				} else {
1098
					$sql = $fnameq . '=:xx' . $fname . 'xx, ';
1099
				}
1100
				//add the variable to the returning clause array
1101
				//so the user can build this later in
1102
				//case they want to add more to it
1103
				$zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
1104
			} else {
1105
				//this is to maintain compatibility
1106
				//with older adodb versions.
1107
				$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1108
			}
1109
			break;
1110
 
1111
		default:
1112
			$sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1113
			break;
1114
	}
1115
 
1116
	return $sql;
1117
}
1118
 
1119
function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, $recurse=true)
1120
{
1121
 
1122
	if ($recurse) {
1123
		switch($zthis->dataProvider) {
1124
		case 'postgres':
1125
			if ($type == 'L') $type = 'C';
1126
			break;
1127
		case 'oci8':
1128
			return _adodb_column_sql_oci8($zthis, $action, $type, $fname, $fnameq, $arrFields);
1129
 
1130
		}
1131
	}
1132
 
1133
	switch($type) {
1134
		case "C":
1135
		case "X":
1136
		case 'B':
1137
			$val = $zthis->qstr($arrFields[$fname]);
1138
			break;
1139
 
1140
		case "D":
1141
			$val = $zthis->DBDate($arrFields[$fname]);
1142
			break;
1143
 
1144
		case "T":
1145
			$val = $zthis->DBTimeStamp($arrFields[$fname]);
1146
			break;
1147
 
1148
		case "N":
1149
			$val = $arrFields[$fname];
1150
			if (!is_numeric($val)) $val = str_replace(',', '.', (float)$val);
1151
			break;
1152
 
1153
		case "I":
1154
		case "R":
1155
			$val = $arrFields[$fname];
1156
			if (!is_numeric($val)) $val = (integer) $val;
1157
			break;
1158
 
1159
		default:
1160
			$val = str_replace(array("'"," ","("),"",$arrFields[$fname]); // basic sql injection defence
1161
			if (empty($val)) $val = '0';
1162
			break;
1163
	}
1164
 
1165
	if ($action == 'I') return $val . ", ";
1166
 
1167
	return $fnameq . "=" . $val . ", ";
1168
}
1169
 
1170
 
1171
/**
1172
* Replaces standard _execute when debug mode is enabled
1173
*
1174
* @param ADOConnection   $zthis    An ADOConnection object
1175
* @param string|string[] $sql      A string or array of SQL statements
1176
* @param string[]|null   $inputarr An optional array of bind parameters
1177
*
1178
* @return  handle|void A handle to the executed query
1179
*/
1180
function _adodb_debug_execute($zthis, $sql, $inputarr)
1181
{
1182
	// Unpack the bind parameters
1183
	$ss = '';
1184
	if ($inputarr) {
1185
		foreach ($inputarr as $kk => $vv) {
1186
			if (is_string($vv) && strlen($vv) > 64) {
1187
				$vv = substr($vv, 0, 64) . '...';
1188
			}
1189
			if (is_null($vv)) {
1190
				$ss .= "($kk=>null) ";
1191
			} else {
1192
				if (is_array($vv)) {
1193
					$vv = sprintf("Array Of Values: [%s]", implode(',', $vv));
1194
				}
1195
				$ss .= "($kk=>'$vv') ";
1196
			}
1197
		}
1198
		$ss = "[ $ss ]";
1199
	}
1200
 
1201
	$sqlTxt = is_array($sql) ? $sql[0] : $sql;
1202
 
1203
	// Remove newlines and tabs, compress repeating spaces
1204
	$sqlTxt = preg_replace('/\s+/', ' ', $sqlTxt);
1205
 
1206
	// check if running from browser or command-line
1207
	$inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
1208
 
1209
	$myDatabaseType = $zthis->databaseType;
1210
	if (!isset($zthis->dsnType)) {
1211
		// Append the PDO driver name
1212
		$myDatabaseType .= '-' . $zthis->dsnType;
1213
	}
1214
 
1215
	if ($inBrowser) {
1216
		if ($ss) {
1217
			// Default formatting for passed parameter
1218
			$ss = sprintf('<code class="adodb-debug">%s</code>', htmlspecialchars($ss));
1219
		}
1220
		if ($zthis->debug === -1) {
1221
			$outString = "<br class='adodb-debug'>(%s):  %s &nbsp; %s<br class='adodb-debug'>";
1222
			ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1223
		} elseif ($zthis->debug !== -99) {
1224
			$outString = "<hr class='adodb-debug'>(%s):  %s &nbsp; %s<hr class='adodb-debug'>";
1225
			ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1226
		}
1227
	} else {
1228
		// CLI output
1229
		if ($zthis->debug !== -99) {
1230
			$outString = sprintf("%s\n%s\n    %s %s \n%s\n", str_repeat('-', 78), $myDatabaseType, $sqlTxt, $ss, str_repeat('-', 78));
1231
			ADOConnection::outp($outString, false);
1232
		}
1233
	}
1234
 
1235
	// Now execute the query
1236
	$qID = $zthis->_query($sql, $inputarr);
1237
 
1238
	// Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
1239
	// because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
1240
	if ($zthis->databaseType == 'mssql') {
1241
		// ErrorNo is a slow function call in mssql
1242
		if ($emsg = $zthis->ErrorMsg()) {
1243
			if ($err = $zthis->ErrorNo()) {
1244
				if ($zthis->debug === -99) {
1245
					ADOConnection::outp("<hr>\n($myDatabaseType): " . htmlspecialchars($sqlTxt) . " &nbsp; $ss\n<hr>\n", false);
1246
				}
1247
 
1248
				ADOConnection::outp($err . ': ' . $emsg);
1249
			}
1250
		}
1251
	} else {
1252
		if (!$qID) {
1253
			// Statement execution has failed
1254
			if ($zthis->debug === -99) {
1255
				if ($inBrowser) {
1256
					$outString = "<hr class='adodb-debug'>(%s):  %s &nbsp; %s<hr class='adodb-debug'>";
1257
					ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1258
				} else {
1259
					$outString = sprintf("%s\n%s\n    %s %s \n%s\n",str_repeat('-',78),$myDatabaseType,$sqlTxt,$ss,str_repeat('-',78));
1260
					ADOConnection::outp($outString, false);
1261
				}
1262
			}
1263
 
1264
			// Send last error to output
1265
			$errno = $zthis->ErrorNo();
1266
			if ($errno) {
1267
				ADOConnection::outp($errno . ': ' . $zthis->ErrorMsg());
1268
			}
1269
		}
1270
	}
1271
 
1272
	if ($qID === false || $zthis->debug === 99) {
1273
		_adodb_backtrace();
1274
	}
1275
	return $qID;
1276
}
1277
 
1278
/**
1279
 * Pretty print the debug_backtrace function
1280
 *
1281
 * @param string[]|bool $printOrArr       Whether to print the result directly or return the result
1282
 * @param int           $maximumDepth     The maximum depth of the array to traverse
1283
 * @param int           $elementsToIgnore The backtrace array indexes to ignore
1284
 * @param null|bool     $ishtml           True if we are in a CGI environment, false for CLI,
1285
 *                                        null to auto detect
1286
 *
1287
 * @return string Formatted backtrace
1288
 */
1289
function _adodb_backtrace($printOrArr=true, $maximumDepth=9999, $elementsToIgnore=0, $ishtml=null)
1290
{
1291
	if (!function_exists('debug_backtrace')) {
1292
		return '';
1293
	}
1294
 
1295
	if ($ishtml === null) {
1296
		// Auto determine if we in a CGI enviroment
1297
		$html = (isset($_SERVER['HTTP_USER_AGENT']));
1298
	} else {
1299
		$html = $ishtml;
1300
	}
1301
 
1302
	$cgiString = "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>";
1303
	$cliString = "%% line %4d, file: %s";
1304
	$fmt = ($html) ? $cgiString : $cliString;
1305
 
1306
	$MAXSTRLEN = 128;
1307
 
1308
	$s = ($html) ? '<pre align=left>' : '';
1309
 
1310
	if (is_array($printOrArr)) {
1311
		$traceArr = $printOrArr;
1312
	} else {
1313
		$traceArr = debug_backtrace();
1314
	}
1315
 
1316
	// Remove first 2 elements that just show calls to adodb_backtrace
1317
	array_shift($traceArr);
1318
	array_shift($traceArr);
1319
 
1320
	// We want last element to have no indent
1321
	$tabs = sizeof($traceArr) - 1;
1322
 
1323
	foreach ($traceArr as $arr) {
1324
		if ($elementsToIgnore) {
1325
			// Ignore array element at start of array
1326
			$elementsToIgnore--;
1327
			$tabs--;
1328
			continue;
1329
		}
1330
		$maximumDepth--;
1331
		if ($maximumDepth < 0) {
1332
			break;
1333
		}
1334
 
1335
		$args = array();
1336
 
1337
		if ($tabs) {
1338
			$s .= str_repeat($html ? ' &nbsp; ' : "\t", $tabs);
1339
			$tabs--;
1340
		}
1341
		if ($html) {
1342
			$s .= '<font face="Courier New,Courier">';
1343
		}
1344
 
1345
		if (isset($arr['class'])) {
1346
			$s .= $arr['class'] . '.';
1347
		}
1348
 
1349
		if (isset($arr['args'])) {
1350
			foreach ($arr['args'] as $v) {
1351
				if (is_null($v)) {
1352
					$args[] = 'null';
1353
				} elseif (is_array($v)) {
1354
					$args[] = 'Array[' . sizeof($v) . ']';
1355
				} elseif (is_object($v)) {
1356
					$args[] = 'Object:' . get_class($v);
1357
				} elseif (is_bool($v)) {
1358
					$args[] = $v ? 'true' : 'false';
1359
				} else {
1360
					$v = (string)@$v;
1361
					// Truncate
1362
					$v = substr($v, 0, $MAXSTRLEN);
1363
					// Remove newlines and tabs, compress repeating spaces
1364
					$v = preg_replace('/\s+/', ' ', $v);
1365
					// Convert htmlchars (not sure why we do this in CLI)
1366
					$str = htmlspecialchars($v);
1367
 
1368
					if (strlen($v) > $MAXSTRLEN) {
1369
						$str .= '...';
1370
					}
1371
 
1372
					$args[] = $str;
1373
				}
1374
			}
1375
		}
1376
		$s .= $arr['function'] . '(' . implode(', ', $args) . ')';
1377
		$s .= @sprintf($fmt, $arr['line'], $arr['file'], basename($arr['file']));
1378
		$s .= "\n";
1379
	}
1380
	if ($html) {
1381
		$s .= '</pre>';
1382
	}
1383
	if ($printOrArr) {
1384
		print $s;
1385
	}
1386
 
1387
	return $s;
1388
}