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