Location: PHPKode > scripts > Class-db > class-db-master/class-db.php
<?php
/**
* @package class-db
* @file
* @author Jonathan Gotti <jgotti at jgotti dot org>
* @copyleft (l) 2003-2008  Jonathan Gotti
* @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
* @since 2006-04-16 first splitted version
*  - get_field and list_fields have changed -> list_table_fields (list_fields indexed by name)
*  - smart '?' on conditions strings
* @svnInfos:
*            - $LastChangedDate$
*            - $LastChangedRevision$
*            - $LastChangedBy$
*            - $HeadURL$
* @changelog
*            - 2011-04-19 - small change in db::select_slice method to allow more complex queries (such as GROUP BY) to be counted properly
*            - 2011-02-03 - add named smart params to conds ie: array('WHERE field > paramName?','paramName'=>value);
*            - 2010-09-28 - change empty sliceAttrs to false instead of '' because of weird extract behaviour on empty strings
*            - 2010-07-07 - introduce freeResults method
*            - 2010-05-20 - litle enhancement of trace display in dbProfiler::printReport()
*            - 2010-03-24 - add optional parameter $fullSliceAttrs to select_slice() method
*            - 2010-02-01 - new db::show_table_primaryKey() method
*            - 2010-01-04 - new db::insert_extended() method
*            - 2009-12-14 - add some cleanup to profiler reports (htmlentities)
*                         - new sliceAttrs (first|prev|next|last)Disabled + htmlentities replace default prev|next|first|last values
*                         - replace verbose msgs by DIV instead of B tags
*            - 2009-04-03 - add one level trace context information to dbProfiler reports
*            - 2009-02-06 - add css class dbMsg to verbose messages
*            - 2008-10-10 - new static property $_default_verbosity to set default beverbose value of any further new db instance
*            - 2008-07-30 - some minor changes in dbProfiler report representation and bug correction in colors
*            - 2008-04-14 - add location of queries and some colors to dbProfiler
*            - 2008-04-10 - new class dbprofiler
*            - 2008-04-06 - autoconnect is now a static property
*                         - now db::getInstance call require on missing class-xxxxdb.php
*            - 2008-02-19 - now get_count() method can receive optional clause as second parameter
*                         - add method _call() and static property db::$aliases to support methods aliases to be user defined (you can set your own aliases for any methods)
*                         - rename most of select_* methods with 'better' name (shorter and better meaning I hope) But old names will work just fine with new methods aliases support
*            - 2007-11-20 - now beverbose property is int instead of bool and can takes 4 values
*                           0 -> no output, 1-> only errors, 2-> only queries, 3-> queries + errors
*                           (don't forget to change true by 1 in your scripts or you won't have expected output anymore)
*                         - remove call to set_error() in query_to_array only query has to do it
*            - 2007-10-11 - change default curpage for an input in set_slice_attrs()
*                         - no more autoload of console_app when not in sapi cli
*            - 2007-10-08 - new methods getInstance, setDefaultConnectionStr, and __destruct
*                           to ease the way of getting uniques db instances for each db
*            - 2007-03-28 - protect_field_names() isn't called automaticly anymore to allow
*                           the use of function, wild char or alias in fields list.
*                           will perhaps permit this again with a more effective regex in the future.
*                           so at this time it's up to you to use this method as needed on any select_* method (still applyed for insert and update)
*                         - move last_q2a_res assignment from fetch_res() method to query_to_array() (seems more ligical to me)
*            - 2007-03-26 - better fields name handling (auto-protect fieldsname even if string is given)
*            - 2007-01-12 - better values type handling in update and insert methods
*            - 2007-01-10 - correct a bug about page counting in set_slice_attrs() and add %page replacement to fromatStr
*                         - better params type handling in method process_conds()  (int/string/array/null)
*            - 2006-12-05 - new method select_field_to_array()
*            - 2006-05-15 - new methods set_slice_attrs() and select_array_slice() to easily paginate your results
*/

/**
* this class is an encapsulator for any db extended class and is used to help profiling at developpment time.
* @class dbProfiler
* @see db
* @code
* // instanciate and encapsulate a db class
* $db = new dbProfiler(db::getInstance());
* // do your normal job as if you where working with any other class-db extended object
* $db->list_tables();
* //then when finished work print the report
* $db->printReport();
* @endcode
*/
class dbProfiler{
	static public $precision = 4;

	static public $stats     = array();
	static public $traceMaxDepth  = 5;
	protected $db            = null;
	protected $statFuncs     = null;

	function __construct($dbInstance){
		$this->db  = $dbInstance;
		$this->statFuncs = array_merge(array_keys(db::$aliases),array_values(db::$aliases),array('delete','update','insert','query','query_to_array','list_tables','list_table_fields','show_table_keys','optimize','get_count'));
	}

	function __get($k){
		return $this->db->$k;
	}

