includes/dbal.php
changeset 1375 d66e43ac35b6
parent 1369 cfce82063776
child 1377 fa2b0825bbc5
equal deleted inserted replaced
1373:851b91febb85 1375:d66e43ac35b6
   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 ( !function_exists('mysql_connect') )
       
   147 		{
       
   148 			if ( class_exists('PDO') && extension_loaded("pdo_mysql") )
       
   149 			{
       
   150 				$pdo_string = '<p><strong>Never fear!</strong> Your server has PDO and the PDO MySQL driver installed. This means you can simply edit your config.php to use
       
   151 									an alternate database driver, without losing any of your existing site data. Look for the line that says:</p>
       
   152 								<pre>$dbdriver = \'mysql\';</pre>
       
   153 								<p>and change it to:</p>
       
   154 								<pre>$dbdriver = \'mysql_pdo\';</pre>';
       
   155 			}
       
   156 			else
       
   157 			{
       
   158 				$pdo_string = '<p>Enano can\'t load right now, but it will resume working once you install the PDO MySQL extension and then edit Enano\'s config.php file to
       
   159 									use the new database driver. Once the PDO and PDO_MySQL PHP extensions are installed, look in Enano\'s config.php file for
       
   160 									the line that says:</p>
       
   161 								<pre>$dbdriver = \'mysql\';</pre>
       
   162 								<p>and change it to:</p>
       
   163 								<pre>$dbdriver = \'mysql_pdo\';</pre>';
       
   164 			}
       
   165 			grinding_halt('MySQL no longer supported', '<p>Your site configuration specifies that the MySQL database driver is to be used, however PHP has removed this
       
   166 								driver from newer versions.</p>' . $pdo_string .
       
   167 								'<p><strong>Your site\'s data is not at risk.</strong> This notice by itself simply means you upgraded to a newer version of PHP. Your site\'s
       
   168 									data ordinarily will not have been touched and your site will function properly again as soon as you switch over to the PDO MySQL
       
   169 									driver.</p>');
       
   170 		}
       
   171 		
   146 		if ( !defined('ENANO_SQL_CONSTANTS') )
   172 		if ( !defined('ENANO_SQL_CONSTANTS') )
   147 		{
   173 		{
   148 			define('ENANO_SQL_CONSTANTS', '');
   174 			define('ENANO_SQL_CONSTANTS', '');
   149 			define('ENANO_DBLAYER', 'MYSQL');
   175 			define('ENANO_DBLAYER', 'MYSQL');
   150 			define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
   176 			define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
   541 	{
   567 	{
   542 		return mysql_affected_rows($this->_conn);
   568 		return mysql_affected_rows($this->_conn);
   543 	}
   569 	}
   544 	
   570 	
   545 	/**
   571 	/**
       
   572 	* Get MySQL server version
       
   573 	* @return string
       
   574 	*/
       
   575 	
       
   576 	function get_server_version()
       
   577 	{
       
   578 		return mysql_get_server_info();
       
   579 	}
       
   580 	
       
   581 	/**
   546  	* Close the database connection
   582  	* Close the database connection
   547  	*/
   583  	*/
   548 	
   584 	
   549 	function close()
   585 	function close()
   550 	{
   586 	{
       
   587 		if ( !$this->_conn )
       
   588 			return;
       
   589 		
   551 		// anything we locked should certainly be unlocked now;
   590 		// anything we locked should certainly be unlocked now;
   552 		@mysql_query("COMMIT;", $this->_conn);
   591 		@mysql_query("COMMIT;", $this->_conn);
   553 		@mysql_query("UNLOCK TABLES;", $this->_conn);
   592 		@mysql_query("UNLOCK TABLES;", $this->_conn);
   554 		@mysql_close($this->_conn);
   593 		@mysql_close($this->_conn);
   555 		unset($this->_conn);
   594 		unset($this->_conn);
   583  	*/
   622  	*/
   584 	
   623 	
   585 	function sql_error()
   624 	function sql_error()
   586 	{
   625 	{
   587 		return mysql_error();
   626 		return mysql_error();
       
   627 	}
       
   628 	
       
   629 	/**
       
   630  	* Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
       
   631  	*/
       
   632 	
       
   633 	function sql_report()
       
   634 	{
       
   635 		global $db, $session, $paths, $template, $plugins; // Common objects
       
   636 		if ( !$session->get_permissions('mod_misc') )
       
   637 		{
       
   638 			die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>');
       
   639 		}
       
   640 		// Create copies of variables that may be changed after header is called
       
   641 		$backtrace = $this->query_backtrace;
       
   642 		$times = $this->query_times;
       
   643 		$template->header();
       
   644 		echo '<h3>SQL query log and timetable</h3>';
       
   645 		echo '<div class="tblholder">
       
   646 						<table border="0" cellspacing="1" cellpadding="4">';
       
   647 		$i = 0;
       
   648 		foreach ( $backtrace as $query )
       
   649 		{
       
   650 			$i++;
       
   651 			$unbuffered = false;
       
   652 			if ( substr($query, 0, 13) == '(UNBUFFERED) ' )
       
   653 			{
       
   654 				$query = substr($query, 13);
       
   655 				$unbuffered = true;
       
   656 			}
       
   657 			if ( $i == 1 )
       
   658 			{
       
   659 				echo '<tr>
       
   660 								<th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th>
       
   661 							</tr>';
       
   662 			}
       
   663 			else
       
   664 			{
       
   665 				echo '<tr>
       
   666 								<th class="subhead" colspan="2">&nbsp;</th>
       
   667 							</tr>';
       
   668 			}
       
   669 			echo '<tr>
       
   670 							<td class="row2">Query:</td>
       
   671 							<td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td>
       
   672 						</tr>
       
   673 						<tr>
       
   674 							<td class="row2">Time:</td>
       
   675 							<td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td>
       
   676 						</tr>
       
   677 						<tr>
       
   678 							<td class="row2">Unbuffered:</td>
       
   679 							<td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td>
       
   680 						</tr>';
       
   681 			if ( isset($this->query_sources[$query]) )
       
   682 			{
       
   683 				echo '<tr>
       
   684 								<td class="row2">Called from:</td>
       
   685 								<td class="row1">' . $this->query_sources[$query] . '</td>
       
   686 							</tr>';
       
   687 			}
       
   688 		}
       
   689 		if ( function_exists('array_sum') )
       
   690 		{
       
   691 			$query_time_total = array_sum($this->query_times);
       
   692 			echo '<tr>
       
   693 							<th class="subhead" colspan="2">
       
   694 								Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds
       
   695 							</th>
       
   696 						</tr>';
       
   697 		}
       
   698 		echo '  </table>
       
   699 					</div>';
       
   700 		$template->footer();
       
   701 	}
       
   702 	
       
   703 	/**
       
   704 	 * Begin transaction
       
   705 	 */
       
   706 	
       
   707 	function transaction_begin()
       
   708 	{
       
   709 		$this->sql_query('BEGIN;');
       
   710 	}
       
   711 	
       
   712 	/**
       
   713 	 * Commit transaction
       
   714 	 */
       
   715 	
       
   716 	function transaction_commit()
       
   717 	{
       
   718 		$this->sql_query('COMMIT;');
       
   719 	}
       
   720 	
       
   721 	/**
       
   722 	 * Rollback transaction
       
   723 	 */
       
   724 	
       
   725 	function transaction_rollback()
       
   726 	{
       
   727 		$this->sql_query('ROLLBACK;');
       
   728 	}
       
   729 }
       
   730 
       
   731 class mysql_pdo {
       
   732 	var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug;
       
   733 	var $row = array();
       
   734 	var $rowset = array();
       
   735 	var $errhandler;
       
   736 	var $dbms_name = 'MySQL';
       
   737 	
       
   738 	/**
       
   739  	* Get a flat textual list of queries that have been made.
       
   740  	*/
       
   741 	
       
   742 	function sql_backtrace()
       
   743 	{
       
   744 		return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace);
       
   745 	}
       
   746 	
       
   747 	/**
       
   748  	* Connect to the database, but only if a connection isn't already up.
       
   749  	*/
       
   750 	
       
   751 	function ensure_connection()
       
   752 	{
       
   753 		if(!$this->_conn)
       
   754 		{
       
   755 			$this->connect();
       
   756 		}
       
   757 	}
       
   758 	
       
   759 	/**
       
   760  	* Exit Enano, dumping out a friendly error message indicating a database error on the way out.
       
   761  	* @param string Description or location of error; defaults to none
       
   762  	*/
       
   763  
       
   764 	function _die($t = '')
       
   765 	{
       
   766 		if ( defined('ENANO_HEADERS_SENT') )
       
   767 			ob_clean();
       
   768 		
       
   769 		$internal_text = $this->get_error($t);
       
   770 		
       
   771 		if ( defined('ENANO_CONFIG_FETCHED') )
       
   772 			// config is in, we can show a slightly nicer looking error page
       
   773 			die_semicritical('Database error', $internal_text);
       
   774 		else
       
   775 			// no config, display using no-DB template engine
       
   776 			grinding_halt('Database error', $internal_text);
       
   777 		
       
   778 		exit;
       
   779 	}
       
   780 	
       
   781 	/**
       
   782  	* Get the internal text used for a database error message.
       
   783  	* @param string Description or location of error; defaults to none
       
   784  	*/
       
   785 	
       
   786 	function get_error($t = '')
       
   787 	{
       
   788 		@header('HTTP/1.1 500 Internal Server Error');
       
   789 		
       
   790 		$bt = $this->latest_query;
       
   791 		$e = htmlspecialchars($this->sql_error());
       
   792 		if ( empty($e) )
       
   793 			$e = '&lt;none&gt;';
       
   794 		
       
   795 		global $email;
       
   796 		
       
   797 		// As long as the admin's e-mail is accessible, display it.
       
   798 		$email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) )
       
   799 										? ', at &lt;' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '&gt;'
       
   800 										: '';
       
   801 		
       
   802 		$internal_text = "<h3>The site was unable to finish serving your request.</h3>
       
   803 											<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>
       
   804 											<p>Description or location of error: $t<br />
       
   805 											Error returned by $this->dbms_name extension: $e</p>
       
   806 											<p>Most recent SQL query:</p>
       
   807 											<pre>$bt</pre>";
       
   808 		return $internal_text;
       
   809 	}
       
   810 	
       
   811 	/**
       
   812  	* Exit Enano and output a JSON format datbase error.
       
   813  	* @param string Description or location of error; defaults to none
       
   814  	*/
       
   815 	
       
   816 	function die_json($loc = false)
       
   817 	{
       
   818 		$e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error())));
       
   819 		$q = str_replace("\n", "\\n", addslashes($this->latest_query));
       
   820 		$loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : "";
       
   821 		$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.";
       
   822 		$loc = str_replace("\n", "\\n", $loc);
       
   823 		$t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n  $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}";
       
   824 		die($t);
       
   825 	}
       
   826 	
       
   827 	/**
       
   828  	* Connect to the database.
       
   829  	* @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.
       
   830  	* @param string Database server hostname
       
   831  	* @param string Database server username
       
   832  	* @param string Database server password
       
   833  	* @param string Name of the database
       
   834  	* @param int Optional port number to connect over
       
   835  	*/
       
   836 	
       
   837 	function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false)
       
   838 	{
       
   839 		if ( !defined('ENANO_SQL_CONSTANTS') )
       
   840 		{
       
   841 			define('ENANO_SQL_CONSTANTS', '');
       
   842 			define('ENANO_DBLAYER', 'MYSQL');
       
   843 			define('ENANO_SQLFUNC_LOWERCASE', 'lcase');
       
   844 			define('ENANO_SQL_MULTISTRING_PRFIX', '');
       
   845 			define('ENANO_SQL_BOOLEAN_TRUE', 'true');
       
   846 			define('ENANO_SQL_BOOLEAN_FALSE', 'false');
       
   847 		}
       
   848 		
       
   849 		if ( !$manual_credentials )
       
   850 		{
       
   851 			if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') )
       
   852 			{
       
   853 				@include(ENANO_ROOT.'/config.new.php');
       
   854 			}
       
   855 			else
       
   856 			{
       
   857 				@include(ENANO_ROOT.'/config.php');
       
   858 			}
       
   859 			
       
   860 			if ( isset($crypto_key) )
       
   861 				unset($crypto_key); // Get this sucker out of memory fast
       
   862 			if ( empty($dbport) )
       
   863 				$dbport = 3306;
       
   864 			
       
   865 			if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') )
       
   866 			{
       
   867 				// scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects
       
   868 				if ( !defined('scriptPath') )
       
   869 				{
       
   870 					if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) )
       
   871 					{
       
   872 						$_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']);
       
   873 					}
       
   874 					if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) )
       
   875 					{
       
   876 						// user requested http://foo/enano as opposed to http://foo/enano/index.php
       
   877 						$_SERVER['REQUEST_URI'] .= '/index.php';
       
   878 					}
       
   879 					$sp = dirname($_SERVER['REQUEST_URI']);
       
   880 					if($sp == '/' || $sp == '\\') $sp = '';
       
   881 					define('scriptPath', $sp);
       
   882 					define('contentPath', "$sp/index.php?title=");
       
   883 				}
       
   884 				$loc = scriptPath . '/install/index.php';
       
   885 				define('IN_ENANO_INSTALL', 1);
       
   886 				$GLOBALS['lang'] = new Language('eng');
       
   887 				global $lang;
       
   888 				$lang->load_file('./language/english/core.json');
       
   889 				$lang->load_file('./language/english/install.json');
       
   890 				// header("Location: $loc");
       
   891 				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);
       
   892 				exit;
       
   893 			}
       
   894 		}
       
   895 		
       
   896 		if ( !$dbport )
       
   897 			$dbport = 3306;
       
   898 		
       
   899 		if ( $dbhost && !empty($dbport) && $dbport != 3306 )
       
   900 			$dbhost = '127.0.0.1';
       
   901 		
       
   902 		$host_line = ( preg_match('/^:/', $dbhost) ) ? $dbhost : "{$dbhost}:{$dbport}";
       
   903 		
       
   904 		$this->_conn = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=UTF8", $dbuser, $dbpasswd);
       
   905 		
       
   906 		unset($dbuser);
       
   907 		unset($dbpasswd); // Security
       
   908 		
       
   909 		if ( !$this->_conn && !$manual_credentials )
       
   910 		{
       
   911 			grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to MySQL.<br />'.mysql_error().'</p>');
       
   912 		}
       
   913 		else if ( !$this->_conn && $manual_credentials )
       
   914 		{
       
   915 			return false;
       
   916 		}
       
   917 		
       
   918 		// Reset some variables
       
   919 		$this->query_backtrace = array();
       
   920 		$this->query_times = array();
       
   921 		$this->query_sources = array();
       
   922 		$this->num_queries = 0;
       
   923 		
       
   924 		$this->debug = ( defined('ENANO_DEBUG') );
       
   925 		
       
   926 		// We're in!
       
   927 		return true;
       
   928 	}
       
   929 	
       
   930 	/**
       
   931  	* Make a SQL query.
       
   932  	* @param string Query
       
   933  	* @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.
       
   934  	* @return resource or false on failure
       
   935  	*/
       
   936 	
       
   937 	function sql_query($q, $log_query = true)
       
   938 	{
       
   939 		if ( $this->debug && function_exists('debug_backtrace') )
       
   940 		{
       
   941 			$backtrace = @debug_backtrace();
       
   942 			if ( is_array($backtrace) )
       
   943 			{
       
   944 				$bt = $backtrace[0];
       
   945 				if ( isset($backtrace[1]['class']) )
       
   946 				{
       
   947 					if ( $backtrace[1]['class'] == 'sessionManager' )
       
   948 					{
       
   949 						$bt = $backtrace[1];
       
   950 					}
       
   951 				}
       
   952 				$this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line'];
       
   953 			}
       
   954 			unset($backtrace);
       
   955 		}
       
   956 		
       
   957 		$this->num_queries++;
       
   958 		if ( $log_query || defined('ENANO_DEBUG') )
       
   959 		{
       
   960 			$this->query_backtrace[] = $q;
       
   961 			$this->latest_query = $q;
       
   962 		}
       
   963 		// First make sure we have a connection
       
   964 		if ( !$this->_conn )
       
   965 		{
       
   966 			$this->_die('A database connection has not yet been established.');
       
   967 		}
       
   968 		// Start the timer
       
   969 		if ( $log_query || defined('ENANO_DEBUG') )
       
   970 			$time_start = microtime_float();
       
   971 		// Does this query look malicious?
       
   972 		if ( $log_query || defined('ENANO_DEBUG') )
       
   973 		{
       
   974 			if ( !$this->check_query($q) )
       
   975 			{
       
   976 				$this->report_query($q);
       
   977 				$debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : '';
       
   978 				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);
       
   979 			}
       
   980 		}
       
   981 		
       
   982 		try
       
   983 		{
       
   984 			$r = $this->_conn->query($q);
       
   985 		}
       
   986 		catch ( PDOException $e )
       
   987 		{
       
   988 			return false;
       
   989 		}
       
   990 		
       
   991 		if ( $log_query )
       
   992 			$this->query_times[$q] = microtime_float() - $time_start;
       
   993 		
       
   994 		$this->latest_result = $r;
       
   995 		
       
   996 		return $r;
       
   997 	}
       
   998 	
       
   999 	/**
       
  1000  	* 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.
       
  1001  	* @param string Query
       
  1002  	* @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.
       
  1003  	* @return resource or false on failure
       
  1004  	*/
       
  1005 	
       
  1006 	function sql_unbuffered_query($q, $log_query = true)
       
  1007 	{
       
  1008 		$this->num_queries++;
       
  1009 		if ( $log_query || defined('ENANO_DEBUG') )
       
  1010 			$this->query_backtrace[] = '(UNBUFFERED) ' . $q;
       
  1011 		$this->latest_query = $q;
       
  1012 		// First make sure we have a connection
       
  1013 		if ( !$this->_conn )
       
  1014 		{
       
  1015 			$this->_die('A database connection has not yet been established.');
       
  1016 		}
       
  1017 		// Does this query look malicious?
       
  1018 		if ( !$this->check_query($q) )
       
  1019 		{
       
  1020 			$this->report_query($q);
       
  1021 			$debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : '';
       
  1022 			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);
       
  1023 		}
       
  1024 		
       
  1025 		$time_start = microtime_float();
       
  1026 		try
       
  1027 		{
       
  1028 			$r = $this->_conn->query($q);
       
  1029 		}
       
  1030 		catch ( PDOException $e )
       
  1031 		{
       
  1032 			return false;
       
  1033 		}
       
  1034 		$this->query_times[$q] = microtime_float() - $time_start;
       
  1035 		$this->latest_result = $r;
       
  1036 		return $r;
       
  1037 	}
       
  1038 	
       
  1039 	/**
       
  1040  	* Performs heuristic analysis on a SQL query to check for known attack patterns.
       
  1041  	* @param string $q the query to check
       
  1042  	* @return bool true if query passed check, otherwise false
       
  1043  	*/
       
  1044 	
       
  1045 	function check_query($q, $debug = false)
       
  1046 	{
       
  1047 		global $db_sql_parse_time;
       
  1048 		$ts = microtime_float();
       
  1049 		
       
  1050 		// remove properly escaped quotes
       
  1051 		$q = str_replace('\\\\', '', $q);
       
  1052 		$q = str_replace(array("\\\"", "\\'"), '', $q);
       
  1053 		
       
  1054 		// make sure quotes match
       
  1055 		foreach ( array("'", '"') as $quote )
       
  1056 		{
       
  1057 			$n_quotes = get_char_count($q, $quote);
       
  1058 			if ( $n_quotes % 2 == 1 )
       
  1059 			{
       
  1060 				// mismatched quotes
       
  1061 				if ( $debug ) echo "Found mismatched quotes in query; parsed:\n$q\n";
       
  1062 				return false;
       
  1063 			}
       
  1064 			// this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token
       
  1065 			$q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q);
       
  1066 		}
       
  1067 		$q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q);
       
  1068 		
       
  1069 		// quotes are now matched out. does this string have a comment marker in it?
       
  1070 		if ( strstr($q, '--') )
       
  1071 		{
       
  1072 			return false;
       
  1073 		}
       
  1074 		
       
  1075 		if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) )
       
  1076 		{
       
  1077 			if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>';
       
  1078 			return false;
       
  1079 		}
       
  1080 		
       
  1081 		$ts = microtime_float() - $ts;
       
  1082 		$db_sql_parse_time += $ts;
       
  1083 		return true;
       
  1084 	}
       
  1085 	
       
  1086 	/**
       
  1087  	* Set the internal result pointer to X
       
  1088  	* @param int $pos The number of the row
       
  1089  	* @param resource $result The MySQL result resource - if not given, the latest cached query is assumed
       
  1090  	* @return true on success, false on failure
       
  1091  	*/
       
  1092  	
       
  1093 	function sql_data_seek($pos, $result = false)
       
  1094 	{
       
  1095 		if ( !$result )
       
  1096 			$result = $this->latest_result;
       
  1097 		if ( !$result )
       
  1098 			return false;
       
  1099 		
       
  1100 		return mysql_data_seek($result, $pos) ? true : false;
       
  1101 	}
       
  1102 	
       
  1103 	/**
       
  1104  	* Reports a bad query to the admin
       
  1105  	* @param string $query the naughty query
       
  1106  	* @access private
       
  1107  	*/
       
  1108  	
       
  1109 	function report_query($query)
       
  1110 	{
       
  1111 		global $session;
       
  1112 		if ( is_object($session) && defined('ENANO_MAINSTREAM') )
       
  1113 		{
       
  1114 			$username = $session->username;
       
  1115 			$user_id = $session->user_id;
       
  1116 		}
       
  1117 		else
       
  1118 		{
       
  1119 			$username = 'Unavailable';
       
  1120 			$user_id = 1;
       
  1121 		} 
       
  1122 		
       
  1123 		$query = $this->escape($query);
       
  1124 		$q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type,     action,         time_id,    date_string, page_text,      author,            author_uid,       edit_summary)
       
  1125  																										VALUES(\'security\', \'sql_inject\', '.time().', \'\',        \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');');
       
  1126 	}
       
  1127 	
       
  1128 	/**
       
  1129  	* Returns the ID of the row last inserted.
       
  1130  	* @return int
       
  1131  	*/
       
  1132 	
       
  1133 	function insert_id()
       
  1134 	{
       
  1135 		$q = $this->sql_query("SELECT LAST_INSERT_ID();");
       
  1136 		if ( !$q )
       
  1137 			return false;
       
  1138 		
       
  1139 		list($iid) = $this->fetchrow_num();
       
  1140 		return $iid;
       
  1141 	}
       
  1142 	
       
  1143 	/**
       
  1144  	* Fetch one row from the given query as an associative array.
       
  1145  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
       
  1146  	* @return array
       
  1147  	*/
       
  1148 	
       
  1149 	function fetchrow($r = false)
       
  1150 	{
       
  1151 		if ( !$this->_conn )
       
  1152 			return false;
       
  1153 		
       
  1154 		if ( !$r )
       
  1155 			$r = $this->latest_result;
       
  1156 		
       
  1157 		if ( !$r )
       
  1158 			$this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1159 		
       
  1160 		$row = $r->fetch(PDO::FETCH_ASSOC);
       
  1161 		
       
  1162 		return integerize_array($row);
       
  1163 	}
       
  1164 	
       
  1165 	/**
       
  1166  	* Fetch one row from the given query as a numeric array.
       
  1167  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
       
  1168  	* @return array
       
  1169  	*/
       
  1170 	
       
  1171 	function fetchrow_num($r = false)
       
  1172 	{
       
  1173 		if ( !$r )
       
  1174 			$r = $this->latest_result;
       
  1175 		if ( !$r )
       
  1176 			$this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1177 		
       
  1178 		$row = $r->fetch(PDO::FETCH_NUM);
       
  1179 		return integerize_array($row);
       
  1180 	}
       
  1181 	
       
  1182 	/**
       
  1183  	* Get the number of results for a given query.
       
  1184  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
       
  1185  	* @return array
       
  1186  	*/
       
  1187 	
       
  1188 	function numrows($r = false)
       
  1189 	{
       
  1190 		if ( !$r )
       
  1191 			$r = $this->latest_result;
       
  1192 		if ( !$r )
       
  1193 			$this->_die('$db->fetchrow(): an invalid MySQL resource was passed.');
       
  1194 		
       
  1195 		return $r->rowCount();
       
  1196 	}
       
  1197 	
       
  1198 	/**
       
  1199  	* Escape a string so that it may safely be included in a SQL query.
       
  1200  	* @param string String to escape
       
  1201  	* @return string Escaped string
       
  1202  	*/
       
  1203 	
       
  1204 	function escape($str)
       
  1205 	{
       
  1206 		$str = substr($this->_conn->quote($str), 1, -1);
       
  1207 		return $str;
       
  1208 	}
       
  1209 	
       
  1210 	/**
       
  1211  	* Free the given result from memory. Use this when completely finished with a result resource.
       
  1212  	* @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used.
       
  1213  	* @return null
       
  1214  	*/
       
  1215 	
       
  1216 	function free_result($result = false)
       
  1217 	{
       
  1218 		if ( !$result )
       
  1219 			$result = $this->latest_result;
       
  1220 		if ( !$result )
       
  1221 			return null;
       
  1222 		
       
  1223 		//@mysql_free_result($result);
       
  1224 		return null;
       
  1225 	}
       
  1226 	
       
  1227 	/**
       
  1228  	* Returns the number of rows affected.
       
  1229  	* @return int
       
  1230  	*/
       
  1231 	
       
  1232 	function sql_affectedrows()
       
  1233 	{
       
  1234 		return mysql_affected_rows($this->_conn);
       
  1235 	}
       
  1236 	
       
  1237 	/**
       
  1238 	* Get MySQL server version
       
  1239 	* @return string
       
  1240 	*/
       
  1241 	
       
  1242 	function get_server_version()
       
  1243 	{
       
  1244 		$q = $this->sql_query("SHOW VARIABLES WHERE Variable_name = 'version';");
       
  1245 		if ( !$q )
       
  1246 			$this->_die("while fetching MySQL server version");
       
  1247 		$row = $this->fetchrow($q);
       
  1248 		return $row['Value'];
       
  1249 	}
       
  1250 	
       
  1251 	/**
       
  1252  	* Close the database connection
       
  1253  	*/
       
  1254 	
       
  1255 	function close()
       
  1256 	{
       
  1257 		// anything we locked should certainly be unlocked now;
       
  1258 		@mysql_query("COMMIT;", $this->_conn);
       
  1259 		@mysql_query("UNLOCK TABLES;", $this->_conn);
       
  1260 		@mysql_close($this->_conn);
       
  1261 		unset($this->_conn);
       
  1262 	}
       
  1263 	
       
  1264 	/**
       
  1265  	* Get a list of columns in the given table
       
  1266  	* @param string Table
       
  1267  	* @return array
       
  1268  	*/
       
  1269 	
       
  1270 	function columns_in($table)
       
  1271 	{
       
  1272 		if ( !is_string($table) )
       
  1273 			return false;
       
  1274 		$q = $this->sql_query("SHOW COLUMNS IN $table;");
       
  1275 		if ( !$q )
       
  1276 			$this->_die();
       
  1277 		
       
  1278 		$columns = array();
       
  1279 		while ( $row = $this->fetchrow_num() )
       
  1280 		{
       
  1281 			$columns[] = $row[0];
       
  1282 		}
       
  1283 		return $columns;
       
  1284 	}
       
  1285 	
       
  1286 	/**
       
  1287  	* Get the text of the most recent error.
       
  1288  	* @return string
       
  1289  	*/
       
  1290 	
       
  1291 	function sql_error()
       
  1292 	{
       
  1293 		list(,,$errtext) = $this->_conn->errorInfo();
       
  1294 		return $errtext;
   588 	}
  1295 	}
   589 	
  1296 	
   590 	/**
  1297 	/**
   591  	* Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
  1298  	* Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with.
   592  	*/
  1299  	*/