Failed Conditions
Push — master ( e74684...764644 )
by Ryan
11:04
created

src/Database.php (3 issues)

1
<?php
2
3
/**
4
 *
5
 */
6
namespace Godsgood33\Php_Db;
7
8
use Monolog\Logger;
9
use Monolog\Handler\StreamHandler;
10
use Psr\Log\LogLevel;
11
use Exception;
12
use mysqli;
13
14
/**
15
 * A generic database class
16
 *
17
 * @author Ryan Prather
18
 */
19
class Database
20
{
21
22
    /**
23
     * Constant defining a SELECT query
24
     *
25
     * @var integer
26
     */
27
    const SELECT = 1;
28
29
    /**
30
     * Constant defining a SELECT COUNT query
31
     *
32
     * @var integer
33
     */
34
    const SELECT_COUNT = 2;
35
36
    /**
37
     * Constant defining a CREATE TABLE query
38
     *
39
     * @var integer
40
     */
41
    const CREATE_TABLE = 3;
42
43
    /**
44
     * Constant defining DROP query
45
     *
46
     * @var integer
47
     */
48
    const DROP = 4;
49
50
    /**
51
     * Constant defining DELETE query
52
     *
53
     * @var integer
54
     */
55
    const DELETE = 5;
56
57
    /**
58
     * Constant defining INSERT query
59
     *
60
     * @var integer
61
     */
62
    const INSERT = 6;
63
64
    /**
65
     * Constant defining REPLACE query
66
     *
67
     * @var integer
68
     */
69
    const REPLACE = 7;
70
71
    /**
72
     * Constant defining UPDATE query
73
     *
74
     * @var integer
75
     */
76
    const UPDATE = 8;
77
78
    /**
79
     * Constant defining EXTENDED INSERT query
80
     *
81
     * @var integer
82
     */
83
    const EXTENDED_INSERT = 9;
84
85
    /**
86
     * Constant defining EXTENDED REPLACE query
87
     *
88
     * @var integer
89
     */
90
    const EXTENDED_REPLACE = 10;
91
92
    /**
93
     * Constant defining EXTENDED UPDATE query
94
     *
95
     * @var integer
96
     */
97
    const EXTENDED_UPDATE = 11;
98
99
    /**
100
     * Constant defining ALTER TABLE query
101
     *
102
     * @var integer
103
     */
104
    const ALTER_TABLE = 12;
105
106
    /**
107
     * Constant defining a TRUNCATE TABLE query
108
     *
109
     * @var integer
110
     */
111
    const TRUNCATE = 13;
112
113
    /**
114
     * Global to represent an IN statement (e.g.
115
     * WHERE field IN (1,2))
116
     *
117
     * @var string
118
     */
119
    const IN = 'IN';
120
121
    /**
122
     * Global to represent a NOT IN statement (e.g.
123
     * WHERE field NOT IN (1,2))
124
     *
125
     * @var string
126
     */
127
    const NOT_IN = 'NOT IN';
128
129
    /**
130
     * Global to represent a BETWEEN statement (e.g.
131
     * WHERE field BETWEEN 1 and 2)
132
     *
133
     * @var string
134
     */
135
    const BETWEEN = 'BETWEEN';
136
137
    /**
138
     * Global to represent a LIKE statement (e.g.
139
     * WHERE field LIKE '%value%')
140
     *
141
     * @var string
142
     */
143
    const LIKE = 'LIKE';
144
145
    /**
146
     * Global to represent a NOT LIKE statement (e.g.
147
     * WHERE field NOT LIKE '%value%')
148
     *
149
     * @var string
150
     */
151
    const NOT_LIKE = 'NOT LIKE';
152
153
    /**
154
     * Global to represent an IS statement (e.g.
155
     * WHERE field IS NULL)
156
     *
157
     * @var string
158
     */
159
    const IS = 'IS';
160
161
    /**
162
     * Global to represent an IS NOT statement (e.g.
163
     * WHERE field IS NOT NULL)
164
     *
165
     * @var string
166
     */
167
    const IS_NOT = 'IS NOT';
168
169
    /**
170
     * The mysqli connection
171
     *
172
     * @var \mysqli
173
     */
174
    private $_c;
175
176
    /**
177
     * To store the SQL statement
178
     *
179
     * @var string
180
     */
181
    private $_sql = null;
182
183
    /**
184
     * A string to store the type of query that is being run
185
     *
186
     * @var int
187
     */
188
    private $_queryType = null;
189
190
    /**
191
     * The result of the query
192
     *
193
     * @var mixed
194
     */
195
    private $_result = null;
196
197
    /**
198
     * Log level
199
     *
200
     * @var string
201
     */
202
    private $_logLevel = Logger::ERROR;
203
204
    /**
205
     * Variable to store the logger
206
     *
207
     * @var \Monolog\Logger
208
     */
209
    private $_logger = null;
210
211
    /**
212
     * Path for the logger to log the file
213
     *
214
     * @var string
215
     */
216
    private $_logPath = null;
217
218
    /**
219
     * Variable to decide if we need to automatically run the queries after generating them
220
     *
221
     * @var boolean
222
     */
223
    public static $autorun = false;
224
225
    /**
226
     * Constructor
227
     *
228
     * @param \mysqli $dbh
229
     *            [optional]
230
     *            [by ref]
231
     *            mysqli object to perform queries.
232
     * @param string $logPath
233
     */
234
    public function __construct(mysqli &$dbh = null, string $logPath = null)
235
    {
236
        require_once 'DBConfig.php';
237
        if (! is_null($dbh) && is_a($dbh, "mysqli")) {
238
            $this->_c = $dbh;
239
        } else {
240
            $this->_c = new mysqli(PHP_DB_SERVER, PHP_DB_USER, PHP_DB_PWD, PHP_DB_SCHEMA);
241
        }
242
243
        if ($this->_c->connect_errno) {
244
            throw new Exception("Could not create database class due to error {$this->_c->error}", E_ERROR);
245
        }
246
247
        $this->_logPath = $logPath;
248
        touch($this->_logPath . "/db.log");
249
250
        $this->_logger = new Logger('db', [
251
            new StreamHandler("php://output", Logger::INFO),
252
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
253
        ]);
254
255
        $this->setVar("time_zone", "+00:00");
256
        $this->setVar("sql_mode", "");
257
    }
258
259
    /**
260
     * Function to make sure that the database is connected
261
     *
262
     * @return boolean
263
     */
264
    public function isConnected()
265
    {
266
        return $this->_c->ping();
267
    }
268
269
    /**
270
     * Setter function for _logger
271
     *
272
     * @param Logger $log
273
     */
274
    public function setLogger(Logger $log)
275
    {
276
        $this->_logger = $log;
277
    }
278
279
    /**
280
     * Getter function for _logLevel
281
     *
282
     * @return string
283
     */
284
    public function getLogLevel()
285
    {
286
        return $this->_logLevel;
287
    }
288
289
    /**
290
     * Function to set the log level just in case there needs to be a change to the default log level
291
     *
292
     * @param string $strLevel
293
     */
294
    public function setLogLevel(string $strLevel)
295
    {
296
        $this->_logLevel = $strLevel;
297
        $this->_logger->setHandlers([
298
            new StreamHandler("php://output", Logger::INFO),
299
            new StreamHandler(realpath("{$this->_logPath}/db.log"), $this->_logLevel)
300
        ]);
301
    }
302
303
    /**
304
     * Getter function for _sql
305
     *
306
     * @return string
307
     */
308
    public function getSql()
309
    {
310
        return $this->_sql;
311
    }
312
313
    /**
314
     * Function to return the currently selected database schema
315
     *
316
     * @return string
317
     */
318
    public function getSchema()
319
    {
320
        if ($res = $this->_c->query("SELECT DATABASE()")) {
321
            $row = $res->fetch_row();
322
            return $row[0];
323
        }
324
        return null;
325
    }
326
327
    /**
328
     * Function to set schema
329
     *
330
     * @param string $strSchema
331
     */
332
    public function setSchema(string $strSchema)
333
    {
334
        if (! $this->_c->select_db($strSchema)) {
335
            throw new Exception("Failed to change databases to {$strSchema}", E_ERROR);
336
        }
337
        return true;
338
    }
339
340
    /**
341
     * Method to set a MYSQL variable
342
     *
343
     * @param string $strName
344
     * @param string $strVal
345
     * @return boolean
346
     */
347
    public function setVar(string $strName, string $strVal)
348
    {
349
        if (! $strName || ! $strVal) {
350
            return false;
351
        }
352
353
        return $this->_c->real_query("SET $strName = {$this->_escape($strVal)}");
354
    }
355
356
    /**
357
     * Function to execute the statement
358
     *
359
     * @param mixed $return
360
     *            [optional]
361
     *            MYSQLI constant to control what is returned from the mysqli_result object
362
     * @param string $class
363
     *            [optional]
364
     *            Class to use when returning object
365
     * @param string $sql
366
     *            [optional]
367
     *            Optional SQL query
368
     *
369
     * @return mixed
370
     */
371
    public function execute($return = MYSQLI_ASSOC, $class = null, $sql = null)
372
    {
373
        if (! is_null($sql)) {
374
            $this->_sql = $sql;
375
        }
376
377
        if (is_a($this->_c, 'mysqli')) {
378
            if (! $this->_c->ping()) {
379
                require_once 'DBConfig.php';
380
                $this->_c = null;
381
                $this->_c = new mysqli(PHP_DB_SERVER, PHP_DB_USER, PHP_DB_PWD, PHP_DB_SCHEMA);
382
            }
383
        } else {
384
            throw new Exception('Database was not connected', E_ERROR);
385
        }
386
387
        $this->_logger->debug($this->_sql);
388
389
        try {
390
            if (in_array($this->_queryType, [
391
                self::SELECT,
392
                self::SELECT_COUNT
393
            ])) {
394
                $this->_result = $this->_c->query($this->_sql);
395
                if ($this->_c->error) {
396
                    $this->log("There is an error {$this->_c->error}", Logger::ERROR);
397
                    throw new Exception("There was an error {$this->_c->error}", E_ERROR);
398
                }
399
            } else {
400
                $this->_result = $this->_c->real_query($this->_sql);
401
                if ($this->_c->errno) {
402
                    $this->log("There was an error {$this->_c->error}", Logger::ERROR);
403
                    throw new Exception("There was an error {$this->_c->error}", E_ERROR);
404
                }
405
            }
406
407
            if ($return == MYSQLI_OBJECT && ! is_null($class) && class_exists($class)) {
408
                $this->_result = $this->checkResults($return, $class);
409
            } elseif ($return == MYSQLI_OBJECT && is_null($class)) {
410
                $this->_result = $this->checkResults($return, 'stdClass');
411
            } else {
412
                $this->_result = $this->checkResults(MYSQLI_ASSOC);
413
            }
414
        } catch (Exception $e) {}
415
416
        return $this->_result;
417
    }
418
419
    /**
420
     * Function to check the results and return what is expected
421
     *
422
     * @param mixed $return_type
423
     *            [optional]
424
     *            Optional return mysqli_result return type
425
     *
426
     * @return mixed
427
     */
428
    public function checkResults($return_type = MYSQLI_ASSOC, $class = null)
429
    {
430
        $res = null;
431
432
        switch ($this->_queryType) {
433
            case self::SELECT_COUNT:
434
                if (! is_a($this->_result, 'mysqli_result')) {
435
                    $this->log("Error with return on query", Logger::ERROR);
436
                }
437
438
                if ($this->_result->num_rows == 1) {
439
                    $row = $this->_result->fetch_assoc();
440
                    if (isset($row['count'])) {
441
                        $res = $row['count'];
442
                    }
443
                } elseif ($this->_result->num_rows > 1) {
444
                    $res = $this->_result->num_rows;
445
                }
446
447
                mysqli_free_result($this->_result);
448
449
                return $res;
450
            case self::SELECT:
451
                if (! is_a($this->_result, 'mysqli_result')) {
452
                    $this->log("Error with return on query", Logger::ERROR);
453
                }
454
455
                if ($return_type == MYSQLI_OBJECT && ! is_null($class) && class_exists($class)) {
456
                    if ($this->_result->num_rows == 1) {
457
                        $res = $this->_result->fetch_object($class);
458
                    } elseif ($this->_result->num_rows > 1) {
459
                        while ($row = $this->_result->fetch_object($class)) {
460
                            $res[] = $row;
461
                        }
462
                    }
463
                } else {
464
                    if ($this->_result->num_rows == 1) {
465
                        $res = $this->_result->fetch_array($return_type);
466
                    } elseif ($this->_result->num_rows > 1) {
467
                        $res = $this->fetch_all($return_type);
0 ignored issues
show
The method fetch_all() does not exist on Godsgood33\Php_Db\Database. Did you maybe mean fetchAll()? ( Ignorable by Annotation )

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

467
                        /** @scrutinizer ignore-call */ 
468
                        $res = $this->fetch_all($return_type);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
468
                    }
469
                }
470
471
                mysqli_free_result($this->_result);
472
473
                return $res;
474
            case self::INSERT:
475
                if ($this->_c->error) {
476
                    $this->log("Database Error {$this->_c->error}", Logger::ERROR);
477
                    return 0;
478
                }
479
480
                if ($this->_c->insert_id) {
481
                    return $this->_c->insert_id;
482
                } elseif ($this->_c->affected_rows) {
483
                    return $this->_c->affected_rows;
484
                }
485
486
                return 1;
487
            // intentional fall through
488
            case self::EXTENDED_INSERT:
489
            // intentional fall through
490
            case self::EXTENDED_REPLACE:
491
            // intentional fall through
492
            case self::EXTENDED_UPDATE:
493
            // intentional fall through
494
            case self::REPLACE:
495
            // intentional fall through
496
            case self::UPDATE:
497
            // intentional fall through
498
            case self::DELETE:
499
            // intentional fall through
500
            case self::ALTER_TABLE:
501
                if ($this->_c->error && $this->_c->errno == 1060) {
502
                    return ($this->_c->affected_rows ? $this->_c->affected_rows : true);
503
                } elseif ($this->_c->error) {
504
                    $this->log("Database Error {$this->_c->error}", Logger::ERROR);
505
                    return false;
506
                } elseif ($this->_c->affected_rows) {
507
                    return $this->_c->affected_rows;
508
                } else {
509
                    return true;
510
                }
511
            case self::CREATE_TABLE:
512
            case self::DROP:
513
            case self::TRUNCATE:
514
                return true;
515
        }
516
    }
517
518
    /**
519
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
520
     * Nothing is escaped
521
     *
522
     * @param string $sql
523
     *            [optional]
524
     *            Optional query to pass in and execute
525
     *
526
     * @return \mysqli_result|boolean
527
     */
528
    public function query($sql = null)
529
    {
530
        if (is_null($sql)) {
531
            return $this->_c->query($this->_sql);
532
        } else {
533
            return $this->_c->query($sql);
534
        }
535
    }
536
537
    /**
538
     * A function to build a select query
539
     *
540
     * @param string $table_name
541
     *            The table to query
542
     * @param array $fields
543
     *            [optional]
544
     *            Optional array of fields to return (defaults to '*')
545
     * @param array $where
546
     *            [optional]
547
     *            Optional 2-dimensional array to build where clause from
548
     * @param array $flags
549
     *            [optional]
550
     *            Optional 2-dimensional array to allow other flags
551
     *
552
     * @see Database::where()
553
     * @see Database::flags()
554
     *
555
     * @return mixed
556
     */
557
    public function select($table_name, $fields = null, $where = null, $flags = null)
558
    {
559
        $this->_sql = null;
560
        $this->_query_type = self::SELECT;
0 ignored issues
show
Bug Best Practice introduced by
The property _query_type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
561
562
        if (! is_null($table_name) && is_string($table_name)) {
563
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $table_name";
564
        } else {
565
            throw new Exception("Table name is invalid", E_ERROR);
566
        }
567
568
        if (isset($flags['joins']) && is_array($flags['joins'])) {
569
            $this->_sql .= " " . implode(" ", $flags['joins']);
570
        }
571
572
        if (! is_null($where) && is_array($where) && count($where)) {
573
            $where_str = " WHERE";
574
            foreach ($where as $x => $w) {
575
                $where_str .= $this->parseClause($w, $x);
576
            }
577
            if (strlen($where_str) > strlen(" WHERE")) {
578
                $this->_sql .= $where_str;
579
            }
580
        }
581
582
        if (is_array($flags) && count($flags)) {
583
            $this->_sql .= $this->flags($flags);
584
        }
585
586
        if (self::$autorun) {
587
            return $this->execute(MYSQLI_BOTH);
588
        }
589
590
        return $this->_sql;
591
    }
592
593
    /**
594
     * Function to build a query to check the number of rows in a table
595
     *
596
     * @param string $table_name
597
     *            The table to query
598
     * @param array $where
599
     *            [optional]
600
     *            Optional 2-dimensional array to build where clause
601
     * @param array $flags
602
     *            [optional]
603
     *            Optional 2-dimensional array to add flags
604
     *
605
     * @see Database::where()
606
     * @see Database::flags()
607
     *
608
     * @return string|NULL
609
     */
610
    public function selectCount($table_name, $where = null, $flags = null)
611
    {
612
        $this->_sql = null;
613
        $this->_queryType = self::SELECT_COUNT;
614
615
        if (! is_null($table_name) && is_string($table_name)) {
616
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $table_name";
617
        } else {
618
            return null;
619
        }
620
621
        if (isset($flags['joins']) && is_array($flags['joins'])) {
622
            $this->_sql .= " " . implode(" ", $flags['joins']);
623
        }
624
625
        if (! is_null($where) && is_array($where) && count($where)) {
626
            $where_str = " WHERE";
627
            foreach ($where as $x => $w) {
628
                $where_str .= $this->parseClause($w, $x);
629
            }
630
            if (strlen($where_str) > strlen(" WHERE")) {
631
                $this->_sql .= $where_str;
632
            }
633
        }
634
635
        if (is_array($flags) && count($flags)) {
636
            $this->_sql .= $this->flags($flags);
637
        }
638
639
        if (self::$autorun) {
640
            return $this->execute(MYSQLI_BOTH);
641
        }
642
643
        return $this->_sql;
644
    }
645
646
    /**
647
     * Function to build an insert query statement
648
     *
649
     * @param string $table_name
650
     * @param array $params
651
     * @param boolean $to_ignore
652
     *
653
     * @return string|NULL
654
     */
655
    public function insert($table_name, $params = null, $to_ignore = false)
656
    {
657
        $this->_sql = null;
658
        $this->_queryType = self::INSERT;
659
660
        if (! is_null($table_name) && is_string($table_name)) {
661
            $this->_sql = "INSERT" . ($to_ignore ? " IGNORE" : "") . " INTO $table_name" . (is_array($params) && count($params) ? " (`" . implode("`,`", array_keys($params)) . "`)" : null);
662
        } else {
663
            throw (new Exception("Missing table name in insert function", E_ERROR));
664
        }
665
666
        if (is_array($params) && count($params)) {
667
            $this->_sql .= " VALUES (" . implode(",", array_map([
668
                $this,
669
                '_escape'
670
            ], array_values($params))) . ")";
671
        } elseif (is_string($params) && stripos($params, 'SELECT') !== false) {
672
            $this->_sql .= " {$params}";
673
        } else {
674
            throw (new Exception("Invalid type passed to insert " . gettype($params), E_ERROR));
675
        }
676
677
        if (self::$autorun) {
678
            return $this->execute(MYSQLI_BOTH);
679
        }
680
681
        return $this->_sql;
682
    }
683
684
    /**
685
     * Function to create an extended insert query statement
686
     *
687
     * @param string $table_name
688
     *            The table name that the data is going to be inserted on
689
     * @param array $fields
690
     *            An array of field names that each value represents
691
     * @param array|string $params
692
     *            An array of array of values or a string with a SELECT statement to populate the insert with
693
     * @param boolean $to_ignore
694
     *            [optional]
695
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
696
     *
697
     * @return NULL|string Returns the SQL if self::$autorun is set to false, else it returns the output from running.
698
     */
699
    public function extendedInsert($table_name, $fields, $params, $to_ignore = false)
700
    {
701
        $this->_sql = null;
702
        $this->_queryType = self::EXTENDED_INSERT;
703
704
        if (! is_null($table_name) && is_string($table_name)) {
705
            $this->_sql = "INSERT " . ($to_ignore ? "IGNORE " : "") . "INTO $table_name " . "(`" . implode("`,`", $fields) . "`)";
706
        } else {
707
            throw (new Exception("Missing table name in extended_insert", E_ERROR));
708
        }
709
710
        if (is_array($params) && count($params)) {
711
            $this->_sql .= " VALUES ";
712
            if (isset($params[0]) && is_array($params[0])) {
713
                foreach ($params as $p) {
714
                    if (count($p) != count($fields)) {
715
                        throw (new Exception("Inconsistent number of fields in fields and values in extended_insert " . print_r($p, true), E_ERROR));
716
                    }
717
                    $this->_sql .= "(" . implode(",", array_map([
718
                        $this,
719
                        '_escape'
720
                    ], array_values($p))) . ")";
721
722
                    if ($p != end($params)) {
723
                        $this->_sql .= ",";
724
                    }
725
                }
726
            }
727
        }
728
729
        if (self::$autorun) {
730
            return $this->execute(MYSQLI_BOTH);
731
        }
732
733
        return $this->_sql;
734
    }
735
736
    /**
737
     * Build a statement to update a table
738
     *
739
     * @param string $table_name
740
     *            The table name to update
741
     * @param array $params
742
     *            Name/value pairs of the field name and value
743
     * @param array $where
744
     *            [optional]
745
     *            Two-dimensional array to create where clause
746
     * @param array $flags
747
     *            [optional]
748
     *            Two-dimensional array to create other flag options (joins, order, and group)
749
     *
750
     * @see Database::where()
751
     * @see Database::flags()
752
     *
753
     * @return NULL|string
754
     */
755
    public function update($table_name, $params, $where = null, $flags = null)
756
    {
757
        $this->_sql = "UPDATE ";
758
        $this->_queryType = self::UPDATE;
759
760
        if (! is_null($table_name) && is_string($table_name)) {
761
            $this->_sql .= $table_name;
762
763
            if (isset($flags['joins']) && is_array($flags['joins'])) {
764
                $this->_sql .= " " . implode(" ", $flags['joins']);
765
                unset($flags['joins']);
766
            }
767
768
            $this->_sql .= " SET ";
769
        } else {
770
            throw new Exception("Invalid table name datatype", E_ERROR);
771
        }
772
773
        foreach ($params as $f => $p) {
774
            if ((strpos($f, "`") === false) && (strpos($f, ".") === false) && (strpos($f, "*") === false) && (stripos($f, " as ") === false)) {
775
                $f = "`{$f}`";
776
            }
777
778
            if (! is_null($p)) {
779
                $this->_sql .= "$f={$this->_escape($p)},";
780
            } else {
781
                $this->_sql .= "$f=NULL,";
782
            }
783
        }
784
785
        $this->_sql = substr($this->_sql, 0, - 1);
786
787
        if (! is_null($where) && is_array($where) && count($where)) {
788
            $where_str = " WHERE";
789
            foreach ($where as $x => $w) {
790
                $where_str .= $this->parseClause($w, $x);
791
            }
792
            if (strlen($where_str) > strlen(" WHERE")) {
793
                $this->_sql .= $where_str;
794
            }
795
        }
796
797
        if (! is_null($flags) && is_array($flags) && count($flags)) {
798
            $this->_sql .= $this->flags($flags);
799
        }
800
801
        if (self::$autorun) {
802
            return $this->execute(MYSQLI_BOTH);
803
        }
804
805
        return $this->_sql;
806
    }
807
808
    /**
809
     * Function to offer an extended updated functionality by using two different tables.
810
     *
811
     * @param string $to_be_updated
812
     *            The table that you want to update (alias 'tbu' is automatically added)
813
     * @param string $original
814
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
815
     * @param string $using
816
     *            The common index value between them that will join the fields
817
     * @param array|string $params
818
     *            If string only a single field is updated (tbu.$params = o.$params)
819
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
820
     *
821
     * @return mixed
822
     */
823
    public function extendedUpdate($to_be_updated, $original, $using, $params)
824
    {
825
        $this->_sql = "UPDATE ";
826
        $this->_queryType = self::EXTENDED_UPDATE;
827
828
        if (! is_null($to_be_updated) && ! is_null($original) && ! is_null($using)) {
829
            $this->_sql .= "$to_be_updated tbu INNER JOIN $original o USING ($using) SET ";
830
        }
831
832
        if (is_array($params) && count($params)) {
833
            foreach ($params as $param) {
834
                if ($param != $using) {
835
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
836
                }
837
            }
838
            $this->_sql = substr($this->_sql, 0, - 1);
839
        } elseif (is_string($params)) {
840
            $this->_sql .= "tbu.`$params` = o.`$params`";
841
        } else {
842
            throw new Exception("Do not understand datatype " . gettype($params), E_ERROR);
843
        }
844
845
        if (self::$autorun) {
846
            return $this->execute(MYSQLI_BOTH);
847
        }
848
849
        return $this->_sql;
850
    }
851
852
    /**
853
     * Function to build a replace query
854
     *
855
     * @param string $table_name
856
     *            The table to update
857
     * @param array $params
858
     *            Name/value pair to insert
859
     *
860
     * @return NULL|string
861
     */
862
    public function replace($table_name, $params)
863
    {
864
        $this->_sql = null;
865
        $this->_queryType = self::REPLACE;
866
867
        if (! is_null($table_name) && is_string($table_name)) {
868
            $this->_sql = "REPLACE INTO $table_name " . "(`" . implode("`,`", array_keys($params)) . "`)";
869
        } else {
870
            throw (new Exception("Table name is not valid", E_ERROR));
871
        }
872
873
        $this->_sql .= " VALUES (" . implode(",", array_map([
874
            $this,
875
            '_escape'
876
        ], array_values($params))) . ")";
877
878
        if (self::$autorun) {
879
            return $this->execute(MYSQLI_BOTH);
880
        }
881
882
        return $this->_sql;
883
    }
884
885
    /**
886
     * Function to build an extended replace statement
887
     *
888
     * @param string $table_name
889
     *            Table name to update
890
     * @param array $fields
891
     *            Array of fields
892
     * @param array $params
893
     *            Two-dimensional array of values
894
     *
895
     * @return NULL|string
896
     */
897
    public function extendedReplace($table_name, $fields, $params)
898
    {
899
        $this->_sql = null;
900
        $this->_queryType = self::EXTENDED_REPLACE;
901
902
        if (! is_null($table_name) && is_string($table_name)) {
903
            $this->_sql = "REPLACE INTO $table_name " . "(`" . implode("`,`", $fields) . "`)";
904
        } else {
905
            throw (new Exception("Table name is not valid", E_ERROR));
906
        }
907
908
        if (is_array($params) && count($params)) {
909
            $this->_sql .= " VALUES ";
910
            foreach ($params as $p) {
911
                $this->_sql .= "(" . implode(",", array_map([
912
                    $this,
913
                    '_escape'
914
                ], array_values($p))) . ")";
915
916
                if ($p != end($params)) {
917
                    $this->_sql .= ",";
918
                }
919
            }
920
        }
921
922
        if (self::$autorun) {
923
            return $this->execute(MYSQLI_BOTH);
924
        }
925
926
        return $this->_sql;
927
    }
928
929
    /**
930
     * Function to build a delete statement
931
     *
932
     * @param string $table_name
933
     *            Table name to act on
934
     * @param array $fields
935
     *            [optional]
936
     *            Optional list of fields to delete (used when including multiple tables)
937
     * @param array $where
938
     *            [optional]
939
     *            Optional 2-dimensional array to build where clause from
940
     * @param array $joins
941
     *            [optional]
942
     *            Optional 2-dimensional array to add other flags
943
     *
944
     * @see Database::where()
945
     * @see Database::flags()
946
     *
947
     * @return string|NULL
948
     */
949
    public function delete($table_name, $fields = null, $where = null, $joins = null)
950
    {
951
        $this->_sql = "DELETE";
952
        $this->_queryType = self::DELETE;
953
954
        if (! is_null($fields) && is_array($fields)) {
955
            $this->_sql .= " " . implode(",", $fields);
956
        }
957
958
        if (! is_null($table_name) && is_string($table_name)) {
959
            $this->_sql .= " FROM $table_name";
960
        } else {
961
            throw (new Exception("Failed to create delete query, no table name", E_ERROR));
962
        }
963
964
        if (! is_null($joins) && is_array($joins) && count($joins)) {
965
            $this->_sql .= " " . implode(" ", $joins);
966
        }
967
968
        if (! is_null($where) && is_array($where) && count($where)) {
969
            $where_str = " WHERE";
970
            foreach ($where as $x => $w) {
971
                $where_str .= $this->parseClause($w, $x);
972
            }
973
            if (strlen($where_str) > strlen(" WHERE")) {
974
                $this->_sql .= $where_str;
975
            }
976
        }
977
978
        if (self::$autorun) {
979
            return $this->execute(MYSQLI_BOTH);
980
        }
981
982
        return $this->_sql;
983
    }
984
985
    /**
986
     * Function to build a drop table statement (automatically executes)
987
     *
988
     * @param string $name
989
     *            Table to drop
990
     * @param string $type
991
     *            [optional]
992
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
993
     * @param boolean $is_tmp
994
     *            [optional]
995
     *            Optional boolean if this is a temporary table
996
     *
997
     * @return string|NULL
998
     */
999
    public function drop($name, $type = 'table', $is_tmp = false)
1000
    {
1001
        $this->_sql = null;
1002
        $this->_queryType = self::DROP;
1003
1004
        switch ($type) {
1005
            case 'table':
1006
                $type = 'TABLE';
1007
                break;
1008
            case 'view':
1009
                $type = 'VIEW';
1010
                break;
1011
            default:
1012
                throw new Exception("Invalid type " . gettype($type), E_ERROR);
1013
        }
1014
1015
        if (! is_null($name) && is_string($name)) {
1016
            $this->_sql = "DROP" . ($is_tmp ? " TEMPORARY" : "") . " $type IF EXISTS `$name`";
1017
        } else {
1018
            throw new Exception("Table name is invalid", E_ERROR);
1019
        }
1020
1021
        if (self::$autorun) {
1022
            return $this->execute(MYSQLI_BOTH);
1023
        }
1024
1025
        return $this->_sql;
1026
    }
1027
1028
    /**
1029
     * Function to build a truncate table statement (automatically executes)
1030
     *
1031
     * @param string $table_name
1032
     *            Table to truncate
1033
     *
1034
     * @return string|NULL
1035
     */
1036
    public function truncate($table_name)
1037
    {
1038
        $this->_sql = null;
1039
        $this->_queryType = self::TRUNCATE;
1040
1041
        if (! is_null($table_name) && is_string($table_name)) {
1042
            $this->_sql = "TRUNCATE TABLE $table_name";
1043
        } else {
1044
            throw new Exception("Table name is invalid", E_ERROR);
1045
        }
1046
1047
        if (self::$autorun) {
1048
            return $this->execute(MYSQLI_BOTH);
1049
        }
1050
1051
        return $this->_sql;
1052
    }
1053
1054
    /**
1055
     * Function to build a create temporary table statement
1056
     *
1057
     * @param string $table_name
1058
     *            Name to give the table when creating
1059
     * @param boolean $is_tmp
1060
     *            [optional]
1061
     *            Optional boolean to make the table a temporary table
1062
     * @param mixed $select
1063
     *            [optional]
1064
     *            Optional parameter if null uses last built statement
1065
     *            If string, will be made the SQL statement executed to create the table
1066
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1067
     *
1068
     * @return NULL|string
1069
     */
1070
    public function createTable($table_name, $is_tmp = false, $select = null)
1071
    {
1072
        $this->_queryType = self::CREATE_TABLE;
1073
1074
        if (is_null($select) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1075
            $this->_sql = "CREATE" . ($is_tmp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $table_name AS ($this->_sql)";
1076
        }
1077
        if (! is_null($table_name) && is_string($table_name) && is_string($select)) {
1078
            $this->_sql = "CREATE" . ($is_tmp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $table_name AS ($select)";
1079
        } elseif (! is_null($table_name) && is_string($table_name) && is_array($select)) {
1080
            $this->_sql = "CREATE" . ($is_tmp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $table_name (";
1081
1082
            foreach ($select as $field) {
1083
                $default = null;
1084
                if (isset($field['default'])) {
1085
                    $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1086
                }
1087
                $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1088
            }
1089
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1090
        }
1091
1092
        if (self::$autorun) {
1093
            return $this->execute();
1094
        }
1095
1096
        return $this->_sql;
1097
    }
1098
1099
    /**
1100
     * Function to create a table using a stdClass object derived from JSON
1101
     *
1102
     * @param \stdClass $json
1103
     *
1104
     * @example /examples/create_table_json.json
1105
     *
1106
     */
1107
    public function createTableJson($json)
1108
    {
1109
        $this->_queryType = self::CREATE_TABLE;
1110
        $this->_c->select_db($json->schema);
1111
1112
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1113
        foreach ($json->fields as $field) {
1114
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1115
1116
            if ($field->dataType == 'enum') {
1117
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1118
            }
1119
1120
            if ($field->ai) {
1121
                $this->_sql .= " AUTO_INCREMENT";
1122
            }
1123
1124
            if ($field->nn) {
1125
                $this->_sql .= " NOT NULL";
1126
            } else {
1127
                if ($field->default === null) {
1128
                    $this->_sql .= " DEFAULT NULL";
1129
                } elseif (strlen($field->default)) {
1130
                    $this->_sql .= " DEFAULT '{$field->default}'";
1131
                }
1132
            }
1133
1134
            if ($field != end($json->fields)) {
1135
                $this->_sql .= ",";
1136
            }
1137
        }
1138
1139
        if (isset($json->index) && count($json->index)) {
1140
            foreach ($json->index as $ind) {
1141
                $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ind->ref}`)";
1142
            }
1143
        }
1144
1145
        if (isset($json->constraints) && count($json->constraints)) {
1146
            foreach ($json->constraints as $con) {
1147
                $this->_sql .= ", CONSTRAINT `{$con->id}` " . "FOREIGN KEY (`{$con->local}`) " . "REFERENCES `{$con->schema}`.`{$con->table}` (`{$con->field}`) " . "ON DELETE " . (is_null($con->delete) ? "NO ACTION" : strtoupper($con->delete)) . " " . "ON UPDATE " . (is_null($con->update) ? "NO ACTION" : strtoupper($con->update));
1148
            }
1149
        }
1150
1151
        if (isset($json->unique) && count($json->unique)) {
1152
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1153
        }
1154
1155
        if (isset($json->primary_key) && count($json->primary_key)) {
1156
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1157
        } else {
1158
            if (substr($this->_sql, - 1) == ',') {
1159
                $this->_sql = substr($this->_sql, 0, - 1);
1160
            }
1161
1162
            $this->_sql .= ")";
1163
        }
1164
1165
        $this->execute(MYSQLI_BOTH);
1166
    }
1167
1168
    /**
1169
     * Function to alter a existing table
1170
     *
1171
     * @param string $table_name
1172
     *            Table to alter
1173
     * @param string $action
1174
     *            What action should be taken ('add-column', 'drop-column', 'modify-column')
1175
     * @param mixed $params
1176
     *            For add column this is a stdClass object that has the same elements as the example json
1177
     *
1178
     * @return mixed
1179
     */
1180
    public function alterTable($table_name, $action, $params)
1181
    {
1182
        $this->_queryType = self::ALTER_TABLE;
1183
        $this->_sql = "ALTER TABLE $table_name";
1184
        if ($action == 'add-column') {
1185
            $nn = ($params->nn ? " NOT NULL" : "");
1186
            $default = null;
1187
            if ($params->default === null) {
1188
                $default = " DEFAULT NULL";
1189
            } elseif (strlen($params->default)) {
1190
                $default = " DEFAULT {$this->_escape($params->default)}";
1191
            }
1192
            $this->_sql .= " ADD COLUMN `{$params->name}` {$params->dataType}" . $nn . $default;
1193
        } elseif ($action == 'drop-column') {
1194
            $this->_sql .= " DROP COLUMN ";
1195
            foreach ($params as $col) {
1196
                $this->_sql .= "`{$col->name}`";
1197
1198
                if ($col != end($params)) {
1199
                    $this->_sql .= ",";
1200
                }
1201
            }
1202
        } elseif ($action == 'modify-column') {
1203
            $this->_sql .= " MODIFY COLUMN";
1204
            $nn = ($params->nn ? " NOT NULL" : "");
1205
            $default = null;
1206
            if ($params->default === null) {
1207
                $default = " DEFAULT NULL";
1208
            } elseif (strlen($params->default)) {
1209
                $default = " DEFAULT {$this->_escape($params->default)}";
1210
            }
1211
            $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1212
        }
1213
1214
        if (self::$autorun) {
1215
            return $this->execute();
1216
        }
1217
1218
        return $this->_sql;
1219
    }
1220
1221
    /**
1222
     * Check to see if a field in a table exists
1223
     *
1224
     * @param string $table_name
1225
     *            Table to check
1226
     * @param string $field_name
1227
     *            Field name to find
1228
     *
1229
     * @return boolean Returns TRUE if field is found in that schema and table, otherwise FALSE
1230
     */
1231
    public function fieldExists($table_name, $field_name)
1232
    {
1233
        $fdata = $this->fieldData($table_name);
1234
1235
        if (is_array($fdata) && count($fdata)) {
1236
            foreach ($fdata as $field) {
1237
                if ($field->name == $field_name) {
1238
                    return true;
1239
                }
1240
            }
1241
        }
1242
1243
        return false;
1244
    }
1245
1246
    /**
1247
     * Function to get the column data (datatype, flags, defaults, etc)
1248
     *
1249
     * @param string $table_name
1250
     *            Table to query
1251
     * @param mixed $field
1252
     *            [optional]
1253
     *            Optional field to retrieve data (if null, returns data from all fields)
1254
     *
1255
     * @return array
1256
     */
1257
    public function fieldData($table_name, $field = null)
1258
    {
1259
        if (is_null($field)) {
1260
            $res = $this->_c->query("SELECT * FROM $table_name LIMIT 1");
1261
        } elseif (is_array($field)) {
1262
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $table_name LIMIT 1");
1263
        } elseif (is_string($field)) {
1264
            $res = $this->_c->query("SELECT $field FROM $table_name LIMIT 1");
1265
        } else {
1266
            return null;
1267
        }
1268
1269
        $fields = null;
1270
        if (is_a($res, 'mysqli_result')) {
1271
            $fields = $res->fetch_fields();
1272
            foreach ($fields as $i => $f) {
1273
                $fields["{$f->name}"] = $f;
1274
                unset($fields[$i]);
1275
            }
1276
        }
1277
1278
        return $fields;
1279
    }
1280
1281
    /**
1282
     * Function to check that all field parameters are set correctly
1283
     *
1284
     * @param object $field_data
1285
     * @param object $check
1286
     * @param array $pks
1287
     * @param object $index
1288
     *
1289
     * @return array|string
1290
     */
1291
    public function fieldCheck($field_data, $check, $pks, $index)
1292
    {
1293
        $default = null;
1294
        $ret = null;
1295
1296
        $nn = ($check->nn ? " NOT NULL" : null);
1297
        if ($check->default === null) {
1298
            $default = " DEFAULT NULL";
1299
        } elseif (strlen($check->default)) {
1300
            $default = " DEFAULT '{$check->default}'";
1301
        }
1302
1303
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1304
            $this->log("{$field_data->name} wrong datatype, changing to {$check->dataType}", Logger::NOTICE);
1305
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1306
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1307
            $this->log("{$field_data->name} incorrect size ({$field_data->length} != {$check->length})", Logger::NOTICE);
1308
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1309
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1310
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1311
        }
1312
1313
        if (! is_null($index) && count($index)) {
1314
            foreach ($index as $ind) {
1315
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1316
                    $this->log("{$field_data->name} is not an index", LogLevel::NOTICE);
1317
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1318
                }
1319
            }
1320
        }
1321
1322
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1323
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1324
        }
1325
1326
        return $ret;
1327
    }
1328
1329
    /**
1330
     * Function to check for the existence of a table within a schema
1331
     *
1332
     * @param string $strSchema
1333
     *            The schema to search in
1334
     * @param string $table_name
1335
     *            Table to search for
1336
     *
1337
     * @return integer|boolean Returns number of tables that match if table is found in that schema, otherwise FALSE
1338
     */
1339
    public function tableExists($strSchema, $table_name)
1340
    {
1341
        if (! $this->_c->select_db($strSchema)) {
1342
            fwrite(STDOUT, $this->_c->error . PHP_EOL);
1343
        }
1344
        $sql = "SHOW TABLES LIKE '{$table_name}'";
1345
1346
        if ($res = $this->_c->query($sql)) {
1347
            if (gettype($res) == 'object' && is_a(/**
1348
             * @scrutinizer ignore-type
1349
             */
1350
            $res, 'mysqli_result') && $res->num_rows) {
1351
                return $res->num_rows;
1352
            }
1353
        } else {
1354
            if ($this->_c->errno) {
1355
                fwrite(STDOUT, $this->_c->error . PHP_EOL);
1356
            }
1357
        }
1358
1359
        return false;
1360
    }
1361
1362
    /**
1363
     * Function to detect if string is a JSON object or not
1364
     *
1365
     * @param string $val
1366
     *
1367
     * @return boolean
1368
     */
1369
    public function isJson($val)
1370
    {
1371
        json_decode($val);
1372
        return (json_last_error() == JSON_ERROR_NONE);
1373
    }
1374
1375
    /**
1376
     * Function to escape SQL characters to prevent SQL injection
1377
     *
1378
     * @param mixed $val
1379
     *            Value to escape
1380
     * @param boolean $escape
1381
     *            Decide if we should escape or not
1382
     *
1383
     * @return string Escaped value
1384
     */
1385
    public function _escape($val, $escape = true)
1386
    {
1387
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1388
            return 'NULL';
1389
        } elseif (is_numeric($val) || is_string($val)) {
1390
            if ($escape) {
1391
                return "'{$this->_c->real_escape_string($val)}'";
1392
            }
1393
            return $val;
1394
        } elseif (is_a($val, 'DateTime')) {
1395
            return "'{$val->format(MYSQL_DATETIME)}'";
1396
        } elseif (is_bool($val)) {
1397
            return $val ? "'1'" : "'0'";
1398
        } elseif (gettype($val) == 'object' && method_exists($val, '_escape')) {
1399
            $ret = call_user_func([
1400
                $val,
1401
                '_escape'
1402
            ]);
1403
            if ($ret !== false) {
1404
                return $ret;
1405
            } else {
1406
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1407
            }
1408
        } elseif (gettype($val) == 'object') {
1409
            $this->log("Unknown object to escape " . get_class($val) . " in SQL string {$this->_sql}", LogLevel::ERROR);
1410
        }
1411
1412
        throw new Exception("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1413
    }
1414
1415
    /**
1416
     * Function to retrieve all results
1417
     *
1418
     * @param string $resulttype
1419
     *
1420
     * @return mixed
1421
     */
1422
    public function fetchAll($resulttype = MYSQLI_ASSOC)
1423
    {
1424
        $res = [];
1425
        if (method_exists('mysqli_result', 'fetch_all')) { // Compatibility layer with PHP < 5.3
1426
            $res = $this->result->fetch_all($resulttype);
0 ignored issues
show
The property result does not exist on Godsgood33\Php_Db\Database. Did you mean _result?
Loading history...
1427
        } else {
1428
            while ($tmp = $this->result->fetch_array($resulttype)) {
1429
                $res[] = $tmp;
1430
            }
1431
        }
1432
1433
        return $res;
1434
    }
1435
1436
    /**
1437
     * Function to populate the fields for the SQL
1438
     *
1439
     * @param array $fields
1440
     *            [optional]
1441
     *            Optional array of fields to string together to create a field list
1442
     *
1443
     * @return string
1444
     */
1445
    public function fields($fields = null)
1446
    {
1447
        $str_fields = null;
1448
1449
        if (is_array($fields) && count($fields)) {
1450
            foreach ($fields as $field) {
1451
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false)) {
1452
                    $str_fields .= "`$field`,";
1453
                } else {
1454
                    $str_fields .= "$field,";
1455
                }
1456
            }
1457
            $str_fields = substr($str_fields, 0, - 1);
1458
        } elseif (is_string($fields)) {
1459
            $str_fields = $fields;
1460
        } elseif (is_null($fields)) {
1461
            $str_fields = "*";
1462
        }
1463
1464
        return $str_fields;
1465
    }
1466
1467
    /**
1468
     * Function to parse the flags
1469
     *
1470
     * @param array $flags
1471
     *            Two-dimensional array to added flags
1472
     *
1473
     *            <code>
1474
     *            [
1475
     *            &nbsp;&nbsp;'joins' => [
1476
     *            &nbsp;&nbsp;&nbsp;&nbsp;"JOIN table2 t2 ON t2.id=t1.id"
1477
     *            &nbsp;&nbsp;],
1478
     *            &nbsp;&nbsp;'group' => 'field',
1479
     *            &nbsp;&nbsp;'having' => 'field',
1480
     *            &nbsp;&nbsp;'order' => 'field',
1481
     *            &nbsp;&nbsp;'start' => 0,
1482
     *            &nbsp;&nbsp;'limit' => 0
1483
     *            ]
1484
     *            </code>
1485
     *
1486
     * @see Database::groups()
1487
     * @see Database::having()
1488
     * @see Database::order()
1489
     *
1490
     * @return string
1491
     */
1492
    public function flags($flags)
1493
    {
1494
        $ret = '';
1495
1496
        if (isset($flags['group'])) {
1497
            $ret .= $this->groups($flags['group']);
1498
        }
1499
1500
        if (isset($flags['having']) && is_array($flags['having'])) {
1501
            $having = " HAVING";
1502
            foreach ($flags['having'] as $x => $h) {
1503
                $having .= $this->parseClause($h, $x);
1504
            }
1505
            if (strlen($having) > strlen(" HAVING")) {
1506
                $ret .= $having;
1507
            }
1508
        }
1509
1510
        if (isset($flags['order'])) {
1511
            $ret .= $this->order($flags['order']);
1512
        }
1513
1514
        if (isset($flags['limit']) && (is_string($flags['limit']) || is_numeric($flags['limit']))) {
1515
            $ret .= " LIMIT ";
1516
            if (isset($flags['start']) && (is_string($flags['start']) || is_numeric($flags['start']))) {
1517
                $ret .= "{$flags['start']},";
1518
            }
1519
            $ret .= "{$flags['limit']}";
1520
        }
1521
1522
        return $ret;
1523
    }
1524
1525
    /**
1526
     * Function to parse SQL GROUP BY statements
1527
     *
1528
     * @param mixed $groups
1529
     *
1530
     * @return string
1531
     */
1532
    public function groups($groups)
1533
    {
1534
        $ret = '';
1535
        if (is_array($groups) && count($groups)) {
1536
            $ret .= " GROUP BY";
1537
1538
            foreach ($groups as $grp) {
1539
                $ret .= " $grp";
1540
1541
                if ($grp != end($groups)) {
1542
                    $ret .= ",";
1543
                }
1544
            }
1545
        } elseif (is_string($groups)) {
1546
            $ret .= " GROUP BY {$groups}";
1547
        } else {
1548
            throw (new Exception("Error in datatype for groups " . gettype($groups), E_ERROR));
1549
        }
1550
1551
        return $ret;
1552
    }
1553
1554
    /**
1555
     * Function to parse SQL ORDER BY statements
1556
     *
1557
     * @param mixed $order
1558
     *
1559
     * @return string
1560
     */
1561
    public function order($order)
1562
    {
1563
        $ret = '';
1564
        if (is_array($order)) {
1565
            $ret .= " ORDER BY";
1566
1567
            foreach ($order as $ord) {
1568
                $ret .= " {$ord['field']} {$ord['sort']}";
1569
1570
                if ($ord != end($order)) {
1571
                    $ret .= ",";
1572
                }
1573
            }
1574
        } elseif (is_string($order)) {
1575
            $ret .= " ORDER BY {$order}";
1576
        }
1577
1578
        return $ret;
1579
    }
1580
1581
    /**
1582
     * Function to see if a constraint exists
1583
     *
1584
     * @param string $con_id
1585
     *
1586
     * @return boolean
1587
     */
1588
    public function isConstraint(string $strConstraintId)
1589
    {
1590
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
1591
1592
        if ($res->num_rows) {
1593
            return true;
1594
        }
1595
1596
        return false;
1597
    }
1598
1599
    /**
1600
     * Function to call logger and log activity
1601
     *
1602
     * @param string $msg
1603
     * @param string $level
1604
     *            [optional]
1605
     * @param array $context
1606
     *            [optional]
1607
     */
1608
    public function log($msg, $level = LogLevel::ERROR, $context = [])
1609
    {
1610
        if ($level == Logger::INFO) {
1611
            $this->_logger->info($msg, $context);
1612
        } elseif ($level == Logger::WARNING) {
1613
            $this->_logger->warning($msg, $context);
1614
        } elseif ($level == Logger::ERROR) {
1615
            $this->_logger->error($msg, $context);
1616
        } elseif ($level == Logger::NOTICE) {
1617
            $this->_logger->notice($msg, $context);
1618
        } elseif ($level == Logger::DEBUG) {
1619
            $this->_logger->debug($msg, $context);
1620
        }
1621
    }
1622
1623
    /**
1624
     * Function to parse where and having clauses
1625
     *
1626
     * @param array $clause
1627
     * @param int $index
1628
     */
1629
    public function parseClause($clause, $index)
1630
    {
1631
        $ret = null;
1632
1633
        if (! isset($clause['field']) && isset($clause['close-paren']) && $clause['close-paren']) {
1634
            $ret .= ")";
1635
            return $ret;
1636
        } elseif ($index > 0 && ! isset($clause['sql_op'])) {
1637
            $this->log("Missing sql_op field to identify how current and previous WHERE clause statements should be linked ('AND', 'OR', 'XOR', etc), skipped", LogLevel::WARNING, $clause);
1638
            return;
1639
        }
1640
1641
        $op = '=';
1642
        if (isset($clause['op'])) {
1643
            $op = $clause['op'];
1644
        }
1645
1646
        switch ($op) {
1647
            case self::BETWEEN:
1648
                if (! isset($clause['field']) || ! isset($clause['low']) || ! isset($clause['high'])) {
1649
                    $this->log("Missing field, low, or high for BETWEEN where clause, skipping", LogLevel::WARNING, $clause);
1650
                    return;
1651
                }
1652
                break;
1653
            default:
1654
                if (! isset($clause['field']) || ! isset($clause['value'])) {
1655
                    $this->log("Missing field or value for WHERE clause, skipping", LogLevel::WARNING, $clause);
1656
                    return;
1657
                }
1658
        }
1659
1660
        if ($index > 0) {
1661
            $ret .= " {$clause['sql_op']}";
1662
        }
1663
1664
        if (isset($clause['open-paren']) && $clause['open-paren']) {
1665
            $ret .= " (";
1666
        }
1667
1668
        if (isset($clause['backticks']) && ! $clause['backticks']) {
1669
            $field = $clause['field'];
1670
        } else {
1671
            $field = "`{$clause['field']}`";
1672
        }
1673
1674
        if ($op == self::IN || $op == self::NOT_IN) {
1675
            if (is_string($clause['value'])) {
1676
                $ret .= " {$field} {$op} " . (strpos($clause['value'], '(') !== false ? $clause['value'] : "({$clause['value']})");
1677
            } elseif (is_array($clause['value'])) {
1678
                $ret .= " {$field} {$op} (" . implode(",", array_map([
1679
                    $this,
1680
                    '_escape'
1681
                ], $clause['value'])) . ")";
1682
            } else {
1683
                $this->log("Invalid datatype for IN WHERE clause, only string and array allowed " . gettype($clause['value']), LogLevel::ERROR, $clause);
1684
                throw new Exception("Invalid datatype for IN WHERE clause", E_ERROR);
1685
            }
1686
        } elseif ($op == self::BETWEEN) {
1687
            $ret .= " {$field} BETWEEN {$this->_escape($clause['low'])} AND {$this->_escape($clause['high'])}";
1688
        } else {
1689
            if (isset($clause['escape']) && ! $clause['escape']) {
1690
                $value = $clause['value'];
1691
            } else {
1692
                $value = $this->_escape($clause['value']);
1693
            }
1694
1695
            if (isset($clause['case_insensitive']) && $clause['case_insensitive']) {
1696
                $ret .= " LOWER({$field}) {$op} LOWER({$this->_escape($clause['value'])})";
1697
            } elseif (preg_match("/\(SELECT/", $clause['value'])) {
1698
                $ret .= " {$field} {$op} {$clause['value']}";
1699
            } else {
1700
                $ret .= " {$field} {$op} {$value}";
1701
            }
1702
        }
1703
1704
        if (isset($clause['close-paren']) && $clause['close-paren']) {
1705
            $ret .= ")";
1706
        }
1707
1708
        return $ret;
1709
    }
1710
}