phpDocumentor pond
[ class tree: pond ] [ index: pond ] [ all elements ]

Source for file _db.class.php

Documentation is available at _db.class.php

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