Completed
Push — development ( 6a24df...5afdf5 )
by Nils
07:52
created

DB::getMDB()   F

Complexity

Conditions 11
Paths 1024

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 23
nc 1024
nop 0
dl 0
loc 37
rs 3.1764
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
    Copyright (C) 2008-2012 Sergey Tsalkov ([email protected])
4
5
    This program is free software: you can redistribute it and/or modify
6
    it under the terms of the GNU Lesser General Public License as published by
7
    the Free Software Foundation, either version 3 of the License, or
8
    (at your option) any later version.
9
10
    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
    GNU Lesser General Public License for more details.
14
15
    You should have received a copy of the GNU Lesser General Public License
16
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
20
class DB {
21
    // initial connection
22
    public static $dbName = '';
23
    public static $user = '';
24
    public static $password = '';
25
    public static $host = 'localhost';
26
    public static $port = null;
27
    public static $encoding = 'utf8';
28
29
    // configure workings
30
    public static $param_char = '%';
31
    public static $named_param_seperator = '_';
32
    public static $success_handler = false;
33
    public static $error_handler = true;
34
    public static $throw_exception_on_error = false;
35
    public static $nonsql_error_handler = null;
36
    public static $throw_exception_on_nonsql_error = false;
37
    public static $nested_transactions = false;
38
    public static $usenull = true;
39
40
    // internal
41
    protected static $mdb = null;
42
43
    public static function getMDB() {
44
    $mdb = DB::$mdb;
45
46
    if ($mdb === null) {
47
        $mdb = DB::$mdb = new MeekroDB();
48
    }
49
50
    if ($mdb->param_char !== DB::$param_char) {
51
        $mdb->param_char = DB::$param_char;
52
    }
53
    if ($mdb->named_param_seperator !== DB::$named_param_seperator) {
54
        $mdb->named_param_seperator = DB::$named_param_seperator;
55
    }
56
    if ($mdb->success_handler !== DB::$success_handler) {
57
        $mdb->success_handler = DB::$success_handler;
58
    }
59
    if ($mdb->error_handler !== DB::$error_handler) {
60
        $mdb->error_handler = DB::$error_handler;
61
    }
62
    if ($mdb->throw_exception_on_error !== DB::$throw_exception_on_error) {
63
        $mdb->throw_exception_on_error = DB::$throw_exception_on_error;
64
    }
65
    if ($mdb->nonsql_error_handler !== DB::$nonsql_error_handler) {
66
        $mdb->nonsql_error_handler = DB::$nonsql_error_handler;
67
    }
68
    if ($mdb->throw_exception_on_nonsql_error !== DB::$throw_exception_on_nonsql_error) {
69
        $mdb->throw_exception_on_nonsql_error = DB::$throw_exception_on_nonsql_error;
70
    }
71
    if ($mdb->nested_transactions !== DB::$nested_transactions) {
72
        $mdb->nested_transactions = DB::$nested_transactions;
73
    }
74
    if ($mdb->usenull !== DB::$usenull) {
75
        $mdb->usenull = DB::$usenull;
76
    }
77
78
    return $mdb;
79
    }
80
81
    public static function get() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'get'), $args); }
82
    public static function disconnect() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'disconnect'), $args); }
83
    public static function query() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'query'), $args); }
84
    public static function queryFirstRow() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstRow'), $args); }
85
    public static function queryOneRow() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneRow'), $args); }
86
    public static function queryAllLists() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryAllLists'), $args); }
87
    public static function queryFullColumns() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFullColumns'), $args); }
88
    public static function queryFirstList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstList'), $args); }
89
    public static function queryOneList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneList'), $args); }
90
    public static function queryFirstColumn() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstColumn'), $args); }
91
    public static function queryOneColumn() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneColumn'), $args); }
92
    public static function queryFirstField() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstField'), $args); }
93
    public static function queryOneField() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneField'), $args); }
94
    public static function queryRaw() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryRaw'), $args); }
95
    public static function queryRawUnbuf() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryRawUnbuf'), $args); }
96
97
    public static function insert() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insert'), $args); }
98
    public static function insertIgnore() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insertIgnore'), $args); }
99
    public static function insertUpdate() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insertUpdate'), $args); }
100
    public static function replace() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'replace'), $args); }
101
    public static function update() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'update'), $args); }
102
    public static function delete() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'delete'), $args); }
103
104
    public static function insertId() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insertId'), $args); }
105
    public static function count() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'count'), $args); }
106
    public static function affectedRows() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'affectedRows'), $args); }
107
108
    public static function useDB() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'useDB'), $args); }
109
    public static function startTransaction() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'startTransaction'), $args); }
