Subversion Repositories oidplus

Rev

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
}