Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   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   	 Code originally from "Cornel G" <conyg@fx.ro>
 513  
 514  	 This code might not work with SQL that has UNION in it
 515  
 516  	 Also if you are using CachePageExecute(), there is a strong possibility that
 517  	 data will get out of synch. use CachePageExecute() only with tables that
 518  	 rarely change.
 519  */
 520  function _adodb_pageexecute_all_rows($zthis, $sql, $nrows, $page,
 521  	 	 	 	 	 	 $inputarr=false, $secs2cache=0)
 522  {
 523  	 $atfirstpage = false;
 524  	 $atlastpage = false;
 525  
 526  	 // If an invalid nrows is supplied,
 527  	 // we assume a default value of 10 rows per page
 528  	 if (!isset($nrows) || $nrows <= 0) $nrows = 10;
 529  
 530  	 $qryRecs = _adodb_getcount($zthis,$sql,$inputarr,$secs2cache);
 531  	 $lastpageno = (int) ceil($qryRecs / $nrows);
 532  	 $zthis->_maxRecordCount = $qryRecs;
 533  
 534  	 // ***** Here we check whether $page is the last page or
 535  	 // whether we are trying to retrieve
 536  	 // a page number greater than the last page number.
 537  	 if ($page >= $lastpageno) {
 538  	 	 $page = $lastpageno;
 539  	 	 $atlastpage = true;
 540  	 }
 541  
 542  	 // If page number <= 1, then we are at the first page
 543  	 if (empty($page) || $page <= 1) {
 544  	 	 $page = 1;
 545  	 	 $atfirstpage = true;
 546  	 }
 547  
 548  	 // We get the data we want
 549  	 $offset = $nrows * ($page-1);
 550  	 if ($secs2cache > 0)
 551  	 	 $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
 552  	 else
 553  	 	 $rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
 554  
 555  
 556  	 // Before returning the RecordSet, we set the pagination properties we need
 557  	 if ($rsreturn) {
 558  	 	 $rsreturn->_maxRecordCount = $qryRecs;
 559  	 	 $rsreturn->rowsPerPage = $nrows;
 560  	 	 $rsreturn->AbsolutePage($page);
 561  	 	 $rsreturn->AtFirstPage($atfirstpage);
 562  	 	 $rsreturn->AtLastPage($atlastpage);
 563  	 	 $rsreturn->LastPageNo($lastpageno);
 564  	 }
 565  	 return $rsreturn;
 566  }
 567  
 568  // Iván Oliva version
 569  function _adodb_pageexecute_no_last_page($zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0)
 570  {
 571  	 $atfirstpage = false;
 572  	 $atlastpage = false;
 573  
 574  	 if (!isset($page) || $page <= 1) {
 575  	 	 // If page number <= 1, then we are at the first page
 576  	 	 $page = 1;
 577  	 	 $atfirstpage = true;
 578  	 }
 579  	 if ($nrows <= 0) {
 580  	 	 // If an invalid nrows is supplied, we assume a default value of 10 rows per page
 581  	 	 $nrows = 10;
 582  	 }
 583  
 584  	 $pagecounteroffset = ($page * $nrows) - $nrows;
 585  
 586  	 // To find out if there are more pages of rows, simply increase the limit or
 587  	 // nrows by 1 and see if that number of records was returned. If it was,
 588  	 // then we know there is at least one more page left, otherwise we are on
 589  	 // the last page. Therefore allow non-Count() paging with single queries
 590  	 // rather than three queries as was done before.
 591  	 $test_nrows = $nrows + 1;
 592  	 if ($secs2cache > 0) {
 593  	 	 $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
 594  	 } else {
 595  	 	 $rsreturn = $zthis->SelectLimit($sql, $test_nrows, $pagecounteroffset, $inputarr, $secs2cache);
 596  	 }
 597  
 598  	 // Now check to see if the number of rows returned was the higher value we asked for or not.
 599  	 if ( $rsreturn->_numOfRows == $test_nrows ) {
 600  	 	 // Still at least 1 more row, so we are not on last page yet...
 601  	 	 // Remove the last row from the RS.
 602  	 	 $rsreturn->_numOfRows = ( $rsreturn->_numOfRows - 1 );
 603  	 } elseif ( $rsreturn->_numOfRows == 0 && $page > 1 ) {
 604  	 	 // Likely requested a page that doesn't exist, so need to find the last
 605  	 	 // page and return it. Revert to original method and loop through pages
 606  	 	 // until we find some data...
 607  	 	 $pagecounter = $page + 1;
 608  
 609  	 	 $rstest = $rsreturn;
 610  	 	 if ($rstest) {
 611  	 	 	 while ($rstest && $rstest->EOF && $pagecounter > 0) {
 612  	 	 	 	 $atlastpage = true;
 613  	 	 	 	 $pagecounter--;
 614  	 	 	 	 $pagecounteroffset = $nrows * ($pagecounter - 1);
 615  	 	 	 	 $rstest->Close();
 616  	 	 	 	 if ($secs2cache>0) {
 617  	 	 	 	 	 $rstest = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr);
 618  	 	 	 	 }
 619  	 	 	 	 else {
 620  	 	 	 	 	 $rstest = $zthis->SelectLimit($sql, $nrows, $pagecounteroffset, $inputarr, $secs2cache);
 621  	 	 	 	 }
 622  	 	 	 }
 623  	 	 	 if ($rstest) $rstest->Close();
 624  	 	 }
 625  	 	 if ($atlastpage) {
 626  	 	 	 // If we are at the last page or beyond it, we are going to retrieve it
 627  	 	 	 $page = $pagecounter;
 628  	 	 	 if ($page == 1) {
 629  	 	 	 	 // We have to do this again in case the last page is the same as
 630  	 	 	 	 // the first page, that is, the recordset has only 1 page.
 631  	 	 	 	 $atfirstpage = true;
 632  	 	 	 }
 633  	 	 }
 634  	 	 // We get the data we want
 635  	 	 $offset = $nrows * ($page-1);
 636  	 	 if ($secs2cache > 0) {
 637  	 	 	 $rsreturn = $zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr);
 638  	 	 }
 639  	 	 else {
 640  	 	 	 $rsreturn = $zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache);
 641  	 	 }
 642  	 } elseif ( $rsreturn->_numOfRows < $test_nrows ) {
 643  	 	 // Rows is less than what we asked for, so must be at the last page.
 644  	 	 $atlastpage = true;
 645  	 }
 646  
 647  	 // Before returning the RecordSet, we set the pagination properties we need
 648  	 if ($rsreturn) {
 649  	 	 $rsreturn->rowsPerPage = $nrows;
 650  	 	 $rsreturn->AbsolutePage($page);
 651  	 	 $rsreturn->AtFirstPage($atfirstpage);
 652  	 	 $rsreturn->AtLastPage($atlastpage);
 653  	 }
 654  	 return $rsreturn;
 655  }
 656  
 657  /**
 658   * Performs case conversion and quoting of the given field name.
 659   *
 660   * See Global variable $ADODB_QUOTE_FIELDNAMES.
 661   *
 662   * @param ADOConnection $zthis
 663   * @param string $fieldName
 664   *
 665   * @return string Quoted field name
 666   */
 667  function _adodb_quote_fieldname($zthis, $fieldName)
 668  {
 669  	 global $ADODB_QUOTE_FIELDNAMES;
 670  
 671  	 // Case conversion - defaults to UPPER
 672  	 $case = is_bool($ADODB_QUOTE_FIELDNAMES) ? 'UPPER' : $ADODB_QUOTE_FIELDNAMES;
 673  	 switch ($case) {
 674  	 	 case 'LOWER':
 675  	 	 	 $fieldName = strtolower($fieldName);
 676  	 	 	 break;
 677  	 	 case 'NATIVE':
 678  	 	 	 // Do nothing
 679  	 	 	 break;
 680  	 	 case 'UPPER':
 681  	 	 case 'BRACKETS':
 682  	 	 default:
 683  	 	 	 $fieldName = strtoupper($fieldName);
 684  	 	 	 break;
 685  	 }
 686  
 687  	 // Quote field if requested, or necessary (field contains space)
 688  	 if ($ADODB_QUOTE_FIELDNAMES || strpos($fieldName, ' ') !== false ) {
 689  	 	 if ($ADODB_QUOTE_FIELDNAMES === 'BRACKETS') {
 690  	 	 	 return $zthis->leftBracket . $fieldName . $zthis->rightBracket;
 691  	 	 } else {
 692  	 	 	 return $zthis->nameQuote . $fieldName . $zthis->nameQuote;
 693  	 	 }
 694  	 } else {
 695  	 	 return $fieldName;
 696  	 }
 697  }
 698  
 699  function _adodb_getupdatesql(&$zthis, $rs, $arrFields, $forceUpdate=false, $force=2)
 700  {
 701  	 if (!$rs) {
 702  	 	 printf(ADODB_BAD_RS,'GetUpdateSQL');
 703  	 	 return false;
 704  	 }
 705  
 706  	 $fieldUpdatedCount = 0;
 707  	 if (is_array($arrFields))
 708  	 	 $arrFields = array_change_key_case($arrFields,CASE_UPPER);
 709  
 710  	 $hasnumeric = isset($rs->fields[0]);
 711  	 $setFields = '';
 712  
 713  	 // Loop through all of the fields in the recordset
 714  	 for ($i=0, $max=$rs->fieldCount(); $i < $max; $i++) {
 715  	 	 // Get the field from the recordset
 716  	 	 $field = $rs->fetchField($i);
 717  
 718  	 	 // If the recordset field is one
 719  	 	 // of the fields passed in then process.
 720  	 	 $upperfname = strtoupper($field->name);
 721  	 	 if (adodb_key_exists($upperfname, $arrFields, $force)) {
 722  
 723  	 	 	 // If the existing field value in the recordset
 724  	 	 	 // is different from the value passed in then
 725  	 	 	 // go ahead and append the field name and new value to
 726  	 	 	 // the update query.
 727  
 728  	 	 	 if ($hasnumeric) $val = $rs->fields[$i];
 729  	 	 	 else if (isset($rs->fields[$upperfname])) $val = $rs->fields[$upperfname];
 730  	 	 	 else if (isset($rs->fields[$field->name])) $val = $rs->fields[$field->name];
 731  	 	 	 else if (isset($rs->fields[strtolower($upperfname)])) $val = $rs->fields[strtolower($upperfname)];
 732  	 	 	 else $val = '';
 733  
 734  	 	 	 if ($forceUpdate || $val !== $arrFields[$upperfname]) {
 735  	 	 	 	 // Set the counter for the number of fields that will be updated.
 736  	 	 	 	 $fieldUpdatedCount++;
 737  
 738  	 	 	 	 // Based on the datatype of the field
 739  	 	 	 	 // Format the value properly for the database
 740  	 	 	 	 $type = $rs->metaType($field->type);
 741  
 742  	 	 	 	 if ($type == 'null') {
 743  	 	 	 	 	 $type = 'C';
 744  	 	 	 	 }
 745  
 746  	 	 	 	 $fnameq = _adodb_quote_fieldname($zthis, $field->name);
 747  
 748  	 	 	 	 //********************************************************//
 749  	 	 	 	 if (is_null($arrFields[$upperfname])
 750  	 	 	 	 	 || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
 751  	 	 	 	 	 || $arrFields[$upperfname] === $zthis->null2null
 752  	 	 	 	 	 ) {
 753  
 754  	 	 	 	 	 switch ($force) {
 755  
 756  	 	 	 	 	 	 //case 0:
 757  	 	 	 	 	 	 //	 // Ignore empty values. This is already handled in "adodb_key_exists" function.
 758  	 	 	 	 	 	 //	 break;
 759  
 760  	 	 	 	 	 	 case 1:
 761  	 	 	 	 	 	 	 // set null
 762  	 	 	 	 	 	 	 $setFields .= $fnameq . " = null, ";
 763  	 	 	 	 	 	 	 break;
 764  
 765  	 	 	 	 	 	 case 2:
 766  	 	 	 	 	 	 	 // set empty
 767  	 	 	 	 	 	 	 $arrFields[$upperfname] = "";
 768  	 	 	 	 	 	 	 $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
 769  	 	 	 	 	 	 	 break;
 770  
 771  	 	 	 	 	 	 default:
 772  	 	 	 	 	 	 case 3:
 773  	 	 	 	 	 	 	 // set the value that was given in array, so you can give both null and empty values
 774  	 	 	 	 	 	 	 if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
 775  	 	 	 	 	 	 	 	 $setFields .= $fnameq . " = null, ";
 776  	 	 	 	 	 	 	 } else {
 777  	 	 	 	 	 	 	 	 $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
 778  	 	 	 	 	 	 	 }
 779  	 	 	 	 	 	 	 break;
 780  
 781  	 	 	 	 	 	 case ADODB_FORCE_NULL_AND_ZERO:
 782  
 783  	 	 	 	 	 	 	 switch ($type) {
 784  	 	 	 	 	 	 	 	 case 'N':
 785  	 	 	 	 	 	 	 	 case 'I':
 786  	 	 	 	 	 	 	 	 case 'L':
 787  	 	 	 	 	 	 	 	 	 $setFields .= $fnameq . ' = 0, ';
 788  	 	 	 	 	 	 	 	 	 break;
 789  	 	 	 	 	 	 	 	 default:
 790  	 	 	 	 	 	 	 	 	 $setFields .= $fnameq . ' = null, ';
 791  	 	 	 	 	 	 	 	 	 break;
 792  	 	 	 	 	 	 	 }
 793  	 	 	 	 	 	 	 break;
 794  
 795  	 	 	 	 	 }
 796  	 	 	 	 //********************************************************//
 797  	 	 	 	 } else {
 798  	 	 	 	 	 // we do this so each driver can customize the sql for
 799  	 	 	 	 	 // DB specific column types.
 800  	 	 	 	 	 // Oracle needs BLOB types to be handled with a returning clause
 801  	 	 	 	 	 // postgres has special needs as well
 802  	 	 	 	 	 $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, $arrFields);
 803  	 	 	 	 }
 804  	 	 	 }
 805  	 	 }
 806  	 }
 807  
 808  	 // If there were any modified fields then build the rest of the update query.
 809  	 if ($fieldUpdatedCount > 0 || $forceUpdate) {
 810  	 	 // Get the table name from the existing query.
 811  	 	 if (!empty($rs->tableName)) {
 812  	 	 	 $tableName = $rs->tableName;
 813  	 	 } else {
 814  	 	 	 preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName);
 815  	 	 	 $tableName = $tableName[1];
 816  	 	 }
 817  
 818  	 	 // Get the full where clause excluding the word "WHERE" from the existing query.
 819  	 	 preg_match('/\sWHERE\s(.*)/is', $rs->sql, $whereClause);
 820  
 821  	 	 $discard = false;
 822  	 	 // not a good hack, improvements?
 823  	 	 if ($whereClause) {
 824  	 	 	 if (preg_match('/\s(ORDER\s.*)/is', $whereClause[1], $discard));
 825  	 	 	 else if (preg_match('/\s(LIMIT\s.*)/is', $whereClause[1], $discard));
 826  	 	 	 else if (preg_match('/\s(FOR UPDATE.*)/is', $whereClause[1], $discard));
 827  	 	 	 else preg_match('/\s.*(\) WHERE .*)/is', $whereClause[1], $discard); # see https://sourceforge.net/p/adodb/bugs/37/
 828  	 	 } else {
 829  	 	 	 $whereClause = array(false, false);
 830  	 	 }
 831  
 832  	 	 if ($discard) {
 833  	 	 	 $whereClause[1] = substr($whereClause[1], 0, strlen($whereClause[1]) - strlen($discard[1]));
 834  	 	 }
 835  
 836  	 	 $sql = 'UPDATE '.$tableName.' SET '.substr($setFields, 0, -2);
 837  	 	 if (strlen($whereClause[1]) > 0) {
 838  	 	 	 $sql .= ' WHERE '.$whereClause[1];
 839  	 	 }
 840  	 	 return $sql;
 841  	 } else {
 842  	 	 return false;
 843  	 }
 844  }
 845  
 846  function adodb_key_exists($key, $arr,$force=2)
 847  {
 848  	 if ($force<=0) {
 849  	 	 // the following is the old behaviour where null or empty fields are ignored
 850  	 	 return (!empty($arr[$key])) || (isset($arr[$key]) && strlen($arr[$key])>0);
 851  	 }
 852  
 853  	 if (isset($arr[$key]))
 854  	 	 return true;
 855  	 ## null check below
 856  	 return array_key_exists($key,$arr);
 857  }
 858  
 859  /**
 860   * There is a special case of this function for the oci8 driver.
 861   * The proper way to handle an insert w/ a blob in oracle requires
 862   * a returning clause with bind variables and a descriptor blob.
 863   *
 864   *
 865   */
 866  function _adodb_getinsertsql(&$zthis, $rs, $arrFields, $force=2)
 867  {
 868  static $cacheRS = false;
 869  static $cacheSig = 0;
 870  static $cacheCols;
 871  
 872  	 $tableName = '';
 873  	 $values = '';
 874  	 $fields = '';
 875  	 if (is_array($arrFields))
 876  	 	 $arrFields = array_change_key_case($arrFields,CASE_UPPER);
 877  	 $fieldInsertedCount = 0;
 878  
 879  	 if (is_string($rs)) {
 880  	 	 //ok we have a table name
 881  	 	 //try and get the column info ourself.
 882  	 	 $tableName = $rs;
 883  
 884  	 	 //we need an object for the recordSet
 885  	 	 //because we have to call MetaType.
 886  	 	 //php can't do a $rsclass::MetaType()
 887  	 	 $rsclass = $zthis->rsPrefix.$zthis->databaseType;
 888  	 	 $recordSet = new $rsclass(ADORecordSet::DUMMY_QUERY_ID, $zthis->fetchMode);
 889  	 	 $recordSet->connection = $zthis;
 890  
 891  	 	 if (is_string($cacheRS) && $cacheRS == $rs) {
 892  	 	 	 $columns = $cacheCols;
 893  	 	 } else {
 894  	 	 	 $columns = $zthis->MetaColumns( $tableName );
 895  	 	 	 $cacheRS = $tableName;
 896  	 	 	 $cacheCols = $columns;
 897  	 	 }
 898  	 } else if (is_subclass_of($rs, 'adorecordset')) {
 899  	 	 if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) {
 900  	 	 	 $columns = $cacheCols;
 901  	 	 } else {
 902  	 	 	 $columns = [];
 903  	 	 	 for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)
 904  	 	 	 	 $columns[] = $rs->FetchField($i);
 905  	 	 	 $cacheRS = $cacheSig;
 906  	 	 	 $cacheCols = $columns;
 907  	 	 	 $rs->insertSig = $cacheSig++;
 908  	 	 }
 909  	 	 $recordSet = $rs;
 910  
 911  	 } else {
 912  	 	 printf(ADODB_BAD_RS,'GetInsertSQL');
 913  	 	 return false;
 914  	 }
 915  
 916  	 // Loop through all of the fields in the recordset
 917  	 foreach( $columns as $field ) {
 918  	 	 $upperfname = strtoupper($field->name);
 919  	 	 if (adodb_key_exists($upperfname, $arrFields, $force)) {
 920  	 	 	 $bad = false;
 921  	 	 	 $fnameq = _adodb_quote_fieldname($zthis, $field->name);
 922  	 	 	 $type = $recordSet->MetaType($field->type);
 923  
 924  	 	 	 /********************************************************/
 925  	 	 	 if (is_null($arrFields[$upperfname])
 926  	 	 	 	 || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)
 927  	 	 	 	 || $arrFields[$upperfname] === $zthis->null2null
 928  	 	 	 ) {
 929  	 	 	 	 switch ($force) {
 930  
 931  	 	 	 	 	 case ADODB_FORCE_IGNORE: // we must always set null if missing
 932  	 	 	 	 	 	 $bad = true;
 933  	 	 	 	 	 	 break;
 934  
 935  	 	 	 	 	 case ADODB_FORCE_NULL:
 936  	 	 	 	 	 	 $values .= "null, ";
 937  	 	 	 	 	 	 break;
 938  
 939  	 	 	 	 	 case ADODB_FORCE_EMPTY:
 940  	 	 	 	 	 	 //Set empty
 941  	 	 	 	 	 	 $arrFields[$upperfname] = "";
 942  	 	 	 	 	 	 $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
 943  	 	 	 	 	 	 break;
 944  
 945  	 	 	 	 	 default:
 946  	 	 	 	 	 case ADODB_FORCE_VALUE:
 947  	 	 	 	 	 	 //Set the value that was given in array, so you can give both null and empty values
 948  	 	 	 	 	 	 if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {
 949  	 	 	 	 	 	 	 $values .= "null, ";
 950  	 	 	 	 	 	 } else {
 951  	 	 	 	 	 	 	 $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
 952  	 	 	 	 	 	 }
 953  	 	 	 	 	 	 break;
 954  
 955  	 	 	 	 	 case ADODB_FORCE_NULL_AND_ZERO:
 956  	 	 	 	 	 	 switch ($type) {
 957  	 	 	 	 	 	 	 case 'N':
 958  	 	 	 	 	 	 	 case 'I':
 959  	 	 	 	 	 	 	 case 'L':
 960  	 	 	 	 	 	 	 	 $values .= '0, ';
 961  	 	 	 	 	 	 	 	 break;
 962  	 	 	 	 	 	 	 default:
 963  	 	 	 	 	 	 	 	 $values .= "null, ";
 964  	 	 	 	 	 	 	 	 break;
 965  	 	 	 	 	 	 }
 966  	 	 	 	 	 	 break;
 967  
 968  	 	 	 	 } // switch
 969  
 970  	 	 	 	 /*********************************************************/
 971  	 	 	 } else {
 972  	 	 	 	 //we do this so each driver can customize the sql for
 973  	 	 	 	 //DB specific column types.
 974  	 	 	 	 //Oracle needs BLOB types to be handled with a returning clause
 975  	 	 	 	 //postgres has special needs as well
 976  	 	 	 	 $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields);
 977  	 	 	 }
 978  
 979  	 	 	 if ($bad) {
 980  	 	 	 	 continue;
 981  	 	 	 }
 982  	 	 	 // Set the counter for the number of fields that will be inserted.
 983  	 	 	 $fieldInsertedCount++;
 984  
 985  	 	 	 // Get the name of the fields to insert
 986  	 	 	 $fields .= $fnameq . ", ";
 987  	 	 }
 988  	 }
 989  
 990  
 991  	 // If there were any inserted fields then build the rest of the insert query.
 992  	 if ($fieldInsertedCount <= 0) return false;
 993  
 994  	 // Get the table name from the existing query.
 995  	 if (!$tableName) {
 996  	 	 if (!empty($rs->tableName)) $tableName = $rs->tableName;
 997  	 	 else if (preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName))
 998  	 	 	 $tableName = $tableName[1];
 999  	 	 else