110
    public static function commit() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'commit'), $args); }
111
    public static function rollback() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'rollback'), $args); }
112
    public static function tableList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'tableList'), $args); }
113
    public static function columnList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'columnList'), $args); }
114
115
    public static function sqlEval() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'sqlEval'), $args); }
116
    public static function nonSQLError() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'nonSQLError'), $args); }
117
118
    public static function serverVersion() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'serverVersion'), $args); }
119
    public static function transactionDepth() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'transactionDepth'), $args); }
120
121
122
    public static function debugMode($handler = true) {
123
    DB::$success_handler = $handler;
124
    }
125
126
    public function version() {
127
    return $this->version_info();
0 ignored issues
show
Bug introduced by
The method version_info() does not exist on DB. Did you maybe mean version()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
128
    }
129
130
}
131
132
133
class MeekroDB {
134
    // initial connection
135
    public $dbName = '';
136
    public $user = '';
137
    public $password = '';
138
    public $host = 'localhost';
139
    public $port = null;
140
    public $encoding = 'latin1';
141
142
    // configure workings
143
    public $param_char = '%';
144
    public $named_param_seperator = '_';
145
    public $success_handler = false;
146
    public $error_handler = true;
147
    public $throw_exception_on_error = false;
148
    public $nonsql_error_handler = null;
149
    public $throw_exception_on_nonsql_error = false;
150
    public $nested_transactions = false;
151
    public $usenull = true;
152
153
    // internal
154
    public $internal_mysql = null;
155
    public $server_info = null;
156
    public $insert_id = 0;
157
    public $num_rows = 0;
158
    public $affected_rows = 0;
159
    public $current_db = null;
160
    public $nested_transactions_count = 0;
161
162
163
    public function __construct($host=null, $user=null, $password=null, $dbName=null, $port=null, $encoding=null) {
164
    if ($host === null) $host = DB::$host;
165
    if ($user === null) $user = DB::$user;
166
    if ($password === null) $password = DB::$password;
167
    if ($dbName === null) $dbName = DB::$dbName;
168
    if ($port === null) $port = DB::$port;
169
    if ($encoding === null) $encoding = DB::$encoding;
170
171
    $this->host = $host;
172
    $this->user = $user;
173
    $this->password = $password;
174
    $this->dbName = $dbName;
175
    $this->port = $port;
176
    $this->encoding = $encoding;
177
    }
178
179
    public function get() {
180
    $mysql = $this->internal_mysql;
181
182
    if (!($mysql instanceof MySQLi)) {
183
        if (! $this->port) $this->port = ini_get('mysqli.default_port');
184
        $this->current_db = $this->dbName;
185
      
186
        $mysql = new mysqli($this->host, $this->user, $this->password, $this->dbName, $this->port);
187
188
        if ($mysql->connect_error) {
189
        $this->nonSQLError('Unable to connect to MySQL server! Error: ' . $mysql->connect_error);
190
        }
191
192
        $mysql->set_charset($this->encoding);
193
        $this->internal_mysql = $mysql;
194
        $this->server_info = $mysql->server_info;
195
    }
196
197
    return $mysql;
198
    }
199
200
    public function disconnect() {
201
    $mysqli = $this->internal_mysql;
202
    if ($mysqli instanceof MySQLi) {
203
        if ($thread_id = $mysqli->thread_id) $mysqli->kill($thread_id);
204
        $mysqli->close();
205
    }
206
    $this->internal_mysql = null;
207
    }
208
209
    public function nonSQLError($message) {
210
    if ($this->throw_exception_on_nonsql_error) {
211
        $e = new MeekroDBException($message);
212
        throw $e;
213
    }
214
215
    $error_handler = is_callable($this->nonsql_error_handler) ? $this->nonsql_error_handler : 'meekrodb_error_handler';
216
217
    call_user_func($error_handler, array(
218
        'type' => 'nonsql',
219
        'error' => $message
220
    ));
221
    }
222
223
    public function debugMode($handler = true) {
224
    $this->success_handler = $handler;
225
    }
226
227
    public function serverVersion() { $this->get(); return $this->server_info; }
228
    public function transactionDepth() { return $this->nested_transactions_count; }
229
    public function insertId() { return $this->insert_id; }
230
    public function affectedRows() { return $this->affected_rows; }
231
    public function count() { $args = func_get_args(); return call_user_func_array(array($this, 'numRows'), $args); }
232
    public function numRows() { return $this->num_rows; }
233
234
    public function useDB() { $args = func_get_args(); return call_user_func_array(array($this, 'setDB'), $args); }
235
    public function setDB($dbName) {
236
    $db = $this->get();
237
    if (! $db->select_db($dbName)) {
238
        $this->nonSQLError("Unable to set database to $dbName");
239
    }
240
    $this->current_db = $dbName;
241
    }
242
243
244
    public function startTransaction() {
245
    if ($this->nested_transactions && $this->serverVersion() < '5.5') {
246
        return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL " . $this->serverVersion());
247
    }
248
249
    if (!$this->nested_transactions || $this->nested_transactions_count == 0) {
250
        $this->query('START TRANSACTION');
251
        $this->nested_transactions_count = 1;
252
    } else {
253
        $this->query("SAVEPOINT LEVEL{$this->nested_transactions_count}");
254
        $this->nested_transactions_count++;
255
    }
256
257
    return $this->nested_transactions_count;
258
    }
259
260 View Code Duplication
    public function commit($all=false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
261
    if ($this->nested_transactions && $this->serverVersion() < '5.5') {
262
        return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL " . $this->serverVersion());
263
    }
264
265
    if ($this->nested_transactions && $this->nested_transactions_count > 0)
266
        $this->nested_transactions_count--;
267
268
    if (!$this->nested_transactions || $all || $this->nested_transactions_count == 0) {
269
        $this->nested_transactions_count = 0;
270
        $this->query('COMMIT');
271
    } else {
272
        $this->query("RELEASE SAVEPOINT LEVEL{$this->nested_transactions_count}");
273
    }
274
275
    return $this->nested_transactions_count;
276
    }
277
278 View Code Duplication
    public function rollback($all=false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
    if ($this->nested_transactions && $this->serverVersion() < '5.5') {
280
        return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL " . $this->serverVersion());
281
    }
282
283
    if ($this->nested_transactions && $this->nested_transactions_count > 0)
284
        $this->nested_transactions_count--;
285
286
    if (!$this->nested_transactions || $all || $this->nested_transactions_count == 0) {
287
        $this->nested_transactions_count = 0;
288
        $this->query('ROLLBACK');
289
    } else {
290
        $this->query("ROLLBACK TO SAVEPOINT LEVEL{$this->nested_transactions_count}");
291
    }
292
293
    return $this->nested_transactions_count;
294
    }
295
296
    protected function formatTableName($table) {
297
    $table = trim($table, '`');
298
299 View Code Duplication
    if (strpos($table, '.')) return implode('.', array_map(array($this, 'formatTableName'), explode('.', $table)));
300
    else return '`' . str_replace('`', '``', $table) . '`';
301
    }
302
303
    public function update() {
304
    $args = func_get_args();
305
    $table = array_shift($args);
306
    $params = array_shift($args);
307
    $where = array_shift($args);
308
309
    $query = "UPDATE %b SET %? WHERE ".$where;
310
311
    array_unshift($args, $params);
312
    array_unshift($args, $table);
313
    array_unshift($args, $query);
314
    return call_user_func_array(array($this, 'query'), $args);
315
  }
316
317
  /**
318
   * @param string $which
319
   */
320
  public function insertOrReplace($which, $table, $datas, $options=array()) {
321
    $datas = unserialize(serialize($datas)); // break references within array
322
    $keys = $values = array();
323
324
    if (isset($datas[0]) && is_array($datas[0])) {
325
      foreach ($datas as $datum) {
326
        ksort($datum);
327
        if (! $keys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $keys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
328
            $keys = array_keys($datum);
329
        }
330
        $values[] = array_values($datum);
331
      }
332
333
    } else {
334
      $keys = array_keys($datas);
335
      $values = array_values($datas);
336
    }
337
338
    if (isset($options['ignore']) && $options['ignore']) {
339
        $which = 'INSERT IGNORE';
340
    }
341
342
    if (isset($options['update']) && is_array($options['update']) && $options['update'] && strtolower($which) == 'insert') {
343
      if (array_values($options['update']) !== $options['update']) {
344
        return $this->query("INSERT INTO %b %lb VALUES %? ON DUPLICATE KEY UPDATE %?", $table, $keys, $values, $options['update']);
345
      } else {
346
        $update_str = array_shift($options['update']);
347
        $query_param = array("INSERT INTO %b %lb VALUES %? ON DUPLICATE KEY UPDATE $update_str", $table, $keys, $values);
348
        $query_param = array_merge($query_param, $options['update']);
349
        return call_user_func_array(array($this, 'query'), $query_param);
350
      }
351
352
    }
353
354
    return $this->query("%l INTO %b %lb VALUES %?", $which, $table, $keys, $values);
355
  }
356
357
  public function insert($table, $data) { return $this->insertOrReplace('INSERT', $table, $data); }
358
  public function insertIgnore($table, $data) { return $this->insertOrReplace('INSERT', $table, $data, array('ignore' => true)); }
359
  public function replace($table, $data) { return $this->insertOrReplace('REPLACE', $table, $data); }
360
361
  public function insertUpdate() {
362
    $args = func_get_args();
363
    $table = array_shift($args);
364
    $data = array_shift($args);
365
366
    if (!isset($args[0])) { // update will have all the data of the insert
367
      if (isset($data[0]) && is_array($data[0])) { //multiple insert rows specified -- failing!
368
        $this->nonSQLError("Badly formatted insertUpdate() query -- you didn't specify the update component!");
369
      }
370
371
      $args[0] = $data;
372
    }
373
374
    if (is_array($args[0])) {
375
        $update = $args[0];
376
    } else {
377
        $update = $args;
378
    }
379
380
    return $this->insertOrReplace('INSERT', $table, $data, array('update' => $update));
381
  }
382
383
  public function delete() {
384
    $args = func_get_args();
385
    $table = $this->formatTableName(array_shift($args));
386
    $where = array_shift($args);
387
    $buildquery = "DELETE FROM $table WHERE $where";
388
    array_unshift($args, $buildquery);
389
    return call_user_func_array(array($this, 'query'), $args);
390
  }
391
392
  public function sqleval() {
393
    $args = func_get_args();
394
    $text = call_user_func_array(array($this, 'parseQueryParams'), $args);
395
    return new MeekroDBEval($text);
396
  }
397
398
  public function columnList($table) {
399
    return $this->queryOneColumn('Field', "SHOW COLUMNS FROM $table");
400
  }
401
402
  public function tableList($db = null) {
403
    if ($db) {
404
      $olddb = $this->current_db;
405
      $this->useDB($db);
406
    }
407
408
    $result = $this->queryFirstColumn('SHOW TABLES');
409
    if (isset($olddb)) {
410
        $this->useDB($olddb);
411
    }
412
    return $result;
413
  }
414
415
  protected function preparseQueryParams() {
416
    $args = func_get_args();
417
    $sql = trim(strval(array_shift($args)));
418
    $args_all = $args;
419
420
    if (count($args_all) == 0) {
421
        return array($sql);
422
    }
423
424
    $param_char_length = strlen($this->param_char);
425
    $named_seperator_length = strlen($this->named_param_seperator);
426
427
    $types = array(
428
        $this->param_char . 'll', // list of literals
429
        $this->param_char . 'ls', // list of strings
430
        $this->param_char . 'l',  // literal
431
        $this->param_char . 'li', // list of integers
432
        $this->param_char . 'ld', // list of decimals
433
        $this->param_char . 'lb', // list of backticks
434
        $this->param_char . 'lt', // list of timestamps
435
        $this->param_char . 's',  // string
436
        $this->param_char . 'i',  // integer
437
        $this->param_char . 'd',  // double / decimal
438
        $this->param_char . 'b',  // backtick
439
        $this->param_char . 't',  // timestamp
440
        $this->param_char . '?',  // infer type
441
        $this->param_char . 'ss'  // search string (like string, surrounded with %'s)
442
    );
443
444
    // generate list of all MeekroDB variables in our query, and their position
445
    // in the form "offset => variable", sorted by offsets
446
    $posList = array();
447
    foreach ($types as $type) {
448
        $lastPos = 0;
449
        while (($pos = strpos($sql, $type, $lastPos)) !== false) {
450
        $lastPos = $pos + 1;
451
        if (isset($posList[$pos]) && strlen($posList[$pos]) > strlen($type)) {
452
            continue;
453
        }
454
        $posList[$pos] = $type;
455
        }
456
    }
457
458
    ksort($posList);
459
460
    // for each MeekroDB variable, substitute it with array(type: i, value: 53) or whatever
461
    $chunkyQuery = array(); // preparsed query
462
    $pos_adj = 0; // how much we've added or removed from the original sql string
463
    foreach ($posList as $pos => $type) {
464
        $type = substr($type, $param_char_length); // variable, without % in front of it
465
        $length_type = strlen($type) + $param_char_length; // length of variable w/o %
466
467
        $new_pos = $pos + $pos_adj; // position of start of variable
468
        $new_pos_back = $new_pos + $length_type; // position of end of variable
469
        $arg_number_length = 0; // length of any named or numbered parameter addition
0 ignored issues
show
Unused Code introduced by
$arg_number_length is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
470
471
        // handle numbered parameters
472
        if ($arg_number_length = strspn($sql, '0123456789', $new_pos_back)) {
473
        $arg_number = substr($sql, $new_pos_back, $arg_number_length);
474
        if (!array_key_exists($arg_number, $args_all)) $this->nonSQLError("Non existent argument reference (arg $arg_number): $sql");
475
476
        $arg = $args_all[$arg_number];
477
478
        // handle named parameters
479
        } else if (substr($sql, $new_pos_back, $named_seperator_length) == $this->named_param_seperator) {
480
        $arg_number_length = strspn($sql, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_',
481
            $new_pos_back + $named_seperator_length) + $named_seperator_length;
482
483
        $arg_number = substr($sql, $new_pos_back + $named_seperator_length, $arg_number_length - $named_seperator_length);
484
        if (count($args_all) != 1 || !is_array($args_all[0])) $this->nonSQLError("If you use named parameters, the second argument must be an array of parameters");
485
        if (!array_key_exists($arg_number, $args_all[0])) $this->nonSQLError("Non existent argument reference (arg $arg_number): $sql");
486
487
        $arg = $args_all[0][$arg_number];
488
489
        } else {
490
        $arg_number = 0;
0 ignored issues
show
Unused Code introduced by
$arg_number is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
491
        $arg = array_shift($args);
492
        }
493
494
        if ($new_pos > 0) $chunkyQuery[] = substr($sql, 0, $new_pos);
495
496
        if (is_object($arg) && ($arg instanceof WhereClause)) {
497
        list($clause_sql, $clause_args) = $arg->textAndArgs();
498
        array_unshift($clause_args, $clause_sql);
499
        $preparsed_sql = call_user_func_array(array($this, 'preparseQueryParams'), $clause_args);
500
        $chunkyQuery = array_merge($chunkyQuery, $preparsed_sql);
501
        } else {
502
        $chunkyQuery[] = array('type' => $type, 'value' => $arg);
503
        }
504
505
        $sql = substr($sql, $new_pos_back + $arg_number_length);
506
        $pos_adj -= $new_pos_back + $arg_number_length;
507
    }
508
509
    if (strlen($sql) > 0) {
510
        $chunkyQuery[] = $sql;
511
    }
512
513
    return $chunkyQuery;
514
    }
515
516
    protected function escape($str) { return "'" . $this->get()->real_escape_string(strval($str)) . "'"; }
517
518
    protected function sanitize($value) {
519
    if (is_object($value)) {
520
        if ($value instanceof MeekroDBEval) return $value->text;
521
        else if ($value instanceof DateTime) return $this->escape($value->format('Y-m-d H:i:s'));
522
        else return '';
523
    }
524
525
    if (is_null($value)) return $this->usenull ? 'NULL' : "''";
526
    else if (is_bool($value)) return ($value ? 1 : 0);
527
    else if (is_int($value)) return $value;
528
    else if (is_float($value)) return $value;
529
530
    else if (is_array($value)) {
531
        // non-assoc array?
532
        if (array_values($value) === $value) {
533 View Code Duplication
        if (is_array($value[0])) return implode(', ', array_map(array($this, 'sanitize'), $value));
534
        else return '(' . implode(', ', array_map(array($this, 'sanitize'), $value)) . ')';
535
        }
536
537
        $pairs = array();
538
        foreach ($value as $k => $v) {
539
        $pairs[] = $this->formatTableName($k) . '=' . $this->sanitize($v);
540
        }
541
542
        return implode(', ', $pairs);
543
    }
544
    else return $this->escape($value);
545
    }
546
547
    protected function parseTS($ts) {
548
    if (is_string($ts)) return date('Y-m-d H:i:s', strtotime($ts));
549
    else if (is_object($ts) && ($ts instanceof DateTime)) return $ts->format('Y-m-d H:i:s');
550
    }
551
552
    protected function intval($var) {
553
    if (PHP_INT_SIZE == 8) return intval($var);
554
    return floor(doubleval($var));
555
    }
556
557
    protected function parseQueryParams() {
558
    $args = func_get_args();
559
    $chunkyQuery = call_user_func_array(array($this, 'preparseQueryParams'), $args);
560
561
    $query = '';
562
    $array_types = array('ls', 'li', 'ld', 'lb', 'll', 'lt');
563
564
    foreach ($chunkyQuery as $chunk) {
565
      if (is_string($chunk)) {
566
        $query .= $chunk;
567
        continue;
568
      }
569
570
      $type = $chunk['type'];
571
      $arg = $chunk['value'];
572
      $result = '';
573
574
      if ($type != '?') {
575
        $is_array_type = in_array($type, $array_types, true);
576
        if ($is_array_type && !is_array($arg)) $this->nonSQLError("Badly formatted SQL query: Expected array, got scalar instead!");
577
        else if (!$is_array_type && is_array($arg)) $this->nonSQLError("Badly formatted SQL query: Expected scalar, got array instead!");
578
      }
579
580
      if ($type == 's') $result = $this->escape($arg);
581
      else if ($type == 'i') $result = $this->intval($arg);
582
      else if ($type == 'd') $result = doubleval($arg);
583
      else if ($type == 'b') $result = $this->formatTableName($arg);
584
      else if ($type == 'l') $result = $arg;
585
      else if ($type == 'ss') $result = $this->escape("%".str_replace(array('%', '_'), array('\%', '\_'), $arg)."%");
586
      else if ($type == 't') $result = $this->escape($this->parseTS($arg));
587
588
      else if ($type == 'ls') $result = array_map(array($this, 'escape'), $arg);
589
      else if ($type == 'li') $result = array_map(array($this, 'intval'), $arg);
590
      else if ($type == 'ld') $result = array_map('doubleval', $arg);
591
      else if ($type == 'lb') $result = array_map(array($this, 'formatTableName'), $arg);
592
      else if ($type == 'll') $result = $arg;
593
      else if ($type == 'lt') $result = array_map(array($this, 'escape'), array_map(array($this, 'parseTS'), $arg));
594
595
      else if ($type == '?') $result = $this->sanitize($arg);
596
597
      else $this->nonSQLError("Badly formatted SQL query: Invalid MeekroDB param $type");
598
599
      if (is_array($result)) $result = '('.implode(',', $result).')';
600
601
      $query .= $result;
602
    }
603
604
    return $query;
605
  }
606
607
  /**
608
   * @param string $prepend
609
   */
610
  protected function prependCall($function, $args, $prepend) { array_unshift($args, $prepend); return call_user_func_array($function, $args); }
611
  public function query() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'assoc'); }
