Rev 224 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
150 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * OIDplus 2.0 |
||
5 | * Copyright 2019 Daniel Marschall, ViaThinkSoft |
||
6 | * |
||
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
8 | * you may not use this file except in compliance with the License. |
||
9 | * You may obtain a copy of the License at |
||
10 | * |
||
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
12 | * |
||
13 | * Unless required by applicable law or agreed to in writing, software |
||
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
16 | * See the License for the specific language governing permissions and |
||
17 | * limitations under the License. |
||
18 | */ |
||
19 | |||
20 | if (!defined('IN_OIDPLUS')) die(); |
||
21 | |||
22 | define('OIDPLUS_MYSQL_QUERYLOG', false); |
||
208 | daniel-mar | 23 | define('MYSQLND_AVAILABLE', function_exists('mysqli_fetch_all')); |
150 | daniel-mar | 24 | |
25 | if (OIDPLUS_MYSQL_QUERYLOG) { |
||
26 | function CallingFunctionName() { |
||
27 | $ex = new Exception(); |
||
28 | $trace = $ex->getTrace(); |
||
29 | if (!isset($trace[2])) return '(main)'; |
||
30 | $final_call = $trace[2]; |
||
31 | return $final_call['file'].':'.$final_call['line'].'/'.$final_call['function'].'()'; |
||
32 | } |
||
33 | } |
||
34 | |||
227 | daniel-mar | 35 | class OIDplusDataBasePluginMySQLi extends OIDplusDataBasePlugin { |
150 | daniel-mar | 36 | private $mysqli; |
37 | private $last_query; |
||
38 | private $prepare_cache = array(); |
||
39 | |||
222 | daniel-mar | 40 | public static function getPluginInformation() { |
41 | $out = array(); |
||
42 | $out['name'] = 'MySQLi'; |
||
43 | $out['author'] = 'ViaThinkSoft'; |
||
44 | $out['version'] = null; |
||
45 | $out['descriptionHTML'] = null; |
||
46 | return $out; |
||
47 | } |
||
48 | |||
150 | daniel-mar | 49 | public static function name() { |
50 | return "MySQL"; |
||
51 | } |
||
52 | |||
53 | public function query($sql, $prepared_args=null) { |
||
54 | $this->last_query = $sql; |
||
55 | if (OIDPLUS_MYSQL_QUERYLOG) file_put_contents("query.log", "$sql <== ".CallingFunctionName()."\n", FILE_APPEND); |
||
56 | if (is_null($prepared_args)) { |
||
57 | return $this->mysqli->query($sql, MYSQLI_STORE_RESULT); |
||
58 | } else { |
||
59 | if (!is_array($prepared_args)) { |
||
60 | throw new Exception("'prepared_args' must be either NULL or an ARRAY."); |
||
61 | } |
||
62 | if (isset($this->prepare_cache[$sql])) { |
||
63 | $ps = $this->prepare_cache[$sql]; |
||
64 | } else { |
||
65 | $ps = $this->mysqli->prepare($sql); |
||
66 | if (!$ps) { |
||
67 | throw new Exception("Cannot prepare statement '$sql'"); |
||
68 | } |
||
69 | $this->prepare_cache[$sql] = $ps; |
||
70 | } |
||
71 | |||
72 | bind_placeholder_vars($ps,$prepared_args); |
||
73 | if (!$ps->execute()) return false; |
||
208 | daniel-mar | 74 | |
75 | $res = MYSQLND_AVAILABLE ? $ps->get_result() : iimysqli_stmt_get_result($ps); |
||
224 | daniel-mar | 76 | |
150 | daniel-mar | 77 | if ($res === false) return true; // A non-SELECT statement does not give a result-set, but it is still successful |
78 | return $res; |
||
79 | } |
||
80 | } |
||
81 | public function num_rows($res) { |
||
82 | if (!is_object($res)) { |
||
83 | throw new Exception("num_rows called on non object. Last query: ".$this->last_query); |
||
84 | } else { |
||
208 | daniel-mar | 85 | return (get_class($res)=='mysqli_result') || MYSQLND_AVAILABLE ? $res->num_rows : $res->num_rows(); |
150 | daniel-mar | 86 | } |
87 | } |
||
88 | public function fetch_array($res) { |
||
89 | if (!is_object($res)) { |
||
90 | throw new Exception("fetch_array called on non object. Last query: ".$this->last_query); |
||
91 | } else { |
||
208 | daniel-mar | 92 | return (get_class($res)=='mysqli_result') || MYSQLND_AVAILABLE ? $res->fetch_array(MYSQLI_BOTH) : $res->fetch_array(); |
150 | daniel-mar | 93 | } |
94 | } |
||
95 | public function fetch_object($res) { |
||
96 | if (!is_object($res)) { |
||
97 | throw new Exception("fetch_object called on non object. Last query: ".$this->last_query); |
||
98 | } else { |
||
208 | daniel-mar | 99 | return (get_class($res)=='mysqli_result') || MYSQLND_AVAILABLE ? $res->fetch_object("stdClass") : $res->fetch_object(); |
150 | daniel-mar | 100 | } |
101 | } |
||
102 | public function insert_id() { |
||
103 | return $this->mysqli->insert_id; |
||
104 | } |
||
105 | public function error() { |
||
106 | return !empty($this->mysqli->connect_error) ? $this->mysqli->connect_error : $this->mysqli->error; |
||
107 | } |
||
108 | |||
109 | public function connect() { |
||
110 | if (OIDPLUS_MYSQL_QUERYLOG) file_put_contents("query.log", ''); |
||
111 | |||
112 | $html = OIDPLUS_HTML_OUTPUT; |
||
113 | |||
114 | // Try connecting to the database |
||
115 | list($hostname,$port) = explode(':', OIDPLUS_MYSQL_HOST.':'.ini_get("mysqli.default_port")); |
||
116 | $this->mysqli = @new mysqli($hostname, OIDPLUS_MYSQL_USERNAME, base64_decode(OIDPLUS_MYSQL_PASSWORD), OIDPLUS_MYSQL_DATABASE, $port); |
||
117 | if (!empty($this->mysqli->connect_error) || ($this->mysqli->connect_errno != 0)) { |
||
118 | if ($html) { |
||
173 | daniel-mar | 119 | echo "<h1>Error</h1><p>Database connection failed! (".$this->error().")</p>"; |
120 | if (is_dir(__DIR__.'/../../../setup')) { |
||
150 | daniel-mar | 121 | echo '<p>If you believe that the login credentials are wrong, please run <a href="setup/">setup</a> again.</p>'; |
122 | } |
||
123 | } else { |
||
173 | daniel-mar | 124 | echo "Error: Database connection failed! (".$this->error().")"; |
125 | if (is_dir(__DIR__.'/../../../setup')) { |
||
150 | daniel-mar | 126 | echo ' If you believe that the login credentials are wrong, please run setup again.'; |
127 | } |
||
128 | } |
||
129 | die(); |
||
130 | } |
||
131 | |||
132 | $this->query("SET NAMES 'utf8'"); |
||
133 | $this->afterConnect($html); |
||
134 | $this->connected = true; |
||
135 | } |
||
136 | |||
137 | private $intransaction = false; |
||
138 | |||
139 | public function transaction_begin() { |
||
140 | if ($this->intransaction) throw new Exception("Nested transactions are not supported by this database plugin."); |
||
141 | $this->mysqli->autocommit(true); |
||
142 | $this->intransaction = true; |
||
143 | } |
||
144 | |||
145 | public function transaction_commit() { |
||
146 | $this->mysqli->commit(); |
||
147 | $this->mysqli->autocommit(false); |
||
148 | $this->intransaction = false; |
||
149 | } |
||
150 | |||
151 | public function transaction_rollback() { |
||
152 | $this->mysqli->rollback(); |
||
153 | $this->mysqli->autocommit(false); |
||
154 | $this->intransaction = false; |
||
155 | } |
||
156 | |||
157 | } |
||
158 | |||
159 | function bind_placeholder_vars(&$stmt,$params,$debug=0) { |
||
208 | daniel-mar | 160 | // Credit to: Dave Morgan |
161 | // Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/ |
||
162 | // https://stackoverflow.com/questions/17219214/how-to-bind-in-mysqli-dynamically |
||
163 | if ($params != null) { |
||
164 | $types = ''; //initial sting with types |
||
165 | foreach ($params as $param) { //for each element, determine type and add |
||
166 | if (is_int($param)) { |
||
167 | $types .= 'i'; //integer |
||
168 | } elseif (is_float($param)) { |
||
169 | $types .= 'd'; //double |
||
170 | } elseif (is_string($param)) { |
||
171 | $types .= 's'; //string |
||
172 | } else { |
||
173 | $types .= 'b'; //blob and unknown |
||
174 | } |
||
175 | } |
||
150 | daniel-mar | 176 | |
208 | daniel-mar | 177 | $bind_names = array(); |
178 | $bind_names[] = $types; //first param needed is the type string |
||
179 | // eg: 'issss' |
||
150 | daniel-mar | 180 | |
208 | daniel-mar | 181 | for ($i=0; $i<count($params);$i++) { //go through incoming params and added em to array |
182 | $bind_name = 'bind' . $i; //give them an arbitrary name |
||
183 | $$bind_name = $params[$i]; //add the parameter to the variable variable |
||
184 | $bind_names[] = &$$bind_name; //now associate the variable as an element in an array |
||
185 | } |
||
150 | daniel-mar | 186 | |
208 | daniel-mar | 187 | if ($debug) { |
188 | echo "\$bind_names:<br />\n"; |
||
189 | var_dump($bind_names); |
||
190 | echo "<br />\n"; |
||
191 | } |
||
192 | //error_log("better_mysqli has params ".print_r($bind_names, 1)); |
||
193 | //call the function bind_param with dynamic params |
||
194 | call_user_func_array(array($stmt,'bind_param'),$bind_names); |
||
195 | return true; |
||
196 | }else{ |
||
197 | return false; |
||
198 | } |
||
150 | daniel-mar | 199 | } |
200 | |||
201 | function bind_result_array($stmt, &$row) { |
||
208 | daniel-mar | 202 | // Credit to: Dave Morgan |
203 | // Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/ |
||
204 | $meta = $stmt->result_metadata(); |
||
205 | while ($field = $meta->fetch_field()) { |
||
206 | $params[] = &$row[$field->name]; |
||
207 | } |
||
208 | call_user_func_array(array($stmt, 'bind_result'), $params); |
||
209 | return true; |
||
150 | daniel-mar | 210 | } |
211 | |||
208 | daniel-mar | 212 | if (!MYSQLND_AVAILABLE) { |
213 | class iimysqli_result { |
||
214 | // Source: https://www.php.net/manual/de/mysqli-stmt.get-result.php#113398 |
||
224 | daniel-mar | 215 | |
208 | daniel-mar | 216 | public $stmt, $nCols; |
150 | daniel-mar | 217 | |
208 | daniel-mar | 218 | function fetch_array() { |
219 | // https://stackoverflow.com/questions/10752815/mysqli-get-result-alternative , modified |
||
220 | $stmt = $this->stmt; |
||
221 | $stmt->store_result(); |
||
222 | $resultkeys = array(); |
||
223 | $thisName = ""; |
||
224 | daniel-mar | 224 | |
208 | daniel-mar | 225 | if ($stmt->num_rows==0) return false; |
224 | daniel-mar | 226 | |
208 | daniel-mar | 227 | for ( $i = 0; $i < $stmt->num_rows; $i++ ) { |
228 | $metadata = $stmt->result_metadata(); |
||
229 | while ( $field = $metadata->fetch_field() ) { |
||
230 | $thisName = $field->name; |
||
231 | $resultkeys[] = $thisName; |
||
232 | } |
||
233 | } |
||
234 | |||
235 | $ret = array(); |
||
236 | $code = "return mysqli_stmt_bind_result(\$this->stmt "; |
||
237 | for ($i=0; $i<$this->nCols; $i++) { |
||
238 | $ret[$i] = NULL; |
||
239 | $theValue = $resultkeys[$i]; |
||
240 | $code .= ", \$ret['$theValue']"; |
||
241 | } |
||
242 | |||
243 | $code .= ");"; |
||
224 | daniel-mar | 244 | if (!eval($code)) { |
245 | return NULL; |
||
208 | daniel-mar | 246 | } |
247 | |||
248 | // This should advance the "$stmt" cursor. |
||
249 | if (!mysqli_stmt_fetch($this->stmt)) { |
||
224 | daniel-mar | 250 | return NULL; |
208 | daniel-mar | 251 | } |
252 | |||
253 | // Return the array we built. |
||
254 | return $ret; |
||
224 | daniel-mar | 255 | } |
256 | |||
208 | daniel-mar | 257 | public function num_rows() { |
258 | $this->stmt->store_result(); |
||
259 | return $this->stmt->num_rows; |
||
260 | } |
||
224 | daniel-mar | 261 | |
208 | daniel-mar | 262 | public function fetch_object() { |
263 | $obj = new stdClass; |
||
264 | $ary = $this->fetch_array(); |
||
265 | if (!$ary) return false; |
||
266 | foreach ($ary as $name => $val) { |
||
267 | $obj->$name = $val; |
||
268 | } |
||
269 | return $obj; |
||
270 | } |
||
224 | daniel-mar | 271 | } |
272 | |||
208 | daniel-mar | 273 | function iimysqli_stmt_get_result($stmt) { |
274 | // Source: https://www.php.net/manual/de/mysqli-stmt.get-result.php#113398 |
||
224 | daniel-mar | 275 | |
208 | daniel-mar | 276 | /** EXPLANATION: |
277 | * We are creating a fake "result" structure to enable us to have |
||
278 | * source-level equivalent syntax to a query executed via |
||
279 | * mysqli_query(). |
||
280 | * |
||
281 | * $stmt = mysqli_prepare($conn, ""); |
||
282 | * mysqli_bind_param($stmt, "types", ...); |
||
283 | * |
||
284 | * $param1 = 0; |
||
285 | * $param2 = 'foo'; |
||
286 | * $param3 = 'bar'; |
||
287 | * mysqli_execute($stmt); |
||
288 | * $result _mysqli_stmt_get_result($stmt); |
||
289 | * [ $arr = _mysqli_result_fetch_array($result); |
||
290 | * || $assoc = _mysqli_result_fetch_assoc($result); ] |
||
291 | * mysqli_stmt_close($stmt); |
||
292 | * mysqli_close($conn); |
||
293 | * |
||
294 | * At the source level, there is no difference between this and mysqlnd. |
||
295 | **/ |
||
296 | $metadata = mysqli_stmt_result_metadata($stmt); |
||
297 | $ret = new iimysqli_result; |
||
298 | if (!$ret) return NULL; |
||
224 | daniel-mar | 299 | |
208 | daniel-mar | 300 | if (is_bool($metadata)) { |
301 | return $metadata; |
||
302 | } |
||
224 | daniel-mar | 303 | |
208 | daniel-mar | 304 | $ret->nCols = mysqli_num_fields($metadata); |
305 | $ret->stmt = $stmt; |
||
224 | daniel-mar | 306 | |
208 | daniel-mar | 307 | mysqli_free_result($metadata); |
308 | return $ret; |
||
309 | } |
||
310 | } |