includes/dbal.php
changeset 320 112debff64bd
parent 317 f8356d9c3481
child 322 5f1cd51bf1be
equal deleted inserted replaced
319:8be996c3740d 320:112debff64bd
   121   }
   121   }
   122   
   122   
   123   function connect()
   123   function connect()
   124   {
   124   {
   125     $this->enable_errorhandler();
   125     $this->enable_errorhandler();
       
   126     
       
   127     define('ENANO_DBLAYER', 'MYSQL');
       
   128     define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
       
   129     define('ENANO_SQL_MULTISTRING_PRFIX', '');
       
   130     define('ENANO_SQL_BOOLEAN_TRUE', 'true');
       
   131     define('ENANO_SQL_BOOLEAN_FALSE', 'false');
   126     
   132     
   127     if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
   133     if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
   128     {
   134     {
   129       @include(ENANO_ROOT.'/config.new.php');
   135       @include(ENANO_ROOT.'/config.new.php');
   130     }
   136     }
   776           </div>';
   782           </div>';
   777     $template->footer();
   783     $template->footer();
   778   }
   784   }
   779 }
   785 }
   780 
   786 
       
   787 class postgresql {
       
   788   var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
       
   789   var $row = array();
       
   790 	var $rowset = array();
       
   791   var $errhandler;
       
   792   
       
   793   function enable_errorhandler()
       
   794   {
       
   795     // echo "DBAL: enabling error handler<br />";
       
   796     if ( function_exists('debug_backtrace') )
       
   797     {
       
   798       $this->errhandler = set_error_handler('db_error_handler');
       
   799     }
       
   800   }
       
   801   
       
   802   function disable_errorhandler()
       
   803   {
       
   804     // echo "DBAL: disabling error handler<br />";
       
   805     if ( $this->errhandler )
       
   806     {
       
   807       set_error_handler($this->errhandler);
       
   808     }
       
   809     else
       
   810     {
       
   811       restore_error_handler();
       
   812     }
       
   813   }
       
   814   
       
   815   function sql_backtrace()
       
   816   {
       
   817     return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
       
   818   }
       
   819   
       
   820   function ensure_connection()
       
   821   {
       
   822     if(!$this->_conn)
       
   823     {
       
   824       $this->connect();
       
   825     }
       
   826   }
       
   827   
       
   828   function _die($t = '') {
       
   829     if(defined('ENANO_HEADERS_SENT')) {
       
   830       ob_clean();
       
   831     }
       
   832     header('HTTP/1.1 500 Internal Server Error');
       
   833     $bt = $this->latest_query; // $this->sql_backtrace();
       
   834     $e = htmlspecialchars(pg_last_error());
       
   835     if($e=='') $e='&lt;none&gt;';
       
   836     $t = ( !empty($t) ) ? $t : '&lt;No error description provided&gt;';
       
   837     global $email;
       
   838     $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;' : '';
       
   839     $internal_text = '<h3>The site was unable to finish serving your request.</h3>
       
   840                       <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p>
       
   841                       <p>Description or location of error: '.$t.'<br />
       
   842                       Error returned by PostgreSQL extension: ' . $e . '<br />
       
   843                       Most recent SQL query:</p>
       
   844                       <pre>'.$bt.'</pre>';
       
   845     if(defined('ENANO_CONFIG_FETCHED')) die_semicritical('Database error', $internal_text);
       
   846     else                                   grinding_halt('Database error', $internal_text);
       
   847     exit;
       
   848   }
       
   849   
       
   850   function die_json()
       
   851   {
       
   852     $e = addslashes(htmlspecialchars(pg_last_error()));
       
   853     $q = addslashes($this->latest_query);
       
   854     $t = "{'mode':'error','error':'An error occurred during database query.\nQuery was:\n  $q\n\nError returned by PostgreSQL: $e'}";
       
   855     die($t);
       
   856   }
       
   857   
       
   858   function get_error($t = '') {
       
   859     header('HTTP/1.1 500 Internal Server Error');
       
   860     $bt = $this->sql_backtrace();
       
   861     $e = htmlspecialchars(pg_last_error());
       
   862     if($e=='') $e='&lt;none&gt;';
       
   863     global $email;
       
   864     $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;' : '';
       
   865     $internal_text = '<h3>The site was unable to finish serving your request.</h3>
       
   866                       <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p>
       
   867                       <p>Description or location of error: '.$t.'<br />
       
   868                       Error returned by MySQL extension: ' . $e . '<br />
       
   869                       Most recent SQL query:</p>
       
   870                       <pre>'.$bt.'</pre>';
       
   871     return $internal_text;
       
   872   }
       
   873   
       
   874   function connect()
       
   875   {
       
   876     $this->enable_errorhandler();
       
   877     
       
   878     define('ENANO_DBLAYER', 'PGSQL');
       
   879     define('ENANO_SQLFUNC_LOWERCASE', 'lower');
       
   880     define('ENANO_SQL_MULTISTRING_PRFIX', 'E');
       
   881     define('ENANO_SQL_BOOLEAN_TRUE', '1');
       
   882     define('ENANO_SQL_BOOLEAN_FALSE', '0');
       
   883     
       
   884     if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
       
   885     {
       
   886       @include(ENANO_ROOT.'/config.new.php');
       
   887     }
       
   888     else
       
   889     {
       
   890       @include(ENANO_ROOT.'/config.php');
       
   891     }
       
   892       
       
   893     if ( isset($crypto_key) )
       
   894       unset($crypto_key); // Get this sucker out of memory fast
       
   895     
       
   896     if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
       
   897     {
       
   898       // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
       
   899       if ( !defined('scriptPath') )
       
   900       {
       
   901         if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
       
   902         {
       
   903           $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
       
   904         }
       
   905         if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
       
   906         {
       
   907           // user requested http://foo/enano as opposed to http://foo/enano/index.php
       
   908           $_SERVER['REQUEST_URI'] .= '/index.php';
       
   909         }
       
   910         $sp = dirname($_SERVER['REQUEST_URI']);
       
   911         if($sp == '/' || $sp == '\\') $sp = '';
       
   912         define('scriptPath', $sp);
       
   913         define('contentPath', "$sp/index.php?title=");
       
   914       }
       
   915       $loc = scriptPath . '/install.php';
       
   916       // header("Location: $loc");
       
   917       redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 3);
       
   918       exit;
       
   919     }
       
   920     $this->_conn = @pg_connect("host=$dbhost port=5432 dbname=$dbname user=$dbuser password=$dbpasswd");
       
   921     unset($dbuser);
       
   922     unset($dbpasswd); // Security
       
   923     
       
   924     if ( !$this->_conn )
       
   925     {
       
   926       grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to PostgreSQL.<br />'.pg_last_error().'</p>');
       
   927     }
       
   928     
       
   929     // Reset some variables
       
   930     $this->query_backtrace = array();
       
   931     $this->query_times = array();
       
   932     $this->query_sources = array();
       
   933     $this->num_queries = 0;
       
   934     
       
   935     $this->debug = ( defined('ENANO_DEBUG') );
       
   936     
       
   937     // We're in!
       
   938     $this->disable_errorhandler();
       
   939     return true;
       
   940   }
       
   941   
       
   942   function sql_query($q)
       
   943   {
       
   944     $this->enable_errorhandler();
       
   945     
       
   946     if ( $this->debug && function_exists('debug_backtrace') )
       
   947     {
       
   948       $backtrace = @debug_backtrace();
       
   949       if ( is_array($backtrace) )
       
   950       {
       
   951         $bt = $backtrace[0];
       
   952         if ( isset($backtrace[1]['class']) )
       
   953         {
       
   954           if ( $backtrace[1]['class'] == 'sessionManager' )
       
   955           {
       
   956             $bt = $backtrace[1];
       
   957           }
       
   958         }
       
   959         $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
       
   960       }
       
   961       unset($backtrace);
       
   962     }
       
   963     
       
   964     $this->num_queries++;
       
   965     $this->query_backtrace[] = $q;
       
   966     $this->latest_query = $q;
       
   967     // First make sure we have a connection
       
   968     if ( !$this->_conn )
       
   969     {
       
   970       $this->_die('A database connection has not yet been established.');
       
   971     }
       
   972     // Does this query look malicious?
       
   973     if ( !$this->check_query($q) )
       
   974     {
       
   975       $this->report_query($q);
       
   976       grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p><p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>');
       
   977     }
       
   978     
       
   979     $time_start = microtime_float();
       
   980     $r = pg_query($q);
       
   981     $this->query_times[$q] = microtime_float() - $time_start;
       
   982     $this->latest_result = $r;
       
   983     $this->disable_errorhandler();
       
   984     return $r;
       
   985   }
       
   986   
       
   987   function sql_unbuffered_query($q)
       
   988   {
       
   989     $this->enable_errorhandler();
       
   990     
       
   991     $this->num_queries++;
       
   992     $this->query_backtrace[] = '(UNBUFFERED) ' . $q;
       
   993     $this->latest_query = $q;
       
   994     // First make sure we have a connection
       
   995     if ( !$this->_conn )
       
   996     {
       
   997       $this->_die('A database connection has not yet been established.');
       
   998     }
       
   999     // Does this query look malicious?
       
  1000     if ( !$this->check_query($q) )
       
  1001     {
       
  1002       $this->report_query($q);
       
  1003       grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p><p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>');
       
  1004     }
       
  1005     
       
  1006     $time_start = microtime_float();
       
  1007     $r = pg_query($q);
       
  1008     $this->query_times[$q] = microtime_float() - $time_start;
       
  1009     $this->latest_result = $r;
       
  1010     $this->disable_errorhandler();
       
  1011     return $r;
       
  1012   }
       
  1013   
       
  1014   /**
       
  1015    * Checks a SQL query for possible signs of injection attempts
       
  1016    * @param string $q the query to check
       
  1017    * @return bool true if query passed check, otherwise false
       
  1018    */
       
  1019   
       
  1020   function check_query($q, $debug = false)
       
  1021   {
       
  1022     if($debug) echo "\$db-&gt;check_query(): checking query: ".htmlspecialchars($q).'<br />'."\n";
       
  1023     $sz = strlen($q);
       
  1024     $quotechar = false;
       
  1025     $quotepos  = 0;
       
  1026     $prev_is_quote = false;
       
  1027     $just_started = false;
       
  1028     for ( $i = 0; $i < strlen($q); $i++, $c = substr($q, $i, 1) )
       
  1029     {
       
  1030       $next = substr($q, $i+1, 1);
       
  1031       $next2 = substr($q, $i+2, 1);
       
  1032       $prev = substr($q, $i-1, 1);
       
  1033       $prev2 = substr($q, $i-2, 1);
       
  1034       if(isset($c) && in_array($c, Array('"', "'", '`')))
       
  1035       {
       
  1036         if($quotechar)
       
  1037         {
       
  1038           if (
       
  1039               ( $quotechar == $c && $quotechar != $next && ( $quotechar != $prev || $just_started ) && $prev != '\\') ||
       
  1040               ( $prev2 == '\\' && $prev == $quotechar && $quotechar == $c )
       
  1041             )
       
  1042           {
       
  1043             $quotechar = false;
       
  1044             if($debug) echo('$db-&gt;check_query(): just finishing a quote section, quoted string: '.htmlspecialchars(substr($q, $quotepos, $i - $quotepos + 1)) . '<br />');
       
  1045             $q = substr($q, 0, $quotepos) . 'SAFE_QUOTE' . substr($q, $i + 1, strlen($q));
       
  1046             if($debug) echo('$db-&gt;check_query(): Filtered query: '.$q.'<br />');
       
  1047             $i = $quotepos;
       
  1048           }
       
  1049         }
       
  1050         else
       
  1051         {
       
  1052           $quotechar = $c;
       
  1053           $quotepos  = $i;
       
  1054           $just_started = true;
       
  1055         }
       
  1056         if($debug) echo '$db-&gt;check_query(): found quote char as pos: '.$i.'<br />';
       
  1057         continue;
       
  1058       }
       
  1059       $just_started = false;
       
  1060     }
       
  1061     if(substr(trim($q), strlen(trim($q))-1, 1) == ';') $q = substr(trim($q), 0, strlen(trim($q))-1);
       
  1062     for($i=0;$i<strlen($q);$i++,$c=substr($q, $i, 1))
       
  1063     {
       
  1064       if ( 
       
  1065            ( ( $c == ';' && $i != $sz-1 ) || $c . substr($q, $i+1, 1) == '--' )
       
  1066         || ( in_array($c, Array('"', "'", '`')) )
       
  1067          ) // Don't permit semicolons in mid-query, and never allow comments
       
  1068       {
       
  1069         // Injection attempt!
       
  1070         if($debug)
       
  1071         {
       
  1072           $e = '';
       
  1073           for($j=$i-5;$j<$i+5;$j++)
       
  1074           {
       
  1075             if($j == $i) $e .= '<span style="color: red; text-decoration: underline;">' . $c . '</span>';
       
  1076             else $e .= $c;
       
  1077           }
       
  1078           echo 'Injection attempt caught at pos: '.$i.'<br />';
       
  1079         }
       
  1080         return false;
       
  1081       }
       
  1082     }
       
  1083     if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
       
  1084     {
       
  1085       if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
       
  1086       return false;
       
  1087     }
       
  1088     return true;
       
  1089   }
       
  1090   
       
  1091   /**
       
  1092    * Set the internal result pointer to X
       
  1093    * @param int $pos The number of the row
       
  1094    * @param resource $result The MySQL result resource - if not given, the latest cached query is assumed
       
  1095    * @return true on success, false on failure
       
  1096    */
       
  1097    
       
  1098   function sql_data_seek($pos, $result = false)
       
  1099   {
       
  1100     $this->enable_errorhandler();
       
  1101     if(!$result)
       
  1102       $result = $this->latest_result;
       
  1103     if(!$result)
       
  1104     {
       
  1105       $this->disable_errorhandler();
       
  1106       return false;
       
  1107     }
       
  1108     if(pg_result_seek($result, $pos))
       
  1109     {
       
  1110       $this->disable_errorhandler();
       
  1111       return true;
       
  1112     }
       
  1113     else
       
  1114     {
       
  1115       $this->disable_errorhandler();
       
  1116       return false;
       
  1117     }
       
  1118   }
       
  1119   
       
  1120   /**
       
  1121    * Reports a bad query to the admin
       
  1122    * @param string $query the naughty query
       
  1123    * @access private
       
  1124    */
       
  1125    
       
  1126   function report_query($query)
       
  1127   {
       
  1128     global $session;
       
  1129     if(is_object($session) && defined('ENANO_MAINSTREAM'))
       
  1130       $username = $session->username;
       
  1131     else
       
  1132       $username = 'Unavailable';
       
  1133     $query = $this->escape($query);
       
  1134     $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            edit_summary)
       
  1135                                                      VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', \''.$_SERVER['REMOTE_ADDR'].'\');');
       
  1136   }
       
  1137   
       
  1138   /**
       
  1139    * Returns the ID of the row last inserted.
       
  1140    * @return int
       
  1141    */
       
  1142   
       
  1143   function insert_id()
       
  1144   {
       
  1145     return @pg_last_oid();
       
  1146   }
       
  1147   
       
  1148   function fetchrow($r = false) {
       
  1149     $this->enable_errorhandler();
       
  1150     if(!$this->_conn) return false;
       
  1151     if(!$r) $r = $this->latest_result;
       
  1152     if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1153     $row = pg_fetch_assoc($r);
       
  1154     $this->disable_errorhandler();
       
  1155     return $row;
       
  1156   }
       
  1157   
       
  1158   function fetchrow_num($r = false) {
       
  1159     $this->enable_errorhandler();
       
  1160     if(!$r) $r = $this->latest_result;
       
  1161     if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1162     $row = pg_fetch_row($r);
       
  1163     $this->disable_errorhandler();
       
  1164     return $row;
       
  1165   }
       
  1166   
       
  1167   function numrows($r = false) {
       
  1168     $this->enable_errorhandler();
       
  1169     if(!$r) $r = $this->latest_result;
       
  1170     if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1171     $n = pg_num_rows($r);
       
  1172     $this->disable_errorhandler();
       
  1173     return $n;
       
  1174   }
       
  1175   
       
  1176   function escape($str)
       
  1177   {
       
  1178     $this->enable_errorhandler();
       
  1179     $str = pg_escape_string($str);
       
  1180     $this->disable_errorhandler();
       
  1181     return $str;
       
  1182   }
       
  1183   
       
  1184   function free_result($result = false)
       
  1185   {
       
  1186     $this->enable_errorhandler();
       
  1187     if(!$result)
       
  1188       $result = $this->latest_result;
       
  1189     if(!$result)
       
  1190     {
       
  1191       $this->disable_errorhandler();
       
  1192       return null;
       
  1193     }
       
  1194     pg_free_result($result);
       
  1195     $this->disable_errorhandler();
       
  1196     return null;
       
  1197   }
       
  1198   
       
  1199   function close() {
       
  1200     pg_close($this->_conn);
       
  1201     unset($this->_conn);
       
  1202   }
       
  1203   
       
  1204   // phpBB DBAL compatibility
       
  1205   function sql_fetchrow($r = false)
       
  1206   {
       
  1207     return $this->fetchrow($r);
       
  1208   }
       
  1209   function sql_freeresult($r = false)
       
  1210   {
       
  1211     if(!$this->_conn) return false;
       
  1212     if(!$r) $r = $this->latest_result;
       
  1213     if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1214     $this->free_result($r);
       
  1215   }
       
  1216   function sql_numrows($r = false)
       
  1217   {
       
  1218     return $this->numrows();
       
  1219   }
       
  1220   function sql_affectedrows($r = false, $f, $n)
       
  1221   {
       
  1222     if(!$this->_conn) return false;
       
  1223     if(!$r) $r = $this->latest_result;
       
  1224     if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1225     return pg_affected_rows();
       
  1226   }
       
  1227   
       
  1228   function sql_type_cast(&$value)
       
  1229 	{
       
  1230 		if ( is_float($value) )
       
  1231 		{
       
  1232 			return doubleval($value);
       
  1233 		}
       
  1234 		if ( is_integer($value) || is_bool($value) )
       
  1235 		{
       
  1236 			return intval($value);
       
  1237 		}
       
  1238 		if ( is_string($value) || empty($value) )
       
  1239 		{
       
  1240 			return '\'' . $this->sql_escape_string($value) . '\'';
       
  1241 		}
       
  1242 		// uncastable var : let's do a basic protection on it to prevent sql injection attempt
       
  1243 		return '\'' . $this->sql_escape_string(htmlspecialchars($value)) . '\'';
       
  1244 	}
       
  1245 
       
  1246 	function sql_statement(&$fields, $fields_inc='')
       
  1247 	{
       
  1248 		// init result
       
  1249 		$this->sql_fields = $this->sql_values = $this->sql_update = '';
       
  1250 		if ( empty($fields) && empty($fields_inc) )
       
  1251 		{
       
  1252 			return;
       
  1253 		}
       
  1254 
       
  1255 		// process
       
  1256 		if ( !empty($fields) )
       
  1257 		{
       
  1258 			$first = true;
       
  1259 			foreach ( $fields as $field => $value )
       
  1260 			{
       
  1261 				// field must contain a field name
       
  1262 				if ( !empty($field) && is_string($field) )
       
  1263 				{
       
  1264 					$value = $this->sql_type_cast($value);
       
  1265 					$this->sql_fields .= ( $first ? '' : ', ' ) . $field;
       
  1266 					$this->sql_values .= ( $first ? '' : ', ' ) . $value;
       
  1267 					$this->sql_update .= ( $first ? '' : ', ' ) . $field . ' = ' . $value;
       
  1268 					$first = false;
       
  1269 				}
       
  1270 			}
       
  1271 		}
       
  1272 		if ( !empty($fields_inc) )
       
  1273 		{
       
  1274 			foreach ( $fields_inc as $field => $indent )
       
  1275 			{
       
  1276 				if ( $indent != 0 )
       
  1277 				{
       
  1278 					$this->sql_update .= (empty($this->sql_update) ? '' : ', ') . $field . ' = ' . $field . ($indent < 0 ? ' - ' : ' + ') . abs($indent);
       
  1279 				}
       
  1280 			}
       
  1281 		}
       
  1282 	}
       
  1283 
       
  1284 	function sql_stack_reset($id='')
       
  1285 	{
       
  1286 		if ( empty($id) )
       
  1287 		{
       
  1288 			$this->sql_stack_fields = array();
       
  1289 			$this->sql_stack_values = array();
       
  1290 		}
       
  1291 		else
       
  1292 		{
       
  1293 			$this->sql_stack_fields[$id] = array();
       
  1294 			$this->sql_stack_values[$id] = array();
       
  1295 		}
       
  1296 	}
       
  1297 
       
  1298 	function sql_stack_statement(&$fields, $id='')
       
  1299 	{
       
  1300 		$this->sql_statement($fields);
       
  1301 		if ( empty($id) )
       
  1302 		{
       
  1303 			$this->sql_stack_fields = $this->sql_fields;
       
  1304 			$this->sql_stack_values[] = '(' . $this->sql_values . ')';
       
  1305 		}
       
  1306 		else
       
  1307 		{
       
  1308 			$this->sql_stack_fields[$id] = $this->sql_fields;
       
  1309 			$this->sql_stack_values[$id][] = '(' . $this->sql_values . ')';
       
  1310 		}
       
  1311 	}
       
  1312 
       
  1313 	function sql_stack_insert($table, $transaction=false, $line='', $file='', $break_on_error=true, $id='')
       
  1314 	{
       
  1315 		if ( (empty($id) && empty($this->sql_stack_values)) || (!empty($id) && empty($this->sql_stack_values[$id])) )
       
  1316 		{
       
  1317 			return false;
       
  1318 		}
       
  1319 		switch( SQL_LAYER )
       
  1320 		{
       
  1321 			case 'mysql':
       
  1322 			case 'mysql4':
       
  1323 				if ( empty($id) )
       
  1324 				{
       
  1325 					$sql = 'INSERT INTO ' . $table . '
       
  1326 								(' . $this->sql_stack_fields . ') VALUES ' . implode(",\n", $this->sql_stack_values);
       
  1327 				}
       
  1328 				else
       
  1329 				{
       
  1330 					$sql = 'INSERT INTO ' . $table . '
       
  1331 								(' . $this->sql_stack_fields[$id] . ') VALUES ' . implode(",\n", $this->sql_stack_values[$id]);
       
  1332 				}
       
  1333 				$this->sql_stack_reset($id);
       
  1334 				return $this->sql_query($sql, $transaction, $line, $file, $break_on_error);
       
  1335 				break;
       
  1336 			default:
       
  1337 				$count_sql_stack_values = empty($id) ? count($this->sql_stack_values) : count($this->sql_stack_values[$id]);
       
  1338 				$result = !empty($count_sql_stack_values);
       
  1339 				for ( $i = 0; $i < $count_sql_stack_values; $i++ )
       
  1340 				{
       
  1341 					if ( empty($id) )
       
  1342 					{
       
  1343 						$sql = 'INSERT INTO ' . $table . '
       
  1344 									(' . $this->sql_stack_fields . ') VALUES ' . $this->sql_stack_values[$i];
       
  1345 					}
       
  1346 					else
       
  1347 					{
       
  1348 						$sql = 'INSERT INTO ' . $table . '
       
  1349 									(' . $this->sql_stack_fields[$id] . ') VALUES ' . $this->sql_stack_values[$id][$i];
       
  1350 					}
       
  1351 					$result &= $this->sql_query($sql, $transaction, $line, $file, $break_on_error);
       
  1352 				}
       
  1353 				$this->sql_stack_reset($id);
       
  1354 				return $result;
       
  1355 				break;
       
  1356 		}
       
  1357 	}
       
  1358 
       
  1359 	function sql_subquery($field, $sql, $line='', $file='', $break_on_error=true, $type=TYPE_INT)
       
  1360 	{
       
  1361 		// sub-queries doable
       
  1362 		$this->sql_get_version();
       
  1363 		if ( !in_array(SQL_LAYER, array('mysql', 'mysql4')) || (($this->sql_version[0] + ($this->sql_version[1] / 100)) >= 4.01) )
       
  1364 		{
       
  1365 			return $sql;
       
  1366 		}
       
  1367 
       
  1368 		// no sub-queries
       
  1369 		$ids = array();
       
  1370 		$result = $this->sql_query(trim($sql), false, $line, $file, $break_on_error);
       
  1371 		while ( $row = $this->sql_fetchrow($result) )
       
  1372 		{
       
  1373 			$ids[] = $type == TYPE_INT ? intval($row[$field]) : '\'' . $this->sql_escape_string($row[$field]) . '\'';
       
  1374 		}
       
  1375 		$this->sql_freeresult($result);
       
  1376 		return empty($ids) ? 'NULL' : implode(', ', $ids);
       
  1377 	}
       
  1378 
       
  1379 	function sql_col_id($expr, $alias)
       
  1380 	{
       
  1381 		$this->sql_get_version();
       
  1382 		return in_array(SQL_LAYER, array('mysql', 'mysql4')) && (($this->sql_version[0] + ($this->sql_version[1] / 100)) <= 4.01) ? $alias : $expr;
       
  1383 	}
       
  1384 
       
  1385 	function sql_get_version()
       
  1386 	{
       
  1387 		if ( empty($this->sql_version) )
       
  1388 		{
       
  1389 			$this->sql_version = array(0, 0, 0);
       
  1390 			switch ( SQL_LAYER )
       
  1391 			{
       
  1392 				case 'mysql':
       
  1393 				case 'mysql4':
       
  1394 					if ( function_exists('mysql_get_server_info') )
       
  1395 					{
       
  1396 						$lo_version = explode('-', mysql_get_server_info());
       
  1397 						$this->sql_version = explode('.', $lo_version[0]);
       
  1398 						$this->sql_version = array(intval($this->sql_version[0]), intval($this->sql_version[1]), intval($this->sql_version[2]), $lo_version[1]);
       
  1399 					}
       
  1400 					break;
       
  1401 
       
  1402 				case 'postgresql':
       
  1403 				case 'mssql':
       
  1404 				case 'mssql-odbc':
       
  1405 				default:
       
  1406 					break;
       
  1407 			}
       
  1408 		}
       
  1409 		return $this->sql_version;
       
  1410 	}
       
  1411 
       
  1412 	function sql_error()
       
  1413 	{
       
  1414 		if ( $this->_conn )
       
  1415 		{
       
  1416 			return mysql_error();
       
  1417 		}
       
  1418 		else
       
  1419 		{
       
  1420 			return array();
       
  1421 		}
       
  1422 	}
       
  1423   function sql_escape_string($t) 
       
  1424   {
       
  1425     return mysql_real_escape_string($t);
       
  1426   }
       
  1427   function sql_close()
       
  1428   {
       
  1429     $this->close();
       
  1430   }
       
  1431   function sql_fetchrowset($query_id = 0)
       
  1432 	{
       
  1433 		if( !$query_id )
       
  1434 		{
       
  1435 			$query_id = $this->query_result;
       
  1436 		}
       
  1437 
       
  1438 		if( $query_id )
       
  1439 		{
       
  1440 			unset($this->rowset[$query_id]);
       
  1441 			unset($this->row[$query_id]);
       
  1442 
       
  1443 			while($this->rowset[$query_id] = mysql_fetch_array($query_id, MYSQL_ASSOC))
       
  1444 			{
       
  1445 				$result[] = $this->rowset[$query_id];
       
  1446 			}
       
  1447 
       
  1448 			return $result;
       
  1449 		}
       
  1450 		else
       
  1451 		{
       
  1452 			return false;
       
  1453 		}
       
  1454 	}
       
  1455   /**
       
  1456    * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
       
  1457    */
       
  1458   
       
  1459   function sql_report()
       
  1460   {
       
  1461     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1462     if ( !$session->get_permissions('mod_misc') )
       
  1463     {
       
  1464       die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
       
  1465     }
       
  1466     // Create copies of variables that may be changed after header is called
       
  1467     $backtrace = $this->query_backtrace;
       
  1468     $times = $this->query_times;
       
  1469     $template->header();
       
  1470     echo '<h3>SQL query log and timetable</h3>';
       
  1471     echo '<div class="tblholder">
       
  1472             <table border="0" cellspacing="1" cellpadding="4">';
       
  1473     $i = 0;
       
  1474     foreach ( $backtrace as $query )
       
  1475     {
       
  1476       $i++;
       
  1477       $unbuffered = false;
       
  1478       if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
       
  1479       {
       
  1480         $query = substr($query, 13);
       
  1481         $unbuffered = true;
       
  1482       }
       
  1483       if ( $i == 1 )
       
  1484       {
       
  1485         echo '<tr>
       
  1486                 <th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
       
  1487               </tr>';
       
  1488       }
       
  1489       else
       
  1490       {
       
  1491         echo '<tr>
       
  1492                 <th class="subhead" colspan="2">&nbsp;</th>
       
  1493               </tr>';
       
  1494       }
       
  1495       echo '<tr>
       
  1496               <td class="row2">Query:</td>
       
  1497               <td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
       
  1498             </tr>
       
  1499             <tr>
       
  1500               <td class="row2">Time:</td>
       
  1501               <td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
       
  1502             </tr>
       
  1503             <tr>
       
  1504               <td class="row2">Unbuffered:</td>
       
  1505               <td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
       
  1506             </tr>';
       
  1507       if ( isset($this->query_sources[$query]) )
       
  1508       {
       
  1509         echo '<tr>
       
  1510                 <td class="row2">Called from:</td>
       
  1511                 <td class="row1">' . $this->query_sources[$query] . '</td>
       
  1512               </tr>';
       
  1513       }
       
  1514     }
       
  1515     if ( function_exists('array_sum') )
       
  1516     {
       
  1517       $query_time_total = array_sum($this->query_times);
       
  1518       echo '<tr>
       
  1519               <th class="subhead" colspan="2">
       
  1520                 Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
       
  1521               </th>
       
  1522             </tr>';
       
  1523     }
       
  1524     echo '  </table>
       
  1525           </div>';
       
  1526     $template->footer();
       
  1527   }
       
  1528 }
       
  1529 
   781 ?>
  1530 ?>