Issues (994)

src/DB/pdo.php (27 issues)

1
<?php
2
3
namespace DB;
4
5
use JSON\json;
6
use MVC\Exception;
7
use PDO as GlobalPDO;
8
use PDOException;
9
use mysqli as MYSQLi;
10
11
/*
12
 function SQL_Connect    ($user, $pass, $dbname, $host = "localhost", $charset = "utf8mb4");
13
 function SQL_Exec       ($pdo, $query, $values = false);
14
 function SQL_Fetch      ($pdo, $query, $values = false);
15
 function SQL_MultiFetch ($pdo, $query, $values = false);
16
 function SQL_LastID     ($pdo);
17
 function SQL_Error      ($e, $query);
18
19
 ------------------------------------------------------------
20
21
 Define PDO_DEBUG for detailed error messages.
22
23
 PDO_DEBUG values:
24
25
  1: Print the error message.
26
  2: Print also the SQL Statement.
27
  3: Print SQL Statement and traceback with detailed information where the error occurs.
28
29
 Example: define ("PDO_DEBUG", "2");
30
31
*/
32
33
if (!defined('PDO_DEBUG')) {
34
  $env = \MVC\router::get_env();
35
  if ('development' == $env) {
36
    $env = '3';
37
  } else {
38
    $env = '1';
39
  }
40
  define('PDO_DEBUG', $env);
41
}
42
43
/**
44
 * Database Management.
45
 *
46
 * @author DimasLanjaka <[email protected]>
47
 */
