b2evolution PHP Cross Reference Blogging Systems

Source: /inc/_core/model/db/_db.class.php - 1753 lines - 48917 bytes - Summary - Text - Print

Description: This file implements the DB class. Based on ezSQL - Class to make it very easy to deal with MySQL database connections. b2evo Additions: - nested transactions - symbolic table names - query log - get_list - dynamic extension loading - Debug features (EXPLAIN...) and more...

   1  <?php
   2  /**
   3   * This file implements the DB class.
   4   *
   5   * Based on ezSQL - Class to make it very easy to deal with MySQL database connections.
   6   * b2evo Additions:
   7   * - nested transactions
   8   * - symbolic table names
   9   * - query log
  10   * - get_list
  11   * - dynamic extension loading
  12   * - Debug features (EXPLAIN...)
  13   * and more...
  14   *
  15   * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.
  16   * See also {@link http://sourceforge.net/projects/evocms/}.
  17   *
  18   * @copyright (c)2003-2014 by Francois Planque - {@link http://fplanque.com/}.
  19   * Parts of this file are copyright (c)2004 by Justin Vincent - {@link http://php.justinvincent.com}
  20   * Parts of this file are copyright (c)2004-2005 by Daniel HAHLER - {@link http://thequod.de/contact}.
  21   *
  22   * {@internal License choice
  23   * - If you have received this file as part of a package, please find the license.txt file in
  24   *   the same folder or the closest folder above for complete license terms.
  25   * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
  26   *   then you must choose one of the following licenses before using the file:
  27   *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  28   *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  29   * }}
  30   *
  31   * {@internal Origin:
  32   * This file is based on the following package (excerpt from ezSQL's readme.txt):
  33   * =======================================================================
  34   * Author:  Justin Vincent (justin@visunet.ie)
  35   * Web:      http://php.justinvincent.com
  36   * Name:      ezSQL
  37   * Desc:      Class to make it very easy to deal with database connections.
  38   * License: FREE / Donation (LGPL - You may do what you like with ezSQL - no exceptions.)
  39   * =======================================================================
  40   * A $10 donation has been made to Justin VINCENT on behalf of the b2evolution team.
  41   * The package has been relicensed as GPL based on
  42   * "You may do what you like with ezSQL - no exceptions."
  43   * 2004-10-14 (email): Justin VINCENT grants Francois PLANQUE the right to relicense
  44   * this modified class under other licenses. "Just include a link to where you got it from."
  45   * }}
  46   *
  47   * {@internal Open Source relicensing agreement:
  48   * Daniel HAHLER grants Francois PLANQUE the right to license
  49   * Daniel HAHLER's contributions to this file and the b2evolution project
  50   * under any OSI approved OSS license (http://www.opensource.org/licenses/).
  51   * }}
  52   *
  53   * @package evocore
  54   *
  55   * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  56   * @author blueyed: Daniel HAHLER
  57   * @author fplanque: Francois PLANQUE
  58   * @author Justin VINCENT
  59   *
  60   * @version $Id: _db.class.php 6690 2014-05-14 13:02:06Z yura $
  61   * @todo transaction support
  62   */
  63  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  64  
  65  /**
  66   * ezSQL Constants
  67   */
  68  define( 'EZSQL_VERSION', '1.25' );
  69  define( 'OBJECT', 'OBJECT', true );
  70  define( 'ARRAY_A', 'ARRAY_A', true);
  71  define( 'ARRAY_N', 'ARRAY_N', true);
  72  
  73  
  74  /**
  75   * The Main Class
  76   *
  77   * @package evocore
  78   */
  79  class DB
  80  {
  81      /**
  82       * Show/Print errors?
  83       * @var boolean
  84       */
  85      var $show_errors = true;
  86      /**
  87       * Halt on errors?
  88       * @var boolean
  89       */
  90      var $halt_on_error = true;
  91      /**
  92       * Log errors using {@link error_log()}?
  93       * There's no reason to disable this, apart from when you are expecting
  94       * to get an error, like with {@link get_db_version()}.
  95       * @var boolean
  96       */
  97      var $log_errors = true;
  98      /**
  99       * Has an error occured?
 100       * @var boolean
 101       */
 102      var $error = false;
 103      /**
 104       * Number of done queries.
 105       * @var integer
 106       */
 107      var $num_queries = 0;
 108      /**
 109       * last query SQL string
 110       * @var string
 111       */
 112      var $last_query = '';
 113      /**
 114       * last DB error string
 115       * @var string
 116       */
 117      var $last_error = '';
 118  
 119      /**
 120       * Last insert ID
 121       * @var integer
 122       */
 123      var $insert_id = 0;
 124  
 125      /**
 126       * Last query's resource
 127       * @access protected
 128       * @var resource
 129       */
 130      var $result;
 131  
 132      /**
 133       * Number of rows in result set
 134       */
 135      var $num_rows = 0;
 136  
 137      /**
 138       * Number of rows affected by insert, delete, update or replace
 139       */
 140      var $rows_affected = 0;
 141  
 142      /**
 143       * Aliases that will be replaced in queries:
 144       */
 145      var $dbaliases = array();
 146      /**
 147       * Strings that will replace the aliases in queries:
 148       */
 149      var $dbreplaces = array();
 150  
 151      /**
 152       * CREATE TABLE options.
 153       *
 154       * This gets appended to every "CREATE TABLE" query.
 155       *
 156       * Edit those if you have control over you MySQL server and want a more professional
 157       * database than what is commonly offered by popular hosting providers.
 158       *
 159       * @todo dh> If the query itself uses already e.g. "CHARACTER SET latin1" it should not get overridden..
 160       * @var string
 161       */
 162      var $table_options = '';
 163  
 164      /**
 165       * Use transactions in DB?
 166       *
 167       * You need to use InnoDB in order to enable this.  See the {@link $db_config "table_options" key}.
 168       */
 169      var $use_transactions = false;
 170  
 171      /**
 172       * Which transaction isolation level should be used?
 173       *
 174       * Possible values in case of MySQL: REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED | SERIALIZABLE
 175       * Defailt value is REPEATABLE READ
 176       */
 177      var $transaction_isolation_level = 'REPEATABLE READ';
 178  
 179      /**
 180       * How many transactions are currently nested?
 181       */
 182      var $transaction_nesting_level = 0;
 183  
 184      /**
 185       * Rememeber if we have to rollback at the end of a nested transaction construct
 186       */
 187      var $rollback_nested_transaction = false;
 188  
 189      /**
 190       * MySQL Database handle
 191       * @var object
 192       */
 193      var $dbhandle;
 194  
 195      /**
 196       * Database username
 197       * @var string
 198       */
 199      var $dbuser;
 200  
 201      /**
 202       * Database username's password
 203       * @var string
 204       */
 205      var $dbpassword;
 206  
 207      /**
 208       * Database name
 209       * @var string
 210       * @see select()
 211       */
 212      var $dbname;
 213  
 214      /**
 215       * Database hostname
 216       * @var string
 217       */
 218      var $dbhost = 'localhost';
 219  
 220      /**
 221       * Current connection charset
 222       * @var string
 223       * @access protected
 224       * @see DB::set_connection_charset()
 225       */
 226      var $connection_charset;
 227  
 228  
 229      // DEBUG:
 230  
 231      /**
 232       * Do we want to log queries?
 233       * If null, it gets set according to {@link $debug}.
 234       * A subclass may set it by default (e.g. DbUnitTestCase_DB).
 235       * This requires {@link $debug} to be true.
 236       * @var boolean
 237       */
 238      var $log_queries;
 239  
 240      /**
 241       * Log of queries:
 242       * @var array
 243       */
 244      var $queries = array();
 245  
 246      /**
 247       * Do we want to explain joins?
 248       * This requires {@link DB::$log_queries} to be true.
 249       *
 250       * @todo fp> we'd probably want to group all the advanced debug vars under a single setting now. We might even auto enable it when $debug=2. (And we might actually want to include a $debug="cookie" mode for easy switching with bookmarks or a bookmarklet)
 251       *
 252       * @var boolean
 253       */
 254      var $debug_explain_joins = false;
 255  
 256      /**
 257       * Do we want to profile queries?
 258       * This requires {@link DB::$log_queries} to be true.
 259       *
 260       * This sets "profiling=1" for the session and queries "SHOW PROFILE" after
 261       * each query.
 262       *
 263       * @var boolean
 264       */
 265      var $debug_profile_queries = false;
 266  
 267      /**
 268       * Do we want to output a function backtrace for every query?
 269       * Number of stack entries to show (from last to first) (Default: 0); true means 'all'.
 270       *
 271       * This requires {@link DB::$log_queries} to be true.
 272       *
 273       * @var integer
 274       */
 275      var $debug_dump_function_trace_for_queries = 0;
 276  
 277      /**
 278       * Number of rows we want to dump in debug output (0 disables it)
 279       * This requires {@link DB::$log_queries} to be true.
 280       * @var integer
 281       */
 282      var $debug_dump_rows = 0;
 283  
 284      /**
 285       * Time in seconds that is considered a fast query (green).
 286       * @var float
 287       * @see dump_queries()
 288       */
 289      var $query_duration_fast = 0.05;
 290  
 291      /**
 292       * Time in seconds that is considered a slow query (red).
 293       * @var float
 294       * @see dump_queries()
 295       */
 296      var $query_duration_slow = 0.3;
 297  
 298  
 299      /**
 300       * DB Constructor
 301       *
 302       * Connects to the server and selects a database.
 303       *
 304       * @param array An array of parameters.
 305       *   Manadatory:
 306       *    - 'user': username to connect with
 307       *    - 'password': password to connect with
 308       *    OR
 309       *    - 'handle': a MySQL Database handle (from a previous {@link mysql_connect()})
 310       *   Optional:
 311       *    - 'name': the name of the default database, see {@link DB::select()}
 312       *    - 'host': host of the database; Default: 'localhost'
 313       *    - 'show_errors': Display SQL errors? (true/false); Default: don't change member default ({@link $show_errors})
 314       *    - 'halt_on_error': Halt on error? (true/false); Default: don't change member default ({@link $halt_on_error})
 315       *    - 'table_options': sets {@link $table_options}
 316       *    - 'use_transactions': sets {@link $use_transactions}
 317       *    - 'aliases': Aliases for tables (array( alias => table name )); Default: no aliases.
 318       *    - 'new_link': create a new link to the DB, even if there was a mysql_connect() with
 319       *       the same params before. (requires PHP 4.2)
 320       *    - 'client_flags': optional settings like compression or SSL encryption. See {@link http://www.php.net/manual/en/ref.mysql.php#mysql.client-flags}.
 321       *       (requires PHP 4.3)
 322       *    - 'log_queries': should queries get logged internally? (follows $debug by default, and requires it to be enabled otherwise)
 323       *      This is a requirement for the following options:
 324       *    - 'debug_dump_rows': Number of rows to dump
 325       *    - 'debug_explain_joins': Explain JOINS? (calls "EXPLAIN $query")
 326       *    - 'debug_profile_queries': Profile queries? (calls "SHOW PROFILE" after each query)
 327       *    - 'debug_dump_function_trace_for_queries': Collect call stack for queries? (showing where queries have been called)
 328       */
 329      function DB( $params )
 330      {
 331          global $debug;
 332  
 333          // Mandatory parameters:
 334          if( isset( $params['handle'] ) )
 335          { // DB-Link provided:
 336              $this->dbhandle = $params['handle'];
 337          }
 338          else
 339          {
 340              $this->dbuser = $params['user'];
 341              $this->dbpassword = $params['password'];
 342          }
 343  
 344          // Optional parameters (Allow overriding through $params):
 345          if( isset($params['name']) ) $this->dbname = $params['name'];
 346          if( isset($params['host']) ) $this->dbhost = $params['host'];
 347          if( isset($params['show_errors']) ) $this->show_errors = $params['show_errors'];
 348          if( isset($params['halt_on_error']) ) $this->halt_on_error = $params['halt_on_error'];
 349          if( isset($params['table_options']) ) $this->table_options = $params['table_options'];
 350          if( isset($params['use_transactions']) ) $this->use_transactions = $params['use_transactions'];
 351          if( isset($params['debug_dump_rows']) ) $this->debug_dump_rows = $params['debug_dump_rows']; // Nb of rows to dump
 352          if( isset($params['debug_explain_joins']) ) $this->debug_explain_joins = $params['debug_explain_joins'];
 353          if( isset($params['debug_profile_queries']) ) $this->debug_profile_queries = $params['debug_profile_queries'];
 354          if( isset($params['debug_dump_function_trace_for_queries']) ) $this->debug_dump_function_trace_for_queries = $params['debug_dump_function_trace_for_queries'];
 355          if( isset($params['log_queries']) )
 356          {
 357              $this->log_queries = $debug && $params['log_queries'];
 358          }
 359          elseif( isset($debug) && ! isset($this->log_queries) )
 360          { // $log_queries follows $debug and respects subclasses, which may define it:
 361              $this->log_queries = (bool)$debug;
 362          }
 363  
 364          if( ! extension_loaded('mysql') )
 365          { // The mysql extension is not loaded, try to dynamically load it:
 366              if( function_exists('dl') )
 367              {
 368                  $mysql_ext_file = is_windows() ? 'php_mysql.dll' : 'mysql.so';
 369                  $php_errormsg = null;
 370                  $old_track_errors = ini_set('track_errors', 1);
 371                  $old_html_errors = ini_set('html_errors', 0);
 372                  @dl( $mysql_ext_file );
 373                  $error_msg = $php_errormsg;
 374                  if( $old_track_errors !== false ) ini_set('track_errors', $old_track_errors);
 375                  if( $old_html_errors !== false ) ini_set('html_errors', $old_html_errors);
 376              }
 377              else
 378              {
 379                  $error_msg = 'The PHP mysql extension is not installed and we cannot load it dynamically.';
 380              }
 381              if( ! extension_loaded('mysql') )
 382              { // Still not loaded:
 383                  $this->print_error( 'The PHP MySQL module could not be loaded.', '
 384                      <p><strong>Error:</strong> '.$error_msg.'</p>
 385                      <p>You probably have to edit your php configuration (php.ini) and enable this module ('.$mysql_ext_file.').</p>
 386                      <p>Do not forget to restart your webserver (if necessary) after editing the PHP conf.</p>', false );
 387                  return;
 388              }
 389          }
 390  
 391          $new_link = isset( $params['new_link'] ) ? $params['new_link'] : false;
 392          $client_flags = isset( $params['client_flags'] ) ? $params['client_flags'] : 0;
 393  
 394          if( ! $this->dbhandle )
 395          { // Connect to the Database:
 396              // echo "mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags )";
 397              // mysql_error() is tied to an established connection
 398              // if the connection fails we need a different method to get the error message
 399              $php_errormsg = null;
 400              $old_track_errors = ini_set('track_errors', 1);
 401              $old_html_errors = ini_set('html_errors', 0);
 402              $this->dbhandle = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
 403              $mysql_error = $php_errormsg;
 404              if( $old_track_errors !== false ) ini_set('track_errors', $old_track_errors);
 405              if( $old_html_errors !== false ) ini_set('html_errors', $old_html_errors);
 406          }
 407  
 408          if( ! $this->dbhandle )
 409          {
 410              $this->print_error( 'Error establishing a database connection!',
 411                  ( $mysql_error ? '<p>('.$mysql_error.')</p>' : '' ).'
 412                  <ol>
 413                      <li>Are you sure you have typed the correct user/password?</li>
 414                      <li>Are you sure that you have typed the correct hostname?</li>
 415                      <li>Are you sure that the database server is running?</li>
 416                  </ol>', false );
 417          }
 418          elseif( isset($this->dbname) )
 419          {
 420              $this->select($this->dbname);
 421          }
 422  
 423          if( !empty($params['connection_charset']) )
 424          {    // Specify which charset we are using on the client:
 425              $this->set_connection_charset( $params['connection_charset'] );
 426          }
 427  
 428          /*
 429          echo '<br />Server: '.$this->get_var( 'SELECT @@character_set_server' );
 430          echo '<br />Database: '.$this->get_var( 'SELECT @@character_set_database' );
 431          echo '<br />Connection: '.$this->get_var( 'SELECT @@character_set_connection' );
 432          echo '<br />Client: '.$this->get_var( 'SELECT @@character_set_client' );
 433          echo '<br />Results: '.$this->get_var( 'SELECT @@character_set_results' );
 434          */
 435  
 436  
 437          if( isset($params['aliases']) )
 438          { // Prepare aliases for replacements:
 439              foreach( $params['aliases'] as $dbalias => $dbreplace )
 440              {
 441                  $this->dbaliases[] = '#\b'.$dbalias.'\b#'; // \b = word boundary
 442                  $this->dbreplaces[] = $dbreplace;
 443                  // echo '<br />'.'#\b'.$dbalias.'\b#';
 444              }
 445              // echo count($this->dbaliases);
 446          }
 447  
 448          if( $debug )
 449          { // Force MySQL strict mode
 450              // TRADITIONAL mode is only available to mysql > 5.0.2
 451              $mysql_version = $this->get_version( 'we do this in DEBUG mode only' );
 452              if( version_compare( $mysql_version, '5.0.2' ) > 0 )
 453              {
 454                  $this->query( 'SET sql_mode = "TRADITIONAL"', 'we do this in DEBUG mode only' );
 455              }
 456          }
 457  
 458          if( $this->debug_profile_queries )
 459          {
 460              // dh> this will fail, if it is not supported, but has to be enabled manually anyway.
 461              $this->query('SET profiling = 1'); // Requires 5.0.37.
 462          }
 463      }
 464  
 465  
 466      /**
 467       * Select a DB (if another one needs to be selected)
 468       */
 469  	function select($db)
 470      {
 471          if( !@mysql_select_db($db, $this->dbhandle) )
 472          {
 473              $this->print_error( 'Error selecting database ['.$db.']!', '
 474                  <ol>
 475                      <li>Are you sure the database exists?</li>
 476                      <li>Are you sure the DB user is allowed to use that database?</li>
 477                      <li>Are you sure there is a valid database connection?</li>
 478                  </ol>', false );
 479          }
 480          $this->dbname = $db;
 481      }
 482  
 483  
 484      /**
 485       * Escapes text for SQL LIKE special characters % and _
 486       */
 487  	function like_escape($str)
 488      {
 489          $str = str_replace( array('%', '_'), array('\\%', '\\_'), $str );
 490          return $this->escape($str);
 491      }
 492  
 493  
 494      /**
 495       * Format a string correctly for safe insert under all PHP conditions
 496       */
 497  	function escape($str)
 498      {
 499          return mysql_real_escape_string($str, $this->dbhandle);
 500      }
 501  
 502  
 503      /**
 504       * Quote a value, either in single quotes (and escaped) or if it's NULL as 'NULL'.
 505       *
 506       * @param string|array|null
 507       * @return string Quoted (and escaped) value or 'NULL'.
 508       */
 509  	function quote($str)
 510      {
 511          if( is_null( $str ) )
 512          {
 513              return 'NULL';
 514          }
 515          elseif( is_array( $str ) )
 516          {
 517              $r = '';
 518              foreach( $str as $elt )
 519              {
 520                  $r .= $this->quote($elt).',';
 521              }
 522              return substr( $r, 0, -1 );
 523          }
 524          else
 525          {
 526              return "'".$this->escape($str)."'";
 527          }
 528      }
 529  
 530  
 531      /**
 532       * @return string Return the given value or 'NULL', if it's === NULL.
 533       */
 534  	function null($val)
 535      {
 536          if( $val === NULL )
 537              return 'NULL';
 538          else
 539              return $val;
 540      }
 541  
 542  
 543      /**
 544       * Returns the correct WEEK() function to get the week number for the given date.
 545       *
 546       * @link http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
 547       *
 548       * @todo disable when MySQL < 4
 549       * @param string will be used as is
 550       * @param integer 0 for sunday, 1 for monday
 551       */
 552  	function week( $date, $startofweek )
 553      {
 554          if( $startofweek == 1 )
 555          { // Week starts on Monday, week 1 must have a monday in this year:
 556              return ' WEEK( '.$date.', 5 ) ';
 557          }
 558  
 559          // Week starts on Sunday, week 1 must have a sunday in this year:
 560          return ' WEEK( '.$date.', 0 ) ';
 561      }
 562  
 563  
 564      /**
 565       * Print SQL/DB error.
 566       *
 567       * TODO: fp> bloated: it probably doesn't make sense to display errors if we don't stop. Any use case?
 568       *       dh> Sure. Local testing (and test cases).
 569       *
 570       * @param string Short error (no HTML)
 571       * @param string Extended description/help for the error (for HTML)
 572       * @param string|false Query title; false if {@link DB::$last_query} should not get displayed
 573       */
 574  	function print_error( $title = '', $html_str = '', $query_title = '' )
 575      {
 576          // All errors go to the global error array $EZSQL_ERROR..
 577          global $EZSQL_ERROR, $is_cli;
 578  
 579          $this->error = true;
 580  
 581          // If no special error string then use mysql default..
 582          if( ! strlen($title) )
 583          {
 584              if( is_resource($this->dbhandle) )
 585              { // use mysql_error:
 586                  $this->last_error = mysql_error($this->dbhandle).'(Errno='.mysql_errno($this->dbhandle).')';
 587              }
 588              else
 589              {
 590                  $this->last_error = 'Unknown (and no $dbhandle available)';
 591              }
 592          }
 593          else
 594          {
 595              $this->last_error = $title;
 596          }
 597  
 598          // Log this error to the global array..
 599          $EZSQL_ERROR[] = array(
 600              'query' => $this->last_query,
 601              'error_str'  => $this->last_error
 602          );
 603  
 604  
 605          // Send error to PHP's system logger.
 606          if( $this->log_errors )
 607          {
 608              // TODO: dh> respect $log_app_errors? Create a wrapper, e.g. evo_error_log, which can be used later to write into e.g. a DB table?!
 609              if( isset($_SERVER['REQUEST_URI']) )
 610              {
 611                  $req_url = ( (isset($_SERVER['HTTPS']) && ( $_SERVER['HTTPS'] != 'off' ) ) ? 'https://' : 'http://' )
 612                      .$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
 613              }
 614              else
 615              {
 616                  $req_url = '-';
 617              }
 618              $error_text = 'SQL ERROR: '. $this->last_error
 619                      . ', QUERY: "'.trim($this->last_query).'"'
 620                      . ', BACKTRACE: '.trim(strip_tags(debug_get_backtrace()))
 621                      . ', URL: '.$req_url;
 622              error_log( preg_replace( '#\s+#', ' ', $error_text ) );
 623          }
 624  
 625  
 626          if( ! ( $this->halt_on_error || $this->show_errors ) )
 627          { // no reason to generate a nice message:
 628              return;
 629          }
 630  
 631          if( $this->halt_on_error && ! $this->show_errors )
 632          { // do not show errors, just die:
 633              die();
 634          }
 635  
 636          if( $is_cli )
 637          { // Clean error message for command line interface:
 638              $err_msg = "MySQL error!\n{$this->last_error}\n";
 639              if( ! empty($this->last_query) && $query_title !== false )
 640              {
 641                  $err_msg .= "Your query: $query_title\n";
 642                  $err_msg .= $this->format_query( $this->last_query, false );
 643              }
 644          }
 645          else
 646          {
 647              $err_msg = '<p class="error">MySQL error!</p>'."\n";
 648              $err_msg .= "<div><p><strong>{$this->last_error}</strong></p>\n";
 649              $err_msg .= $html_str;
 650              if( !empty($this->last_query) && $query_title !== false )
 651              {
 652                  $err_msg .= '<p class="error">Your query: '.$query_title.'</p>';
 653                  $err_msg .= '<pre>';
 654                  $err_msg .= $this->format_query( $this->last_query, ! $is_cli );
 655                  $err_msg .= '</pre>';
 656              }
 657              $err_msg .= "</div>\n";
 658          }
 659  
 660          if( $this->halt_on_error )
 661          {
 662              if( function_exists('debug_die') )
 663              {
 664                  debug_die( $err_msg );
 665              }
 666              else
 667              {
 668                  die( $err_msg );
 669              }
 670          }
 671          elseif( $this->show_errors )
 672          { // If there is an error then take note of it
 673              echo '<div class="error">';
 674              echo $err_msg;
 675              echo '</div>';
 676          }
 677      }
 678  
 679  
 680      /**
 681       * Kill cached query results
 682       */
 683  	function flush()
 684      {
 685          $this->result = NULL;
 686          $this->last_query = NULL;
 687          $this->num_rows = 0;
 688          if( isset($this->result) && is_resource($this->result) )
 689          { // Free last result resource
 690              mysql_free_result($this->result);
 691          }
 692      }
 693  
 694  
 695      /**
 696       * Get MYSQL version
 697       */
 698  	function get_version( $query_title = NULL )
 699      {
 700          if( isset( $this->version ) )
 701          {
 702              return $this->version;
 703          }
 704  
 705          $this->save_error_state();
 706          // Blatantly ignore any error generated by potentially unknown function...
 707          $this->show_errors = false;
 708          $this->halt_on_error = false;
 709  
 710          if( ($this->version_long = $this->get_var( 'SELECT VERSION()', 0, 0, $query_title ) ) === NULL )
 711          {    // Very old version ( < 4.0 )
 712              $this->version = '';
 713              $this->version_long = '';
 714          }
 715          else
 716          {
 717              $this->version = preg_replace( '~-.*~', '', $this->version_long );
 718          }
 719          $this->restore_error_state();
 720  
 721          return $this->version;
 722      }
 723  
 724  
 725      /**
 726       * Save the vars responsible for error handling.
 727       * This can be chained.
 728       * @see DB::restore_error_state()
 729       */
 730  	function save_error_state()
 731      {
 732          $this->saved_error_states[] = array(
 733              'show_errors'   => $this->show_errors,
 734              'halt_on_error' => $this->halt_on_error,
 735              'last_error'    => $this->last_error,
 736              'error'         => $this->error,
 737              'log_errors'    => $this->log_errors,
 738          );
 739      }
 740  
 741      /**
 742       * Call this after {@link save_halt_on_error()} to
 743       * restore the previous error state.
 744       * This can be chained.
 745       * @see DB::save_error_state()
 746       */
 747  	function restore_error_state()
 748      {
 749          if( empty($this->saved_error_states)
 750              || ! is_array($this->saved_error_states) )
 751          {
 752              return false;
 753          }
 754          $state = array_pop($this->saved_error_states);
 755  
 756          foreach( $state as $k => $v )
 757              $this->$k = $v;
 758      }
 759  
 760  
 761      /**
 762       * Basic Query
 763       *
 764       * @param string SQL query
 765       * @param string title for debugging
 766       * @return mixed # of rows affected or false if error
 767       */
 768  	function query( $query, $title = '' )
 769      {
 770          global $Timer;
 771  
 772          // initialise return
 773          $return_val = 0;
 774  
 775          // Flush cached values..
 776          $this->flush();
 777  
 778          // Replace aliases:
 779          if( ! empty($this->dbaliases) )
 780          {
 781              // TODO: this should only replace the table name part(s), not the whole query!
 782              // blueyed> I've changed it to replace in table name parts for UPDATE, INSERT and REPLACE, because
 783              //          it corrupted serialized data..
 784              //          IMHO, a cleaner solution would be to use {T_xxx} in the queries and replace it here. In object properties (e.g. DataObject::$dbtablename), only "T_xxx" would get used and surrounded by "{..}" in the queries it creates.
 785  
 786              if( preg_match( '~^\s*(UPDATE\s+)(.*?)(\sSET\s.*)$~is', $query, $match ) )
 787              { // replace only between UPDATE and SET, but check subqueries:
 788                  if( preg_match( '~^(.*SELECT.*FROM\s+)(.*?)(\s.*)$~is', $match[3], $subquery_match ) )
 789                  { // replace in subquery
 790                      $match[3] = $subquery_match[1].preg_replace( $this->dbaliases, $this->dbreplaces, $subquery_match[2] ).$subquery_match[3];
 791                  }
 792                  if( preg_match( '~^(.*SELECT.*JOIN\s+)(.*?)(\s.*)$~is', $match[3], $subquery_match ) )
 793                  { // replace in whole subquery, there can be any number of JOIN:
 794                      $match[3] = preg_replace( $this->dbaliases, $this->dbreplaces, $match[3] );
 795                  }
 796                  $query = $match[1].preg_replace( $this->dbaliases, $this->dbreplaces, $match[2] ).$match[3];
 797              }
 798              elseif( preg_match( '~^\s*(INSERT|REPLACE\s+)(.*?)(\s(VALUES|SET)\s.*)$~is', $query, $match ) )
 799              { // replace only between INSERT|REPLACE and VALUES|SET:
 800                  $query = $match[1].preg_replace( $this->dbaliases, $this->dbreplaces, $match[2] ).$match[3];
 801              }
 802              else
 803              { // replace in whole query:
 804                  $query = preg_replace( $this->dbaliases, $this->dbreplaces, $query );
 805  
 806                  if( ! empty($this->table_options) && preg_match( '#^ \s* create \s* table \s #ix', $query) )
 807                  { // Query is a table creation, we add table options:
 808                      $query = preg_replace( '~;\s*$~', '', $query ); // remove any ";" at the end
 809                      $query .= ' '.$this->table_options;
 810                  }
 811              }
 812          }
 813          elseif( ! empty($this->table_options) )
 814          { // No aliases, but table_options:
 815              if( preg_match( '#^ \s* create \s* table \s #ix', $query) )
 816              { // Query is a table creation, we add table options:
 817                  $query = preg_replace( '~;\s*$~', '', $query ); // remove any ";" at the end
 818                  $query .= $this->table_options;
 819              }
 820          }
 821          // echo '<p>'.$query.'</p>';
 822  
 823          // Keep track of the last query for debug..
 824          $this->last_query = $query;
 825  
 826          // Perform the query via std mysql_query function..
 827          $this->num_queries++;
 828  
 829          if( $this->log_queries )
 830          {    // We want to log queries:
 831              $this->queries[ $this->num_queries - 1 ] = array(
 832                  'title' => $title,
 833                  'sql' => $query,
 834                  'rows' => -1,
 835                  'time' => 'unknown',
 836                  'results' => 'unknown' );
 837          }
 838  
 839          if( is_object($Timer) )
 840          {
 841              // Resume global query timer
 842              $Timer->resume( 'SQL QUERIES' , false );
 843              // Start a timer for this particular query:
 844              $Timer->start( 'sql_query', false );
 845  
 846              // Run query:
 847              $this->result = @mysql_query( $query, $this->dbhandle );
 848  
 849              if( $this->log_queries )
 850              {    // We want to log queries:
 851                  // Get duration for last query:
 852                  $this->queries[ $this->num_queries - 1 ]['time'] = $Timer->get_duration( 'sql_query', 10 );
 853              }
 854  
 855              // Pause global query timer:
 856              $Timer->pause( 'SQL QUERIES' , false );
 857          }
 858          else
 859          {
 860              // Run query:
 861              $this->result = @mysql_query( $query, $this->dbhandle );
 862          }
 863  
 864          // If there is an error then take note of it..
 865          if( is_resource($this->dbhandle) && mysql_error($this->dbhandle) )
 866          {
 867              if( is_resource($this->result) )
 868              {
 869                  mysql_free_result($this->result);
 870              }
 871              $last_errno = mysql_errno($this->dbhandle);
 872              if( $this->use_transactions && ( $this->transaction_isolation_level == 'SERIALIZABLE' ) && ( 1213 == $last_errno ) )
 873              { // deadlock exception occured, transaction must be rolled back
 874                  $this->rollback_nested_transaction = true;
 875                  return false;
 876              }
 877              $this->print_error( '', '', $title );
 878              return false;
 879          }
 880  
 881          if( preg_match( '#^\s*(INSERT|DELETE|UPDATE|REPLACE)\s#i', $query, $match ) )
 882          { // Query was an insert, delete, update, replace:
 883  
 884              $this->rows_affected = mysql_affected_rows($this->dbhandle);
 885              if( $this->log_queries )
 886              {    // We want to log queries:
 887                  $this->queries[ $this->num_queries - 1 ]['rows'] = $this->rows_affected;
 888              }
 889  
 890              // Take note of the insert_id, for INSERT and REPLACE:
 891              $match[1] = strtoupper($match[1]);
 892              if( $match[1] == 'INSERT' || $match[1] == 'REPLACE' )
 893              {
 894                  $this->insert_id = mysql_insert_id($this->dbhandle);
 895              }
 896  
 897              // Return number of rows affected
 898              $return_val = $this->rows_affected;
 899          }
 900          else
 901          { // Query was a select, alter, etc...:
 902              if( is_resource($this->result) )
 903              { // It's not a resource for CREATE or DROP for example and can even trigger a fatal error (see http://forums.b2evolution.net//viewtopic.php?t=9529)
 904                  $this->num_rows = mysql_num_rows($this->result);
 905              }
 906  
 907              if( $this->log_queries )
 908              {    // We want to log queries:
 909                  $this->queries[ $this->num_queries - 1 ]['rows'] = $this->num_rows;
 910              }
 911  
 912              // Return number of rows selected
 913              $return_val = $this->num_rows;
 914          }
 915          if( $this->log_queries )
 916          {    // We want to log queries:
 917              if( $this->debug_dump_function_trace_for_queries )
 918              {
 919                  $this->queries[ $this->num_queries - 1 ]['function_trace'] = debug_get_backtrace( $this->debug_dump_function_trace_for_queries, array( array( 'class' => 'DB' ) ), 1 ); // including first stack entry from class DB
 920              }
 921  
 922              if( $this->debug_dump_rows && $this->num_rows )
 923              {
 924                  $this->queries[ $this->num_queries - 1 ]['results'] = $this->debug_get_rows_table( $this->debug_dump_rows );
 925              }
 926  
 927              // Profile queries
 928              if( $this->debug_profile_queries )
 929              {
 930                  // save values:
 931                  $saved_last_result = $this->result;
 932                  $saved_num_rows = $this->num_rows;
 933  
 934                  $this->num_rows = 0;
 935  
 936                  $this->result = @mysql_query( 'SHOW PROFILE', $this->dbhandle );
 937                  $this->num_rows = mysql_num_rows($this->result);
 938  
 939                  if( $this->num_rows )
 940                  {
 941                      $this->queries[$this->num_queries-1]['profile'] = $this->debug_get_rows_table( 100, true );
 942  
 943                      // Get time information from PROFILING table (which corresponds to "SHOW PROFILE")
 944                      $this->result = mysql_query( 'SELECT FORMAT(SUM(DURATION), 6) AS DURATION FROM INFORMATION_SCHEMA.PROFILING GROUP BY QUERY_ID ORDER BY QUERY_ID DESC LIMIT 1', $this->dbhandle );
 945                      $this->queries[$this->num_queries-1]['time_profile'] = array_shift(mysql_fetch_row($this->result));
 946                  }
 947  
 948                  // Free "PROFILE" result resource:
 949                  mysql_free_result($this->result);
 950  
 951  
 952                  // Restore:
 953                  $this->result = $saved_last_result;
 954                  $this->num_rows = $saved_num_rows;
 955              }
 956          }
 957          return $return_val;
 958      }
 959  
 960  
 961      /**
 962       * Get one variable from the DB - see docs for more detail
 963       *
 964       * Note: To be sure that you received NULL from the DB and not "no rows" check
 965       *       for {@link $num_rows}.
 966       *
 967       * @param string Optional query to execute
 968       * @param integer Column number (starting at and defaulting to 0)
 969       * @param integer Row (defaults to NULL for "next"/"do not seek")
 970       * @param string Optional title of query
 971       * @return mixed NULL if not found, the value otherwise (which may also be NULL).
 972       */
 973  	function get_var( $query = NULL, $x = 0, $y = NULL, $title = '' )
 974      {
 975          // If there is a query then perform it if not then use cached results..
 976          if( $query )
 977          {
 978              $this->query($query, $title);
 979          }
 980  
 981          if( $this->num_rows
 982              && ( $y === NULL || mysql_data_seek($this->result, $y) ) )
 983          {
 984              $row = mysql_fetch_row($this->result);
 985  
 986              if( isset($row[$x]) )
 987              {
 988                  return $row[$x];
 989              }
 990          }
 991  
 992          return NULL;
 993      }
 994  
 995  
 996      /**
 997       * Get one row from the DB.
 998       *
 999       * @param string Query (or NULL for previous query)
1000       * @param string Output type ("OBJECT", "ARRAY_A", "ARRAY_N")
1001       * @param int Row to fetch (or NULL for next - useful with $query=NULL)
1002       * @param string Optional title for $query (if any)
1003       * @return mixed
1004       */
1005  	function get_row( $query = NULL, $output = OBJECT, $y = NULL, $title = '' )
1006      {
1007          // If there is a query then perform it if not then use cached results..
1008          if( $query )
1009          {
1010              $this->query($query, $title);
1011          }
1012  
1013          if( ! $this->num_rows
1014              || ( isset($y) && ! mysql_data_seek($this->result, $y) ) )
1015          {
1016              if( $output == OBJECT )
1017                  return NULL;
1018              else
1019                  return array();
1020          }
1021  
1022          // If the output is an object then return object using the row offset..
1023          switch( $output )
1024          {
1025          case OBJECT:
1026              return mysql_fetch_object($this->result);
1027  
1028          case ARRAY_A:
1029              return mysql_fetch_array($this->result, MYSQL_ASSOC);
1030  
1031          case ARRAY_N:
1032              return mysql_fetch_array($this->result, MYSQL_NUM);
1033  
1034          default:
1035              $this->print_error('DB::get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N', '', false);
1036              break;
1037          }
1038      }
1039  
1040  
1041      /**
1042       * Function to get 1 column from the cached result set based on X index
1043       * see docs for usage and info
1044       *
1045       * @return array
1046       */
1047  	function get_col( $query = NULL, $x = 0, $title = '' )
1048      {
1049          // If there is a query then perform it if not then use cached results..
1050          if( $query )
1051          {
1052              $this->query( $query, $title );
1053          }
1054  
1055          // Extract the column values
1056          $new_array = array();
1057          for( $i = 0; $i < $this->num_rows; $i++ )
1058          {
1059              $new_array[$i] = $this->get_var( NULL, $x, $i );
1060          }
1061  
1062          return $new_array;
1063      }
1064  
1065  
1066      /**
1067       * Function to get the second column from the cached result indexed by the first column
1068       *
1069       * @return array [col_0] => col_1
1070       */
1071  	function get_assoc( $query = NULL, $title = '' )
1072      {
1073          // If there is a query then perform it if not then use cached results..
1074          if( $query )
1075          {
1076              $this->query( $query, $title );
1077          }
1078  
1079          // Extract the column values
1080          $new_array = array();
1081          for( $i = 0; $i < $this->num_rows; $i++ )
1082          {
1083              $key = $this->get_var( NULL, 0, $i );
1084  
1085              $new_array[$key] = $this->get_var( NULL, 1, $i );
1086          }
1087  
1088          return $new_array;
1089      }
1090  
1091  
1092      /**
1093       * Return the the query as a result set - see docs for more details
1094       *
1095       * @return mixed
1096       */
1097  	function get_results( $query = NULL, $output = OBJECT, $title = '' )
1098      {
1099          // If there is a query then perform it if not then use cached results..
1100          if( $query )
1101          {
1102              $this->query($query, $title);
1103          }
1104  
1105          $r = array();
1106  
1107          if( $this->num_rows )
1108          {
1109              mysql_data_seek($this->result, 0);
1110              switch( $output )
1111              {
1112              case OBJECT:
1113                  while( $row = mysql_fetch_object($this->result) )
1114                  {
1115                      $r[] = $row;
1116                  }
1117                  break;
1118  
1119              case ARRAY_A:
1120                  while( $row = mysql_fetch_array($this->result, MYSQL_ASSOC) )
1121                  {
1122                      $r[] = $row;
1123                  }
1124                  break;
1125  
1126              case ARRAY_N:
1127                  while( $row = mysql_fetch_array($this->result, MYSQL_NUM) )
1128                  {
1129                      $r[] = $row;
1130                  }
1131                  break;
1132              }
1133          }
1134          return $r;
1135      }
1136  
1137  
1138      /**
1139       * Get a table (or "<p>No Results.</p>") for the SELECT query results.
1140       *
1141       * @return string HTML table or "No Results" if the
1142       */
1143  	function debug_get_rows_table( $max_lines, $break_at_comma = false )
1144      {
1145          $r = '';
1146  
1147          if( ! $this->result || ! $this->num_rows )
1148          {
1149              return '<p>No Results.</p>';
1150          }
1151  
1152          // Get column info:
1153          $col_info = array();
1154          $n = mysql_num_fields($this->result);
1155          $i = 0;
1156          while( $i < $n )
1157          {
1158              $col_info[$i] = mysql_fetch_field($this->result, $i);
1159              $i++;
1160          }
1161  
1162          // =====================================================
1163          // Results top rows
1164          $r .= '<table cellspacing="0" summary="Results for query"><tr>';
1165          for( $i = 0, $count = count($col_info); $i < $count; $i++ )
1166          {
1167              $r .= '<th><span class="type">'.$col_info[$i]->type.' '.$col_info[$i]->max_length.'</span><br />'
1168                          .$col_info[$i]->name.'</th>';
1169          }
1170          $r .= '</tr>';
1171  
1172  
1173          // ======================================================
1174          // print main results
1175          $i=0;
1176          // Rewind to first row (should be there already).
1177          mysql_data_seek($this->result, 0);
1178          while( $one_row = $this->get_row(NULL, ARRAY_N) )
1179          {
1180              $i++;
1181              if( $i >= $max_lines )
1182              {
1183                  break;
1184              }
1185              $r .= '<tr>';
1186              foreach( $one_row as $item )
1187              {
1188                  if( $i % 2 )
1189                  {
1190                      $r .= '<td class="odd">';
1191                  }
1192                  else
1193                  {
1194                      $r .= '<td>';
1195                  }
1196  
1197                  if( $break_at_comma )
1198                  {
1199                      $item = str_replace( ',', '<br />', $item );
1200                      $item = str_replace( ';', '<br />', $item );
1201                      $r .= $item;
1202                  }
1203                  else
1204                  {
1205                      $r .= strmaxlen($item, 50, NULL, 'htmlspecialchars');
1206                  }
1207                  $r .= '</td>';
1208              }
1209  
1210              $r .= '</tr>';
1211          }
1212          // Rewind to first row again.
1213          mysql_data_seek($this->result, 0);
1214          if( $i >= $max_lines )
1215          {
1216              $r .= '<tr><td colspan="'.(count($col_info)+1).'">Max number of dumped rows has been reached.</td></tr>';
1217          }
1218  
1219          $r .= '</table>';
1220  
1221          return $r;
1222      }
1223  
1224  
1225      /**
1226       * Format a SQL query
1227       * @static
1228       * @param string SQL
1229       * @param boolean Format with/for HTML?
1230       */
1231  	function format_query( $sql, $html = true, $maxlen = NULL )
1232      {
1233          $sql = trim( str_replace("\t", '  ', $sql ) );
1234          if( $maxlen )
1235          {
1236              $sql = strmaxlen($sql, $maxlen, '...');
1237          }
1238  
1239          $new = '';
1240          $word = '';
1241          $in_comment = false;
1242          $in_literal = false;
1243          for( $i = 0, $n = strlen($sql); $i < $n; $i++ )
1244          {
1245              $c = $sql[$i];
1246              if( $in_comment )
1247              {
1248                  if( $in_comment === '/*' && substr($sql, $i, 2) == '*/' )
1249                      $in_comment = false;
1250                  elseif( $c == "\n" )
1251                      $in_comment = false;
1252              }
1253              elseif( $in_literal )
1254              {
1255                  if( $c == $in_literal )
1256                      $in_literal = false;
1257              }
1258              elseif( $c == '#' || ($c == '-' && substr($sql, $i, 3) == '-- ') )
1259              {
1260                  $in_comment = true;
1261              }
1262              elseif( ctype_space($c) )
1263              {
1264                  $uword = strtoupper($word);
1265                  if( in_array($uword, array('SELECT', 'FROM', 'WHERE', 'GROUP', 'ORDER', 'LIMIT', 'VALUES', 'AND', 'OR', 'LEFT', 'RIGHT', 'INNER')) )
1266                  {
1267                      $new = rtrim($new)."\n".str_pad($word, 6, ' ', STR_PAD_LEFT).' ';
1268                      # Remove any trailing whitespace after keywords
1269                      while( ctype_space($sql[$i+1]) ) {
1270                          ++$i;
1271                      }
1272                  }
1273                  else
1274                  {
1275                      $new .= $word.$c;
1276                  }
1277                  $word = '';
1278                  continue;
1279              }
1280              $word .= $c;
1281          }
1282          $sql = trim($new.$word);
1283  
1284          if( $html )
1285          { // poor man's indent
1286              $sql = preg_replace_callback("~^(\s+)~m", create_function('$m', 'return str_replace(" ", "&nbsp;", $m[1]);'), $sql);
1287              $sql = nl2br($sql);
1288          }
1289          return $sql;
1290      }
1291  
1292  
1293      /**
1294       * Displays all queries that have been executed
1295       *
1296       * @param boolean Use HTML.
1297       */
1298  	function dump_queries( $html = true )
1299      {
1300          if ( $html )
1301          {
1302              echo '<strong>DB queries:</strong> '.$this->num_queries."<br />\n";
1303          }
1304          else
1305          {
1306              echo 'DB queries: '.$this->num_queries."\n\n";
1307          }
1308  
1309          if( ! $this->log_queries )
1310          { // nothing more to do here..
1311              return;
1312          }
1313  
1314          global $Timer;
1315          if( is_object( $Timer ) )
1316          {
1317              $time_queries = $Timer->get_duration( 'SQL QUERIES' , 4 );
1318          }
1319          else
1320          {
1321              $time_queries = 0;
1322          }
1323  
1324          $count_queries = 0;
1325          $count_rows = 0;
1326          $time_queries_profiled = 0;
1327  
1328          // Javascript function to toggle DIVs (EXPLAIN, results, backtraces).
1329          if( $html )
1330          {
1331              global $rsc_url;
1332              echo '<script type="text/javascript" src="'.$rsc_url.'js/debug.js"></script>';
1333          }
1334  
1335          foreach( $this->queries as $i => $query )
1336          {
1337              $count_queries++;
1338  
1339              $get_md5_query = create_function( '', '
1340                  static $r; if( isset($r) ) return $r;
1341                  global $query;
1342                  $r = md5(serialize($query))."-".rand();
1343                  return $r;' );
1344  
1345              if ( $html )
1346              {
1347                  echo '<h4>Query #'.$count_queries.': '.$query['title']."</h4>\n";
1348  
1349                  $div_id = 'db_query_sql_'.$i.'_'.$get_md5_query();
1350                  if( strlen($query['sql']) > 512 )
1351                  {
1352                      $sql_short = $this->format_query( $query['sql'], true, 512 );
1353                      $sql = $this->format_query( $query['sql'], true );
1354  
1355                      echo '<code id="'.$div_id.'" style="display:none">'.$sql_short.'</code>';
1356                      echo '<code id="'.$div_id.'_full">'.$sql.'</code>';
1357                      echo '<script type="text/javascript">debug_onclick_toggle_div("'.$div_id.','.$div_id.'_full", "Show less", "Show more", false);</script>';
1358                  }
1359                  else
1360                  {
1361                      echo '<code>'.$this->format_query( $query['sql'] ).'</code>';
1362                  }
1363                  echo "\n";
1364              }
1365              else
1366              {
1367                  echo '= Query #'.$count_queries.': '.$query['title']." =\n";
1368                  echo $this->format_query( $query['sql'], false )."\n\n";
1369              }
1370  
1371              // Color-Format duration: long => red, fast => green, normal => black
1372              if( $query['time'] > $this->query_duration_slow )
1373              {
1374                  $style_time_text = 'color:red;font-weight:bold;';
1375                  $style_time_graph = 'background-color:red;';
1376                  $plain_time_text = ' [slow]';
1377              }
1378              elseif( $query['time'] < $this->query_duration_fast )
1379              {
1380                  $style_time_text = 'color:green;';
1381                  $style_time_graph = 'background-color:green;';
1382                  $plain_time_text = ' [fast]';
1383              }
1384              else
1385              {
1386                  $style_time_text = '';
1387                  $style_time_graph = 'background-color:black;';
1388                  $plain_time_text = '';
1389              }
1390  
1391              // Number of rows with time (percentage and graph, if total time available)
1392              if ( $html )
1393              {
1394                  echo '<div class="query_info">';
1395                  echo 'Rows: '.$query['rows'];
1396  
1397                  echo ' &ndash; Time: ';
1398              }
1399              else
1400              {
1401                  echo 'Rows: '.$query['rows'].' - Time: ';
1402              }
1403  
1404              if( $html && $style_time_text )
1405              {
1406                  echo '<span style="'.$style_time_text.'">';
1407              }
1408              echo number_format( $query['time'], 4 ).'s';
1409  
1410              if( $time_queries > 0 )
1411              { // We have a total time we can use to calculate percentage:
1412                  echo ' ('.number_format( 100/$time_queries * $query['time'], 2 ).'%)';
1413              }
1414  
1415              if( isset($query['time_profile']) )
1416              {
1417                  echo ' (real: '.number_format($query['time_profile'], 4).'s)';
1418                  $time_queries_profiled += $query['time_profile'];
1419              }
1420  
1421              if( $style_time_text || $plain_time_text )
1422              {
1423                  echo $html ? '</span>' : $plain_time_text;
1424              }
1425  
1426              if( $time_queries > 0 )
1427              { // We have a total time we can use to display a graph/bar:
1428                  $perc = round( 100/$time_queries * $query['time'] );
1429  
1430                  if ( $html )
1431                  {
1432                      echo '<div style="margin:0; padding:0; height:12px; width:'.$perc.'%;'.$style_time_graph.'"></div>';
1433                  }
1434                  else
1435                  {    // display an ASCII bar
1436                      printf( "\n".'[%-50s]', str_repeat( '=', $perc / 2 ) );
1437                  }
1438              }
1439              echo $html ? '</div>' : "\n\n";
1440  
1441              // EXPLAIN JOINS ??
1442              if( $this->debug_explain_joins && preg_match( '#^ [\s(]* SELECT \s #ix', $query['sql']) )
1443              { // Query was a select, let's try to explain joins...
1444  
1445                  $this->result = mysql_query( 'EXPLAIN '.$query['sql'], $this->dbhandle );
1446                  if( is_resource($this->result) )
1447                  {
1448                      $this->num_rows = mysql_num_rows($this->result);
1449  
1450                      if( $html )
1451                      {
1452                          $div_id = 'db_query_explain_'.$i.'_'.$get_md5_query();
1453                          echo '<div id="'.$div_id.'">';
1454                          echo $this->debug_get_rows_table( 100, true );
1455                          echo '</div>';
1456                          echo '<script type="text/javascript">debug_onclick_toggle_div("'.$div_id.'", "Show EXPLAIN", "Hide EXPLAIN");</script>';
1457                      }
1458                      else
1459                      { // TODO: dh> contains html.
1460                          echo $this->debug_get_rows_table( 100, true );
1461                      }
1462                  }
1463                  mysql_free_result($this->result);
1464              }
1465  
1466              // Profile:
1467              if( isset($query['profile']) )
1468              {
1469                  if( $html )
1470                  {
1471                      $div_id = 'db_query_profile_'.$i.'_'.$get_md5_query();
1472                      echo '<div id="'.$div_id.'">';
1473                      echo $query['profile'];
1474                      echo '</div>';
1475                      echo '<script type="text/javascript">debug_onclick_toggle_div("'.$div_id.'", "Show PROFILE", "Hide PROFILE");</script>';
1476                  }
1477                  else
1478                  { // TODO: dh> contains html.
1479                      echo $this->debug_get_rows_table( 100, true );
1480                  }
1481              }
1482  
1483              // Results:
1484              if( $query['results'] != 'unknown' )
1485              {
1486                  if( $html )
1487                  {
1488                      $div_id = 'db_query_results_'.$i.'_'.$get_md5_query();
1489                      echo '<div id="'.$div_id.'">';
1490                      echo $query['results'];
1491                      echo '</div>';
1492                      echo '<script type="text/javascript">debug_onclick_toggle_div("'.$div_id.'", "Show results", "Hide results");</script>';
1493                  }
1494                  else
1495                  { // TODO: dh> contains html.
1496                      echo $query['results'];
1497                  }
1498              }
1499  
1500              // Function trace:
1501              if( isset($query['function_trace']) )
1502              {
1503                  if( $html )
1504                  {
1505                      $div_id = 'db_query_backtrace_'.$i.'_'.$get_md5_query();
1506                      echo '<div id="'.$div_id.'">';
1507                      echo $query['function_trace'];
1508                      echo '</div>';
1509                      echo '<script type="text/javascript">debug_onclick_toggle_div("'.$div_id.'", "Show function trace", "Hide function trace");</script>';
1510                  }
1511                  else
1512                  { // TODO: dh> contains html.
1513                      echo $query['function_trace'];
1514                  }
1515              }
1516  
1517              echo $html ? '<hr />' : "=============================================\n";
1518  
1519              $count_rows += $query['rows'];
1520          }
1521  
1522          $time_queries_profiled = number_format($time_queries_profiled, 4);
1523          $time_diff_percentage = $time_queries_profiled != 0 ? round($time_queries / $time_queries_profiled * 100) : false;
1524          if ( $html )
1525          {
1526              echo "\nTotal rows: $count_rows<br />\n";
1527              echo "\nMeasured time: {$time_queries}s<br />\n";
1528              echo "\nProfiled time: {$time_queries_profiled}s<br />\n";
1529              if( $time_diff_percentage !== false )
1530              {
1531                  echo "\nTime difference: {$time_diff_percentage}%<br />\n";
1532              }
1533          }
1534          else
1535          {
1536              echo 'Total rows: '.$count_rows."\n";
1537              echo "Measured time: {$time_queries}s\n";
1538              echo "Profiled time: {$time_queries_profiled}s\n";
1539              if( $time_diff_percentage !== false )
1540              {
1541                  echo "Time difference: {$time_diff_percentage}%\n";
1542              }
1543          }
1544      }
1545  
1546  
1547      /**
1548       * BEGIN A TRANSCATION
1549       *
1550       * Note:  By default, MySQL runs with autocommit mode enabled.
1551       * This means that as soon as you execute a statement that updates (modifies)
1552       * a table, MySQL stores the update on disk.
1553       * Once you execute a BEGIN, the updates are "pending" until you execute a
1554       * {@link DB::commit() COMMIT} or a {@link DB:rollback() ROLLBACK}
1555       *
1556       * Note 2: standard syntax would be START TRANSACTION but it's not supported by older
1557       * MySQL versions whereas BEGIN is...
1558       *
1559       * Note 3: The default isolation level is REPEATABLE READ.
1560       */
1561  	function begin( $transaction_isolation_level = 'REPEATABLE READ' )
1562      {
1563          if( !$this->use_transactions )
1564          { // don't use transactions at all
1565              return;
1566          }
1567  
1568          $transaction_isolation_level = strtoupper( $transaction_isolation_level );
1569          if( !in_array( $transaction_isolation_level, array( 'REPEATABLE READ', 'READ COMMITTED', 'READ UNCOMMITTED', 'SERIALIZABLE' ) ) )
1570          {
1571              debug_die( 'Invalid transaction isolation level!' );
1572          }
1573  
1574          if( ( $this->transaction_isolation_level != $transaction_isolation_level ) && ( ( !$this->transaction_nesting_level ) || ( $transaction_isolation_level == 'SERIALIZABLE' ) ) )
1575          { // The isolation level was changed and it is the beggining of a new transaction or this is a nested transaction but it needs 'SERIALIZABLE' isolation level
1576              // Note: We change the transaction isolation level for nested transactions only if the requested isolation level is 'SERIALIZABLE'
1577              // Set session transaction isolation level to the new value
1578              $this->transaction_isolation_level = $transaction_isolation_level;
1579              $this->query( 'SET SESSION TRANSACTION ISOLATION LEVEL '.$transaction_isolation_level, 'Set transaction isolation level' );
1580          }
1581  
1582          if( !$this->transaction_nesting_level )
1583          { // Start a new transaction
1584              $this->query( 'BEGIN', 'BEGIN transaction' );
1585          }
1586  
1587          $this->transaction_nesting_level++;
1588      }
1589  
1590  
1591      /**
1592       * Commit current transaction
1593       */
1594  	function commit()
1595      {
1596          if( !$this->use_transactions )
1597          { // don't use transactions at all
1598              return;
1599          }
1600  
1601          if( $this->transaction_nesting_level == 1 )
1602          { // Only COMMIT if there are no remaining nested transactions:
1603              if( $this->rollback_nested_transaction )
1604              {
1605                  $this->query( 'ROLLBACK', 'ROLLBACK transaction because there was a failure somewhere in the nesting of transactions' );
1606              }
1607              else
1608              {
1609                  $this->query( 'COMMIT', 'COMMIT transaction' );
1610              }
1611              $this->rollback_nested_transaction = false;
1612          }
1613  
1614          if( $this->transaction_nesting_level )
1615          { // decrease transaction nesting level
1616              $this->transaction_nesting_level--;
1617          }
1618      }
1619  
1620  
1621      /**
1622       * Rollback current transaction
1623       */
1624  	function rollback()
1625      {
1626          if( !$this->use_transactions )
1627          { // don't use transactions at all
1628              return;
1629          }
1630  
1631          if( $this->transaction_nesting_level == 1 )
1632          { // Only ROLLBACK if there are no remaining nested transactions:
1633              $this->query( 'ROLLBACK', 'ROLLBACK transaction' );
1634              $this->rollback_nested_transaction = false;
1635          }
1636          else
1637          { // Remember we'll have to roll back at the end!
1638              $this->rollback_nested_transaction = true;
1639          }
1640          if( $this->transaction_nesting_level )
1641          {
1642              $this->transaction_nesting_level--;
1643          }
1644      }
1645  
1646  
1647      /**
1648       * Convert a PHP charset to its MySQL equivalent.
1649       *
1650       * @param string PHP charset
1651       * @return string MYSQL charset or unchanged
1652       */
1653  	static function php_to_mysql_charmap( $php_charset )
1654      {
1655          $php_charset = strtolower( $php_charset );
1656  
1657          /**
1658           * This is taken from phpMyAdmin (libraries/select_lang.lib.php).
1659           */
1660          static $mysql_charset_map = array(
1661                  'big5'         => 'big5',
1662                  'cp-866'       => 'cp866',
1663                  'euc-jp'       => 'ujis',
1664                  'euc-kr'       => 'euckr',
1665                  'gb2312'       => 'gb2312',
1666                  'gbk'          => 'gbk',
1667                  'iso-8859-1'   => 'latin1',
1668                  'iso-8859-2'   => 'latin2',
1669                  'iso-8859-7'   => 'greek',
1670                  'iso-8859-8'   => 'hebrew',
1671                  'iso-8859-8-i' => 'hebrew',
1672                  'iso-8859-9'   => 'latin5',
1673                  'iso-8859-13'  => 'latin7',
1674                  'iso-8859-15'  => 'latin1',
1675                  'koi8-r'       => 'koi8r',
1676                  'shift_jis'    => 'sjis',
1677                  'tis-620'      => 'tis620',
1678                  'utf-8'        => 'utf8',
1679                  'windows-1250' => 'cp1250',
1680                  'windows-1251' => 'cp1251',
1681                  'windows-1252' => 'latin1',
1682                  'windows-1256' => 'cp1256',
1683                  'windows-1257' => 'cp1257',
1684              );
1685  
1686          if( isset( $mysql_charset_map[ $php_charset ] ) )
1687          {
1688              return $mysql_charset_map[ $php_charset ];
1689          }
1690  
1691          // for lack of a better answer:
1692          return $php_charset;
1693      }
1694  
1695      /**
1696       * Set the charset of the connection.
1697       *
1698       * WARNING: this will fail on MySQL 3.23
1699       *
1700       * @staticvar array "regular charset => mysql charset map"
1701       * @param string Charset
1702       * @param boolean Use the "regular charset => mysql charset map"?
1703       * @return boolean true on success, false on failure
1704       */
1705  	function set_connection_charset( $charset, $use_map = true )
1706      {
1707          global $Debuglog;
1708  
1709          // pre_dump( 'set_connection_charset', $charset );
1710  
1711          $charset = strtolower($charset);
1712  
1713          if( $use_map )
1714          {    // We want to use the map
1715              $charset = self::php_to_mysql_charmap( $charset );
1716          }
1717  
1718          $r = true;
1719          if( $charset != $this->connection_charset )
1720          {
1721              // SET NAMES is not supported by MySQL 3.23 and for a non-supported charset even not in MySQL 5 probably..
1722  
1723              $save_show_errors = $this->show_errors;
1724              $save_halt_on_error = $this->halt_on_error;
1725              $this->show_errors = false;
1726              $this->halt_on_error = false;
1727              $last_error = $this->last_error;
1728              $error = $this->error;
1729              if( $this->query( 'SET NAMES '.$charset ) === false )
1730              {
1731                  $Debuglog->add( 'Could not "SET NAMES '.$charset.'"! (MySQL error: '.strip_tags($this->last_error).')', 'locale' );
1732  
1733                  $r = false;
1734              }
1735              else
1736              {
1737                  $Debuglog->add( 'Set DB connection charset: '.$charset, 'locale' );
1738  
1739                  $this->connection_charset = $charset;
1740              }
1741              $this->show_errors = $save_show_errors;
1742              $this->halt_on_error = $save_halt_on_error;
1743              // Blatantly ignore any error generated by SET NAMES...
1744              $this->last_error = $last_error;
1745              $this->error = $error;
1746          }
1747  
1748          return $r;
1749      }
1750  
1751  }
1752  
1753  ?>

title

Description

title

Description

title

Description

title

title

Body