	function __set($k,$v){
		return $this->db->$k = $v;
	}

	function __call($m,$a){
		if(! in_array($m,$this->statFuncs))
			return call_user_func_array(array($this->db,$m),$a);
		# - get stat on queries
		$trace = debug_backtrace();
		$traceContext = array();
		$maxDepth = self::$traceMaxDepth+2;
		for($i=2; $i < $maxDepth;$i++){
			if( ! isset($trace[$i]))
				break;
			if( isset($trace[$i])){
				$traceContextObject ='';
				if(! empty($trace[$i]['type']))
					$traceContextObject = (($trace[$i]['type']=='::'?$trace[$i]['class']:get_class($trace[$i]['object'])).$trace[$i]['type']);
				$traceContext[] = $traceContextObject.$trace[$i]['function'].'(...)'.(isset($trace[$i]['file'])?' in '.basename($trace[$i]['file']).' ('.$trace[$i]['line'].')':'');
			}
		}
		$traceContext = implode('<br />',$traceContext);
		$trace = basename($trace[1]['file']).' ('.$trace[1]['line'].')';
			$traceExtended = empty($traceContext)?'':($trace.'<br />'.$traceContext);
		$stat = array(
			$trace,
			"<b>$m</b>(".implode('<b>,</b> ',array_map(array($this,'_prepareArgStr'),$a)).')',
			$this->get_microtime(),
			0,
			$traceExtended
		);
		$res  = call_user_func_array(array($this->db,$m),$a);
		$stat[3] = $this->get_microtime();
		if( $res === false){
			$stat[0] = '<span style="color:red;">'.$stat[0].'</span>';
		}
		self::$stats[] = $stat;
		return $res;
	}

	function _prepareArgStr($a){
		static $charset;
		if(! $charset){
			$charset = ini_get('default_charset');
		}
		return stripslashes(htmlentities(var_export($a,1),ENT_NOQUOTES,$charset));
	}

	static function printReport($return=false){
		if(! count(self::$stats) )
			return;
		$total = 0;
		foreach(self::$stats as $k=>$stat){
			list($locInfo,$query,$start,$end,$trace) = $stat;
			$time = round($end-$start,self::$precision);
			$rows[] = '<tr onclick="displayDbProfilerReport.call(this)" style="cursor:pointer;"><td style="border-bottom:solid silver 1px;vertical-align:top;">'.$query.'</td><td style="border-bottom:solid silver 1px;vertical-align:top;"><i>'.$locInfo.'</i></td><td style="border-bottom:solid silver 1px;text-align:right;vertical-align:top;">'.$time.' sec</td></tr>';
			$rows[] = '<tr><td style="display:none;border-bottom:solid silver 1px;vertical-align:top;background:#ffe;color:#664;white-space:nowrap;" colspan="3">'.$trace.'</td></tr>';
			$total += $time;
		}
		$str = '
		<script type="text/javascript">
			if( typeof displayDbProfilerReport == "undefined"){
				var extendedDbProfilerDisplay = function(){
					var tb=this.parentNode
						, disp=tb.tBodies[0].style.display==="none"?"table-row-group":"none";

						try{
							tb.tBodies[0].style.display=disp;
							tb.tHead.style.display=disp;
						}catch(e){
							tb.tBodies[0].style.display="block";
							tb.tHead.style.display="block";
						}
						document.getElementById(\'dbProfilerButton\').innerHTML=(disp==\'none\'?\'&darr;\':\'&uarr;\');
				}
				var displayDbProfilerReport = function(rowId){
					var cell = this.nextSibling.cells[0]
						, bb = ""
						;
					if( cell.style.display !== "none" ){
						cell.style.display = "none";
						bb= "solid silver 1px";
					}else{
						try{
							cell.style.display = "table-cell";
						}catch(e){
							cell.style.display = "block";
						}
					}
					for( var i=0,l=this.cells.length;i<l;i++){
						this.cells[i].style.borderBottom=bb;
					}
				}
			}
		</script>
		<table cellspacing="0" cellpadding="2" style="border:solid silver 1px;text-align:left;position:relative;">
		<caption style="text-align:left;font-weight:bold;cursor:pointer;" title="show / hide report details" onclick="extendedDbProfilerDisplay.call(this);"> <span id="dbProfilerButton" style="float:right;">&dArr;</span>dbProfiler report</caption>
		<thead style="display:none;"><tr><th style="text-align:left;border-bottom:solid silver 1px;">Query</th><th style="border-bottom:solid silver 1px;">at</th><th style="text-align:right;border-bottom:solid silver 1px;">time</th></tr></thead>
		<tfoot><tr><td><b>Total: '.count(self::$stats).' queries</b></td><td>&nbsp;</td><td><b>Total time: '.$total.'sec</b></td></tr></tfoot>
		<tbody id="dbProfilerReport" style="display:none;">'.implode('',$rows)."</tbody>
		</table>";
		if( $return )
			return $str;
		else
			echo $str;
	}

	function get_microtime(){
		list($usec, $sec) = explode(" ",microtime());
		return ((float)$usec + (float)$sec);
	}

}


/**
* Base class for databases object.
* @class db
*/
class db{
	/** array of instances already created, one for each connection strings */
	static protected $instances = array();
	/** the default connection to use */
	static public $defaultConnStr = null;
	/**
	* define methods aliases
	* define your own like this db::$aliases[alias] = 'realMethodName';
	*/
	static public $aliases = array(
		/** aliases required to keep backward compatibility */
		'select_to_array'          => 'select_rows',
		'select_single_to_array'   => 'select_row',
		'select2associative_array' => 'select_associative',
		'select_single_value'      => 'select_value',
		'select_array_slice'       => 'select_slice',
		'select_field_to_array'    => 'select_col',
		/** camelCase style aliases (for my own pleasure) */
		'selectRows'               => 'select_rows',
		'selectRow'                => 'select_row',
		'selectAssociative'        => 'select_associative',
		'selectCol'                => 'select_col',
		'selectSlice'              => 'select_slice',
		'selectValue'              => 'select_value',
		'extendedInsert'           => 'insert_extended',
		'insertExtended'           => 'insert_extended',
		'getTablePK'               => 'show_table_primaryKey',
		'getTablePrimaryKey'       => 'show_table_primaryKey',
		'count'                    => 'get_count',
		/** convenience aliases */
		'extended_insert'          => 'insert_extended',
		'show_table_PK'            => 'show_table_primaryKey',
	);

