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"> </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 = '<none>'; |
|
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 <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
|
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 */ |