0) return; $TABLE_LANG_FIELDS=function_exists('persistent_cache_get')?persistent_cache_get('TABLE_LANG_FIELDS'):NULL; if ($TABLE_LANG_FIELDS===NULL) { $TABLE_LANG_FIELDS=array(); $_table_lang_fields=$GLOBALS['SITE_DB']->query('SELECT m_name,m_table FROM '.get_table_prefix().'db_meta WHERE '.db_string_equal_to('m_type','SHORT_TRANS').' OR '.db_string_equal_to('m_type','LONG_TRANS').' OR '.db_string_equal_to('m_type','*SHORT_TRANS').' OR '.db_string_equal_to('m_type','*LONG_TRANS').' OR '.db_string_equal_to('m_type','?SHORT_TRANS').' OR '.db_string_equal_to('m_type','?LONG_TRANS'),NULL,NULL,true); if ($_table_lang_fields!==NULL) { foreach ($_table_lang_fields as $lang_field) { if (!isset($TABLE_LANG_FIELDS[$lang_field['m_table']])) $TABLE_LANG_FIELDS[$lang_field['m_table']]=array(); $TABLE_LANG_FIELDS[$lang_field['m_table']][]=$lang_field['m_name']; } } if (function_exists('persistent_cache_set')) persistent_cache_set('TABLE_LANG_FIELDS',$TABLE_LANG_FIELDS); } } /** * Find whether the database may run GROUP BY unfettered with restrictions on the SELECT'd fields having to be represented in it or aggregate functions * * @return boolean Whether it can */ function can_arbitrary_groupby() { if (!method_exists($GLOBALS['DB_STATIC_OBJECT'],'can_arbitrary_groupby')) return false; return $GLOBALS['DB_STATIC_OBJECT']->can_arbitrary_groupby(); } /** * Assemble part of a WHERE clause for doing full-text search * * @param string Our match string (assumes "?" has been stripped already) * @param boolean Whether to do a boolean full text search * @return string Part of a WHERE clause for doing full-text search */ function db_full_text_assemble($content,$boolean) { return $GLOBALS['DB_STATIC_OBJECT']->db_full_text_assemble($content,$boolean); } /** * Get the ID of the first row in an auto-increment table (used whenever we need to reference the first). * * @return integer First ID used */ function db_get_first_id() { return $GLOBALS['DB_STATIC_OBJECT']->db_get_first_id(); } /** * Encode an SQL statement fragment for a conditional to see if two strings are equal. * * @param ID_TEXT The attribute * @param string The comparison * @return string The SQL */ function db_string_equal_to($attribute,$compare) { return $GLOBALS['DB_STATIC_OBJECT']->db_string_equal_to($attribute,$compare); } /** * Encode an SQL statement fragment for a conditional to see if two strings are not equal. * * @param ID_TEXT The attribute * @param string The comparison * @return string The SQL */ function db_string_not_equal_to($attribute,$compare) { return $GLOBALS['DB_STATIC_OBJECT']->db_string_not_equal_to($attribute,$compare); } /** * Encode a LIKE string comparision fragement for the database system. The pattern is a mixture of characters and ? and % wilcard symbols. * * @param string The pattern * @return string The encoded pattern */ function db_encode_like($pattern) { return $GLOBALS['DB_STATIC_OBJECT']->db_encode_like($pattern); } /** * Find whether full-text-search is present * * @param array A DB connection * @return boolean Whether it is */ function db_has_full_text($db) { if (count($db)>4) // Okay, we can't be lazy anymore { $db=call_user_func_array(array($GLOBALS['DB_STATIC_OBJECT'],'db_get_connection'),$db); _general_db_init(); } return $GLOBALS['DB_STATIC_OBJECT']->db_has_full_text($db); } /** * Find whether subquery support is present * * @param array A DB connection * @return boolean Whether it is */ function db_has_subqueries($db) { if (!method_exists($GLOBALS['DB_STATIC_OBJECT'],'db_has_subqueries')) return true; if (count($db)>4) // Okay, we can't be lazy anymore { $db=call_user_func_array(array($GLOBALS['DB_STATIC_OBJECT'],'db_get_connection'),$db); _general_db_init(); } return $GLOBALS['DB_STATIC_OBJECT']->db_has_subqueries($db); } /** * Find whether expression ordering support is present * * @param array A DB connection * @return boolean Whether it is */ function db_has_expression_ordering($db) { if (count($db)>4) // Okay, we can't be lazy anymore { $db=call_user_func_array(array($GLOBALS['DB_STATIC_OBJECT'],'db_get_connection'),$db); _general_db_init(); } if (!method_exists($GLOBALS['DB_STATIC_OBJECT'],'db_has_expression_ordering')) return false; return $GLOBALS['DB_STATIC_OBJECT']->db_has_expression_ordering($db); } /** * Escape a string so it may be inserted into a query. If SQL statements are being built up and passed using db_query then it is essential that this is used for security reasons. Otherwise, the abstraction layer deals with the situation. * * @param string The string * @return string The escaped string */ function db_escape_string($string) { return $GLOBALS['DB_STATIC_OBJECT']->db_escape_string($string); } /** * Get the type of database installed, such as MySQL, or Oracle. * * @return string The database type */ function get_db_type() { global $SITE_INFO; if (!isset($SITE_INFO['db_type'])) return is_dir(get_custom_file_base().'/uploads/website_specific/'.get_db_site())?'xml':'mysql'; return $SITE_INFO['db_type']; } /** * Find ocPortal was installed to use persistent database connections or not. * * @return boolean Whether to use persistent database connections */ function get_use_persistent() { global $SITE_INFO; return array_key_exists('use_persistent',$SITE_INFO)?($SITE_INFO['use_persistent']=='1'):false; } /** * Get the table prefixes used for all ocPortal tables, commonly used when you are installing ocPortal in the same database as your forums. The default table prefix is 'ocp4_'. Note that anything that might write to an arbitrary db, must ask that db for it's table prefix (if it needs it of course... the db abstracts away most needs for it) * * @return string The table prefix */ function get_table_prefix() { global $SITE_INFO; if (!isset($SITE_INFO['table_prefix'])) return 'ocp'.strval(ocp_version()).'_'; return $SITE_INFO['table_prefix']; } /** * Get the host of the database ('localhost', for example). * * @return string The database host */ function get_db_site_host() { global $SITE_INFO; return array_key_exists('db_site_host',$SITE_INFO)?$SITE_INFO['db_site_host']:'localhost'; } /** * Get the name of the database. * * @return string The database site */ function get_db_site() { global $SITE_INFO; if ((!array_key_exists('db_site',$SITE_INFO)) || ($SITE_INFO['db_site']===NULL)) return basename(get_file_base()); return $SITE_INFO['db_site'].(($GLOBALS['CURRENT_SHARE_USER']===NULL)?'':('_'.$GLOBALS['CURRENT_SHARE_USER'])); } /** * Get the database username. * * @return string The database username */ function get_db_site_user() { global $SITE_INFO; if ($GLOBALS['CURRENT_SHARE_USER']!==NULL) return substr(md5($SITE_INFO['db_forums_user'].'_'.$GLOBALS['CURRENT_SHARE_USER']),0,16); return ((array_key_exists('db_site_user',$SITE_INFO)) && ($SITE_INFO['db_site_user']!==NULL))?$SITE_INFO['db_site_user']:'root'; } /** * Get the database password. * * @return string The database password */ function get_db_site_password() { global $SITE_INFO; return array_key_exists('db_site_password',$SITE_INFO)?$SITE_INFO['db_site_password']:''; } /** * Get the host of the forum database ('localhost', for example). * * @return string The database host */ function get_db_forums_host() { global $SITE_INFO; return array_key_exists('db_forums_host',$SITE_INFO)?$SITE_INFO['db_forums_host']:(array_key_exists('db_site_host',$SITE_INFO)?$SITE_INFO['db_site_host']:'localhost'); } /** * Get the name of the forum database. * * @return string The forum database site */ function get_db_forums() { global $SITE_INFO; if (!array_key_exists('db_forums',$SITE_INFO)) return get_db_site(); return $SITE_INFO['db_forums'].(($GLOBALS['CURRENT_SHARE_USER']===NULL)?'':('_'.$GLOBALS['CURRENT_SHARE_USER'])); } /** * Get the forum database username. * * @return string The forum database username */ function get_db_forums_user() { global $SITE_INFO; if (!array_key_exists('db_forums_user',$SITE_INFO)) return get_db_site_user(); if ($GLOBALS['CURRENT_SHARE_USER']!==NULL) return substr(md5($SITE_INFO['db_forums_user'].'_'.$GLOBALS['CURRENT_SHARE_USER']),0,16); return $SITE_INFO['db_forums_user']; } /** * Get the forum database password. * * @return string The forum database password */ function get_db_forums_password() { global $SITE_INFO; if (!array_key_exists('db_forums_password',$SITE_INFO)) return get_db_site_password(); return $SITE_INFO['db_forums_password']; } /** * Get the time difference in microseconds between two PHP microtimes. * Original source: php.net * * @param string First microtime * @param string Second microtime * @return float The time difference */ function microtime_diff($a,$b) { $x=explode(' ',$a); $a_micro=floatval($x[0]); $a_int=(float)intval($x[1]); $y=explode(' ',$b); $b_micro=floatval($y[0]); $b_int=(float)intval($y[1]); if ($a_int>$b_int) { return ($a_int-$b_int)+($a_micro-$b_micro); } elseif ($a_int==$b_int) { if ($a_micro>$b_micro) { return ($a_int-$b_int)+($a_micro-$b_micro); } elseif ($a_micro<$b_micro) { return ($b_int-$a_int)+($b_micro-$a_micro); } else { return 0.0; } } else { // $a_int<$b_int return ($b_int-$a_int)+($b_micro-$a_micro); } } /** * Database handling. * @package core */ class database_driver { var $table_prefix; var $connection_read,$connection_write; var $text_lookup_original_cache; var $text_lookup_cache; var $table_exists_cache; var $static_ob; var $dedupe_mode=false; /** * Construct a database driver from connection parameters. * * @param string The database name * @param string The database server * @param string The connection username * @param string The connection password * @param string The table prefix * @param boolean Whether to on error echo an error and return with a NULL, rather than giving a critical error * @param ?object Static call object (NULL: use global static call object) */ function database_driver($db_name,$db_host,$db_user,$db_password,$table_prefix,$fail_ok=false,$static=NULL) { $this->text_lookup_original_cache=array(); $this->text_lookup_cache=array(); $this->table_exists_cache=array(); $servers=explode(',',$db_host); if (count($servers)==1) { $this->connection_write=array(get_use_persistent(),$db_name,$db_host,$db_user,$db_password,$fail_ok); $this->connection_read=$this->connection_write; } else { $this->connection_write=array(get_use_persistent(),$db_name,$servers[0],$db_user,$db_password,$fail_ok); $min=(count($servers)==2)?0:1; $this->connection_read=array(get_use_persistent(),$db_name,$servers[mt_rand($min,count($servers)-1)],$db_user,$db_password,$fail_ok); } $this->table_prefix=$table_prefix; if ($static!==NULL) { $this->static_ob=$static; } else { $this->static_ob=$GLOBALS['DB_STATIC_OBJECT']; } } /** * Get the table prefixes used for all ocPortal tables, commonly used when you are installing ocPortal in the same database as your forums. The default table prefix is 'ocp4_'. * * @return string The table prefix */ function get_table_prefix() { return $this->table_prefix; } /** * Insert a row. * * @param string The table name * @param array The insertion map * @param boolean Whether to return the auto-insert-id * @param boolean Whether to allow failure (outputting a message instead of exiting completely) * @param boolean Whether we are saving as a 'volatile' file extension (used in the XML DB driver, to mark things as being non-syndicated to subversion) * @return integer The id of the new row */ function query_insert($table,$map,$ret=false,$fail_ok=false,$save_as_volatile=false) { if (($table=='cache') && (get_db_type()!='xml') && (get_option('filesystem_caching',true)==='1')) { global $FILECACHE_OBJECT; if ($FILECACHE_OBJECT===NULL) $this->initialise_filesystem_db(); if ($FILECACHE_OBJECT!=$this) return $FILECACHE_OBJECT->query_insert($table,$map,$ret,$fail_ok,$save_as_volatile); } $keys=''; $all_values=array(); $eis=$this->static_ob->db_empty_is_null(); foreach ($map as $key=>$value) { if ($keys!='') $keys.=', '; $keys.=$key; $_value=(!is_array($value))?array($value):$value; $v=mixed(); foreach ($_value as $i=>$v) { if (!isset($all_values[$i])) $all_values[$i]=''; $values=$all_values[$i]; if ($values!='') $values.=', '; if ($value===NULL) { if (($eis) && (is_string($v)) && ($v=='')) $values.='\' \''; else $values.='NULL'; } else { if (($eis) && (is_string($v)) && ($v=='')) { $v=' '; } if (is_integer($v)) $values.=strval($v); elseif (is_float($v)) $values.=float_to_raw_string($v,10); elseif (($key=='begin_num') || ($key=='end_num')) $values.=$v; // Fudge, for all our known large unsigned integers else $values.='\''.$this->static_ob->db_escape_string($v).'\''; } $all_values[$i]=$values; // essentially appends, as $values was loaded from former $all_values[$i] value } } if (count($all_values)==1) // usually $all_values only has length of 1 { if ((in_array($table,array('stats','banner_clicks','member_tracking','usersonline_track','download_logging'))) && (substr(get_db_type(),0,5)=='mysql')) { $query='INSERT DELAYED INTO '.$this->table_prefix.$table.' ('.$keys.') VALUES ('.$all_values[0].')'; } else { $query='INSERT INTO '.$this->table_prefix.$table.' ('.$keys.') VALUES ('.$all_values[0].')'; } } else { // So we can do batch inserts... $all_v=''; foreach ($all_values as $v) { if ($all_v!='') $all_v.=', '; $all_v.='('.$v.')'; } $query='INSERT INTO '.$this->table_prefix.$table.' ('.$keys.') VALUES '.$all_v; } return $this->_query($query,NULL,NULL,$fail_ok,$ret,NULL,'',$save_as_volatile); } /** * Check if a table exists. * * @param ID_TEXT The table name * @return boolean Whether it exists */ function table_exists($tablename) { /* // Just works with MySQL (too complex to do for all SQL's http://forums.whirlpool.net.au/forum-replies-archive.cfm/523219.html) $full_tablename=$this->get_table_prefix().$tablename; $rows=$this->query("SHOW TABLES LIKE '".$full_tablename."'"); foreach ($rows as $row) foreach ($row as $field) if ($field==$full_tablename) return true; return false; */ if (array_key_exists($tablename,$this->table_exists_cache)) { return $this->table_exists_cache[$tablename]; } $test=$this->query_value_null_ok('db_meta','m_name',array('m_table'=>$tablename)); $this->table_exists_cache[$tablename]=($test!==NULL); return $this->table_exists_cache[$tablename]; } /** * Get the specified value from the database. This is the specified value of the first row returned. A fatal error is produced if there is no matching row. * * @param string The table name * @param string The field to select * @param ?array The WHERE map [will all be AND'd together] (NULL: no where conditions) * @param string Something to tack onto the end * @return mixed The first value of the first row returned */ function query_value($table,$selected_value,$where_map=NULL,$end='') { $values=$this->query_select($table,array($selected_value),$where_map,$end,1,NULL); if ($values===NULL) return NULL; // error if (!array_key_exists(0,$values)) fatal_exit(do_lang_tempcode('QUERY_NULL',escape_html($this->_get_where_expand($this->table_prefix.$table,array($selected_value),$where_map,$end)))); // No result found return $this->_query_value($values); } /** * Extract the first of the first of the list of maps. * * @param array The list of maps * @return mixed The first value of the first row in the list */ function _query_value($values) { if (!array_key_exists(0,$values)) return NULL; // No result found $first=$values[0]; $v=current($first); // Result found. Maybe a value of 'null' return $v; } /** * Get the specified value from the database, or NULL if there is no matching row (or if the value itself is NULL). This is good for detection existence of records, or for use if they might may or may not be present. * * @param string The table name * @param string The field to select * @param ?array The WHERE map [will all be AND'd together] (NULL: no where conditions) * @param string Something to tack onto the end * @param boolean Whether to allow failure (outputting a message instead of exiting completely) * @return ?mixed The first value of the first row returned (NULL: nothing found, or null value found) */ function query_value_null_ok($table,$select,$where_map=NULL,$end='',$fail_ok=false) { $values=$this->query_select($table,array($select),$where_map,$end,1,NULL,$fail_ok); if ($values===NULL) return NULL; // error return $this->_query_value($values); } /** * This function is a variant of query_value_null_ok, by the fact that it only accepts a complete (and perfect) SQL query, instead of assembling one itself from the specified parameters. * * @param string The complete SQL query * @param boolean Whether to allow failure (outputting a message instead of exiting completely) * @param boolean Whether to skip the query safety check * @return ?mixed The first value of the first row returned (NULL: nothing found, or null value found) */ function query_value_null_ok_full($query,$fail_ok=false,$skip_safety_check=false) { $values=$this->query($query,1,NULL,$fail_ok,$skip_safety_check); if ($values===NULL) return NULL; // error return $this->_query_value($values); } /** * Deletes rows from the specified table, that match the specified conditions (if any). It may be limited to a row range (it is likely, only a maximum, of 1, will be used, if any kind of range at all). * * @param string The table name * @param ?array The WHERE map [will all be AND'd together] (NULL: no conditions) * @param string Something to tack onto the end of the statement * @param ?integer The maximum number of rows to delete (NULL: no limit) * @param ?integer The starting row to delete (NULL: no specific start) * @param boolean Whether to allow failure (outputting a message instead of exiting completely) */ function query_delete($table,$where_map=NULL,$end='',$max=NULL,$start=NULL,$fail_ok=false) { if (($table=='cache') && (get_db_type()!='xml') && (get_option('filesystem_caching',true)==='1')) { global $FILECACHE_OBJECT; if ($FILECACHE_OBJECT===NULL) $this->initialise_filesystem_db(); if ($FILECACHE_OBJECT!=$this) { $FILECACHE_OBJECT->query_delete($table,$where_map,$end,$max,$start,$fail_ok); return; } } if ($where_map===NULL) { $this->_query('DELETE FROM '.$this->table_prefix.$table.' '.$end,$max,$start,$fail_ok); return; } $where=''; foreach ($where_map as $key=>$value) { if ($where!='') $where.=' AND '; if (is_float($value)) $where.=$key.'='.float_to_raw_string($value,10); elseif (is_integer($value)) $where.=$key.'='.strval($value); elseif (($key=='begin_num') || ($key=='end_num')) $where.=$key.'='.$value; // Fudge, for all our known large unsigned integers else { if ($value===NULL) $where.=$key.' IS NULL'; else { if ((is_string($value)) && ($value=='') && ($this->static_ob->db_empty_is_null())) $where.=$key.' IS NULL'; //$value=' '; else $where.=db_string_equal_to($key,$value); } } } $query='DELETE FROM '.$this->table_prefix.$table.' WHERE ('.$where.') '.$end; $this->_query($query,$max,$start,$fail_ok); } /** * Update (edit) a row in the database. * * @param string The table name * @param array The UPDATE map * @param ?array The WHERE map [will all be AND'd together] (NULL: no conditions) * @param string Something to tack onto the end of the statement * @param ?integer The maximum number of rows to update (NULL: no limit) * @param ?integer The starting row to update (NULL: no specific start) * @param boolean Whether to get the number of touched rows. WARNING: Do not use in core ocPortal code as it does not work on all database drivers * @param boolean Whether to allow failure (outputting a message instead of exiting completely) * @return ?integer The number of touched records (NULL: hasn't been asked / error) */ function query_update($table,$update_map,$where_map=NULL,$end='',$max=NULL,$start=NULL,$num_touched=false,$fail_ok=false) { $where=''; $update=''; $value=mixed(); if ($where_map!==NULL) { foreach ($where_map as $key=>$value) { if ($where!='') $where.=' AND '; if (is_float($value)) $where.=$key.'='.float_to_raw_string($value,10); elseif (is_integer($value)) $where.=$key.'='.strval($value); elseif (($key=='begin_num') || ($key=='end_num')) $where.=$key.'='.$value; // Fudge, for all our known large unsigned integers else { if ($value===NULL) $where.=$key.' IS NULL'; else { if ((is_string($value)) && ($value=='') && ($this->static_ob->db_empty_is_null())) /*$where.=$key.' IS NULL';*/ $value=' '; /*else */$where.=db_string_equal_to($key,$value); } } } } foreach ($update_map as $key=>$value) { if (($value===STRING_MAGIC_NULL) || ($value===INTEGER_MAGIC_NULL)) continue; if ($update!='') $update.=', '; if ($value===NULL) $update.=$key.'=NULL'; else { if (is_float($value)) $update.=$key.'='.float_to_raw_string($value,10); elseif (is_integer($value)) $update.=$key.'='.strval($value); elseif (($key=='begin_num') || ($key=='end_num')) $where.=$key.'='.$value; // Fudge, for all our known large unsigned integers else $update.=$key.'=\''.$this->static_ob->db_escape_string($value).'\''; } } if ($update=='') return NULL; if ($where=='') { return $this->_query('UPDATE '.$this->table_prefix.$table.' SET '.$update.' '.$end,$max,$start,$fail_ok,$num_touched); } else { return $this->_query('UPDATE '.$this->table_prefix.$table.' SET '.$update.' WHERE ('.$where.') '.$end,$max,$start,$fail_ok,$num_touched); } } /** * Create a SELECT query from some abstract data. * * @param string The table to select from * @param ?array List of field selections (NULL: all fields) * @param ?array Map of conditions to enforce (NULL: no conditions) * @param string Additional stuff to tack onto the query * @return string SQL query */ function _get_where_expand($table,$select_map=NULL,$where_map=NULL,$end='') { if ($select_map===NULL) $select_map=array('*'); $select=''; foreach ($select_map as $key) { if (!is_string($key)) $key=strval($key); if ($select!='') $select.=','; $select.=$key; } $where=''; if (($where_map!==NULL) && ($where_map!=array())) { foreach ($where_map as $key=>$value) { if (!is_string($key)) fatal_exit('Parameters to the database API given in the wrong order. Please check the function call.'); if ($where!='') $where.=' AND '; if (is_float($value)) $where.=$key.'='.float_to_raw_string($value,10); elseif (is_integer($value)) $where.=$key.'='.strval($value); elseif (($key=='begin_num') || ($key=='end_num')) $where.=$key.'='.$value; // Fudge, for all our known large unsigned integers else { if ($value===NULL) $where.=$key.' IS NULL'; else { if (($value==='') && ($this->static_ob->db_empty_is_null())) /*$where.=$key.' IS NULL';*/ $value=' '; /*else */ if ($key=='text_original') $table=str_replace(' LEFT JOIN '.$this->get_table_prefix().'translate ',' JOIN '.$this->get_table_prefix().'translate ',$table); $where.=db_string_equal_to($key,$value); } } } return 'SELECT '.$select.' FROM '.$table.' WHERE ('.$where.') '.$end; } return 'SELECT '.$select.' FROM '.$table.' '.$end; } /** * Initialise a filesystem DB that we can use for caching. */ function initialise_filesystem_db() { global $FILECACHE_OBJECT; require_code('database/xml'); $chain_db=new database_driver(get_custom_file_base().'/persistent_cache','','','',get_table_prefix(),false,object_factory('Database_Static_xml')); $chain_connection=&$chain_db->connection_write; if (count($chain_connection)>4) // Okay, we can't be lazy anymore { $chain_connection=call_user_func_array(array($chain_db->static_ob,'db_get_connection'),$chain_connection); _general_db_init(); } $FILECACHE_OBJECT=$chain_db; } /** * Get the database rows found matching the specified parameters. Unlike 'query', it doesn't take raw SQL -- it assembles SQL based the parameters requested. * * @param string The table name * @param ?array The SELECT map (NULL: all fields) * @param ?array The WHERE map [will all be AND'd together] (NULL: no conditions) * @param string Something to tack onto the end of the SQL query * @param ?integer The maximum number of rows to select (NULL: get all) * @param ?integer The starting row to select (NULL: start at first) * @param boolean Whether to allow failure (outputting a message instead of exiting completely) * @param ?array Extra language fields to join in for cache-prefilling. You only need to send this if you are doing a JOIN and carefully craft your query so table field names won't conflict (NULL: none) * @return array The results */ function query_select($table,$select=NULL,$where_map=NULL,$end='',$max=NULL,$start=NULL,$fail_ok=false,$lang_fields=NULL) { if (($table=='cache') && (get_db_type()!='xml') && (get_option('filesystem_caching',true)==='1')) { global $FILECACHE_OBJECT; if ($FILECACHE_OBJECT===NULL) $this->initialise_filesystem_db(); if ($FILECACHE_OBJECT!=$this) return $FILECACHE_OBJECT->query_select($table,$select,$where_map,$end,$max,$start,$fail_ok,$lang_fields); } $full_table=$this->table_prefix.$table; $field_prefix=''; if ($select===NULL) $select=array('*'); // Optimisation for entirely automatic translate table linkage (only done on non-joins, as this removes a whole lot of potential complexities -- if people are doing joins they go a little further to do this manually anyway; also we make sure we're operating on our site's table prefix so we don't collect meta info for the wrong table set) if ($lang_fields===NULL) { if (($table!='translate') && (strpos($table,' ')===false) && (isset($GLOBALS['SITE_DB'])) && ($this->table_prefix==$GLOBALS['SITE_DB']->table_prefix)) { global $TABLE_LANG_FIELDS; $lang_fields_provisional=isset($TABLE_LANG_FIELDS[$table])?$TABLE_LANG_FIELDS[$table]:array(); $lang_fields=array(); if ($lang_fields_provisional!=array()) { $full_table.=' main'; foreach ($select as $i=>$s) { if (!is_string($s)) { $lang_fields_provisional=array(); break; // Bad API call, but we'll let it fail naturally } if (preg_match('#^[A-Za-z\_\*]+$#',$s)!=0) $select[$i]='main.'.$s; } if ($where_map!==NULL) { foreach ($where_map as $i=>$s) { if (!is_string($i)) { $lang_fields_provisional=array(); break; // Bad API call, but we'll let it fail naturally } if (preg_match('#^[A-Za-z\_]+$#',$i)!=0) { unset($where_map[$i]); $where_map['main.'.$i]=$s; } } } if ($end!='') { $end=preg_replace('#(^|,|\s)([a-z]+)($|,|\s)#','${1}main.${2}${3}',$end); } $field_prefix='main.'; foreach ($lang_fields_provisional as $lang_field) { if ((in_array($field_prefix.$lang_field,$select)) || (in_array($field_prefix.'*',$select))) { $lang_fields[]=$lang_field; } } } } } return $this->_query($this->_get_where_expand($full_table,$select,$where_map,$end),$max,$start,$fail_ok,false,$lang_fields,$field_prefix); } /** * This function is a raw query executor. It shouldn't usually be used unless you need to write SQL involving 'OR' statements or other complexities. There are abstracted versions available which you probably want instead (mainly, query_select). * * @param string The complete SQL query * @param ?integer The maximum number of rows to affect (NULL: no limit) * @param ?integer The start row to affect (NULL: no specification) * @param boolean Whether to output an error on failure * @param boolean Whether to skip the query safety check * @param ?array Extra language fields to join in for cache-prefilling. You only need to send this if you are doing a JOIN and carefully craft your query so table field names won't conflict (NULL: none) * @param string All the core fields have a prefix of this on them, so when we fiddle with language lookup we need to use this (only consider this if you're setting $lang_fields) * @return ?mixed The results (NULL: no results) */ function query($query,$max=NULL,$start=NULL,$fail_ok=false,$skip_safety_check=false,$lang_fields=NULL,$field_prefix='') { if (!$skip_safety_check) { $_query=strtolower($query); $queries=1;//substr_count($_query,'insert into ')+substr_count($_query,'replace into ')+substr_count($_query,'update ')+substr_count($_query,'select ')+substr_count($_query,'delete from '); Not reliable if ((strpos(preg_replace('#\'[^\']*\'#','\'\'',str_replace('\\\'','',$_query)),' union ')!==false) || ($queries>1)) log_hack_attack_and_exit('SQL_INJECTION_HACK',$query); } return $this->_query($query,$max,$start,$fail_ok,false,$lang_fields,$field_prefix); } /** * This function is a very basic query executor. It shouldn't usually be used by you, as there are specialised abstracted versions available. * * @param string The complete SQL query * @param ?integer The maximum number of rows to affect (NULL: no limit) * @param ?integer The start row to affect (NULL: no specification) * @param boolean Whether to output an error on failure * @param boolean Whether to get an insert ID * @param ?array Extra language fields to join in for cache-prefilling. You only need to send this if you are doing a JOIN and carefully craft your query so table field names won't conflict (NULL: none) * @param string All the core fields have a prefix of this on them, so when we fiddle with language lookup we need to use this (only consider this if you're setting $lang_fields) * @param boolean Whether we are saving as a 'volatile' file extension (used in the XML DB driver, to mark things as being non-syndicated to subversion) * @return ?mixed The results (NULL: no results) */ function _query($query,$max=NULL,$start=NULL,$fail_ok=false,$get_insert_id=false,$lang_fields=NULL,$field_prefix='',$save_as_volatile=false) { global $QUERY_COUNT,$NO_QUERY_LIMIT,$QUERY_LOG,$QUERY_LIST,$DEV_MODE,$IN_MINIKERNEL_VERSION,$QUERY_FILE_LOG,$UPON_QUERY_HOOKS; if ($QUERY_FILE_LOG!==NULL) { fwrite($QUERY_FILE_LOG,$query.';'.chr(10).chr(10)); } if ($DEV_MODE) { if ((get_forum_type()!='none') && (strpos($query,get_table_prefix().'f_')!==false) && (strpos($query,get_table_prefix().'f_')<100) && (strpos($query,'f_welcome_emails')===false) && ($this->connection_write===$GLOBALS['SITE_DB']->connection_write) && (isset($GLOBALS['FORUM_DB'])) && ($GLOBALS['SITE_DB']->connection_write!==$GLOBALS['FORUM_DB']->connection_write) && (!$GLOBALS['NO_DB_SCOPE_CHECK'])) { /*file_put_contents(get_file_base().'/uploads/downloads/test.txt',var_export(debug_backtrace(),true)); @exit($query); @debug_print_backtrace();*/ fatal_exit('Using OCF queries on the wrong driver'); } } if (!$NO_QUERY_LIMIT) { $QUERY_COUNT++; //@exit('!'); //if ($QUERY_COUNT>10) @ob_end_clean();@print('Query: '.$query.chr(10)); } static $fb=NULL; if ($fb===NULL) $fb=function_exists('fb'); if (($fb) && (!headers_sent()) && (get_param_integer('keep_firephp_queries',0)==1) && (function_exists('fb'))) { fb('Query: '.$query); } if (($QUERY_COUNT==68) && (get_param_integer('keep_no_query_limit',0)==0) && (count($_POST)==0) && (get_page_name()!='admin_importer') && ($IN_MINIKERNEL_VERSION==0) && (get_param('special_page_type','')!='query')) { $NO_QUERY_LIMIT=true; $log_path=get_custom_file_base().'/data_custom/big_query_screens.log'; if (is_writable_wrap($log_path)) { $myfile=fopen($log_path,'at'); fwrite($myfile,get_self_url_easy().chr(10)); fclose($myfile); } if ($DEV_MODE) { $QUERY_COUNT=0; fatal_exit(do_lang_tempcode('TOO_MANY_QUERIES')); } } $lang_strings_expecting=array(); if ((isset($lang_fields[0])) && (function_exists('user_lang'))) { $lang=user_lang(); // We can we assume this, as we will cache against it -- if subsequently code wants something else it'd be a cache miss which is fine foreach ($lang_fields as $i=>$field) { $_i=strval($i); $join=' LEFT JOIN '.$this->table_prefix.'translate t'.$_i.' ON t'.$_i.'.id='.$field_prefix.$field.' AND '.db_string_equal_to('t'.$_i.'.language',$lang); $_query=strtoupper($query); $from_pos=strpos($_query,' FROM '); $where_pos=strpos($_query,' WHERE '); if ($where_pos===false) { $_where_pos=0; do { $_where_pos=strpos($_query,' GROUP BY ',$_where_pos+1); if ($_where_pos!==false) $where_pos=$_where_pos; } while ($_where_pos!==false); } if ($where_pos===false) { $_where_pos=0; do { $_where_pos=strpos($_query,' ORDER BY ',$_where_pos+1); if ($_where_pos!==false) $where_pos=$_where_pos; } while ($_where_pos!==false); } if ($where_pos!==false) { $query=substr($query,0,$where_pos).$join.substr($query,$where_pos); } else { $query.=$join; } $original='t'.$_i.'.text_original AS t'.$_i.'__text_original'; $parsed='t'.$_i.'.text_parsed AS t'.$_i.'__text_parsed'; $query=substr($query,0,$from_pos).','.$original.','.$parsed.substr($query,$from_pos); $lang_strings_expecting[]=array($field,'t'.$_i.'__text_original','t'.$_i.'__text_parsed'); } } if ($start<0) $start=0; if ($max<0) $max=1; if ($QUERY_LOG) { $before=microtime(false); } if ((substr(strtoupper($query),0,7)=='SELECT ') || (substr(strtoupper($query),0,8)=='(SELECT ')) { $connection=&$this->connection_read; } else { $connection=&$this->connection_write; } if (isset($connection[4])) // Okay, we can't be lazy anymore { $connection=call_user_func_array(array($this->static_ob,'db_get_connection'),$connection); _general_db_init(); } // Special handling for searches, which are slow and specific - we want to recognise if previous active searches were the same and kill them (as this would have been a double form submit) if (($this->dedupe_mode) && (substr(get_db_type(),0,5)=='mysql')) { $query.='/* '.strval(get_session_id()).' */'; // Identify query to session, for accurate de-duping $real_query=$query; if (($max!==NULL) && ($start!==NULL)) $real_query.=' LIMIT '.strval($start).','.strval($max); elseif ($max!==NULL) $real_query.=' LIMIT '.strval($max); elseif ($start!==NULL) $real_query.=' LIMIT '.strval($start).',30000000'; $ret=$this->static_ob->db_query('SHOW FULL PROCESSLIST',$connection); foreach ($ret as $process) { if ($process['Info']==$real_query) { $this->static_ob->db_query('KILL '.strval($process['Id']),$connection,NULL,NULL,true); } } } $ret=$this->static_ob->db_query($query,$connection,$max,$start,$fail_ok,$get_insert_id,false,$save_as_volatile); if ($QUERY_LOG) { $after=microtime(false); $text=(!is_null($max))?$query.' ('.strval((integer)$start).'-'.strval((integer)$start+$max).')':$query; $out=array('time'=>microtime_diff($after,$before),'text'=>$text); $QUERY_LIST[]=$out; } // Run hooks, if any exist if ($UPON_QUERY_HOOKS===NULL) { if (!function_exists('find_all_hooks')) return $ret; $UPON_QUERY_HOOKS=array(); if (!running_script('restore')) { $hooks=find_all_hooks('systems','upon_query'); foreach (array_keys($hooks) as $hook) { require_code('hooks/systems/upon_query/'.filter_naughty($hook)); $UPON_QUERY_HOOKS[$hook]=object_factory('upon_query_'.filter_naughty($hook),true); } } } foreach ($UPON_QUERY_HOOKS as $ob) { if ($ob!==NULL) $ob->run($this,$query,$max,$start,$fail_ok,$get_insert_id,$ret); } // Copy results to lang cache, but only if not null AND unset to avoid any confusion if ($ret!==NULL) { foreach ($lang_strings_expecting as $bits) { list($field,$original,$parsed)=$bits; foreach ($ret as $row) { $entry=$row[$field]; if (($row[$original]!==NULL) && (count($this->text_lookup_original_cache)<=1000)) $this->text_lookup_original_cache[$entry]=$row[$original]; if (($row[$parsed]!==NULL) && (count($this->text_lookup_cache)<=1000)) $this->text_lookup_cache[$entry]=$row[$parsed]; unset($row[$original]); unset($row[$parsed]); } } } return $ret; } /** * Create a table with the given name and the given array of field name to type mappings. * If a field type starts '*', then it is part of that field's key. If it starts '?', then it is an optional field. * * @param ID_TEXT The table name * @param array The fields * @param boolean Whether to skip the size check for the table (only do this for addon modules that don't need to support anything other than mySQL) * @param boolean Whether to skip the check for NULL string fields */ function create_table($table_name,$fields,$skip_size_check=false,$skip_null_check=false) { require_code('database_helper'); _helper_create_table($this,$table_name,$fields,$skip_size_check,$skip_null_check); } /** * Add an index to a table without disturbing the contents, after the table has been created. * * @param ID_TEXT The table name * @param ID_TEXT The index name * @param array The fields * @param ID_TEXT The name of the unique key field for the table */ function create_index($table_name,$index_name,$fields,$unique_key_field='id') { require_code('database_helper'); _helper_create_index($this,$table_name,$index_name,$fields,$unique_key_field); } /** * Delete an index from a table. * * @param ID_TEXT The table name * @param ID_TEXT The index name */ function delete_index_if_exists($table_name,$index_name) { require_code('database_helper'); _helper_delete_index_if_exists($this,$table_name,$index_name); } /** * Drop the given table, or if it doesn't exist, silently return. * * @param ID_TEXT The table name */ function drop_if_exists($table) { require_code('database_helper'); _helper_drop_if_exists($this,$table); } /** * Rename the given table. * * @param ID_TEXT The old table name * @param ID_TEXT The new table name */ function rename_table($old,$new) { require_code('database_helper'); _helper_rename_table($this,$old,$new); } /** * Adds a field to an existing table. * * @param ID_TEXT The table name * @param ID_TEXT The field name * @param ID_TEXT The field type * @param ?mixed The default value (NULL: no default) */ function add_table_field($table_name,$name,$_type,$default=NULL) { require_code('database_helper'); _helper_add_table_field($this,$table_name,$name,$_type,$default); } /** * Change the type of a DB field in a table. Note: this function does not support ascession/decession of translatability * * @param ID_TEXT The table name * @param ID_TEXT The field name * @param ID_TEXT The new field type * @param ?ID_TEXT The new field name (NULL: leave name) */ function alter_table_field($table_name,$name,$_type,$new_name=NULL) { require_code('database_helper'); _helper_alter_table_field($this,$table_name,$name,$_type,$new_name); } /** * Change the primary key of a table. * * @param ID_TEXT The name of the table to create the index on * @param array A list of fields to put in the new key */ function change_primary_key($table_name,$new_key) { require_code('database_helper'); _helper_change_primary_key($this,$table_name,$new_key); } /** * If a text field has picked up Comcode support, we will need to run this. * * @param ID_TEXT The table name * @param ID_TEXT The field name * @param ID_TEXT The tables key field name * @param integer The translation level to use * @set 1 2 3 4 * @param boolean Whether our data is already stored in Tempcode assembly format */ function promote_text_field_to_comcode($table_name,$name,$key='id',$level=2,$in_assembly=false) { require_code('database_helper'); _helper_promote_text_field_to_comcode($this,$table_name,$name,$key,$level,$in_assembly); } /** * Delete the specified field from the specified table. * * @param ID_TEXT The table name * @param ID_TEXT The field name */ function delete_table_field($table_name,$name) { require_code('database_helper'); _helper_delete_table_field($this,$table_name,$name); } /** * If we've changed what $type is stored as, this function will need to be called to change the typing in the DB. * * @param ID_TEXT The field type */ function refresh_field_definition($type) { require_code('database_helper'); _helper_refresh_field_definition($this,$type); } /** * Find if a table is locked for more than 5 seconds. Only works with MySQL. * * @param ID_TEXT The table name * @param boolean Whether the table is locked */ function table_is_locked($tbl) { if (substr(get_db_type(),0,5)!='mysql') return false; $tries=0; do { $locks=$GLOBALS['SITE_DB']->query('SHOW OPEN TABLES FROM '.get_db_site().' WHERE `Table`=\''.get_table_prefix().$tbl.'\' AND In_use>=1'); $locked=count($locks)>=1; $tries++; if ($locked) { sleep(1); } } while (($locked) && ($tries<5)); return $locked; } }