	/**Db hostname*/
	public $host = null;
	/**mysql username*/
	public $user = null;
	/**mysql password*/
	public $pass = null;

	/**resource connection (same as $conn if not applicable)*/
	public $conn = null;
	/**resource db selected*/
	public $db = null ;

	/**selected database*/
	public $dbname = '';

	/** resource result handler*/
	public $last_qres = null;
	/**array of last query to array results*/
	public $last_q2a_res = array();
	/**array of error number and msgs*/
	public $error = array();
	/**the last error array*/
	public $last_error = array();

	/**
	* set the level of verbosity.
	* It MUST be an integar not a string or nothing will output!!!!
	* 0 -> no output, 1-> only errors, 2-> only queries, 3-> queries + errors
	*/
	public $beverbose = 0;
	/**
	* set default beverbose value of any further new db instance
	*/
	static public $_default_verbosity = 0;

	static public $autoconnect = TRUE;
	/**
	*chr to protect fields names in queries
	*@private
	*/
	public $_protect_fldname = '`';

	/**
	* return a single instance of the database corresponding to the given connection String.
	* This method must be copyed in the exended class as php is not able to get the name of the calling class (one more poor aspect of this language).
	* @param string $connectionStr the connection string is a semi colon separated list
	*                              of connection parameter in the order they appear in the constructor
	*                              preceeded by classname://
	*                              for exemple a mysqldb connection string will look like:
	*                              "mysqldb://dbname;dbhost:port;dbuser;dbpass"
	*                              and a sqlitedb one will look like:
	*                              "sqlitedb://dbfile;mode"
	* @param bool $setDefault      if true then this database connection will be the default one
	*                              returned when no arguments are given.
	*                              For conveniance the first call to this method
	*                              will set the corresponding instance the default one
	*                              if none has been set before
	* @return db instance
	*/
	static public function getInstance($connectionStr=null,$setDefault=false){
		if( is_null(self::$defaultConnStr) ){
			if(is_null($connectionStr))
				throw new Exception(__class__." Can't return an instance without any valid connection string.");
			self::$defaultConnStr = $connectionStr;
		}
		if(is_null($connectionStr))
			$connectionStr = self::$defaultConnStr;
		if(isset(self::$instances[$connectionStr]))
			return self::$instances[$connectionStr];
		list($class,$params) = explode('://',$connectionStr);
		$params = explode(';',$params);
		$paramNb = count($params);
		for($i=0;$i<$paramNb;++$i){
			$pEval[] = "\$params[$i]";
		}
		if(! class_exists($class,false) )
			require (dirname(__file__)."/adapters/class-$class.php");
		eval( '$instance = new '.$class.'('.implode(',',$pEval).');');
		if( self::$_default_verbosity )
			$instance->beverbose = (int) self::$_default_verbosity;
		return self::$instances[$connectionStr] = $instance;
	}

	static public function setDefaultConnectionStr($connectionStr){
		self::$defaultConnStr = $connectionStr;
	}
	/**
	* This way of creating an instance is not encourage anymore!
	* @deprecated use @see getInstance instead
	* constructor stay public even if we have getInstance for 2 reason
	* 1- backward compatibility with existing scripts
	* 2- permit getInstance to create any derived class without redefining it in each subclass
	*/
	public function __construct(){
		if(self::$autoconnect)
			$this->open();
	}