612
  public function queryAllLists() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'list'); }
613
  public function queryFullColumns() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'full'); }
614
615
  public function queryRaw() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'raw_buf'); }
616
  public function queryRawUnbuf() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'raw_unbuf'); }
617
618
  protected function queryHelper() {
619
    $args = func_get_args();
620
    $type = array_shift($args);
621
    $db = $this->get();
622
623
    $is_buffered = true;
624
    $row_type = 'assoc'; // assoc, list, raw
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
625
    $full_names = false;
626
627
    switch ($type) {
628
        case 'assoc':
629
        break;
630
        case 'list':
631
        $row_type = 'list';
632
        break;
633
        case 'full':
634
        $row_type = 'list';
635
        $full_names = true;
636
        break;
637
        case 'raw_buf':
638
        $row_type = 'raw';
639
        break;
640
        case 'raw_unbuf':
641
        $is_buffered = false;
642
        $row_type = 'raw';
643
        break;
644
        default:
645
        $this->nonSQLError('Error -- invalid argument to queryHelper!');
646
    }
647
648
    $sql = call_user_func_array(array($this, 'parseQueryParams'), $args);
649
650
    if ($this->success_handler) {
651
        $starttime = microtime(true);
652
    }
653
    $result = $db->query($sql, $is_buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT);
654
    if ($this->success_handler) {
655
        $runtime = microtime(true) - $starttime;
0 ignored issues
show
Bug introduced by
The variable $starttime does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
656
    } else {
657
        $runtime = 0;
658
    }
659
660
    // ----- BEGIN ERROR HANDLING
661
    if (!$sql || $db->error) {
662
        if ($this->error_handler) {
663
        $db_error = $db->error;
664
        $db_errno = $db->errno;
665
    $db->query(
666
        "INSERT INTO ".$GLOBALS['pre']."log_system SET
667
      date=".time().",
668
      qui=".$_SESSION['user_id'].",
669
      label='Query: ".addslashes($sql)."<br />Error: ".addslashes($db_error)."<br />@ ".mysqli_real_escape_string($link, filter_var($_SERVER['REQUEST_URI'], FILTER_SANITIZE_STRING))."',
0 ignored issues
show
Bug introduced by
The variable $link does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
670
      type='error'",
671
        MYSQLI_USE_RESULT
672
    );
673
674
        $error_handler = is_callable($this->error_handler) ? $this->error_handler : 'meekrodb_error_handler';
675
676
        call_user_func($error_handler, array(
677
            'type' => 'sql',
678
            'query' => $sql,
679
            'error' => $db_error,
680
            'code' => $db_errno
681
        ));
682
        }
683
684
        if ($this->throw_exception_on_error) {
685
        $e = new MeekroDBException($db_error, $sql, $db_errno);
0 ignored issues
show
Bug introduced by
The variable $db_error does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $db_errno does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
686
        throw $e;
687
        }
688
    } else if ($this->success_handler) {
689
        $runtime = sprintf('%f', $runtime * 1000);
690
        $success_handler = is_callable($this->success_handler) ? $this->success_handler : 'meekrodb_debugmode_handler';
691
692
        call_user_func($success_handler, array(
693
        'query' => $sql,
694
        'runtime' => $runtime,
695
        'affected' => $db->affected_rows
696
        ));
697
    }
698
699
    // ----- END ERROR HANDLING
700
701
    $this->insert_id = $db->insert_id;
702
    $this->affected_rows = $db->affected_rows;
703
704
    // mysqli_result->num_rows won't initially show correct results for unbuffered data
705
    if ($is_buffered && ($result instanceof MySQLi_Result)) $this->num_rows = $result->num_rows;
706
    else $this->num_rows = null;
707
708
    if ($row_type == 'raw' || !($result instanceof MySQLi_Result)) return $result;
709
710
    $return = array();
711
712
    if ($full_names) {
713
        $infos = array();
714
        foreach ($result->fetch_fields() as $info) {
715
        if (strlen($info->table)) $infos[] = $info->table . '.' . $info->name;
716
        else $infos[] = $info->name;
717
        }
718
    }
719
720
    while ($row = ($row_type == 'assoc' ? $result->fetch_assoc() : $result->fetch_row())) {
721
        if ($full_names) $row = array_combine($infos, $row);
0 ignored issues
show
Bug introduced by
The variable $infos does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
722
        $return[] = $row;
723
    }
724
725
    // free results
726
    $result->free();
727
    while ($db->more_results()) {
728
        $db->next_result();
729
        if ($result = $db->use_result()) $result->free();
730
    }
731
732
    return $return;
733
    }