48
class pdo
49
{
50
  protected $userdb;
51
  protected $passdb;
52
  protected $namedb;
53
  protected $hostdb;
54
  protected $charsetdb;
55
  /**
56
   * MYSQLI Instance
57
   *
58
   * @var mysqli
59
   */
60
  protected $mysqli;
61
  /**
62
   * Shimmer MySQL
63
   *
64
   * @var \UniversalFramework\MySQL
65
   */
66
  protected $shimmer;
67
  /**
68
   * PDO instance.
69
   *
70
   * @var GlobalPDO
71
   */
72
  protected $pdo;
73
74
  public function __construct($user = null, $pass = null, $dbname = null, $host = 'localhost', $charset = 'utf8mb4')
75
  {
76
    if (!empty($user) && !empty($dbname)) {
77
      $this->connect($user, $pass, $dbname, $host, $charset);
78
    } else {
79
      exit('Database wrong ' . json_encode(func_get_args()));
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
80
    }
81
  }
82
83
  /**
84
   * Get enum or set value as associative arrays
85
   *
86
   * @param string $table
87
   * @param string $field
88
   * @return array
89
   */
90
  public function get_enum_set_values($table, $field)
91
  {
92
    $type = $this->pdo
93
      ->query("SHOW COLUMNS FROM {$table} WHERE Field = '{$field}'")->fetch();
94
    if (isset($type['Type'])) {
95
      preg_match("/^(enum|set)\(\'(.*)\'\)$/", $type['Type'], $matches);
96
      if (isset($matches[2])) {
97
        $enum = explode("','", $matches[2]);
98
        return $enum;
99
      }
100
    }
101
    return [];
102
  }
103
  //UPDATE `roles` SET `allow` = 'edit-categories,edit-user,add-user' WHERE `roles`.`id` = 1;
104
  public function set_multiple_value(string $table, string $field, array $values, string $where)
105
  {
106
    $combine = implode(',', $values);
107
108
    $sql = "UPDATE `$table` SET `$field` = '$combine' $where;";
0 ignored issues
show
The assignment to $sql is dead and can be removed.
Loading history...
109
  }
110
111
  /**
112
   * Add value to SET field
113
   *
114
   * @param string $table
115
   * @param string $field
116
   * @param string $new_value
117
   * @return array
118
   */
119
  public function add_set_value(string $table, string $field, $new_value)
120
  {
121
    $sets = ["'$new_value'"];
122
    foreach ($this->get_enum_set_values($table, $field) as $value) {
123
      $sets[] = "'$value'";
124
    }
125
    ksort($sets);
126
    $combine = implode(',', array_unique($sets));
127
    $sql = "ALTER TABLE `$table` CHANGE `$field` `$field` SET($combine);";
128
    return $this->query($sql)->exec();
129
  }
130
131
  /**
132
   * Remove value from SET field
133
   *
134
   * @param string $table
135
   * @param string $field
136
   * @param string $old_value
137
   * @return array
138
   */
139
  public function remove_set_value(string $table, string $field, $old_value)
140
  {
141
    $sets = [];
142
    foreach ($this->get_enum_set_values($table, $field) as $value) {
143
      $sets[] = "'$value'";
144
    }
145
    if (isset($sets[$old_value])) {
146
      unset($set[$old_value]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $set does not exist. Did you maybe mean $sets?
Loading history...
147
    }
148
    ksort($sets);
149
    $combine = implode(',', array_unique($sets));
150
    $sql = "ALTER TABLE `$table` CHANGE `$field` `$field` SET($combine);";
151
    return $this->query($sql)->exec();
152
  }
153
154
  /**
155
   * Check table exists.
156
   *
157
   * @return bool
158
   */
159
  public function check_table(string $table)
160
  {
161
    $check = $this->query("SHOW TABLES LIKE '$table';")->row_array();
162
163
    return !empty($check);
164
  }
165
166
  /**
167
   * Get mysqli instances
168
   *
169
   * @return mysqli
170
   */
171
  public function mysqli()
172
  {
173
    return $this->mysqli;
174
  }
175
176
  /**
177
   * Escape query sql
178
   *
179
   * @param string $query
180
   * @return string
181
   */
182
  public function escape(string $query = null)
183
  {
184
    $this->mysqli->query('SET NAMES utf8mb4;');
185
    $new_data = $query != null ? $query : $this->query;
0 ignored issues
show
It seems like you are loosely comparing $query of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
186
    //$new_data = $this->mysqli->real_escape_string();
187
    //$new_data = addcslashes($new_data, '%_$');
188
    //$new_data = htmlspecialchars($new_data, ENT_NOQUOTES);
189
    $new_data = preg_quote($new_data);
190
    return $new_data;
191
  }
192
193
  public function descape(string $query = null)
194
  {
195
    $this->mysqli->query('SET NAMES utf8mb4;');
196
    $new_data = $query != null ? $query : $this->query;
0 ignored issues
show
It seems like you are loosely comparing $query of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
197
    //$new_data = htmlspecialchars_decode($new_data);
198
    $new_data = stripcslashes($new_data);
199
    return $new_data;
200
  }
201
202
  /**
203
   * Set MySQL Timezone.
204
   *
205
   * @requires superuser access
206
   *
207
   * @return array
208
   */
209
  public function setTimezone(string $gmt = '+7:00')
210
  {
211
    return $this->query("SET GLOBAL time_zone = '$gmt';
212
    SET SESSION time_zone = '$gmt';
213
    SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;")->exec();
214
  }
215
216
  /**
217
   * Connect PDO.
218
   *
219
   * @param string $user database user
220
   * @param string $pass database password
221
   * @param string $dbname database name
222
   * @param string $host database host
223
   * @param string $charset database charset
224
   *
225
   * @return \PDO
226
   */
227
  public function connect($user, $pass, $dbname, $host = 'localhost', $charset = 'utf8mb4')
228
  {
229
    $this->userdb = $user;
230
    $this->passdb = $pass;
231
    $this->namedb = $dbname;
232
    $this->hostdb = $host;
233
    $this->charsetdb = $charset;
234
    $options = [
235
      GlobalPDO::ATTR_ERRMODE => GlobalPDO::ERRMODE_EXCEPTION,
236
      GlobalPDO::ATTR_DEFAULT_FETCH_MODE => GlobalPDO::FETCH_ASSOC,
237
      GlobalPDO::ATTR_EMULATE_PREPARES => false,
238
      GlobalPDO::ATTR_CASE => GlobalPDO::CASE_NATURAL,
239
      GlobalPDO::ATTR_ORACLE_NULLS => GlobalPDO::NULL_EMPTY_STRING,
240
      GlobalPDO::ATTR_STATEMENT_CLASS => ['\DB\EPDOStatement', []],
241
    ];
242
243
    $pdo = null;
0 ignored issues
show
The assignment to $pdo is dead and can be removed.
Loading history...
244
    try {
245
      $pdo = new GlobalPDO("mysql:host=$host;dbname=$dbname;charset=$charset", $user, $pass, $options);
246
      $this->mysqli = new MYSQLi($host, $user, $pass, $dbname);
247
      $this->shimmer = new \UniversalFramework\MySQL($this->mysqli);
248
    } catch (PDOException $e) {
249
      exit($e->getMessage());
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
250
      $this->SQL_Error($e);
0 ignored issues
show
$this->SQL_Error($e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
251
    }
252
    $this->pdo = $pdo;
253
254
    return $pdo;
255
  }
256
257
  /**
258
   * Get mysql shimmer instances
259
   *
260
   * @return \UniversalFramework\MySQL
261
   */
262
  function shimmer()
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
263
  {
264
    return $this->shimmer;
265
  }
266
267
  /**
268
   * Switch database.
269
   *
270
   * @todo Switch between databases
271
   *
272
   * @return $this
273
   */
274
  public function switch(string $dbname)
275
  {
276
    $this->query('USE ' . $dbname)->exec();
277
278
    return $this;
279
  }
280
281
  private $from;
282
  private $query = '';
283
284
  public function query(string $sql)
285
  {
286
    $this->query = $this->trim($sql);
287
288
    return $this;
289
  }
290
291
  public function prep(array $prep)
292
  {
293
    $stmt = $this->pdo->prepare($this->query);
294
    foreach ($prep as $key => $value) {
295
      $stmt->bindParam(':' . preg_replace('/^\:/s', '', $key), $value, GlobalPDO::PARAM_STR);
296
    }
297
    //return $stmt->execute();
298
  }
299
300
  /**
301
   * Get where parameter is equals.
302
   *
303
   * @return $this
304
   */
305
  public function get_where(string $tbl, array $param = [])
306
  {
307
    $this->query .= " SELECT * FROM `$tbl` WHERE ";
308
    foreach ($param as $key => $value) {
309
      $this->query .= " `$key` = '$value' ";
310
    }
311
312
    return $this;
313
  }
314
315
  /**
316
   * Reset Query.
317
   *
318
   * @return $this
319
   */
320
  public function resetQuery()
321
  {
322
    $this->query = '';
323
324
    return $this;
325
  }
326
327
  /**
328
   * SELECT tableName.
329
   *
330
   * @param string $tbl table_name
331
   * @param string $row separated with comma
332
   *                    * Ex: name, date
333
   *
334
   * @return $this
335
   */
336
  public function select(string $tbl, string $row = '*')
337
  {
338
    $this->clear();
339
    $this->from = $tbl;
340
    $this->query .= " SELECT $row FROM $tbl ";
341
342
    return $this;
343
  }
344
345
  /**
346
   * Clear last queries
347
   *
348
   * @return $this
349
   */
350
  function clear()
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
351
  {
352
    $this->query = '';
353
    return $this;
354
  }
355
356
  /**
357
   * Search database by row and keyword.
358
   */
359
  public function search(array $data, string $separated = 'OR')
360
  {
361
    $i = 0;
362
    foreach ($data as $key => $value) {
363
      if (0 == $i) {
364
        $this->query .= " WHERE $key LIKE '%$value%' ";
365
      } else {
366
        $this->query .= " $separated $key LIKE '%$value%' ";
367
      }
368
      ++$i;
369
    }
370
371
    return $this;
372
  }
373
374
  public function adv_search(array $data = ['OR' => ['key' => 'keyword', 'key2' => 'keyword2'], 'AND' => ['key' => 'keyword', 'key2' => 'keyword2']])
375
  {
376
    if (isset($data['OR'], $data['or'])) {
377
      $or = isset($data['OR']) ? $data['OR'] : $data['or'];
378
      $i = 0;
379
      foreach ($or as $key => $value) {
380
        if (0 == $i) {
381
          $this->query .= " WHERE $key LIKE '%$value%' ";
382
        } else {
383
          $this->query .= " OR $key LIKE '%$value%' ";
384
        }
385
      }
386
      ++$i;
387
    }
388
  }
389
390
  public function insert($tbl, array $data = [])
391
  {
392
    $this->query .= 'INSERT INTO ';
393
    $dbkey = " `$tbl` ( ";
394
    $dbval = ' VALUES( ';
395
    $keys = array_keys($data);
396
    $values = array_values($data);
397
    for ($i = 0; $i < count($data); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
398
      if ($i == count($data) - 1) { // if last iteration
399
        $dbkey .= " `{$keys[$i]}` ) ";
400
        $this->query .= $dbkey;
401
        $dbval .= " '{$values[$i]}' ) ";
402
        $this->query .= $dbval;
403
      } else {
404
        $dbkey .= " `{$keys[$i]}`, ";
405
        $dbval .= " '{$values[$i]}', ";
406
      }
407
    }
408
409
    return $this;
410
  }
411
412
  public function update(string $tbl, array $data, array $is_equals)
413
  {
414
    $this->query .= " UPDATE `$tbl` SET ";
415
    foreach ($data as $key => $value) {
416
      $this->query .= " `$key`='$value' ";
417
    }
418
    $this->query .= ' WHERE ';
419
    foreach ($is_equals as $key => $value) {
420
      $this->query .= " `$key` = '$value' ";
421
    }
422
423
    return $this;
424
  }
425
426
  public function sum(string $tbl, array $data, array $is_equals)
427
  {
428
    $this->query .= " UPDATE `$tbl` SET ";
429
    foreach ($data as $key => $value) {
430
      if (!is_numeric($value)) {
431
        throw new Exception("$value must be instance of number, instead of " . gettype($value), 1);
432
      }
433
      $this->query .= " `$key`=$key + $value ";
434
    }
435
    $this->query .= ' WHERE ';
436
    foreach ($is_equals as $key => $value) {
437
      $this->query .= " `$key` = '$value' ";
438
    }
439
440
    return $this;
441
  }
442
443
  /**
444
   * PDO Instance.
445
   *
446
   * @return GlobalPDO
447
   */
448
  public function pdo()
449
  {
450
    return $this->pdo;
451
  }
452
453
  public function insert_update($tbl, array $data, array $onduplicate)
454
  {
455
    $this->query .= ' INSERT INTO ';
456
    $dbkey = " `$tbl` ( ";
457
    $dbval = ' VALUES( ';
458
    $keys = array_keys($data);
459
    $values = array_values($data);
460
    for ($i = 0; $i < count($data); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
461
      $V = $values[$i];
462
      $V = str_replace('\'', '\\\'', $V);
463
      if ($i == count($data) - 1) { // if last iteration
464
        $dbkey .= " `{$keys[$i]}` ) ";
465
        $this->query .= $dbkey;
466
        $dbval .= " '{$V}' ) ";
467
        $this->query .= $dbval;
468
      } else {
469
        $dbkey .= " `{$keys[$i]}`, ";
470
        $dbval .= " '{$V}', ";
471
      }
472
    }
473
    $this->query .= ' ON DUPLICATE KEY UPDATE ';
474
    $dup_keys = array_keys($onduplicate);
475
    $dup_values = array_values($onduplicate);
476
    for ($i = 0; $i < count($onduplicate); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
477
      if ($i == count($onduplicate) - 1) { // last iteration
478
        $this->query .= " `{$dup_keys[$i]}` = '{$dup_values[$i]}' ";
479
      } else {
480
        $this->query .= " `{$dup_keys[$i]}` = '{$dup_values[$i]}', ";
481
      }
482
    }
483
    //var_dump($this->query);
484
485
    return $this;
486
  }
487
488
  public function replace(string $tbl, array $data)
489
  {
490
    $this->query .= ' REPLACE INTO ';
491
    $dbkey = " `$tbl` ( ";
492
    $dbval = ' VALUES( ';
493
    $keys = array_keys($data);
494
    $values = array_values($data);
495
    for ($i = 0; $i < count($data); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
496
      if ($i == count($data) - 1) { // if last iteration
497
        $dbkey .= " `{$keys[$i]}` ) ";
498
        $this->query .= $dbkey;
499
        $dbval .= " `{$values[$i]}` ) ";
500
        $this->query .= $dbval;
501
      } else {
502
        $dbkey .= " `{$keys[$i]}`, ";
503
        $dbval .= " `{$values[$i]}`, ";
504
      }
505
    }
506
507
    return $this;
508
  }
509
510
  /**
511
   * Fetch from Queries ($this->query)
512
   *
513
   * @return array $var['result'] for result
514
   */
515
  public function fetch()
516
  {
517
    $result = ['query' => $this->query, 'error' => true];
518
    /**
519
     * @var statement
520
     */
521
    $stmt = $this->pdo->prepare($this->query);
522
    $result['error'] = !$stmt->execute();
523
    $result['result'] = $stmt->fetchAll(\PDO::FETCH_ASSOC);
524
525
    return $result;
526
  }
527
528
  public function execute(array $value = [])
529
  {
530
    $result = ['query' => $this->query, 'error' => true];
531
    /**
532
     * @var statement
533
     */
534
    $stmt = $this->pdo->prepare($this->query);
535
    $exec = $stmt->execute($value);
536
    if ($exec) {
537
      $result['error'] = false;
538
    }
539
540
    return $result;
541
  }
542
543
  /**
544
   * Automated Executor.
545
   *
546
   * @return array
547
   */
548
  public function exec(array $value = [])
549
  {
550
    $exec = $this->SQL_Exec($this->trim($this->query), $value);
551
    $this->resetQuery();
552
553
    return $exec;
554
  }
555
556
  public function exec2(array $value)
557
  {
558
    $result = ['error' => true];
559
    /**
560
     * @var EPDOStatement
561
     */
562
    $stmt = $this->pdo->prepare($this->query);
563
    $bind = [];
564
    $i = 0;
565
    foreach ($value as $key => $value) {
566
      switch (gettype($value)) {
567
        case 'int':
568
          $type = GlobalPDO::PARAM_INT;
569
          break;
570
        case 'number':
571
          $type = GlobalPDO::PARAM_INT;
572
          break;
573
        case 'boolean':
574
          $type = GlobalPDO::PARAM_BOOL;
575
          break;
576
        case 'bool':
577
          $type = GlobalPDO::PARAM_BOOL;
578
          break;
579
580
        default:
581
          $type = GlobalPDO::PARAM_STR;
582
          break;
583
      }
584
      if (!isset($type)) {
585
        throw new Exception('Error Processing Code: var(type) not exists, and value instance of ' . gettype($value), 1);
586
      }
587
      $bind[$i][$key]['key'] = ":$key";
588
      $bind[$i][$key]['value'] = $value;
589
      $bind[$i][$key]['type'] = $type;
590
      $stmt->bindValue(":$key", $value, $type);
591
      ++$i;
592
    }
593
    try {
594
      $exec = $stmt->execute($value);
595
      if ($exec) {
596
        $result['error'] = false;
597
      }
598
      //$user = $stmt->fetch();
599
    } catch (PDOException $e) {
600
      $message = "{$e->getMessage()}
601
      <br>Code: {$stmt->errorCode()}
602
      <br>Info: " . serialize($stmt->errorInfo());
603
      $json = json::json($bind, false, false);
604
      if (PDO_DEBUG == 3) {
605
        $message .= "
606
        <br/>Query: {$this->query}
607
        <br/>Callback Query: {$stmt->queryString}
608
        <br/>Debug: {$stmt->_debugQuery()}
0 ignored issues
show
The method _debugQuery() does not exist on PDOStatement. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

608
        <br/>Debug: {$stmt->/** @scrutinizer ignore-call */ _debugQuery()}
Loading history...
609
        <br/>{$stmt->debugDumpParams()}
610
        <br><pre>{$json}</pre>
611
        ";
612
      }
613
      throw new PDOException($message, (int) $e->getCode());
614
    }
615
  }
616
617
  public function trim(string $str)
618
  {
619
    return trim(preg_replace('/\s+/s', ' ', $str));
620
  }
621
622
  public function where($is_equals = [], $not_equals = [], $param = null)
0 ignored issues
show
The parameter $not_equals is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

622
  public function where($is_equals = [], /** @scrutinizer ignore-unused */ $not_equals = [], $param = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
623
  {
624
    $this->query .= ' WHERE ';
625
    if (!empty($is_equals)) {
626
      $this->equals($is_equals);
627
    }
628
    if ($param && empty($param)) {
629
      $this->query .= " $param ";
630
    }
631
632
    return $this;
633
  }
634
635
  /**
636
   * Order by row [ASC=ascending or DESC=descending].
637
   *
638
   * @param string $order
639
   *
640
   * @return $this
641
   */
642
  public function sort(array $by, $order = 'ASC')
643
  {
644
    $this->query .= ' ORDER BY ';
645
    for ($i = 0; $i < count($by); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
646
      $this->query .= " {$by[$i]} ";
647
      if (!($i == count($by) - 1)) {
648
        $this->query .= ',';
649
      }
650
    }
651
    $this->query .= " $order ";
652
653
    return $this;
654
  }
655
656
  public function or($is_equals = [], $not_equals = [])
0 ignored issues
show
The parameter $not_equals is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

656
  public function or($is_equals = [], /** @scrutinizer ignore-unused */ $not_equals = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
657
  {
658
    $this->query .= ' OR ';
659
    if (!empty($is_equals)) {
660
      $this->equals($is_equals);
661
    }
662
663
    return $this;
664
  }
665
666
  public function and($is_equals = [], $not_equals = [])
0 ignored issues
show
The parameter $not_equals is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

666
  public function and($is_equals = [], /** @scrutinizer ignore-unused */ $not_equals = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
667
  {
668
    $this->query .= ' AND ';
669
    if (!empty($is_equals)) {
670
      $this->equals($is_equals);
671
    }
672
673
    return $this;
674
  }
675
676
  /**
677
   * If array data is equals.
678
   */
679
  public function equals(array $is_equals)
680
  {
681
    if (!empty($is_equals)) {
682
      $i = count($is_equals);
683
      foreach ($is_equals as $key => $value) {
684
        if ('NULL' == $value || null === $value) {
685
          $this->query .= " `$key` IS NULL ";
686
        } elseif ($value) {
687
          //$value = "'$value'";
688
          $this->query .= " `$key` = '$value' ";
689
        }
690
        if ($i > 1) { // if condition more than 1, add AND command
691
          $this->query .= ' AND ';
692
        }
693
        --$i;
694
      }
695
    }
696
697
    return $this;
698
  }
699
700
  /**
701
   * Get query result.
702
   *
703
   * @todo check before execution
704
   *
705
   * @return array
706
   *
707
   * @author Dimas Lanjaka <[email protected]>
708
   */
709
  public function getQuery(array $data_value = [])
710
  {
711
    $query = $this->query;
712
    if (!empty($data_value)) {
713
      foreach ($data_value as $key => $value) {
714
        $query = str_replace(":$key", $this->pdo->quote($value), $this->query);
715
      }
716
    }
717
718
    $query = str_replace("\n", ' ', $query);
719
    $this->query = '';
720
721
    return $query;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query returns the type string which is incompatible with the documented return type array.
Loading history...
722
  }
723
724
  /**
725
   * Get last inserted id.
726
   *
727
   * @param GlobalPDO $pdo
728
   */
729
  public function SQL_LastID()
730
  {
731
    return $this->pdo->lastInsertId();
732
  }
733
734
  /**
735
   * PDO Error.
736
   *
737
   * @param PDOException $e
738
   * @param mixed        $query
739
   */
740
  public function SQL_Error($e, $query = null)
741
  {
742
    $result = ['error' => true];
743
    if (headers_sent()) {
744
      $result[] = '<pre>';
745
    }
746
747
    if (defined('PDO_DEBUG')) {
748
      if (headers_sent()) {
749
        $result[] = 'SQL Error: ' . $e->getMessage() . "\n\n";
750
      } else {
751
        $result['message'] = $e->getMessage();
752
      }
753
754
      if (PDO_DEBUG >= '2') {
755
        if (headers_sent()) {
756
          $result[] = "$query\n\n";
757
        } else {
758
          $result['query'] = $query;
759
        }
760
      }
761
762
      if (PDO_DEBUG == '3') {
763
        // Print details like script name and line.
764
        if (headers_sent()) {
765
          print_r($e);
766
        } else {
767
          $result['dump'] = $e;
768
        }
769
      }
770
    } else {
771
      if (headers_sent()) {
772
        $result[] = 'SQL Error! Please contact the administrator.';
773
      } else {
774
        $result['message'] = 'SQL Error! Please contact the administrator.';
775
      }
776
    }
777
    $result['title'] = __CLASS__ . '::' . __FUNCTION__;
778
779
    if (headers_sent()) {
780
      $result[] = '</pre>';
781
      echo implode("\n", $result);
782
    } else {
783
      json::json($result);
784
    }
785
786
    // Stop on error.
787
    exit;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
788
  }
789
790
  public function SQL_Fetch($query, $values = false)
791
  {
792
    if (!$this->pdo) {
793
      throw new \MVC\Exception('Database not properly configured', 1);
794
    }
795
    try {
796
      if (!$values) {
797
        return $this->pdo->query($query)->fetch();
798
      } else {
799
        $stmt = $this->pdo->prepare($query);
800
        $stmt->execute($values);
801
        $arr = $stmt->fetch();
802
        $this->query = $this->parms($query, $values);
803
        //var_dump($this->query);
804
        $stmt = null;
0 ignored issues
show
The assignment to $stmt is dead and can be removed.
Loading history...
805
806
        return $arr;
807
      }
808
    } catch (PDOException $e) {
809
      //var_dump('error true');
810
      $this->SQL_Error($e, $query);
811
    }
812
  }
813
814
  /**
815
   * Reset auto increment value for max id from it's table
816
   *
817
   * @param string $tablename
818
   * @return void
819
   */
820
  public function resetAutoIncrement(string $tablename)
821
  {
822
    $sql = "SELECT @max := MAX(ID)+ 1 FROM $tablename;
823
    PREPARE stmt FROM 'ALTER TABLE $tablename AUTO_INCREMENT = ?';
824
    EXECUTE stmt USING @max;
825
    DEALLOCATE PREPARE stmt;";
826
    $this->query($sql)->exec();
827
  }
828
829
  /**
830
   * Check before insert
831
   * * always equals validation.
832
   *
833
   * @return $this
834
   */
835
  public function insert_not_exists(string $tbl, array $data, array $where = null)
836
  {
837
    $keys = array_keys($data);
838
    $vals = array_values($data);
839
    $valstr = $keystr = $is_equals = '';
840
    for ($i = 0; $i < count($data); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
841
      $value = $vals[$i];
842
      if (!preg_match('/^\:/s', $value)) {
843
        $value = $this->pdo->quote($value);
844
      }
845
      $keystr .= " `{$keys[$i]}` ";
846
      $valstr .= " $value ";
847
      $is_equals .= " `{$keys[$i]}`=$value ";
848
      if ($i == count($data) - 1) { //last iteration
849
        //$keystr .= ')';
850
        //$valstr .= ')';
851
      } else {
852
        $keystr .= ', ';
853
        $valstr .= ', ';
854
        $is_equals .= ' AND ';
855
      }
856
    }
857
    if (null !== $where) {
858
      $is_equals = '';
859
      foreach ($where as $key => $value) {
860
        $is_equals .= " $key = $value ";
861
      }
862
    }
863
    //var_dump($data);
864
    $this->query .= " INSERT INTO $tbl ($keystr) ";
865
    $this->query .= " SELECT * FROM (SELECT $valstr) AS tmp
866
    WHERE NOT EXISTS (
867
      SELECT $keystr FROM $tbl WHERE $is_equals
868
    );";
869
870
    return $this;
871
  }
872
873
  public function parms($string, $data)
874
  {
875
    $indexed = $data == array_values($data);
876
    foreach ($data as $k => $v) {
877
      if (is_string($v)) {
878
        $v = "'$v'";
879
      }
880
      if ($indexed) {
881
        $string = preg_replace('/\?/', $v, $string, 1);
882
      } else {
883
        $string = str_replace(":$k", $v, $string);
884
      }
885
    }
886
887
    return $string;
888
  }
889
890
  /**
891
   * Get result as array.
892
   *
893
   * @param mixed   $value
894
   * @param string $filter filter array by key
895
   *
896
   * @return array|null
897
   */
898
  public function row_array($value = false, $filter = null, bool $unique_filter = false)
899
  {
900
    $exec = $this->SQL_MultiFetch($this->trim($this->query), $value);
901
    $this->query = '';
902
    if ($filter) {
903
      if (!empty($exec) && is_array($exec)) {
904
        $filtered = array_map(function ($data) use ($filter) {
905
          if (isset($data[$filter])) {
906
            return $data[$filter];
907
          }
908
909
          return $data;
910
        }, $exec);
911
        if ($unique_filter) {
912
          return array_unique($filtered);
913
        }
914
915
        return $filtered;
916
      }
917
    }
918
919
    if (!empty($exec) && is_array($exec)) {
920
      return $exec;
921
    }
922
  }
923
924
  /**
925
   * Multi fetch pdo.
926
   *
927
   * @param bool|array $values
928
   * @param bool       $assoc  always association array return
929
   *
930
   * @return void|array
931
   */
932
  public function SQL_MultiFetch(string $query, $values = false, $assoc = false)
933
  {
934
    if (!$this->pdo) {
935
      throw new \MVC\Exception('Database not properly configured', 1);
936
    }
937
    try {
938
      $stmt = $this->pdo->prepare($query);
939
      if (!$values) {
940
        $stmt->execute();
941
      } else {
942
        $stmt->execute($values);
0 ignored issues
show
It seems like $values can also be of type true; however, parameter $params of PDOStatement::execute() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

942
        $stmt->execute(/** @scrutinizer ignore-type */ $values);
Loading history...
943
      }
944
      $arr = $stmt->fetchAll();
945
      $stmt = null;
0 ignored issues
show
The assignment to $stmt is dead and can be removed.
Loading history...
946
947
      if (1 == count($arr) && !$assoc) {
948
        $arr = $arr[0];
949
      }
950
951
      return $arr;
952
    } catch (PDOException $e) {
953
      $this->SQL_Error($e, $query);
954
    }
955
  }
956
957
  public function SQL_Exec($query, $values = false)
958
  {
959
    $result = ['query' => $query, 'error' => false, 'title' => 'Database Management'];
960
    try {
961
      if (!$values) {
962
        $status = $this->pdo->exec($query);
963
        //var_dump($status);
964
        if (!$status && 00000 != (int) $this->pdo->errorCode()) {
965
          $result['error'] = true;
966
          $result['code'] = $this->pdo->errorCode();
967
          $result['message'] = $this->pdo->errorInfo();
968
        }
969
      } else {
970
        $stmt = $this->pdo->prepare($query);
971
        $stmt->execute($values);
972
        $count = 0 == (int) $stmt->rowCount() ? true : false;
973
        $result['error'] = $count;
974
        $stmt = null;
0 ignored issues
show
The assignment to $stmt is dead and can be removed.
Loading history...
975
      }
976
      if (preg_match('/^insert/s', strtolower($query))) {
977
        $result['id'] = $this->SQL_LastID($this->pdo);
0 ignored issues
show
The call to DB\pdo::SQL_LastID() has too many arguments starting with $this->pdo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

977
        /** @scrutinizer ignore-call */ 
978
        $result['id'] = $this->SQL_LastID($this->pdo);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
978
      }
979
    } catch (PDOException $e) {
980
      $this->SQL_Error($e, $query);
981
    }
982
983
    return $result;
984
  }
985
986
  public function show_creation(string $table)
987
  {
988
    return $this->query("SHOW CREATE TABLE `$table`")->row_array();
989
  }
990
}
991