	public function __destruct(){
		foreach( self::$instances as $k=>$db){
			self::$instances[$k]->close();
			unset($db,self::$instances[$k]);
		}
		$this->close(); #- close current object connection if not obtained by getInstance
	}

	function __call($m,$a){
		if( isset(self::$aliases[$m]) ){
			return call_user_func_array(array($this,self::$aliases[$m]),$a);
		}
		throw new BadMethodCallException(get_class($this).'::'.$m.'() method doesn\'t exist.');
	}
	###*** REQUIRED METHODS FOR EXTENDED CLASS ***###

	/** open connection to database (should return false on error)*/
	public function open(){}

	/** close connection to previously opened database */
	public function close(){}
	/**
	* Select the database to work on (it's the same as the use db command or mysql_select_db function)
	* @param string $dbname
	* @return bool
	* /
	function select_db($dbname=null){}*/
	/**
	* take a resource result set and return an array of type 'ASSOC','NUM','BOTH'
	* @see sqlitedb or mysqldb implementation for exemple
	*/
	public function fetch_res($result_set,$result_type='ASSOC'){}

	public function last_insert_id(){}

	/**
	* base method you should replace this one in the extended class, to use the appropriate escape func regarding the database implementation
	* @param string $quotestyle (both/single/double) which type of quote to escape
	* @return str
	*/
	public function escape_string($string,$quotestyle='both'){
		$escapes = array("\x00", "\x0a", "\x0d", "\x1a", "\x09","\\");
		$replace = array('\0',   '\n',    '\r',   '\Z' , '\t',  "\\\\");
		switch(strtolower($quotestyle)){
			case 'double':
			case 'd':
			case '"':
				$escapes[] = '"';
				$replace[] = '\"';
				break;
			case 'single':
			case 's':
			case "'":
				$escapes[] = "'";
				$replace[] = "\'";
				break;
			case 'both':
			case 'b':
			case '"\'':
			case '\'"':
				$escapes[] = '"';
				$replace[] = '\"';
				$escapes[] = "'";
				$replace[] = "\'";
				break;
		}
		return str_replace($escapes,$replace,$string);
	}

	/**
	* perform a query on the database
	* @param string $Q_str
	* @return= result id | FALSE
	**/
	public function query($Q_str){}

	/**
	* perform a query on the database like query but return the affected_rows instead of result
	* give a most suitable answer on query such as INSERT OR DELETE
	* @param string $Q_str
	* @return int affected_rows or FALSE on error!
	* @can work without this method but less smart
	function query_affected_rows($Q_str){}
	*/

	/**
	* get the table list from $this->dbname
	* @return array
	*/
	public function list_tables(){}
	/**
	* return the list of field in $table
	* @param string $table name of the sql table to work on
	* @param bool $extended_info if true will return the result of a show field query in a query_to_array fashion
	*                           (indexed by fieldname instead of int if false)
	* @return array
	*/
	public function list_table_fields($table,$extended_info=FALSE){}

	/** Verifier si cette methode peut s'appliquer a SQLite */
	public function show_table_keys($table){}

	/**
	* return the field name of the given table primary key if exists
	* @param string $table
	* @return string or false
	*/
	public function show_table_primaryKey($table){
		$res = $this->list_table_fields($table,true);
		if( empty($res))
			return false;
		foreach($res as $v){
			if( strpos($v['Key'],'PRI')===0)
				return $v['Field'];
		}
		return false;
	}

	/**
	* optimize table statement query
	* @param string $table name of the table to optimize
	* @return bool
	*/
	public function optimize($table){}

	public function error_no(){}
	public function error_str($errno=null){}

	###*** COMMON METHODS ***###

	/**
	* return the result of a query to an array
	* @param string $Q_str SQL query
	* @param string $result_type 'ASSOC', 'NUM' et 'BOTH'
	* @return array | false if no result
	*/
	public function query_to_array($Q_str,$result_type='ASSOC'){
		$this->last_q2a_res = array();
		if(! $this->query($Q_str)){
			return FALSE;
		}
		return $this->last_q2a_res = $this->fetch_res($this->last_qres,$result_type);
	}
	/**
	* free some memory by dropping cached results of last datas
  * return $this for method chaining
  */
	public function freeResults(){
		$this->last_qres = null;
		$this->last_q2a_res = array();
		return $this;
	}