1000  	 	 	 return false;
1001  	 }
1002  
1003  	 // Strip off the comma and space on the end of both the fields
1004  	 // and their values.
1005  	 $fields = substr($fields, 0, -2);
1006  	 $values = substr($values, 0, -2);
1007  
1008  	 // Append the fields and their values to the insert query.
1009  	 return 'INSERT INTO '.$tableName.' ( '.$fields.' ) VALUES ( '.$values.' )';
1010  }
1011  
1012  
1013  /**
1014   * This private method is used to help construct
1015   * the update/sql which is generated by GetInsertSQL and GetUpdateSQL.
1016   * It handles the string construction of 1 column -> sql string based on
1017   * the column type.  We want to do 'safe' handling of BLOBs
1018   *
1019   * @param string the type of sql we are trying to create
1020   *                'I' or 'U'.
1021   * @param string column data type from the db::MetaType() method
1022   * @param string the column name
1023   * @param array the column value
1024   *
1025   * @return string
1026   *
1027   */
1028  function _adodb_column_sql_oci8(&$zthis,$action, $type, $fname, $fnameq, $arrFields)
1029  {
1030  	 // Based on the datatype of the field
1031  	 // Format the value properly for the database
1032  	 switch ($type) {
1033  	 	 case 'B':
1034  	 	 	 //in order to handle Blobs correctly, we need
1035  	 	 	 //to do some magic for Oracle
1036  
1037  	 	 	 //we need to create a new descriptor to handle
1038  	 	 	 //this properly
1039  	 	 	 if (!empty($zthis->hasReturningInto)) {
1040  	 	 	 	 if ($action == 'I') {
1041  	 	 	 	 	 $sql = 'empty_blob(), ';
1042  	 	 	 	 } else {
1043  	 	 	 	 	 $sql = $fnameq . '=empty_blob(), ';
1044  	 	 	 	 }
1045  	 	 	 	 //add the variable to the returning clause array
1046  	 	 	 	 //so the user can build this later in
1047  	 	 	 	 //case they want to add more to it
1048  	 	 	 	 $zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
1049  	 	 	 } else {
1050  	 	 	 	 if (empty($arrFields[$fname])) {
1051  	 	 	 	 	 if ($action == 'I') {
1052  	 	 	 	 	 	 $sql = 'empty_blob(), ';
1053  	 	 	 	 	 } else {
1054  	 	 	 	 	 	 $sql = $fnameq . '=empty_blob(), ';
1055  	 	 	 	 	 }
1056  	 	 	 	 } else {
1057  	 	 	 	 	 //this is to maintain compatibility
1058  	 	 	 	 	 //with older adodb versions.
1059  	 	 	 	 	 $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1060  	 	 	 	 }
1061  	 	 	 }
1062  	 	 	 break;
1063  
1064  	 	 case "X":
1065  	 	 	 //we need to do some more magic here for long variables
1066  	 	 	 //to handle these correctly in oracle.
1067  
1068  	 	 	 //create a safe bind var name
1069  	 	 	 //to avoid conflicts w/ dupes.
1070  	 	 	 if (!empty($zthis->hasReturningInto)) {
1071  	 	 	 	 if ($action == 'I') {
1072  	 	 	 	 	 $sql = ':xx' . $fname . 'xx, ';
1073  	 	 	 	 } else {
1074  	 	 	 	 	 $sql = $fnameq . '=:xx' . $fname . 'xx, ';
1075  	 	 	 	 }
1076  	 	 	 	 //add the variable to the returning clause array
1077  	 	 	 	 //so the user can build this later in
1078  	 	 	 	 //case they want to add more to it
1079  	 	 	 	 $zthis->_returningArray[$fname] = ':xx' . $fname . 'xx';
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  	 	 	 break;
1086  
1087  	 	 default:
1088  	 	 	 $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, false);
1089  	 	 	 break;
1090  	 }
1091  
1092  	 return $sql;
1093  }
1094  
1095  function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, $recurse=true)
1096  {
1097  
1098  	 if ($recurse) {
1099  	 	 switch($zthis->dataProvider) {
1100  	 	 case 'postgres':
1101  	 	 	 if ($type == 'L') $type = 'C';
1102  	 	 	 break;
1103  	 	 case 'oci8':
1104  	 	 	 return _adodb_column_sql_oci8($zthis, $action, $type, $fname, $fnameq, $arrFields);
1105  
1106  	 	 }
1107  	 }
1108  
1109  	 switch($type) {
1110  	 	 case "C":
1111  	 	 case "X":
1112  	 	 case 'B':
1113  	 	 	 $val = $zthis->qstr($arrFields[$fname]);
1114  	 	 	 break;
1115  
1116  	 	 case "D":
1117  	 	 	 $val = $zthis->DBDate($arrFields[$fname]);
1118  	 	 	 break;
1119  
1120  	 	 case "T":
1121  	 	 	 $val = $zthis->DBTimeStamp($arrFields[$fname]);
1122  	 	 	 break;
1123  
1124  	 	 case "N":
1125  	 	 	 $val = $arrFields[$fname];
1126  	 	 	 if (!is_numeric($val)) $val = str_replace(',', '.', (float)$val);
1127  	 	 	 break;
1128  
1129  	 	 case "I":
1130  	 	 case "R":
1131  	 	 	 $val = $arrFields[$fname];
1132  	 	 	 if (!is_numeric($val)) $val = (integer) $val;
1133  	 	 	 break;
1134  
1135  	 	 default:
1136  	 	 	 $val = str_replace(array("'"," ","("),"",$arrFields[$fname]); // basic sql injection defence
1137  	 	 	 if (empty($val)) $val = '0';
1138  	 	 	 break;
1139  	 }
1140  
1141  	 if ($action == 'I') return $val . ", ";
1142  
1143  	 return $fnameq . "=" . $val . ", ";
1144  }
1145  
1146  
1147  /**
1148  * Replaces standard _execute when debug mode is enabled
1149  *
1150  * @param ADOConnection   $zthis    An ADOConnection object
1151  * @param string|string[] $sql      A string or array of SQL statements
1152  * @param string[]|null   $inputarr An optional array of bind parameters
1153  *
1154  * @return  handle|void A handle to the executed query
1155  */
1156  function _adodb_debug_execute($zthis, $sql, $inputarr)
1157  {
1158  	 // Unpack the bind parameters
1159  	 $ss = '';
1160  	 if ($inputarr) {
1161  	 	 foreach ($inputarr as $kk => $vv) {
1162  	 	 	 if (is_string($vv) && strlen($vv) > 64) {
1163  	 	 	 	 $vv = substr($vv, 0, 64) . '...';
1164  	 	 	 }
1165  	 	 	 if (is_null($vv)) {
1166  	 	 	 	 $ss .= "($kk=>null) ";
1167  	 	 	 } else {
1168  	 	 	 	 if (is_array($vv)) {
1169  	 	 	 	 	 $vv = sprintf("Array Of Values: [%s]", implode(',', $vv));
1170  	 	 	 	 }
1171  	 	 	 	 $ss .= "($kk=>'$vv') ";
1172  	 	 	 }
1173  	 	 }
1174  	 	 $ss = "[ $ss ]";
1175  	 }
1176  
1177  	 $sqlTxt = is_array($sql) ? $sql[0] : $sql;
1178  
1179  	 // Remove newlines and tabs, compress repeating spaces
1180  	 $sqlTxt = preg_replace('/\s+/', ' ', $sqlTxt);
1181  
1182  	 // check if running from browser or command-line
1183  	 $inBrowser = isset($_SERVER['HTTP_USER_AGENT']);
1184  
1185  	 $myDatabaseType = $zthis->databaseType;
1186  	 if (!isset($zthis->dsnType)) {
1187  	 	 // Append the PDO driver name
1188  	 	 $myDatabaseType .= '-' . $zthis->dsnType;
1189  	 }
1190  
1191  	 if ($inBrowser) {
1192  	 	 if ($ss) {
1193  	 	 	 // Default formatting for passed parameter
1194  	 	 	 $ss = sprintf('<code class="adodb-debug">%s</code>', htmlspecialchars($ss));
1195  	 	 }
1196  	 	 if ($zthis->debug === -1) {
1197  	 	 	 $outString = "<br class='adodb-debug'>(%s):  %s &nbsp; %s<br class='adodb-debug'>";
1198  	 	 	 ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1199  	 	 } elseif ($zthis->debug !== -99) {
1200  	 	 	 $outString = "<hr class='adodb-debug'>(%s):  %s &nbsp; %s<hr class='adodb-debug'>";
1201  	 	 	 ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1202  	 	 }
1203  	 } else {
1204  	 	 // CLI output
1205  	 	 if ($zthis->debug !== -99) {
1206  	 	 	 $outString = sprintf("%s\n%s\n    %s %s \n%s\n", str_repeat('-', 78), $myDatabaseType, $sqlTxt, $ss, str_repeat('-', 78));
1207  	 	 	 ADOConnection::outp($outString, false);
1208  	 	 }
1209  	 }
1210  
1211  	 // Now execute the query
1212  	 $qID = $zthis->_query($sql, $inputarr);
1213  
1214  	 // Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
1215  	 // because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
1216  	 if ($zthis->databaseType == 'mssql') {
1217  	 	 // ErrorNo is a slow function call in mssql
1218  	 	 if ($emsg = $zthis->ErrorMsg()) {
1219  	 	 	 if ($err = $zthis->ErrorNo()) {
1220  	 	 	 	 if ($zthis->debug === -99) {
1221  	 	 	 	 	 ADOConnection::outp("<hr>\n($myDatabaseType): " . htmlspecialchars($sqlTxt) . " &nbsp; $ss\n<hr>\n", false);
1222  	 	 	 	 }
1223  
1224  	 	 	 	 ADOConnection::outp($err . ': ' . $emsg);
1225  	 	 	 }
1226  	 	 }
1227  	 } else {
1228  	 	 if (!$qID) {
1229  	 	 	 // Statement execution has failed
1230  	 	 	 if ($zthis->debug === -99) {
1231  	 	 	 	 if ($inBrowser) {
1232  	 	 	 	 	 $outString = "<hr class='adodb-debug'>(%s):  %s &nbsp; %s<hr class='adodb-debug'>";
1233  	 	 	 	 	 ADOConnection::outp(sprintf($outString, $myDatabaseType, htmlspecialchars($sqlTxt), $ss), false);
1234  	 	 	 	 } else {
1235  	 	 	 	 	 $outString = sprintf("%s\n%s\n    %s %s \n%s\n",str_repeat('-',78),$myDatabaseType,$sqlTxt,$ss,str_repeat('-',78));
1236  	 	 	 	 	 ADOConnection::outp($outString, false);
1237  	 	 	 	 }
1238  	 	 	 }
1239  
1240  	 	 	 // Send last error to output
1241  	 	 	 $errno = $zthis->ErrorNo();
1242  	 	 	 if ($errno) {
1243  	 	 	 	 ADOConnection::outp($errno . ': ' . $zthis->ErrorMsg());
1244  	 	 	 }
1245  	 	 }
1246  	 }
1247  
1248  	 if ($qID === false || $zthis->debug === 99) {
1249  	 	 _adodb_backtrace();
1250  	 }
1251  	 return $qID;
1252  }
1253  
1254  /**
1255   * Pretty print the debug_backtrace function
1256   *
1257   * @param string[]|bool $printOrArr       Whether to print the result directly or return the result
1258   * @param int           $maximumDepth     The maximum depth of the array to traverse
1259   * @param int           $elementsToIgnore The backtrace array indexes to ignore
1260   * @param null|bool     $ishtml           True if we are in a CGI environment, false for CLI,
1261   *                                        null to auto detect
1262   *
1263   * @return string Formatted backtrace
1264   */
1265  function _adodb_backtrace($printOrArr=true, $maximumDepth=9999, $elementsToIgnore=0, $ishtml=null)
1266  {
1267  	 if (!function_exists('debug_backtrace')) {
1268  	 	 return '';
1269  	 }
1270  
1271  	 if ($ishtml === null) {
1272  	 	 // Auto determine if we in a CGI enviroment
1273  	 	 $html = (isset($_SERVER['HTTP_USER_AGENT']));
1274  	 } else {
1275  	 	 $html = $ishtml;
1276  	 }
1277  
1278  	 $cgiString = "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>";
1279  	 $cliString = "%% line %4d, file: %s";
1280  	 $fmt = ($html) ? $cgiString : $cliString;
1281  
1282  	 $MAXSTRLEN = 128;
1283  
1284  	 $s = ($html) ? '<pre align=left>' : '';
1285  
1286  	 if (is_array($printOrArr)) {
1287  	 	 $traceArr = $printOrArr;
1288  	 } else {
1289  	 	 $traceArr = debug_backtrace();
1290  	 }
1291  
1292  	 // Remove first 2 elements that just show calls to adodb_backtrace
1293  	 array_shift($traceArr);
1294  	 array_shift($traceArr);
1295  
1296  	 // We want last element to have no indent
1297  	 $tabs = sizeof($traceArr) - 1;
1298  
1299  	 foreach ($traceArr as $arr) {
1300  	 	 if ($elementsToIgnore) {
1301  	 	 	 // Ignore array element at start of array
1302  	 	 	 $elementsToIgnore--;
1303  	 	 	 $tabs--;
1304  	 	 	 continue;
1305  	 	 }
1306  	 	 $maximumDepth--;
1307  	 	 if ($maximumDepth < 0) {
1308  	 	 	 break;
1309  	 	 }
1310  
1311  	 	 $args = array();
1312  
1313  	 	 if ($tabs) {
1314  	 	 	 $s .= str_repeat($html ? ' &nbsp; ' : "\t", $tabs);
1315  	 	 	 $tabs--;
1316  	 	 }
1317  	 	 if ($html) {
1318  	 	 	 $s .= '<font face="Courier New,Courier">';
1319  	 	 }
1320  
1321  	 	 if (isset($arr['class'])) {
1322  	 	 	 $s .= $arr['class'] . '.';
1323  	 	 }
1324  
1325  	 	 if (isset($arr['args'])) {
1326  	 	 	 foreach ($arr['args'] as $v) {
1327  	 	 	 	 if (is_null($v)) {
1328  	 	 	 	 	 $args[] = 'null';
1329  	 	 	 	 } elseif (is_array($v)) {
1330  	 	 	 	 	 $args[] = 'Array[' . sizeof($v) . ']';
1331  	 	 	 	 } elseif (is_object($v)) {
1332  	 	 	 	 	 $args[] = 'Object:' . get_class($v);
1333  	 	 	 	 } elseif (is_bool($v)) {
1334  	 	 	 	 	 $args[] = $v ? 'true' : 'false';
1335  	 	 	 	 } else {
1336  	 	 	 	 	 $v = (string)@$v;
1337  	 	 	 	 	 // Truncate
1338  	 	 	 	 	 $v = substr($v, 0, $MAXSTRLEN);
1339  	 	 	 	 	 // Remove newlines and tabs, compress repeating spaces
1340  	 	 	 	 	 $v = preg_replace('/\s+/', ' ', $v);
1341  	 	 	 	 	 // Convert htmlchars (not sure why we do this in CLI)
1342  	 	 	 	 	 $str = htmlspecialchars($v);
1343  
1344  	 	 	 	 	 if (strlen($v) > $MAXSTRLEN) {
1345  	 	 	 	 	 	 $str .= '...';
1346  	 	 	 	 	 }
1347  
1348  	 	 	 	 	 $args[] = $str;
1349  	 	 	 	 }
1350  	 	 	 }
1351  	 	 }
1352  	 	 $s .= $arr['function'] . '(' . implode(', ', $args) . ')';
1353  	 	 $s .= @sprintf($fmt, $arr['line'], $arr['file'], basename($arr['file']));
1354  	 	 $s .= "\n";
1355  	 }
1356  	 if ($html) {
1357  	 	 $s .= '</pre>';
1358  	 }
1359  	 if ($printOrArr) {
1360  	 	 print $s;
1361  	 }
1362  
1363  	 return $s;
1364  }