734
735
    public function queryOneRow() { $args = func_get_args(); return call_user_func_array(array($this, 'queryFirstRow'), $args); }
736 View Code Duplication
    public function queryFirstRow() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
737
    $args = func_get_args();
738
    $result = call_user_func_array(array($this, 'query'), $args);
739
    if (!$result) return null;
740
    return reset($result);
741
    }
742
743
    public function queryOneList() { $args = func_get_args(); return call_user_func_array(array($this, 'queryFirstList'), $args); }
744 View Code Duplication
    public function queryFirstList() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
745
    $args = func_get_args();
746
    $result = call_user_func_array(array($this, 'queryAllLists'), $args);
747
    if (!$result) return null;
748
    return reset($result);
749
    }
750
751
    public function queryFirstColumn() {
752
    $args = func_get_args();
753
    $results = call_user_func_array(array($this, 'queryAllLists'), $args);
754
    $ret = array();
755
756
    if (!count($results) || !count($results[0])) {
757
        return $ret;
758
    }
759
760
    foreach ($results as $row) {
761
        $ret[] = $row[0];
762
    }
763
764
    return $ret;
765
    }
766
767
    public function queryOneColumn() {
768
    $args = func_get_args();
769
    $column = array_shift($args);
770
    $results = call_user_func_array(array($this, 'query'), $args);
771
    $ret = array();
772
773
    if (!count($results) || !count($results[0])) {
774
        return $ret;
775
    }
776 View Code Duplication
    if ($column === null) {
777
        $keys = array_keys($results[0]);
778
        $column = $keys[0];
779
    }
780
781
    foreach ($results as $row) {
782
        $ret[] = $row[$column];
783
    }
784
785
    return $ret;
786
    }