	/**
	* send a select query to $table with arr $fields requested (all by default) and with arr $conditions
	* @param string|array $Table
	* @param string|array $fields
	* @param string|array $conditions
	* @param string $res_type 'ASSOC', 'NUM' et 'BOTH'
	* @Return  array | false
	**/
	public function select_rows($tables,$fields = '*', $conds = null,$result_type = 'ASSOC'){
		//we make the table list for the Q_str
		if(! $tb_str = $this->array_to_str($tables))
			return FALSE;
		//we make the fields list for the Q_str
    if(! $fld_str = $this->array_to_str($fields) )
			$fld_str = '*';
		//now the WHERE str
		$conds_str = $this->process_conds($conds);

		$Q_str = "SELECT $fld_str FROM $tb_str $conds_str";
		# echo "SQL : $Q_str\n;";
		return $this->query_to_array($Q_str,$result_type);
	}
	/**
	* Same as select_rows but return only the first row.
	* equal to $res = select_rows followed by $res = $res[0];
	* @see select_rows for details
	* @return array of fields
	*/
	public function select_row($tables,$fields = '*', $conds = null,$result_type = 'ASSOC'){
		if(! $res = $this->select_rows($tables,$fields,$conds,$result_type))
			return FALSE;
		return $res[0];
	}
	/**
	* just a quick way to do a select_rows followed by a associative_array_from_q2a_res
	* see both thoose method for more information about parameters or return values
	*/
	public function select_associative($tables,$fields='*',$conds=null,$index_field='id',$value_fields=null,$keep_index=FALSE){
		if(! $this->select_rows($tables,$fields,$conds))
			return FALSE;
		return $this->associative_array_from_q2a_res($index_field,$value_fields,null,$keep_index);
	}
	/**
	* select a single value in database
	* @param string $table
	* @param string $field the field name where to pick-up value
	* @param mixed conds
	* @return mixed or FALSE
	*/
	public function select_value($table,$field,$conds=null){
		if($res = $this->select_row($table,$field,$conds,'NUM'))
			return $res[0];
		else
			return FALSE;
	}
	/**
	* select a single table field and return all values
	* @param string $table
	* @param string $field name of the single field to retrieve
	* @param mixed  $conds
	* @return array or FALSE
	*/
	public function select_col($table,$field,$conds=null){
		$conds_str = $this->process_conds($conds);
		$Q_str = "SELECT $field FROM $table $conds_str";
		if(! $res = $this->query_to_array($Q_str,'NUM') )
			return FALSE;
		foreach($res as $row){
			$_res[] = $row[0];
		}
		return $_res;
	}

	/**
	* @return array  array((array) results,(str) navigationstring, (int) totalrows)
	*/
	public function select_slice($table,$fields='*',$conds=null,$pageId=1,$pageNbRows=10,$fullSliceAttrs=null){
		$conds = $this->process_conds($conds);
		if(! ($tot = $this->select_value("(SELECT 1 FROM ".$this->array_to_str($table).' '.$this->process_conds($conds)." ) as temporaryTable",'count(*)') ) )
			return FALSE;
		$limitStart = (int) $pageNbRows * ($pageId-1);
		$res = $this->select_rows($table,$fields,$conds." Limit $limitStart,$pageNbRows");
		# now prepare navigation links
		$attrs = (null==$fullSliceAttrs?$this->set_slice_attrs():$fullSliceAttrs);
		extract($attrs);
		$nbpages = ceil($tot/max(1,$pageNbRows));

		# start/prev link
		if($nbpages > 1 && $pageId != 1){
			$first = str_replace('%lnk',str_replace('%page',1,$linkStr),$first);
			$prev = str_replace('%lnk',str_replace('%page',$pageId-1,$linkStr),$prev);
		}else{
			$first = str_replace('%lnk',str_replace('%page',1,$linkStr),$firstDisabled);
			$prev = str_replace('%lnk',str_replace('%page',$pageId-1,$linkStr),$prevDisabled);
		}
		# next/end link
		if( $pageId < $nbpages ){
			$last  = str_replace('%lnk',str_replace('%page',$nbpages,$linkStr),$last);
			$next = str_replace('%lnk',str_replace('%page',$pageId+1,$linkStr),$next);
		}else{
			$last  = str_replace('%lnk',str_replace('%page',$nbpages,$linkStr),$lastDisabled);
			$next = str_replace('%lnk',str_replace('%page',$pageId+1,$linkStr),$nextDisabled);
		}

		# pages links
		if(preg_match('!%(\d+)?links!',$formatStr,$m)){
			$nblinks = isset($m[1])?$m[1]:'';
			if(! $nblinks){ # all pages links
				$slideStart = 1;
				$slideEnd   = $nbpages;
			}else{ # range pages link
				$delta      = $nblinks%2?($nblinks-1)/2:$nblinks/2;
				$slideStart = max(1,$pageId - $delta - (($pageId+$delta)<=$nbpages?0: $pageId -($nbpages-$delta)) );
				$slideEnd   = min($nbpages,$pageId + $delta + ($pageId > $delta?0: $delta - $pageId + 1 ) );
			}
			for($i=$slideStart;$i<=$slideEnd;$i++){
				$pageLinks[] = str_replace(
					array('%lnk','%page'),
					array(str_replace('%page',$i,$linkStr),$i),
					($i==$pageId?$curpage:$pages)
				);
			}

			$links = implode($linkSep,$pageLinks);
		}

		$formatStr = str_replace(
			array('%first','%prev','%next','%last','%'.$nblinks.'links','%tot','%nbpages','%page'),
			array($first,$prev,$next,$last,$links,$tot,$nbpages,$pageId),
			$formatStr
		);
		return array($res,$formatStr,$tot);
	}

