includes/dbal.php
changeset 1227 bdac73ed481e
parent 1207 044b0062e3bf
child 1342 2164d18cb10c
equal deleted inserted replaced
1226:de56132c008d 1227:bdac73ed481e
    11  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
    11  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
    12  */
    12  */
    13  
    13  
    14 function db_error_handler($errno, $errstr, $errfile = false, $errline = false, $errcontext = Array() )
    14 function db_error_handler($errno, $errstr, $errfile = false, $errline = false, $errcontext = Array() )
    15 {
    15 {
    16   if ( !defined('ENANO_DEBUG') )
    16 	if ( !defined('ENANO_DEBUG') )
    17     return;
    17 		return;
    18   $e = error_reporting(0);
    18 	$e = error_reporting(0);
    19   error_reporting($e);
    19 	error_reporting($e);
    20   if ( $e < $errno )
    20 	if ( $e < $errno )
    21     return;
    21 		return;
    22   $errtype = 'Notice';
    22 	$errtype = 'Notice';
    23   switch ( $errno )
    23 	switch ( $errno )
    24   {
    24 	{
    25     case E_ERROR: case E_USER_ERROR: case E_CORE_ERROR: case E_COMPILE_ERROR: $errtype = 'Error'; break;
    25 		case E_ERROR: case E_USER_ERROR: case E_CORE_ERROR: case E_COMPILE_ERROR: $errtype = 'Error'; break;
    26     case E_WARNING: case E_USER_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: $errtype = 'Warning'; break;
    26 		case E_WARNING: case E_USER_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: $errtype = 'Warning'; break;
    27   }
    27 	}
    28   $debug = debug_backtrace();
    28 	$debug = debug_backtrace();
    29   if ( !isset($debug[0]['file']) )
    29 	if ( !isset($debug[0]['file']) )
    30     return false;
    30 		return false;
    31   $debug = $debug[0]['file'] . ', line ' . $debug[0]['line'];
    31 	$debug = $debug[0]['file'] . ', line ' . $debug[0]['line'];
    32   echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>";
    32 	echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>";
    33 }
    33 }
    34 
    34 
    35 global $db_sql_parse_time;
    35 global $db_sql_parse_time;
    36 $db_sql_parse_time = 0;
    36 $db_sql_parse_time = 0;
    37 
    37 
    38 class mysql {
    38 class mysql {
    39   var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
    39 	var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
    40   var $row = array();
    40 	var $row = array();
    41 	var $rowset = array();
    41 	var $rowset = array();
    42   var $errhandler;
    42 	var $errhandler;
    43   var $dbms_name = 'MySQL';
    43 	var $dbms_name = 'MySQL';
    44   
    44 	
    45   /**
    45 	/**
    46    * Get a flat textual list of queries that have been made.
    46  	* Get a flat textual list of queries that have been made.
    47    */
    47  	*/
    48   
    48 	
    49   function sql_backtrace()
    49 	function sql_backtrace()
    50   {
    50 	{
    51     return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
    51 		return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
    52   }
    52 	}
    53   
    53 	
    54   /**
    54 	/**
    55    * Connect to the database, but only if a connection isn't already up.
    55  	* Connect to the database, but only if a connection isn't already up.
    56    */
    56  	*/
    57   
    57 	
    58   function ensure_connection()
    58 	function ensure_connection()
    59   {
    59 	{
    60     if(!$this->_conn)
    60 		if(!$this->_conn)
    61     {
    61 		{
    62       $this->connect();
    62 			$this->connect();
    63     }
    63 		}
    64   }
    64 	}
    65   
    65 	
    66   /**
    66 	/**
    67    * Exit Enano, dumping out a friendly error message indicating a database error on the way out.
    67  	* Exit Enano, dumping out a friendly error message indicating a database error on the way out.
    68    * @param string Description or location of error; defaults to none
    68  	* @param string Description or location of error; defaults to none
    69    */
    69  	*/
    70  
    70  
    71   function _die($t = '')
    71 	function _die($t = '')
    72   {
    72 	{
    73     if ( defined('ENANO_HEADERS_SENT') )
    73 		if ( defined('ENANO_HEADERS_SENT') )
    74       ob_clean();
    74 			ob_clean();
    75     
    75 		
    76     $internal_text = $this->get_error($t);
    76 		$internal_text = $this->get_error($t);
    77     
    77 		
    78     if ( defined('ENANO_CONFIG_FETCHED') )
    78 		if ( defined('ENANO_CONFIG_FETCHED') )
    79       // config is in, we can show a slightly nicer looking error page
    79 			// config is in, we can show a slightly nicer looking error page
    80       die_semicritical('Database error', $internal_text);
    80 			die_semicritical('Database error', $internal_text);
    81     else
    81 		else
    82       // no config, display using no-DB template engine
    82 			// no config, display using no-DB template engine
    83       grinding_halt('Database error', $internal_text);
    83 			grinding_halt('Database error', $internal_text);
    84     
    84 		
    85     exit;
    85 		exit;
    86   }
    86 	}
    87   
    87 	
    88   /**
    88 	/**
    89    * Get the internal text used for a database error message.
    89  	* Get the internal text used for a database error message.
    90    * @param string Description or location of error; defaults to none
    90  	* @param string Description or location of error; defaults to none
    91    */
    91  	*/
    92   
    92 	
    93   function get_error($t = '')
    93 	function get_error($t = '')
    94   {
    94 	{
    95     @header('HTTP/1.1 500 Internal Server Error');
    95 		@header('HTTP/1.1 500 Internal Server Error');
    96     
    96 		
    97     $bt = $this->latest_query;
    97 		$bt = $this->latest_query;
    98     $e = htmlspecialchars($this->sql_error());
    98 		$e = htmlspecialchars($this->sql_error());
    99     if ( empty($e) )
    99 		if ( empty($e) )
   100       $e = '&lt;none&gt;';
   100 			$e = '&lt;none&gt;';
   101     
   101 		
   102     global $email;
   102 		global $email;
   103     
   103 		
   104     // As long as the admin's e-mail is accessible, display it.
   104 		// As long as the admin's e-mail is accessible, display it.
   105     $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) )
   105 		$email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) )
   106                     ? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;'
   106 										? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;'
   107                     : '';
   107 										: '';
   108     
   108 		
   109     $internal_text = "<h3>The site was unable to finish serving your request.</h3>
   109 		$internal_text = "<h3>The site was unable to finish serving your request.</h3>
   110                       <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>
   110 											<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>
   111                       <p>Description or location of error: $t<br />
   111 											<p>Description or location of error: $t<br />
   112                       Error returned by $this->dbms_name extension: $e</p>
   112 											Error returned by $this->dbms_name extension: $e</p>
   113                       <p>Most recent SQL query:</p>
   113 											<p>Most recent SQL query:</p>
   114                       <pre>$bt</pre>";
   114 											<pre>$bt</pre>";
   115     return $internal_text;
   115 		return $internal_text;
   116   }
   116 	}
   117   
   117 	
   118   /**
   118 	/**
   119    * Exit Enano and output a JSON format datbase error.
   119  	* Exit Enano and output a JSON format datbase error.
   120    * @param string Description or location of error; defaults to none
   120  	* @param string Description or location of error; defaults to none
   121    */
   121  	*/
   122   
   122 	
   123   function die_json($loc = false)
   123 	function die_json($loc = false)
   124   {
   124 	{
   125     $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error())));
   125 		$e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error())));
   126     $q = str_replace("\n", "\\n", addslashes($this->latest_query));
   126 		$q = str_replace("\n", "\\n", addslashes($this->latest_query));
   127     $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : "";
   127 		$loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : "";
   128     $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org.";
   128 		$loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org.";
   129     $loc = str_replace("\n", "\\n", $loc);
   129 		$loc = str_replace("\n", "\\n", $loc);
   130     $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n  $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}";
   130 		$t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n  $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}";
   131     die($t);
   131 		die($t);
   132   }
   132 	}
   133   
   133 	
   134   /**
   134 	/**
   135    * Connect to the database.
   135  	* Connect to the database.
   136    * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file.
   136  	* @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file.
   137    * @param string Database server hostname
   137  	* @param string Database server hostname
   138    * @param string Database server username
   138  	* @param string Database server username
   139    * @param string Database server password
   139  	* @param string Database server password
   140    * @param string Name of the database
   140  	* @param string Name of the database
   141    * @param int Optional port number to connect over
   141  	* @param int Optional port number to connect over
   142    */
   142  	*/
   143   
   143 	
   144   function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false)
   144 	function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false)
   145   {
   145 	{
   146     if ( !defined('ENANO_SQL_CONSTANTS') )
   146 		if ( !defined('ENANO_SQL_CONSTANTS') )
   147     {
   147 		{
   148       define('ENANO_SQL_CONSTANTS', '');
   148 			define('ENANO_SQL_CONSTANTS', '');
   149       define('ENANO_DBLAYER', 'MYSQL');
   149 			define('ENANO_DBLAYER', 'MYSQL');
   150       define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
   150 			define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
   151       define('ENANO_SQL_MULTISTRING_PRFIX', '');
   151 			define('ENANO_SQL_MULTISTRING_PRFIX', '');
   152       define('ENANO_SQL_BOOLEAN_TRUE', 'true');
   152 			define('ENANO_SQL_BOOLEAN_TRUE', 'true');
   153       define('ENANO_SQL_BOOLEAN_FALSE', 'false');
   153 			define('ENANO_SQL_BOOLEAN_FALSE', 'false');
   154     }
   154 		}
   155     
   155 		
   156     if ( !$manual_credentials )
   156 		if ( !$manual_credentials )
   157     {
   157 		{
   158       if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
   158 			if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
   159       {
   159 			{
   160         @include(ENANO_ROOT.'/config.new.php');
   160 				@include(ENANO_ROOT.'/config.new.php');
   161       }
   161 			}
   162       else
   162 			else
   163       {
   163 			{
   164         @include(ENANO_ROOT.'/config.php');
   164 				@include(ENANO_ROOT.'/config.php');
   165       }
   165 			}
   166       
   166 			
   167       if ( isset($crypto_key) )
   167 			if ( isset($crypto_key) )
   168         unset($crypto_key); // Get this sucker out of memory fast
   168 				unset($crypto_key); // Get this sucker out of memory fast
   169       if ( empty($dbport) )
   169 			if ( empty($dbport) )
   170         $dbport = 3306;
   170 				$dbport = 3306;
   171       
   171 			
   172       if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
   172 			if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
   173       {
   173 			{
   174         // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
   174 				// scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
   175         if ( !defined('scriptPath') )
   175 				if ( !defined('scriptPath') )
   176         {
   176 				{
   177           if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
   177 					if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
   178           {
   178 					{
   179             $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
   179 						$_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
   180           }
   180 					}
   181           if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
   181 					if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
   182           {
   182 					{
   183             // user requested http://foo/enano as opposed to http://foo/enano/index.php
   183 						// user requested http://foo/enano as opposed to http://foo/enano/index.php
   184             $_SERVER['REQUEST_URI'] .= '/index.php';
   184 						$_SERVER['REQUEST_URI'] .= '/index.php';
   185           }
   185 					}
   186           $sp = dirname($_SERVER['REQUEST_URI']);
   186 					$sp = dirname($_SERVER['REQUEST_URI']);
   187           if($sp == '/' || $sp == '\\') $sp = '';
   187 					if($sp == '/' || $sp == '\\') $sp = '';
   188           define('scriptPath', $sp);
   188 					define('scriptPath', $sp);
   189           define('contentPath', "$sp/index.php?title=");
   189 					define('contentPath', "$sp/index.php?title=");
   190         }
   190 				}
   191         $loc = scriptPath . '/install/index.php';
   191 				$loc = scriptPath . '/install/index.php';
   192         define('IN_ENANO_INSTALL', 1);
   192 				define('IN_ENANO_INSTALL', 1);
   193         $GLOBALS['lang'] = new Language('eng');
   193 				$GLOBALS['lang'] = new Language('eng');
   194         global $lang;
   194 				global $lang;
   195         $lang->load_file('./language/english/core.json');
   195 				$lang->load_file('./language/english/core.json');
   196         $lang->load_file('./language/english/install.json');
   196 				$lang->load_file('./language/english/install.json');
   197         // header("Location: $loc");
   197 				// header("Location: $loc");
   198         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...', 0);
   198 				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...', 0);
   199         exit;
   199 				exit;
   200       }
   200 			}
   201     }
   201 		}
   202     
   202 		
   203     if ( !$dbport )
   203 		if ( !$dbport )
   204       $dbport = 3306;
   204 			$dbport = 3306;
   205     
   205 		
   206     if ( $dbhost && !empty($dbport) && $dbport != 3306 )
   206 		if ( $dbhost && !empty($dbport) && $dbport != 3306 )
   207       $dbhost = '127.0.0.1';
   207 			$dbhost = '127.0.0.1';
   208     
   208 		
   209     $host_line = ( preg_match('/^:/', $dbhost) ) ? $dbhost : "{$dbhost}:{$dbport}";
   209 		$host_line = ( preg_match('/^:/', $dbhost) ) ? $dbhost : "{$dbhost}:{$dbport}";
   210     
   210 		
   211     $this->_conn = @mysql_connect($host_line, $dbuser, $dbpasswd);
   211 		$this->_conn = @mysql_connect($host_line, $dbuser, $dbpasswd);
   212     unset($dbuser);
   212 		unset($dbuser);
   213     unset($dbpasswd); // Security
   213 		unset($dbpasswd); // Security
   214     
   214 		
   215     if ( !$this->_conn && !$manual_credentials )
   215 		if ( !$this->_conn && !$manual_credentials )
   216     {
   216 		{
   217       grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to MySQL.<br />'.mysql_error().'</p>');
   217 			grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to MySQL.<br />'.mysql_error().'</p>');
   218     }
   218 		}
   219     else if ( !$this->_conn && $manual_credentials )
   219 		else if ( !$this->_conn && $manual_credentials )
   220     {
   220 		{
   221       return false;
   221 			return false;
   222     }
   222 		}
   223     
   223 		
   224     // Reset some variables
   224 		// Reset some variables
   225     $this->query_backtrace = array();
   225 		$this->query_backtrace = array();
   226     $this->query_times = array();
   226 		$this->query_times = array();
   227     $this->query_sources = array();
   227 		$this->query_sources = array();
   228     $this->num_queries = 0;
   228 		$this->num_queries = 0;
   229     
   229 		
   230     $this->debug = ( defined('ENANO_DEBUG') );
   230 		$this->debug = ( defined('ENANO_DEBUG') );
   231     
   231 		
   232     $q = @mysql_select_db($dbname);
   232 		$q = @mysql_select_db($dbname);
   233     
   233 		
   234     if ( !$q )
   234 		if ( !$q )
   235     {
   235 		{
   236       if ( $manual_credentials )
   236 			if ( $manual_credentials )
   237         return false;
   237 				return false;
   238       $this->_die('The database could not be selected.');
   238 			$this->_die('The database could not be selected.');
   239     }
   239 		}
   240     
   240 		
   241     // We're in!
   241 		// We're in!
   242     return true;
   242 		return true;
   243   }
   243 	}
   244   
   244 	
   245   /**
   245 	/**
   246    * Make a SQL query.
   246  	* Make a SQL query.
   247    * @param string Query
   247  	* @param string Query
   248    * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   248  	* @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   249    * @return resource or false on failure
   249  	* @return resource or false on failure
   250    */
   250  	*/
   251   
   251 	
   252   function sql_query($q, $log_query = true)
   252 	function sql_query($q, $log_query = true)
   253   {
   253 	{
   254     if ( $this->debug && function_exists('debug_backtrace') )
   254 		if ( $this->debug && function_exists('debug_backtrace') )
   255     {
   255 		{
   256       $backtrace = @debug_backtrace();
   256 			$backtrace = @debug_backtrace();
   257       if ( is_array($backtrace) )
   257 			if ( is_array($backtrace) )
   258       {
   258 			{
   259         $bt = $backtrace[0];
   259 				$bt = $backtrace[0];
   260         if ( isset($backtrace[1]['class']) )
   260 				if ( isset($backtrace[1]['class']) )
   261         {
   261 				{
   262           if ( $backtrace[1]['class'] == 'sessionManager' )
   262 					if ( $backtrace[1]['class'] == 'sessionManager' )
   263           {
   263 					{
   264             $bt = $backtrace[1];
   264 						$bt = $backtrace[1];
   265           }
   265 					}
   266         }
   266 				}
   267         $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
   267 				$this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
   268       }
   268 			}
   269       unset($backtrace);
   269 			unset($backtrace);
   270     }
   270 		}
   271     
   271 		
   272     $this->num_queries++;
   272 		$this->num_queries++;
   273     if ( $log_query || defined('ENANO_DEBUG') )
   273 		if ( $log_query || defined('ENANO_DEBUG') )
   274     {
   274 		{
   275       $this->query_backtrace[] = $q;
   275 			$this->query_backtrace[] = $q;
   276       $this->latest_query = $q;
   276 			$this->latest_query = $q;
   277     }
   277 		}
   278     // First make sure we have a connection
   278 		// First make sure we have a connection
   279     if ( !$this->_conn )
   279 		if ( !$this->_conn )
   280     {
   280 		{
   281       $this->_die('A database connection has not yet been established.');
   281 			$this->_die('A database connection has not yet been established.');
   282     }
   282 		}
   283     // Start the timer
   283 		// Start the timer
   284     if ( $log_query || defined('ENANO_DEBUG') )
   284 		if ( $log_query || defined('ENANO_DEBUG') )
   285       $time_start = microtime_float();
   285 			$time_start = microtime_float();
   286     // Does this query look malicious?
   286 		// Does this query look malicious?
   287     if ( $log_query || defined('ENANO_DEBUG') )
   287 		if ( $log_query || defined('ENANO_DEBUG') )
   288     {
   288 		{
   289       if ( !$this->check_query($q) )
   289 			if ( !$this->check_query($q) )
   290       {
   290 			{
   291         $this->report_query($q);
   291 				$this->report_query($q);
   292         $debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : '';
   292 				$debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : '';
   293         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>' . $debug);
   293 				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>' . $debug);
   294       }
   294 			}
   295     }
   295 		}
   296     
   296 		
   297     $r = mysql_query($q, $this->_conn);
   297 		$r = mysql_query($q, $this->_conn);
   298     
   298 		
   299     if ( $log_query )
   299 		if ( $log_query )
   300       $this->query_times[$q] = microtime_float() - $time_start;
   300 			$this->query_times[$q] = microtime_float() - $time_start;
   301     
   301 		
   302     $this->latest_result = $r;
   302 		$this->latest_result = $r;
   303     
   303 		
   304     return $r;
   304 		return $r;
   305   }
   305 	}
   306   
   306 	
   307   /**
   307 	/**
   308    * Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results.
   308  	* Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results.
   309    * @param string Query
   309  	* @param string Query
   310    * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   310  	* @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   311    * @return resource or false on failure
   311  	* @return resource or false on failure
   312    */
   312  	*/
   313   
   313 	
   314   function sql_unbuffered_query($q, $log_query = true)
   314 	function sql_unbuffered_query($q, $log_query = true)
   315   {
   315 	{
   316     $this->num_queries++;
   316 		$this->num_queries++;
   317     if ( $log_query || defined('ENANO_DEBUG') )
   317 		if ( $log_query || defined('ENANO_DEBUG') )
   318       $this->query_backtrace[] = '(UNBUFFERED) ' . $q;
   318 			$this->query_backtrace[] = '(UNBUFFERED) ' . $q;
   319     $this->latest_query = $q;
   319 		$this->latest_query = $q;
   320     // First make sure we have a connection
   320 		// First make sure we have a connection
   321     if ( !$this->_conn )
   321 		if ( !$this->_conn )
   322     {
   322 		{
   323       $this->_die('A database connection has not yet been established.');
   323 			$this->_die('A database connection has not yet been established.');
   324     }
   324 		}
   325     // Does this query look malicious?
   325 		// Does this query look malicious?
   326     if ( !$this->check_query($q) )
   326 		if ( !$this->check_query($q) )
   327     {
   327 		{
   328       $this->report_query($q);
   328 			$this->report_query($q);
   329       $debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : '';
   329 			$debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : '';
   330       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>' . $debug);
   330 			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>' . $debug);
   331     }
   331 		}
   332     
   332 		
   333     $time_start = microtime_float();
   333 		$time_start = microtime_float();
   334     $r = @mysql_unbuffered_query($q, $this->_conn);
   334 		$r = @mysql_unbuffered_query($q, $this->_conn);
   335     $this->query_times[$q] = microtime_float() - $time_start;
   335 		$this->query_times[$q] = microtime_float() - $time_start;
   336     $this->latest_result = $r;
   336 		$this->latest_result = $r;
   337     return $r;
   337 		return $r;
   338   }
   338 	}
   339   
   339 	
   340   /**
   340 	/**
   341    * Performs heuristic analysis on a SQL query to check for known attack patterns.
   341  	* Performs heuristic analysis on a SQL query to check for known attack patterns.
   342    * @param string $q the query to check
   342  	* @param string $q the query to check
   343    * @return bool true if query passed check, otherwise false
   343  	* @return bool true if query passed check, otherwise false
   344    */
   344  	*/
   345   
   345 	
   346   function check_query($q, $debug = false)
   346 	function check_query($q, $debug = false)
   347   {
   347 	{
   348     global $db_sql_parse_time;
   348 		global $db_sql_parse_time;
   349     $ts = microtime_float();
   349 		$ts = microtime_float();
   350     
   350 		
   351     // remove properly escaped quotes
   351 		// remove properly escaped quotes
   352     $q = str_replace('\\\\', '', $q);
   352 		$q = str_replace('\\\\', '', $q);
   353     $q = str_replace(array("\\\"", "\\'"), '', $q);
   353 		$q = str_replace(array("\\\"", "\\'"), '', $q);
   354     
   354 		
   355     // make sure quotes match
   355 		// make sure quotes match
   356     foreach ( array("'", '"') as $quote )
   356 		foreach ( array("'", '"') as $quote )
   357     {
   357 		{
   358       $n_quotes = get_char_count($q, $quote);
   358 			$n_quotes = get_char_count($q, $quote);
   359       if ( $n_quotes % 2 == 1 )
   359 			if ( $n_quotes % 2 == 1 )
   360       {
   360 			{
   361         // mismatched quotes
   361 				// mismatched quotes
   362         if ( $debug ) echo "Found mismatched quotes in query; parsed:\n$q\n";
   362 				if ( $debug ) echo "Found mismatched quotes in query; parsed:\n$q\n";
   363         return false;
   363 				return false;
   364       }
   364 			}
   365       // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
   365 			// this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
   366       $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
   366 			$q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
   367     }
   367 		}
   368     $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
   368 		$q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
   369     
   369 		
   370     // quotes are now matched out. does this string have a comment marker in it?
   370 		// quotes are now matched out. does this string have a comment marker in it?
   371     if ( strstr($q, '--') )
   371 		if ( strstr($q, '--') )
   372     {
   372 		{
   373       return false;
   373 			return false;
   374     }
   374 		}
   375     
   375 		
   376     if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
   376 		if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
   377     {
   377 		{
   378       if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
   378 			if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
   379       return false;
   379 			return false;
   380     }
   380 		}
   381     
   381 		
   382     $ts = microtime_float() - $ts;
   382 		$ts = microtime_float() - $ts;
   383     $db_sql_parse_time += $ts;
   383 		$db_sql_parse_time += $ts;
   384     return true;
   384 		return true;
   385   }
   385 	}
   386   
   386 	
   387   /**
   387 	/**
   388    * Set the internal result pointer to X
   388  	* Set the internal result pointer to X
   389    * @param int $pos The number of the row
   389  	* @param int $pos The number of the row
   390    * @param resource $result The MySQL result resource - if not given, the latest cached query is assumed
   390  	* @param resource $result The MySQL result resource - if not given, the latest cached query is assumed
   391    * @return true on success, false on failure
   391  	* @return true on success, false on failure
   392    */
   392  	*/
   393    
   393  	
   394   function sql_data_seek($pos, $result = false)
   394 	function sql_data_seek($pos, $result = false)
   395   {
   395 	{
   396     if ( !$result )
   396 		if ( !$result )
   397       $result = $this->latest_result;
   397 			$result = $this->latest_result;
   398     if ( !$result )
   398 		if ( !$result )
   399       return false;
   399 			return false;
   400     
   400 		
   401     return mysql_data_seek($result, $pos) ? true : false;
   401 		return mysql_data_seek($result, $pos) ? true : false;
   402   }
   402 	}
   403   
   403 	
   404   /**
   404 	/**
   405    * Reports a bad query to the admin
   405  	* Reports a bad query to the admin
   406    * @param string $query the naughty query
   406  	* @param string $query the naughty query
   407    * @access private
   407  	* @access private
   408    */
   408  	*/
   409    
   409  	
   410   function report_query($query)
   410 	function report_query($query)
   411   {
   411 	{
   412     global $session;
   412 		global $session;
   413     if ( is_object($session) && defined('ENANO_MAINSTREAM') )
   413 		if ( is_object($session) && defined('ENANO_MAINSTREAM') )
   414     {
   414 		{
   415       $username = $session->username;
   415 			$username = $session->username;
   416       $user_id = $session->user_id;
   416 			$user_id = $session->user_id;
   417     }
   417 		}
   418     else
   418 		else
   419     {
   419 		{
   420       $username = 'Unavailable';
   420 			$username = 'Unavailable';
   421       $user_id = 1;
   421 			$user_id = 1;
   422     } 
   422 		} 
   423     
   423 		
   424     $query = $this->escape($query);
   424 		$query = $this->escape($query);
   425     $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            author_uid,       edit_summary)
   425 		$q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            author_uid,       edit_summary)
   426                                                      VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');');
   426  																										VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');');
   427   }
   427 	}
   428   
   428 	
   429   /**
   429 	/**
   430    * Returns the ID of the row last inserted.
   430  	* Returns the ID of the row last inserted.
   431    * @return int
   431  	* @return int
   432    */
   432  	*/
   433   
   433 	
   434   function insert_id()
   434 	function insert_id()
   435   {
   435 	{
   436     return @mysql_insert_id();
   436 		return @mysql_insert_id();
   437   }
   437 	}
   438   
   438 	
   439   /**
   439 	/**
   440    * Fetch one row from the given query as an associative array.
   440  	* Fetch one row from the given query as an associative array.
   441    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   441  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   442    * @return array
   442  	* @return array
   443    */
   443  	*/
   444   
   444 	
   445   function fetchrow($r = false)
   445 	function fetchrow($r = false)
   446   {
   446 	{
   447     if ( !$this->_conn )
   447 		if ( !$this->_conn )
   448       return false;
   448 			return false;
   449     
   449 		
   450     if ( !$r )
   450 		if ( !$r )
   451       $r = $this->latest_result;
   451 			$r = $this->latest_result;
   452     
   452 		
   453     if ( !$r )
   453 		if ( !$r )
   454       $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
   454 			$this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
   455     
   455 		
   456     $row = mysql_fetch_assoc($r);
   456 		$row = mysql_fetch_assoc($r);
   457     
   457 		
   458     return integerize_array($row);
   458 		return integerize_array($row);
   459   }
   459 	}
   460   
   460 	
   461   /**
   461 	/**
   462    * Fetch one row from the given query as a numeric array.
   462  	* Fetch one row from the given query as a numeric array.
   463    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   463  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   464    * @return array
   464  	* @return array
   465    */
   465  	*/
   466   
   466 	
   467   function fetchrow_num($r = false)
   467 	function fetchrow_num($r = false)
   468   {
   468 	{
   469     if ( !$r )
   469 		if ( !$r )
   470       $r = $this->latest_result;
   470 			$r = $this->latest_result;
   471     if ( !$r )
   471 		if ( !$r )
   472       $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
   472 			$this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
   473     
   473 		
   474     $row = mysql_fetch_row($r);
   474 		$row = mysql_fetch_row($r);
   475     return integerize_array($row);
   475 		return integerize_array($row);
   476   }
   476 	}
   477   
   477 	
   478   /**
   478 	/**
   479    * Get the number of results for a given query.
   479  	* Get the number of results for a given query.
   480    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   480  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   481    * @return array
   481  	* @return array
   482    */
   482  	*/
   483   
   483 	
   484   function numrows($r = false)
   484 	function numrows($r = false)
   485   {
   485 	{
   486     if ( !$r )
   486 		if ( !$r )
   487       $r = $this->latest_result;
   487 			$r = $this->latest_result;
   488     if ( !$r )
   488 		if ( !$r )
   489       $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
   489 			$this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
   490     
   490 		
   491     return mysql_num_rows($r);
   491 		return mysql_num_rows($r);
   492   }
   492 	}
   493   
   493 	
   494   /**
   494 	/**
   495    * Escape a string so that it may safely be included in a SQL query.
   495  	* Escape a string so that it may safely be included in a SQL query.
   496    * @param string String to escape
   496  	* @param string String to escape
   497    * @return string Escaped string
   497  	* @return string Escaped string
   498    */
   498  	*/
   499   
   499 	
   500   function escape($str)
   500 	function escape($str)
   501   {
   501 	{
   502     $str = mysql_real_escape_string($str);
   502 		$str = mysql_real_escape_string($str);
   503     return $str;
   503 		return $str;
   504   }
   504 	}
   505   
   505 	
   506   /**
   506 	/**
   507    * Free the given result from memory. Use this when completely finished with a result resource.
   507  	* Free the given result from memory. Use this when completely finished with a result resource.
   508    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   508  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
   509    * @return null
   509  	* @return null
   510    */
   510  	*/
   511   
   511 	
   512   function free_result($result = false)
   512 	function free_result($result = false)
   513   {
   513 	{
   514     if ( !$result )
   514 		if ( !$result )
   515       $result = $this->latest_result;
   515 			$result = $this->latest_result;
   516     if ( !$result )
   516 		if ( !$result )
   517       return null;
   517 			return null;
   518     
   518 		
   519     @mysql_free_result($result);
   519 		@mysql_free_result($result);
   520     return null;
   520 		return null;
   521   }
   521 	}
   522   
   522 	
   523   /**
   523 	/**
   524    * Returns the number of rows affected.
   524  	* Returns the number of rows affected.
   525    * @return int
   525  	* @return int
   526    */
   526  	*/
   527   
   527 	
   528   function sql_affectedrows()
   528 	function sql_affectedrows()
   529   {
   529 	{
   530     return mysql_affected_rows($this->_conn);
   530 		return mysql_affected_rows($this->_conn);
   531   }
   531 	}
   532   
   532 	
   533   /**
   533 	/**
   534    * Close the database connection
   534  	* Close the database connection
   535    */
   535  	*/
   536   
   536 	
   537   function close()
   537 	function close()
   538   {
   538 	{
   539     @mysql_close($this->_conn);
   539 		@mysql_close($this->_conn);
   540     unset($this->_conn);
   540 		unset($this->_conn);
   541   }
   541 	}
   542   
   542 	
   543   /**
   543 	/**
   544    * Get a list of columns in the given table
   544  	* Get a list of columns in the given table
   545    * @param string Table
   545  	* @param string Table
   546    * @return array
   546  	* @return array
   547    */
   547  	*/
   548   
   548 	
   549   function columns_in($table)
   549 	function columns_in($table)
   550   {
   550 	{
   551     if ( !is_string($table) )
   551 		if ( !is_string($table) )
   552       return false;
   552 			return false;
   553     $q = $this->sql_query("SHOW COLUMNS IN $table;");
   553 		$q = $this->sql_query("SHOW COLUMNS IN $table;");
   554     if ( !$q )
   554 		if ( !$q )
   555       $this->_die();
   555 			$this->_die();
   556     
   556 		
   557     $columns = array();
   557 		$columns = array();
   558     while ( $row = $this->fetchrow_num() )
   558 		while ( $row = $this->fetchrow_num() )
   559     {
   559 		{
   560       $columns[] = $row[0];
   560 			$columns[] = $row[0];
   561     }
   561 		}
   562     return $columns;
   562 		return $columns;
   563   }
   563 	}
   564   
   564 	
   565   /**
   565 	/**
   566    * Get the text of the most recent error.
   566  	* Get the text of the most recent error.
   567    * @return string
   567  	* @return string
   568    */
   568  	*/
   569   
   569 	
   570   function sql_error()
   570 	function sql_error()
   571 	{
   571 	{
   572     return mysql_error();
   572 		return mysql_error();
   573 	}
   573 	}
   574   
   574 	
   575   /**
   575 	/**
   576    * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
   576  	* Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
   577    */
   577  	*/
   578   
   578 	
   579   function sql_report()
   579 	function sql_report()
   580   {
   580 	{
   581     global $db, $session, $paths, $template, $plugins; // Common objects
   581 		global $db, $session, $paths, $template, $plugins; // Common objects
   582     if ( !$session->get_permissions('mod_misc') )
   582 		if ( !$session->get_permissions('mod_misc') )
   583     {
   583 		{
   584       die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
   584 			die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
   585     }
   585 		}
   586     // Create copies of variables that may be changed after header is called
   586 		// Create copies of variables that may be changed after header is called
   587     $backtrace = $this->query_backtrace;
   587 		$backtrace = $this->query_backtrace;
   588     $times = $this->query_times;
   588 		$times = $this->query_times;
   589     $template->header();
   589 		$template->header();
   590     echo '<h3>SQL query log and timetable</h3>';
   590 		echo '<h3>SQL query log and timetable</h3>';
   591     echo '<div class="tblholder">
   591 		echo '<div class="tblholder">
   592             <table border="0" cellspacing="1" cellpadding="4">';
   592 						<table border="0" cellspacing="1" cellpadding="4">';
   593     $i = 0;
   593 		$i = 0;
   594     foreach ( $backtrace as $query )
   594 		foreach ( $backtrace as $query )
   595     {
   595 		{
   596       $i++;
   596 			$i++;
   597       $unbuffered = false;
   597 			$unbuffered = false;
   598       if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
   598 			if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
   599       {
   599 			{
   600         $query = substr($query, 13);
   600 				$query = substr($query, 13);
   601         $unbuffered = true;
   601 				$unbuffered = true;
   602       }
   602 			}
   603       if ( $i == 1 )
   603 			if ( $i == 1 )
   604       {
   604 			{
   605         echo '<tr>
   605 				echo '<tr>
   606                 <th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
   606 								<th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
   607               </tr>';
   607 							</tr>';
   608       }
   608 			}
   609       else
   609 			else
   610       {
   610 			{
   611         echo '<tr>
   611 				echo '<tr>
   612                 <th class="subhead" colspan="2">&nbsp;</th>
   612 								<th class="subhead" colspan="2">&nbsp;</th>
   613               </tr>';
   613 							</tr>';
   614       }
   614 			}
   615       echo '<tr>
   615 			echo '<tr>
   616               <td class="row2">Query:</td>
   616 							<td class="row2">Query:</td>
   617               <td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
   617 							<td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
   618             </tr>
   618 						</tr>
   619             <tr>
   619 						<tr>
   620               <td class="row2">Time:</td>
   620 							<td class="row2">Time:</td>
   621               <td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
   621 							<td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
   622             </tr>
   622 						</tr>
   623             <tr>
   623 						<tr>
   624               <td class="row2">Unbuffered:</td>
   624 							<td class="row2">Unbuffered:</td>
   625               <td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
   625 							<td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
   626             </tr>';
   626 						</tr>';
   627       if ( isset($this->query_sources[$query]) )
   627 			if ( isset($this->query_sources[$query]) )
   628       {
   628 			{
   629         echo '<tr>
   629 				echo '<tr>
   630                 <td class="row2">Called from:</td>
   630 								<td class="row2">Called from:</td>
   631                 <td class="row1">' . $this->query_sources[$query] . '</td>
   631 								<td class="row1">' . $this->query_sources[$query] . '</td>
   632               </tr>';
   632 							</tr>';
   633       }
   633 			}
   634     }
   634 		}
   635     if ( function_exists('array_sum') )
   635 		if ( function_exists('array_sum') )
   636     {
   636 		{
   637       $query_time_total = array_sum($this->query_times);
   637 			$query_time_total = array_sum($this->query_times);
   638       echo '<tr>
   638 			echo '<tr>
   639               <th class="subhead" colspan="2">
   639 							<th class="subhead" colspan="2">
   640                 Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
   640 								Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
   641               </th>
   641 							</th>
   642             </tr>';
   642 						</tr>';
   643     }
   643 		}
   644     echo '  </table>
   644 		echo '  </table>
   645           </div>';
   645 					</div>';
   646     $template->footer();
   646 		$template->footer();
   647   }
   647 	}
   648 }
   648 }
   649 
   649 
   650 class postgresql
   650 class postgresql
   651 {
   651 {
   652   var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
   652 	var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
   653   var $row = array();
   653 	var $row = array();
   654 	var $rowset = array();
   654 	var $rowset = array();
   655   var $errhandler;
   655 	var $errhandler;
   656   var $dbms_name = 'PostgreSQL';
   656 	var $dbms_name = 'PostgreSQL';
   657   
   657 	
   658   /**
   658 	/**
   659    * Get a flat textual list of queries that have been made.
   659  	* Get a flat textual list of queries that have been made.
   660    */
   660  	*/
   661   
   661 	
   662   function sql_backtrace()
   662 	function sql_backtrace()
   663   {
   663 	{
   664     return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
   664 		return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
   665   }
   665 	}
   666   
   666 	
   667   /**
   667 	/**
   668    * Connect to the database, but only if a connection isn't already up.
   668  	* Connect to the database, but only if a connection isn't already up.
   669    */
   669  	*/
   670   
   670 	
   671   function ensure_connection()
   671 	function ensure_connection()
   672   {
   672 	{
   673     if(!$this->_conn)
   673 		if(!$this->_conn)
   674     {
   674 		{
   675       $this->connect();
   675 			$this->connect();
   676     }
   676 		}
   677   }
   677 	}
   678   
   678 	
   679   /**
   679 	/**
   680    * Exit Enano, dumping out a friendly error message indicating a database error on the way out.
   680  	* Exit Enano, dumping out a friendly error message indicating a database error on the way out.
   681    * @param string Description or location of error; defaults to none
   681  	* @param string Description or location of error; defaults to none
   682    */
   682  	*/
   683  
   683  
   684   function _die($t = '')
   684 	function _die($t = '')
   685   {
   685 	{
   686     if ( defined('ENANO_HEADERS_SENT') )
   686 		if ( defined('ENANO_HEADERS_SENT') )
   687       ob_clean();
   687 			ob_clean();
   688     
   688 		
   689     $internal_text = $this->get_error($t);
   689 		$internal_text = $this->get_error($t);
   690     
   690 		
   691     if ( defined('ENANO_CONFIG_FETCHED') )
   691 		if ( defined('ENANO_CONFIG_FETCHED') )
   692       // config is in, we can show a slightly nicer looking error page
   692 			// config is in, we can show a slightly nicer looking error page
   693       die_semicritical('Database error', $internal_text);
   693 			die_semicritical('Database error', $internal_text);
   694     else
   694 		else
   695       // no config, display using no-DB template engine
   695 			// no config, display using no-DB template engine
   696       grinding_halt('Database error', $internal_text);
   696 			grinding_halt('Database error', $internal_text);
   697     
   697 		
   698     exit;
   698 		exit;
   699   }
   699 	}
   700   
   700 	
   701   /**
   701 	/**
   702    * Get the internal text used for a database error message.
   702  	* Get the internal text used for a database error message.
   703    * @param string Description or location of error; defaults to none
   703  	* @param string Description or location of error; defaults to none
   704    */
   704  	*/
   705   
   705 	
   706   function get_error($t = '')
   706 	function get_error($t = '')
   707   {
   707 	{
   708     @header('HTTP/1.1 500 Internal Server Error');
   708 		@header('HTTP/1.1 500 Internal Server Error');
   709     
   709 		
   710     $bt = $this->latest_query;
   710 		$bt = $this->latest_query;
   711     $e = htmlspecialchars($this->sql_error());
   711 		$e = htmlspecialchars($this->sql_error());
   712     if ( empty($e) )
   712 		if ( empty($e) )
   713       $e = '&lt;none&gt;';
   713 			$e = '&lt;none&gt;';
   714     
   714 		
   715     global $email;
   715 		global $email;
   716     
   716 		
   717     // As long as the admin's e-mail is accessible, display it.
   717 		// As long as the admin's e-mail is accessible, display it.
   718     $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) )
   718 		$email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) )
   719                     ? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;'
   719 										? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;'
   720                     : '';
   720 										: '';
   721     
   721 		
   722     $internal_text = "<h3>The site was unable to finish serving your request.</h3>
   722 		$internal_text = "<h3>The site was unable to finish serving your request.</h3>
   723                       <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>
   723 											<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>
   724                       <p>Description or location of error: $t<br />
   724 											<p>Description or location of error: $t<br />
   725                       Error returned by $this->dbms_name extension: $e</p>
   725 											Error returned by $this->dbms_name extension: $e</p>
   726                       <p>Most recent SQL query:</p>
   726 											<p>Most recent SQL query:</p>
   727                       <pre>$bt</pre>";
   727 											<pre>$bt</pre>";
   728     return $internal_text;
   728 		return $internal_text;
   729   }
   729 	}
   730   
   730 	
   731   /**
   731 	/**
   732    * Exit Enano and output a JSON format datbase error.
   732  	* Exit Enano and output a JSON format datbase error.
   733    * @param string Description or location of error; defaults to none
   733  	* @param string Description or location of error; defaults to none
   734    */
   734  	*/
   735   
   735 	
   736   function die_json($loc = false)
   736 	function die_json($loc = false)
   737   {
   737 	{
   738     $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error())));
   738 		$e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error())));
   739     $q = str_replace("\n", "\\n", addslashes($this->latest_query));
   739 		$q = str_replace("\n", "\\n", addslashes($this->latest_query));
   740     $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : "";
   740 		$loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : "";
   741     $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org.";
   741 		$loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org.";
   742     $loc = str_replace("\n", "\\n", $loc);
   742 		$loc = str_replace("\n", "\\n", $loc);
   743     $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n  $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}";
   743 		$t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n  $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}";
   744     die($t);
   744 		die($t);
   745   }
   745 	}
   746   
   746 	
   747   /**
   747 	/**
   748    * Connect to the database.
   748  	* Connect to the database.
   749    * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file.
   749  	* @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file.
   750    * @param string Database server hostname
   750  	* @param string Database server hostname
   751    * @param string Database server username
   751  	* @param string Database server username
   752    * @param string Database server password
   752  	* @param string Database server password
   753    * @param string Name of the database
   753  	* @param string Name of the database
   754    * @param int Optional port number to connect over
   754  	* @param int Optional port number to connect over
   755    */
   755  	*/
   756   
   756 	
   757   function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false)
   757 	function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false)
   758   {
   758 	{
   759     if ( !defined('ENANO_SQL_CONSTANTS') )
   759 		if ( !defined('ENANO_SQL_CONSTANTS') )
   760     {
   760 		{
   761       define('ENANO_SQL_CONSTANTS', '');
   761 			define('ENANO_SQL_CONSTANTS', '');
   762       define('ENANO_DBLAYER', 'PGSQL');
   762 			define('ENANO_DBLAYER', 'PGSQL');
   763       define('ENANO_SQLFUNC_LOWERCASE', 'lower');
   763 			define('ENANO_SQLFUNC_LOWERCASE', 'lower');
   764       define('ENANO_SQL_MULTISTRING_PRFIX', 'E');
   764 			define('ENANO_SQL_MULTISTRING_PRFIX', 'E');
   765       define('ENANO_SQL_BOOLEAN_TRUE', '1');
   765 			define('ENANO_SQL_BOOLEAN_TRUE', '1');
   766       define('ENANO_SQL_BOOLEAN_FALSE', '0');
   766 			define('ENANO_SQL_BOOLEAN_FALSE', '0');
   767     }
   767 		}
   768     
   768 		
   769     if ( !$manual_credentials )
   769 		if ( !$manual_credentials )
   770     {
   770 		{
   771       if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
   771 			if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
   772       {
   772 			{
   773         @include(ENANO_ROOT.'/config.new.php');
   773 				@include(ENANO_ROOT.'/config.new.php');
   774       }
   774 			}
   775       else
   775 			else
   776       {
   776 			{
   777         @include(ENANO_ROOT.'/config.php');
   777 				@include(ENANO_ROOT.'/config.php');
   778       }
   778 			}
   779         
   779 				
   780       if ( isset($crypto_key) )
   780 			if ( isset($crypto_key) )
   781         unset($crypto_key); // Get this sucker out of memory fast
   781 				unset($crypto_key); // Get this sucker out of memory fast
   782       if ( empty($dbport) )
   782 			if ( empty($dbport) )
   783         $dbport = 5432;
   783 				$dbport = 5432;
   784       
   784 			
   785       if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
   785 			if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
   786       {
   786 			{
   787         // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
   787 				// scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
   788         if ( !defined('scriptPath') )
   788 				if ( !defined('scriptPath') )
   789         {
   789 				{
   790           if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
   790 					if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
   791           {
   791 					{
   792             $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
   792 						$_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
   793           }
   793 					}
   794           if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
   794 					if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
   795           {
   795 					{
   796             // user requested http://foo/enano as opposed to http://foo/enano/index.php
   796 						// user requested http://foo/enano as opposed to http://foo/enano/index.php
   797             $_SERVER['REQUEST_URI'] .= '/index.php';
   797 						$_SERVER['REQUEST_URI'] .= '/index.php';
   798           }
   798 					}
   799           $sp = dirname($_SERVER['REQUEST_URI']);
   799 					$sp = dirname($_SERVER['REQUEST_URI']);
   800           if($sp == '/' || $sp == '\\') $sp = '';
   800 					if($sp == '/' || $sp == '\\') $sp = '';
   801           define('scriptPath', $sp);
   801 					define('scriptPath', $sp);
   802           define('contentPath', "$sp/index.php?title=");
   802 					define('contentPath', "$sp/index.php?title=");
   803         }
   803 				}
   804         $loc = scriptPath . '/install.php';
   804 				$loc = scriptPath . '/install.php';
   805         // header("Location: $loc");
   805 				// header("Location: $loc");
   806         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);
   806 				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);
   807         exit;
   807 				exit;
   808       }
   808 			}
   809     }
   809 		}
   810     
   810 		
   811     if ( empty($dbport) )
   811 		if ( empty($dbport) )
   812       $dbport = 5432;
   812 			$dbport = 5432;
   813     
   813 		
   814     $this->_conn = @pg_connect("host=$dbhost port=$dbport dbname=$dbname user=$dbuser password=$dbpasswd");
   814 		$this->_conn = @pg_connect("host=$dbhost port=$dbport dbname=$dbname user=$dbuser password=$dbpasswd");
   815     unset($dbuser);
   815 		unset($dbuser);
   816     unset($dbpasswd); // Security
   816 		unset($dbpasswd); // Security
   817     
   817 		
   818     if ( !$this->_conn && !$manual_credentials )
   818 		if ( !$this->_conn && !$manual_credentials )
   819     {
   819 		{
   820       grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to PostgreSQL.<br />'.pg_last_error().'</p>');
   820 			grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to PostgreSQL.<br />'.pg_last_error().'</p>');
   821     }
   821 		}
   822     else if ( !$this->_conn && $manual_credentials )
   822 		else if ( !$this->_conn && $manual_credentials )
   823     {
   823 		{
   824       return false;
   824 			return false;
   825     }
   825 		}
   826     
   826 		
   827     // Reset some variables
   827 		// Reset some variables
   828     $this->query_backtrace = array();
   828 		$this->query_backtrace = array();
   829     $this->query_times = array();
   829 		$this->query_times = array();
   830     $this->query_sources = array();
   830 		$this->query_sources = array();
   831     $this->num_queries = 0;
   831 		$this->num_queries = 0;
   832     
   832 		
   833     $this->debug = ( defined('ENANO_DEBUG') );
   833 		$this->debug = ( defined('ENANO_DEBUG') );
   834     
   834 		
   835     // We're in!
   835 		// We're in!
   836     return true;
   836 		return true;
   837   }
   837 	}
   838   
   838 	
   839   /**
   839 	/**
   840    * Make a SQL query.
   840  	* Make a SQL query.
   841    * @param string Query
   841  	* @param string Query
   842    * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   842  	* @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   843    * @return resource or false on failure
   843  	* @return resource or false on failure
   844    */
   844  	*/
   845   
   845 	
   846   function sql_query($q)
   846 	function sql_query($q)
   847   {
   847 	{
   848     if ( $this->debug && function_exists('debug_backtrace') )
   848 		if ( $this->debug && function_exists('debug_backtrace') )
   849     {
   849 		{
   850       $backtrace = @debug_backtrace();
   850 			$backtrace = @debug_backtrace();
   851       if ( is_array($backtrace) )
   851 			if ( is_array($backtrace) )
   852       {
   852 			{
   853         $bt = $backtrace[0];
   853 				$bt = $backtrace[0];
   854         if ( isset($backtrace[1]['class']) )
   854 				if ( isset($backtrace[1]['class']) )
   855         {
   855 				{
   856           if ( $backtrace[1]['class'] == 'sessionManager' )
   856 					if ( $backtrace[1]['class'] == 'sessionManager' )
   857           {
   857 					{
   858             $bt = $backtrace[1];
   858 						$bt = $backtrace[1];
   859           }
   859 					}
   860         }
   860 				}
   861         $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
   861 				$this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
   862       }
   862 			}
   863       unset($backtrace);
   863 			unset($backtrace);
   864     }
   864 		}
   865     
   865 		
   866     $this->num_queries++;
   866 		$this->num_queries++;
   867     $this->query_backtrace[] = $q;
   867 		$this->query_backtrace[] = $q;
   868     $this->latest_query = $q;
   868 		$this->latest_query = $q;
   869     // First make sure we have a connection
   869 		// First make sure we have a connection
   870     if ( !$this->_conn )
   870 		if ( !$this->_conn )
   871     {
   871 		{
   872       $this->_die('A database connection has not yet been established.');
   872 			$this->_die('A database connection has not yet been established.');
   873     }
   873 		}
   874     // Does this query look malicious?
   874 		// Does this query look malicious?
   875     if ( !$this->check_query($q) )
   875 		if ( !$this->check_query($q) )
   876     {
   876 		{
   877       $this->report_query($q);
   877 			$this->report_query($q);
   878       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>');
   878 			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>');
   879     }
   879 		}
   880     
   880 		
   881     $time_start = microtime_float();
   881 		$time_start = microtime_float();
   882     $r = @pg_query($this->_conn, $q);
   882 		$r = @pg_query($this->_conn, $q);
   883     $this->query_times[$q] = microtime_float() - $time_start;
   883 		$this->query_times[$q] = microtime_float() - $time_start;
   884     $this->latest_result = $r;
   884 		$this->latest_result = $r;
   885     return $r;
   885 		return $r;
   886   }
   886 	}
   887   
   887 	
   888   /**
   888 	/**
   889    * Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results.
   889  	* Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results.
   890    * @param string Query
   890  	* @param string Query
   891    * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   891  	* @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false.
   892    * @return resource or false on failure
   892  	* @return resource or false on failure
   893    */
   893  	*/
   894   
   894 	
   895   function sql_unbuffered_query($q)
   895 	function sql_unbuffered_query($q)
   896   {
   896 	{
   897     return $this->sql_query($q);
   897 		return $this->sql_query($q);
   898   }
   898 	}
   899   
   899 	
   900   /**
   900 	/**
   901    * Checks a SQL query for possible signs of injection attempts
   901  	* Checks a SQL query for possible signs of injection attempts
   902    * @param string $q the query to check
   902  	* @param string $q the query to check
   903    * @return bool true if query passed check, otherwise false
   903  	* @return bool true if query passed check, otherwise false
   904    */
   904  	*/
   905   
   905 	
   906   function check_query($q, $debug = false)
   906 	function check_query($q, $debug = false)
   907   {
   907 	{
   908     global $db_sql_parse_time;
   908 		global $db_sql_parse_time;
   909     $ts = microtime_float();
   909 		$ts = microtime_float();
   910     
   910 		
   911     // remove properly escaped quotes
   911 		// remove properly escaped quotes
   912     $q = str_replace(array("\\\"", "\\'"), '', $q);
   912 		$q = str_replace(array("\\\"", "\\'"), '', $q);
   913     
   913 		
   914     // make sure quotes match
   914 		// make sure quotes match
   915     foreach ( array("'", '"') as $quote )
   915 		foreach ( array("'", '"') as $quote )
   916     {
   916 		{
   917       if ( get_char_count($q, $quote) % 2 == 1 )
   917 			if ( get_char_count($q, $quote) % 2 == 1 )
   918       {
   918 			{
   919         // mismatched quotes
   919 				// mismatched quotes
   920         return false;
   920 				return false;
   921       }
   921 			}
   922       // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
   922 			// this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
   923       $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
   923 			$q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
   924     }
   924 		}
   925     $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
   925 		$q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
   926     
   926 		
   927     // quotes are now matched out. does this string have a comment marker in it?
   927 		// quotes are now matched out. does this string have a comment marker in it?
   928     if ( strstr($q, '--') )
   928 		if ( strstr($q, '--') )
   929     {
   929 		{
   930       return false;
   930 			return false;
   931     }
   931 		}
   932     
   932 		
   933     if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
   933 		if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
   934     {
   934 		{
   935       if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
   935 			if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
   936       return false;
   936 			return false;
   937     }
   937 		}
   938     
   938 		
   939     $ts = microtime_float() - $ts;
   939 		$ts = microtime_float() - $ts;
   940     $db_sql_parse_time += $ts;
   940 		$db_sql_parse_time += $ts;
   941     return true;
   941 		return true;
   942   }
   942 	}
   943   
   943 	
   944   /**
   944 	/**
   945    * Set the internal result pointer to X
   945  	* Set the internal result pointer to X
   946    * @param int $pos The number of the row
   946  	* @param int $pos The number of the row
   947    * @param resource $result The PostgreSQL result resource - if not given, the latest cached query is assumed
   947  	* @param resource $result The PostgreSQL result resource - if not given, the latest cached query is assumed
   948    * @return true on success, false on failure
   948  	* @return true on success, false on failure
   949    */
   949  	*/
   950    
   950  	
   951   function sql_data_seek($pos, $result = false)
   951 	function sql_data_seek($pos, $result = false)
   952   {
   952 	{
   953     if ( !$result )
   953 		if ( !$result )
   954       $result = $this->latest_result;
   954 			$result = $this->latest_result;
   955     if ( !$result )
   955 		if ( !$result )
   956       return false;
   956 			return false;
   957     
   957 		
   958     return pg_result_seek($result, $pos) ? true : false;
   958 		return pg_result_seek($result, $pos) ? true : false;
   959   }
   959 	}
   960   
   960 	
   961   /**
   961 	/**
   962    * Reports a bad query to the admin
   962  	* Reports a bad query to the admin
   963    * @param string $query the naughty query
   963  	* @param string $query the naughty query
   964    * @access private
   964  	* @access private
   965    */
   965  	*/
   966    
   966  	
   967   function report_query($query)
   967 	function report_query($query)
   968   {
   968 	{
   969     global $session;
   969 		global $session;
   970     if ( is_object($session) && defined('ENANO_MAINSTREAM') )
   970 		if ( is_object($session) && defined('ENANO_MAINSTREAM') )
   971     {
   971 		{
   972       $username = $session->username;
   972 			$username = $session->username;
   973       $user_id = $session->user_id;
   973 			$user_id = $session->user_id;
   974     }
   974 		}
   975     else
   975 		else
   976     {
   976 		{
   977       $username = 'Unavailable';
   977 			$username = 'Unavailable';
   978       $user_id = 1;
   978 			$user_id = 1;
   979     } 
   979 		} 
   980     
   980 		
   981     $query = $this->escape($query);
   981 		$query = $this->escape($query);
   982     $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            author_uid,       edit_summary)
   982 		$q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            author_uid,       edit_summary)
   983                                                      VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');');
   983  																										VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');');
   984   }
   984 	}
   985   
   985 	
   986   /**
   986 	/**
   987    * Returns the ID of the row last inserted.
   987  	* Returns the ID of the row last inserted.
   988    * @return int
   988  	* @return int
   989    */
   989  	*/
   990   
   990 	
   991   function insert_id()
   991 	function insert_id()
   992   {
   992 	{
   993     // list of primary keys in Enano tables
   993 		// list of primary keys in Enano tables
   994     // this is a bit hackish, but not much choice.
   994 		// this is a bit hackish, but not much choice.
   995     static $primary_keys = false;
   995 		static $primary_keys = false;
   996     if ( !is_array($primary_keys) )
   996 		if ( !is_array($primary_keys) )
   997     {
   997 		{
   998       $primary_keys = array(
   998 			$primary_keys = array(
   999         table_prefix . 'comments' => 'comment_id',
   999 				table_prefix . 'comments' => 'comment_id',
  1000         table_prefix . 'logs' => 'log_id',
  1000 				table_prefix . 'logs' => 'log_id',
  1001         table_prefix . 'users' => 'user_id',
  1001 				table_prefix . 'users' => 'user_id',
  1002         table_prefix . 'banlist' => 'ban_id',
  1002 				table_prefix . 'banlist' => 'ban_id',
  1003         table_prefix . 'files' => 'file_id',
  1003 				table_prefix . 'files' => 'file_id',
  1004         table_prefix . 'buddies' => 'buddy_id',
  1004 				table_prefix . 'buddies' => 'buddy_id',
  1005         table_prefix . 'privmsgs' => 'message_id',
  1005 				table_prefix . 'privmsgs' => 'message_id',
  1006         table_prefix . 'sidebar' => 'item_id',
  1006 				table_prefix . 'sidebar' => 'item_id',
  1007         table_prefix . 'hits' => 'hit_id',
  1007 				table_prefix . 'hits' => 'hit_id',
  1008         table_prefix . 'groups' => 'group_id',
  1008 				table_prefix . 'groups' => 'group_id',
  1009         table_prefix . 'group_members' => 'member_id',
  1009 				table_prefix . 'group_members' => 'member_id',
  1010         table_prefix . 'acl' => 'rule_id',
  1010 				table_prefix . 'acl' => 'rule_id',
  1011         table_prefix . 'page_groups' => 'pg_id',
  1011 				table_prefix . 'page_groups' => 'pg_id',
  1012         table_prefix . 'page_group_members' => 'pg_member_id',
  1012 				table_prefix . 'page_group_members' => 'pg_member_id',
  1013         table_prefix . 'tags' => 'tag_id',
  1013 				table_prefix . 'tags' => 'tag_id',
  1014         table_prefix . 'lockout' => 'id',
  1014 				table_prefix . 'lockout' => 'id',
  1015         table_prefix . 'language' => 'lang_id',
  1015 				table_prefix . 'language' => 'lang_id',
  1016         table_prefix . 'language_strings' => 'string_id',
  1016 				table_prefix . 'language_strings' => 'string_id',
  1017         table_prefix . 'ranks' => 'rank_id',
  1017 				table_prefix . 'ranks' => 'rank_id',
  1018         table_prefix . 'captcha' => 'code_id',
  1018 				table_prefix . 'captcha' => 'code_id',
  1019         table_prefix . 'diffiehellman' => 'key_id',
  1019 				table_prefix . 'diffiehellman' => 'key_id',
  1020         table_prefix . 'plugins' => 'plugin_id'
  1020 				table_prefix . 'plugins' => 'plugin_id'
  1021       );
  1021 			);
  1022       // allow plugins to patch this if needed
  1022 			// allow plugins to patch this if needed
  1023       global $plugins;
  1023 			global $plugins;
  1024       $code = $plugins->setHook('pgsql_set_serial_list');
  1024 			$code = $plugins->setHook('pgsql_set_serial_list');
  1025       foreach ( $code as $cmd )
  1025 			foreach ( $code as $cmd )
  1026       {
  1026 			{
  1027         eval($cmd);
  1027 				eval($cmd);
  1028       }
  1028 			}
  1029     }
  1029 		}
  1030     $last_was_insert = preg_match('/^INSERT INTO ([a-z0-9_]+)/i', $this->latest_query, $match);
  1030 		$last_was_insert = preg_match('/^INSERT INTO ([a-z0-9_]+)/i', $this->latest_query, $match);
  1031     if ( $last_was_insert )
  1031 		if ( $last_was_insert )
  1032     {
  1032 		{
  1033       // trick based on PunBB's PostgreSQL driver
  1033 			// trick based on PunBB's PostgreSQL driver
  1034       $table =& $match[1];
  1034 			$table =& $match[1];
  1035       if ( isset($primary_keys[$table]) )
  1035 			if ( isset($primary_keys[$table]) )
  1036       {
  1036 			{
  1037         $primary_key = "{$table}_{$primary_keys[$table]}_seq";
  1037 				$primary_key = "{$table}_{$primary_keys[$table]}_seq";
  1038         $q = pg_query("SELECT CURRVAL('$primary_key');");
  1038 				$q = pg_query("SELECT CURRVAL('$primary_key');");
  1039         return ( $q ) ? intval(@pg_fetch_result($q, 0)) : false;
  1039 				return ( $q ) ? intval(@pg_fetch_result($q, 0)) : false;
  1040       }
  1040 			}
  1041     }
  1041 		}
  1042     return false;
  1042 		return false;
  1043   }
  1043 	}
  1044   
  1044 	
  1045   /**
  1045 	/**
  1046    * Fetch one row from the given query as an associative array.
  1046  	* Fetch one row from the given query as an associative array.
  1047    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1047  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1048    * @return array
  1048  	* @return array
  1049    */
  1049  	*/
  1050   
  1050 	
  1051   function fetchrow($r = false)
  1051 	function fetchrow($r = false)
  1052   {
  1052 	{
  1053     if ( !$this->_conn )
  1053 		if ( !$this->_conn )
  1054       return false;
  1054 			return false;
  1055     if ( !$r )
  1055 		if ( !$r )
  1056       $r = $this->latest_result;
  1056 			$r = $this->latest_result;
  1057     if ( !$r )
  1057 		if ( !$r )
  1058       $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.');
  1058 			$this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.');
  1059     
  1059 		
  1060     $row = pg_fetch_assoc($r);
  1060 		$row = pg_fetch_assoc($r);
  1061     return integerize_array($row);
  1061 		return integerize_array($row);
  1062   }
  1062 	}
  1063   
  1063 	
  1064   /**
  1064 	/**
  1065    * Fetch one row from the given query as a numeric array.
  1065  	* Fetch one row from the given query as a numeric array.
  1066    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1066  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1067    * @return array
  1067  	* @return array
  1068    */
  1068  	*/
  1069   
  1069 	
  1070   function fetchrow_num($r = false)
  1070 	function fetchrow_num($r = false)
  1071   {
  1071 	{
  1072     if ( !$r )
  1072 		if ( !$r )
  1073       $r = $this->latest_result;
  1073 			$r = $this->latest_result;
  1074     if ( !$r )
  1074 		if ( !$r )
  1075       $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.');
  1075 			$this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.');
  1076     
  1076 		
  1077     $row = pg_fetch_row($r);
  1077 		$row = pg_fetch_row($r);
  1078     return integerize_array($row);
  1078 		return integerize_array($row);
  1079   }
  1079 	}
  1080   
  1080 	
  1081   /**
  1081 	/**
  1082    * Get the number of results for a given query.
  1082  	* Get the number of results for a given query.
  1083    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1083  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1084    * @return array
  1084  	* @return array
  1085    */
  1085  	*/
  1086   
  1086 	
  1087   function numrows($r = false)
  1087 	function numrows($r = false)
  1088   {
  1088 	{
  1089     if ( !$r )
  1089 		if ( !$r )
  1090       $r = $this->latest_result;
  1090 			$r = $this->latest_result;
  1091     if ( !$r )
  1091 		if ( !$r )
  1092       $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.');
  1092 			$this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.');
  1093     
  1093 		
  1094     $n = pg_num_rows($r);
  1094 		$n = pg_num_rows($r);
  1095     return $n;
  1095 		return $n;
  1096   }
  1096 	}
  1097   
  1097 	
  1098   /**
  1098 	/**
  1099    * Escape a string so that it may safely be included in a SQL query.
  1099  	* Escape a string so that it may safely be included in a SQL query.
  1100    * @param string String to escape
  1100  	* @param string String to escape
  1101    * @return string Escaped string
  1101  	* @return string Escaped string
  1102    */
  1102  	*/
  1103   
  1103 	
  1104   function escape($str)
  1104 	function escape($str)
  1105   {
  1105 	{
  1106     $str = pg_escape_string($this->_conn, $str);
  1106 		$str = pg_escape_string($this->_conn, $str);
  1107     return $str;
  1107 		return $str;
  1108   }
  1108 	}
  1109   
  1109 	
  1110   /**
  1110 	/**
  1111    * Free the given result from memory. Use this when completely finished with a result resource.
  1111  	* Free the given result from memory. Use this when completely finished with a result resource.
  1112    * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1112  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
  1113    * @return null
  1113  	* @return null
  1114    */
  1114  	*/
  1115   
  1115 	
  1116   function free_result($result = false)
  1116 	function free_result($result = false)
  1117   {
  1117 	{
  1118     if ( !$result )
  1118 		if ( !$result )
  1119       $result = $this->latest_result;
  1119 			$result = $this->latest_result;
  1120     
  1120 		
  1121     if ( !$result )
  1121 		if ( !$result )
  1122       return null;
  1122 			return null;
  1123     
  1123 		
  1124     @pg_free_result($result);
  1124 		@pg_free_result($result);
  1125     return null;
  1125 		return null;
  1126   }
  1126 	}
  1127   
  1127 	
  1128   /**
  1128 	/**
  1129    * Close the database connection
  1129  	* Close the database connection
  1130    */
  1130  	*/
  1131   
  1131 	
  1132   function close()
  1132 	function close()
  1133   {
  1133 	{
  1134     @pg_close($this->_conn);
  1134 		@pg_close($this->_conn);
  1135     unset($this->_conn);
  1135 		unset($this->_conn);
  1136   }
  1136 	}
  1137   
  1137 	
  1138   /**
  1138 	/**
  1139    * Get a list of columns in the given table
  1139  	* Get a list of columns in the given table
  1140    * @param string Table
  1140  	* @param string Table
  1141    * @return array
  1141  	* @return array
  1142    */
  1142  	*/
  1143   
  1143 	
  1144   function columns_in($table)
  1144 	function columns_in($table)
  1145   {
  1145 	{
  1146     if ( !is_string($table) )
  1146 		if ( !is_string($table) )
  1147       return false;
  1147 			return false;
  1148     $q = $this->sql_query("SELECT * FROM $table LIMIT 1 OFFSET 0;");
  1148 		$q = $this->sql_query("SELECT * FROM $table LIMIT 1 OFFSET 0;");
  1149     if ( !$q )
  1149 		if ( !$q )
  1150       $this->_die();
  1150 			$this->_die();
  1151     if ( $this->numrows() < 1 )
  1151 		if ( $this->numrows() < 1 )
  1152     {
  1152 		{
  1153       // FIXME: Have another way to do this if the table is empty
  1153 			// FIXME: Have another way to do this if the table is empty
  1154       return false;
  1154 			return false;
  1155     }
  1155 		}
  1156     
  1156 		
  1157     $row = $this->fetchrow();
  1157 		$row = $this->fetchrow();
  1158     $this->free_result();
  1158 		$this->free_result();
  1159     return array_keys($row);
  1159 		return array_keys($row);
  1160   }
  1160 	}
  1161   
  1161 	
  1162 	function sql_error()
  1162 	function sql_error()
  1163 	{
  1163 	{
  1164 		if ( $this->_conn )
  1164 		if ( $this->_conn )
  1165 		{
  1165 		{
  1166 			return pg_last_error();
  1166 			return pg_last_error();
  1170 			return ( defined('IN_ENANO_INSTALL') ) ? $GLOBALS["lang"]->get('dbpgsql_msg_err_auth') : 'Access to the database was denied. Ensure that your database exists and that your username and password are correct.';
  1170 			return ( defined('IN_ENANO_INSTALL') ) ? $GLOBALS["lang"]->get('dbpgsql_msg_err_auth') : 'Access to the database was denied. Ensure that your database exists and that your username and password are correct.';
  1171 		}
  1171 		}
  1172 	}
  1172 	}
  1173 	
  1173 	
  1174 	/**
  1174 	/**
  1175    * Returns the number of rows affected.
  1175  	* Returns the number of rows affected.
  1176    * @return int
  1176  	* @return int
  1177    */
  1177  	*/
  1178   
  1178 	
  1179   function sql_affectedrows()
  1179 	function sql_affectedrows()
  1180   {
  1180 	{
  1181     return pg_affected_rows($this->latest_result);
  1181 		return pg_affected_rows($this->latest_result);
  1182   }
  1182 	}
  1183 
  1183 
  1184   /**
  1184 	/**
  1185    * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
  1185  	* Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
  1186    */
  1186  	*/
  1187   
  1187 	
  1188   function sql_report()
  1188 	function sql_report()
  1189   {
  1189 	{
  1190     global $db, $session, $paths, $template, $plugins; // Common objects
  1190 		global $db, $session, $paths, $template, $plugins; // Common objects
  1191     if ( !$session->get_permissions('mod_misc') )
  1191 		if ( !$session->get_permissions('mod_misc') )
  1192     {
  1192 		{
  1193       die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
  1193 			die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
  1194     }
  1194 		}
  1195     // Create copies of variables that may be changed after header is called
  1195 		// Create copies of variables that may be changed after header is called
  1196     $backtrace = $this->query_backtrace;
  1196 		$backtrace = $this->query_backtrace;
  1197     $times = $this->query_times;
  1197 		$times = $this->query_times;
  1198     $template->header();
  1198 		$template->header();
  1199     echo '<h3>SQL query log and timetable</h3>';
  1199 		echo '<h3>SQL query log and timetable</h3>';
  1200     echo '<div class="tblholder">
  1200 		echo '<div class="tblholder">
  1201             <table border="0" cellspacing="1" cellpadding="4">';
  1201 						<table border="0" cellspacing="1" cellpadding="4">';
  1202     $i = 0;
  1202 		$i = 0;
  1203     foreach ( $backtrace as $query )
  1203 		foreach ( $backtrace as $query )
  1204     {
  1204 		{
  1205       $i++;
  1205 			$i++;
  1206       $unbuffered = false;
  1206 			$unbuffered = false;
  1207       if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
  1207 			if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
  1208       {
  1208 			{
  1209         $query = substr($query, 13);
  1209 				$query = substr($query, 13);
  1210         $unbuffered = true;
  1210 				$unbuffered = true;
  1211       }
  1211 			}
  1212       if ( $i == 1 )
  1212 			if ( $i == 1 )
  1213       {
  1213 			{
  1214         echo '<tr>
  1214 				echo '<tr>
  1215                 <th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
  1215 								<th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
  1216               </tr>';
  1216 							</tr>';
  1217       }
  1217 			}
  1218       else
  1218 			else
  1219       {
  1219 			{
  1220         echo '<tr>
  1220 				echo '<tr>
  1221                 <th class="subhead" colspan="2">&nbsp;</th>
  1221 								<th class="subhead" colspan="2">&nbsp;</th>
  1222               </tr>';
  1222 							</tr>';
  1223       }
  1223 			}
  1224       echo '<tr>
  1224 			echo '<tr>
  1225               <td class="row2">Query:</td>
  1225 							<td class="row2">Query:</td>
  1226               <td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
  1226 							<td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
  1227             </tr>
  1227 						</tr>
  1228             <tr>
  1228 						<tr>
  1229               <td class="row2">Time:</td>
  1229 							<td class="row2">Time:</td>
  1230               <td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
  1230 							<td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
  1231             </tr>
  1231 						</tr>
  1232             <tr>
  1232 						<tr>
  1233               <td class="row2">Unbuffered:</td>
  1233 							<td class="row2">Unbuffered:</td>
  1234               <td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
  1234 							<td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
  1235             </tr>';
  1235 						</tr>';
  1236       if ( isset($this->query_sources[$query]) )
  1236 			if ( isset($this->query_sources[$query]) )
  1237       {
  1237 			{
  1238         echo '<tr>
  1238 				echo '<tr>
  1239                 <td class="row2">Called from:</td>
  1239 								<td class="row2">Called from:</td>
  1240                 <td class="row1">' . $this->query_sources[$query] . '</td>
  1240 								<td class="row1">' . $this->query_sources[$query] . '</td>
  1241               </tr>';
  1241 							</tr>';
  1242       }
  1242 			}
  1243     }
  1243 		}
  1244     if ( function_exists('array_sum') )
  1244 		if ( function_exists('array_sum') )
  1245     {
  1245 		{
  1246       $query_time_total = array_sum($this->query_times);
  1246 			$query_time_total = array_sum($this->query_times);
  1247       echo '<tr>
  1247 			echo '<tr>
  1248               <th class="subhead" colspan="2">
  1248 							<th class="subhead" colspan="2">
  1249                 Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
  1249 								Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
  1250               </th>
  1250 							</th>
  1251             </tr>';
  1251 						</tr>';
  1252     }
  1252 		}
  1253     echo '  </table>
  1253 		echo '  </table>
  1254           </div>';
  1254 					</div>';
  1255     $template->footer();
  1255 		$template->footer();
  1256   }
  1256 	}
  1257 }
  1257 }
  1258 
  1258 
  1259 ?>
  1259 ?>