787
788
    public function queryFirstField() {
789
    $args = func_get_args();
790
    $row = call_user_func_array(array($this, 'queryFirstList'), $args);
791
    if ($row == null) {
792
        return null;
793
    }
794
    return $row[0];
795
    }
796
797
    public function queryOneField() {
798
    $args = func_get_args();
799
    $column = array_shift($args);
800
801
    $row = call_user_func_array(array($this, 'queryOneRow'), $args);
802
    if ($row == null) {
803
        return null;
804 View Code Duplication
    } else if ($column === null) {
805
        $keys = array_keys($row);
806
        $column = $keys[0];
807
    }
808
809
    return $row[$column];
810
    }
811
}
812
813
class WhereClause {
814
    public $type = 'and'; //AND or OR
815
    public $negate = false;
816
    public $clauses = array();
817
818
    function __construct($type) {
819
    $type = strtolower($type);
820
    if ($type !== 'or' && $type !== 'and') {
821
        DB::nonSQLError('you must use either WhereClause(and) or WhereClause(or)');
822
    }
823
    $this->type = $type;
824
    }
825
826
    function add() {
827
    $args = func_get_args();
828
    $sql = array_shift($args);
829
830
    if ($sql instanceof WhereClause) {
831
        $this->clauses[] = $sql;
832
    } else {
833
        $this->clauses[] = array('sql' => $sql, 'args' => $args);
834
    }
835
    }
836
837
    function negateLast() {
838
    $i = count($this->clauses) - 1;
839
    if (!isset($this->clauses[$i])) {
840
        return;
841
    }
842
843
    if ($this->clauses[$i] instanceof WhereClause) {
844
        $this->clauses[$i]->negate();
845
    } else {
846
        $this->clauses[$i]['sql'] = 'NOT (' . $this->clauses[$i]['sql'] . ')';
847
    }
848
    }
849
850
    function negate() {
851
    $this->negate = ! $this->negate;
852
    }
853
854
    function addClause($type) {
855
    $r = new WhereClause($type);
856
    $this->add($r);
857
    return $r;
858
    }
859
860
    function count() {
861
    return count($this->clauses);
862
    }
863
864
    function textAndArgs() {
865
    $sql = array();
866
    $args = array();
867
868
    if (count($this->clauses) == 0) {
869
        return array('(1)', $args);
870
    }
871
872
    foreach ($this->clauses as $clause) {
873
        if ($clause instanceof WhereClause) {
874
        list($clause_sql, $clause_args) = $clause->textAndArgs();
875
        } else {
876
        $clause_sql = $clause['sql'];
877
        $clause_args = $clause['args'];
878
        }
879
880
        $sql[] = "($clause_sql)";
881
        $args = array_merge($args, $clause_args);
882
    }
883
884
    if ($this->type == 'and') $sql = implode(' AND ', $sql);
885
    else $sql = implode(' OR ', $sql);
886
887
    if ($this->negate) $sql = '(NOT '.$sql.')';
888
    return array($sql, $args);
889
    }
890
891
    // backwards compatability
892
    // we now return full WhereClause object here and evaluate it in preparseQueryParams
893
    function text() { return $this; }
894
}
895
896
class DBTransaction {
897
    private $committed = false;
898
899
    function __construct() {
900
    DB::startTransaction();
901
    }
902
    function __destruct() {
903
    if (! $this->committed) DB::rollback();
904
    }
905
    function commit() {
906
    DB::commit();
907
    $this->committed = true;
908
    }
909
910
911
}
912
913
class MeekroDBException extends Exception {
914
    protected $query = '';
915
916
    function __construct($message='', $query='', $code = 0) {
917
    parent::__construct($message);
918
    $this->query = $query;
919
    $this->code = $code;
920
    }
921
922
    public function getQuery() { return $this->query; }
923
}
924
925
class DBHelper {
926
    /*
927
    verticalSlice
928
    1. For an array of assoc rays, return an array of values for a particular key
929
    2. if $keyfield is given, same as above but use that hash key as the key in new array
930
  */
931
932
    public static function verticalSlice($array, $field, $keyfield = null) {
933
    $array = (array) $array;
934
935
    $R = array();
936
    foreach ($array as $obj) {
937
        if (! array_key_exists($field, $obj)) die("verticalSlice: array doesn't have requested field\n");
938
939
        if ($keyfield) {
940
        if (! array_key_exists($keyfield, $obj)) die("verticalSlice: array doesn't have requested field\n");
941
        $R[$obj[$keyfield]] = $obj[$field];
942
        } else {
943
        $R[] = $obj[$field];
944
        }
945
    }
946
    return $R;
947
    }
948
949
    /*
950
    reIndex
951
    For an array of assoc rays, return a new array of assoc rays using a certain field for keys
952
  */
953
954
    public static function reIndex() {
955
    $fields = func_get_args();
956
    $array = array_shift($fields);
957
    $array = (array) $array;
958
959
    $R = array();
960
    foreach ($array as $obj) {
961
        $target =& $R;
962
963
        foreach ($fields as $field) {
964
        if (! array_key_exists($field, $obj)) die("reIndex: array doesn't have requested field\n");
965
966
        $nextkey = $obj[$field];
967
        $target =& $target[$nextkey];
968
        }
969
        $target = $obj;
970
    }
971
    return $R;
972
    }
973
}
974
975
function meekrodb_error_handler($params) {
976
    echo prepareExchangedData('[{"error" : "'.$params['error'].'"}]', "encode");
977
978
    die;
979
}
980
981
function meekrodb_debugmode_handler($params) {
982
    echo "QUERY: " . $params['query'] . " [" . $params['runtime'] . " ms]";
983
    if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) {
984
    echo "\n";
985
    } else {
986
    echo "<br>\n";
987
    }
988
}
989
990
class MeekroDBEval {
991
    public $text = '';
992
993
    function __construct($text) {
994
    $this->text = $text;
995
    }
996
}