	/**
	* set attributes for slice rendering.
	* take an associative array of format strings to render slice links.
	* - firt:  first page link %lnk and %page will be replaced by the link to the page and the number of the page
	* - prev:  previous page link %lnk and %page will be replaced by the link to the page and the number of the page
	* - next:  next page link %lnk and %page will be replaced by the link to the page and the number of the page
	* - last:  last page link %lnk and %page will be replaced by the link to the page and the number of the page
	* - pages: pages link %lnk and %page will be replaced by the link to the page and the number of the page
	* - curpage: selected page link %lnk and %page will be replaced by the link to the page and the number of the page
	* - linkStr: is used for rendering the url of pages %page will be replaced by the corresponding page number
	* - linkSep: separator between pages links
	* - formatStr: is used to render the full pagination string
	*              %start, %prev, %next, %last will be replaced respectively by corresponding links
	*              %Nlinks will be replaced by the pages links. N is the number of link to display
	*              including the selected page ex: %5links will show 5 pages links
	* you can pass only the keys you want to replace ex: db::set_slice_attrs(array('linkStr'=>"myslice.php?page=%page"))
	* all keys can also contain a %tot and %nbpages which will be replaced respectively by
	* the total amount of result and the total number of pages
	*@param array $attrs
	*@return array
	*/
	public function set_slice_attrs($attrs=null){
		static $sliceAttrs;
		if(! isset($sliceAttrs) ){
			$sliceAttrs = array(
				'firstDisabled' => false,
				'prevDisabled'  => false,
				'nextDisabled'  => false,
				'lastDisabled'  => false,
				'first' => '<a href="%lnk" class="pagelnk"><big>&laquo;</big></a>',
				'prev'  => '<a href="%lnk" class="pagelnk"><big>&lsaquo;</big></a>',
				'next'  => '<a href="%lnk" class="pagelnk"><big>&rsaquo;</big></a>',
				'last'  => '<a href="%lnk" class="pagelnk"><big>&raquo;</big></a>',
				'pages' => '<a href="%lnk" class="pagelnk">%page</a>',
				#- 'curpage'  => "<b><a href=\"%lnk\" class=\"pagelnk\">%page</a></b>",
				'curpage'  => '<input type="text" value="%page" onfocus="this.value=\'\';" onkeydown="if(event.keyCode==13){ var p=parseInt(this.value)||1;window.location=\'%lnk\'.replace(/page=%page/,\'page=\'+(p>%nbpages?%nbpages:(p<1?1:p)));return false;}" size="3" title="aller &agrave; la page" style="text-align:center;" />',
				'linkStr'  => '?page=%page',
				'linkSep'  => ' ',
				'formatStr'=> ' %first %prev %5links %next %last'
			);
		}
		if( is_array($attrs) ){
			$sliceAttrs = array_merge($sliceAttrs,array_intersect_key($attrs,$sliceAttrs));
		}
		return $sliceAttrs;
	}

	/**
	* Send an insert query to $table
	* @param string $table
	* @param array $values (arr(FLD=>VALUE,)
	* @param bool $return_id the function will return the inserted_id if $return_id is true (the default value), else it'll return only true or false.
	* @return insert id or FALSE
	**/
	public function insert($table,$values,$return_id=TRUE){
		if(!is_array($values))
			return FALSE;
		$fld = $this->protect_field_names(array_keys($values));
		$val = array_map(array($this,'prepare_smart_param'),$values);

		$Q_str = "INSERT INTO $table ($fld) VALUES (".$this->array_to_str($val).")";
		if(! $this->query($Q_str) )
			return FALSE;
		$this->last_id = $this->last_insert_id();
		return $return_id?$this->last_id:TRUE;
	}
	/**
	* send an extended insert query to $table
	* @param string $table
	* @param array $values list of arr(FLD=>VALUE,)
	* @param string|array $fields list of field names,
	*                             if null will use first values keys as field names if not numeric
	*                             else will simply ignore the fields part of the query
	* @return int affected_rows
	*/
	public function insert_extended($table,array $values,$fields=null){
		if(null===$fields){
			$fields = array_keys(reset($values));
			#- cherck there's not only numeric values
			$_fields = array_filter($fields,'is_int');
			if( count($_fields) === count($fields ))
				$fields = null;
		}
		if( null !== $fields)
			$fields = ' ('.$this->protect_field_names($fields).')';
		foreach($values as $k=>$v)
			$values[$k] = $this->prepare_smart_param($v);
		$Q_str = "INSERT INTO $table$fields VALUES (".implode('),(',$values).")";

		if(method_exists($this,'query_affected_rows')){
			$res = $this->query_affected_rows($Q_str);
			return ($res===FALSE || $res === -1)?FALSE:$res;
		}else{
			if(! $this->query($Q_str) )
				return FALSE;
			return count($values);
		}
	}
	/**
	* Send a delete query to $table
	* @param string $table
	* @param mixed $conds
	* @return int affected_rows
	**/
	public function delete($table,$conds=null){
		$conds_str = $this->process_conds($conds);
		$Q_str = "DELETE FROM $table $conds_str";
		if(method_exists($this,'query_affected_rows')){
			$res = $this->query_affected_rows($Q_str);
			return ($res===FALSE || $res === -1)?FALSE:$res;
		}else{
			$count = (int) $this->get_count($table);
			if(! $this->query($Q_str) )
				return FALSE;
			$count2 = (int) $this->get_count($table);
			return (int) ($count - $count2);
		}
	}
	/**
	* Send an update query to $table
	* @param string $table
	* @param string|array $values ( 'fld=value, fld2=value2' arr(FLD=>VALUE,))
	* @return int affected_rows or bool (depends on the database implementation (have we a query_affected_rows or not?))
	**/
	public function update($table,$values,$conds = null){
		if(is_array($values)){
			$str = array();
			foreach( $values as $k=>$v)
				$str[] = $this->protect_field_names($k)." = ".$this->prepare_smart_param($v).' ';
		}elseif(! is_string($values)){
			return FALSE;
		}
		# now the WHERE str
		$conds_str = $this->process_conds($conds);
		$Q_str = "UPDATE $table SET ".(is_array($values)?$this->array_to_str($str):$values)." $conds_str";
		if(method_exists($this,'query_affected_rows')){
			$res = $this->query_affected_rows($Q_str);
			return ($res===FALSE || $res === -1)?FALSE:$res;
		}else{
			return (bool) $this->query($Q_str);
		}
	}

	/**
	* get the number of row in $table
	* @param string $table table name
	* @param mixed  $conds
	* @return int
	*/
	public function get_count($table,$conds=null){
		return $this->select_value($table,'count(*) as c',$conds);
	}

	/**
	*return an associative array indexed by $index_field with values $value_fields from
	*a mysqldb->select_rows result
	*@param string $index_field default value is id
	*@param mixed $value_fields (string field name or array of fields name default is null so keep all fields
	*@param array $res the mysqldb->select_rows result
	*@param bool $keep_index if set to true then the index field will be keep in the values associated (unused if $value_fields is string)
	*@param bool $sort_keys will automaticly sort the array by key if set to true @deprecated argument
	*@return array
	*/
	public function associative_array_from_q2a_res($index_field='id',$value_fields=null,$res = null,$keep_index=FALSE,$sort_keys=FALSE){
		if($res===null)
			$res = $this->last_q2a_res;
		if(! is_array($res)){
			$this->verbose("associative_array_from_q2a_res with invalid result",__FUNCTION__,1);
			return FALSE;
		}
		# then verify index exists
		if(!isset($res[0][$index_field])){
			$this->verbose("associative_array_from_q2a_res with invalid index field '$index_field'",__FUNCTION__,1);
			return FALSE;
		}
		# then we do the trick
		if(is_string($value_fields)){
			foreach($res as $row)
				$associatives_res[$row[$index_field]] = $row[$value_fields];
		}elseif(is_array($value_fields)||$value_fields===null){
			foreach($res as $row){
				$associatives_res[$row[$index_field]] = $row;
				if(!$keep_index)
					unset($associatives_res[$row[$index_field]][$index_field]);
			}
		}
		if(! count($associatives_res))
			return FALSE;
		if($sort_keys)
			ksort($associatives_res);
		return $this->last_q2a_res = $associatives_res;
	}
	/*########## INTERNAL METHOD ##########*/

	/**
	* used by other methods to parse the conditions param of a QUERY.
	* If $conds is string then nothing more is done.
	* If it's an array, the first value (index 0) will be consider as the full condition string and all '?' will be replaced by other values in the array (sort of sprintf).
	* You can add a number before a ? to replace it by a given index in the array like 2?
	* @param string|array $conds
	* @return string
	*/
	public function process_conds($conds=null){
		if(is_string($conds) ){
			if( preg_match('!^\s*\{!',$conds)){
				if(! class_exists('jsonQueryClause',false) ){
					require dirname(__file__).'/class-jsonQueryClause.php';
				}
				return 'WHERE '.jsonQueryClause::parse($conds,$this);
			}
			return $conds;
		}
		elseif(! is_array($conds) )
			return '';
		$conds_str = array_shift($conds);
		array_unshift($conds,'');
		$i=0;
		return preg_replace('!([a-zA-Z_0-9]*)\?!e',"\$this->prepare_smart_param(isset(\$conds[\$k='\\1'!==''?'\\1':++\$i])?\$conds[\$k]:null)",$conds_str);
		#- return preg_replace('!(\d*)\?!e',"\$this->prepare_smart_param('\\1'!==''?\$conds['\\1']:(isset(\$conds[++\$i])?\$conds[\$i]:null),'single')",$conds_str);
	}

	/**
	* used internally for smart params processing
	* @private
	*/
	public function prepare_smart_param($val){
		if(is_null($val)){
			return 'NULL';
		}elseif (is_int($val) || is_float($val)) {
			return $val;
		} elseif(is_string($val)) {
			return "'".$this->escape_string($val,'single')."'";
		} elseif(is_array($val)) {
			return implode(',', array_map(array(&$this,'prepare_smart_param'),$val));
		}else{
			return "''";
		}
	}
	/**
	* used internally to prepare fields for queries
	* @param string|array $fields list of fields. it's up to you to protect fieldsname if you put in fields as string
	*/
	public function protect_field_names($fields){
		if(is_array($fields)){
			foreach($fields as $k=>$f)
				$fields[$k] = $this->_protect_fldname.$f.$this->_protect_fldname;
			$fields = implode(',',$fields);
		}elseif($fields){
			if( $this->_protect_fldname && ! substr_count($fields,$this->_protect_fldname) ){ # if already protected we do nothing
				$fields = preg_replace('!\s*,\s*!',$this->_protect_fldname.','.$this->_protect_fldname,$fields);
				$fields = $this->_protect_fldname . trim($fields) . $this->_protect_fldname;
			}
		}
		return $fields?$fields:false;
	}

	protected function array_to_str($var,$sep=','){
		return (is_string($var)?$var:(is_array($var)?implode($sep,$var):''));
	}

	protected function set_error($callingfunc=null){
		static $i=0;
		if(! $this->db ){
			$this->error[$i]['nb']  = null;
			$this->error[$i]['str'] = '[ERROR] No Db Handler';
		}else{
			$this->error[$i]['nb']  = $this->error_no();
			$this->error[$i]['str'] = $this->error_str($this->error[$i]['nb']);
		}
		$this->last_error = $this->error[$i];
		$this->verbose($this->error[$i]['str'],$callingfunc,1);
		$i++;
	}

	/**
	* print a msg on STDOUT if $this->beverbose is set to true
	* @param string $msg         message output string
	* @param string $callingFunc name of the calling function
	* @param int $msgLvl         the level corresponding to the message
	*                            1-> error message
	*                            2-> query or similar informative message
	*                            msgLvl must be an int not a string as it will be test by type
	* @private
	*/
	protected function verbose($msg,$callingFunc=null,$msgLvl=1){
		if(! $this->beverbose)
			return;
		if( ($msgLvl===2 && $this->beverbose >=2) || ($msgLvl === 1 && $this->beverbose !== 2) ){
			$msg = get_class($this).($callingFunc?"::$callingFunc":'').' => '.$msg;
			$useConsoleApp = ( php_sapi_name()=='cli' && class_exists('console_app',false))?true:false;
			$isError = $msgLvl===1?true:false;
			if($isError){
				if($useConsoleApp)
					return console_app::msg_error($msg);
				echo "<div style=\"color:red;font-weight:bold;\" class=\"dbMsg\">[ERROR] $msg</div>\n";
			}else{
				if($useConsoleApp)
					return console_app::msg_info($msg);
				echo "<div style=\"color:blue;font-weight:bold;\" class=\"dbMsg\">$msg</div>\n";
			}
		}
	}

	###*** DEPRECATED METHODS ***###

	/**
	* return the list of field in $table
	* @deprecated still here for compatibility with old version
	* @use and @see db::list_table_fields() instead
	* @param string $table name of the sql table to work on
	* @param bool $extended_info will return the result of a show field query in a query_to_array fashion
	*/
	public function get_fields($table,$extended_info=FALSE){
		return $this->list_table_fields($table,$extended_info);
	}

	/**
	* get the fields list of table
	* @deprecated now the $indexed_by_name args won't exists anymore but will considered as TRUE in all case
	* @see db::list_table_fields as a replacement method
	* @param string $table
	* @param bool $indexed_by_name the return array will be indexed by the fields name if set to true (default is FALSE)
	* @return array
	*/
	public function list_fields($table,$indexed_by_name=FALSE){
		return $this->list_table_fields($table,TRUE);
	}

}
Return current item: Class-db