Passed
Push — master ( 00142c...900183 )
by Ryan
03:06 queued 11s
created

Database::setVar()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 2
b 0
f 0
nc 3
nop 2
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 3
rs 9.9
1
<?php
2
namespace Godsgood33\Php_Db;
3
4
use mysqli;
5
use stdClass;
6
use Exception;
7
use InvalidArgumentException;
8
9
use Monolog\Logger;
10
use Monolog\Formatter\LineFormatter;
11
use Monolog\Handler\StreamHandler;
12
13
use Godsgood33\Php_Db\Exceptions\MissingOrInvalidParam;
14
use Godsgood33\Php_Db\Exceptions\ConnectError;
15
use Godsgood33\Php_Db\Exceptions\MissingInterfaceAndMethods;
16
use Godsgood33\Php_Db\Exceptions\QueryError;
17
18
/**
19
 * Constant to define that we want to return an object
20
 *
21
 * @var int
22
 */
23
define('MYSQLI_OBJECT', 4);
24
25
/**
26
 * Constant to return consistent date format
27
 *
28
 * @var string
29
 */
30
define('MYSQL_DATE', 'Y-m-d');
31
32
/**
33
 * Constant to return consistent datetime format
34
 *
35
 * @var string
36
 */
37
define('MYSQL_DATETIME', 'Y-m-d H:i:s');
38
39
/**
40
 * A generic database class
41
 *
42
 * @author Ryan Prather <[email protected]>
43
 */
44
class Database
45
{
46
47
    /**
48
     * Constant defining parameters as a collection of objects
49
     *
50
     * @var int
51
     */
52
    private const COLLECTION = 1;
53
54
    /**
55
     * Constant defining parameters as an array
56
     *
57
     * @var int
58
     */
59
    private const ARRAY = 2;
60
61
    /**
62
     * Constant defining parameters as an objects
63
     *
64
     * @var int
65
     */
66
    private const OBJECT = 3;
67
68
    /**
69
     * Constant defining parameters as a string
70
     *
71
     * @var int
72
     */
73
    private const STRING = 4;
74
75
    /**
76
     * Constant defining parameters as an array of objects
77
     */
78
    private const ARRAY_OBJECT = 5;
79
80
    /**
81
     * Constant defining a SELECT query
82
     *
83
     * @var integer
84
     */
85
    private const SELECT = 1;
86
87
    /**
88
     * Constant defining a SELECT COUNT query
89
     *
90
     * @var integer
91
     */
92
    private const SELECT_COUNT = 2;
93
94
    /**
95
     * Constant defining a CREATE TABLE query
96
     *
97
     * @var integer
98
     */
99
    private const CREATE_TABLE = 3;
100
101
    /**
102
     * Constant defining DROP query
103
     *
104
     * @var integer
105
     */
106
    private const DROP = 4;
107
108
    /**
109
     * Constant defining DELETE query
110
     *
111
     * @var integer
112
     */
113
    private const DELETE = 5;
114
115
    /**
116
     * Constant defining INSERT query
117
     *
118
     * @var integer
119
     */
120
    private const INSERT = 6;
121
122
    /**
123
     * Constant defining REPLACE query
124
     *
125
     * @var integer
126
     */
127
    private const REPLACE = 7;
128
129
    /**
130
     * Constant defining UPDATE query
131
     *
132
     * @var integer
133
     */
134
    private const UPDATE = 8;
135
136
    /**
137
     * Constant defining EXTENDED INSERT query
138
     *
139
     * @var integer
140
     */
141
    private const EXTENDED_INSERT = 9;
142
143
    /**
144
     * Constant defining EXTENDED REPLACE query
145
     *
146
     * @var integer
147
     */
148
    private const EXTENDED_REPLACE = 10;
149
150
    /**
151
     * Constant defining EXTENDED UPDATE query
152
     *
153
     * @var integer
154
     */
155
    private const EXTENDED_UPDATE = 11;
156
157
    /**
158
     * Constant defining ALTER TABLE query
159
     *
160
     * @var integer
161
     */
162
    private const ALTER_TABLE = 12;
163
164
    /**
165
     * Constant defining action for alter table statement
166
     *
167
     * @var integer
168
     */
169
    private const ADD_COLUMN = 1;
170
171
    /**
172
     * Constant defining action for alter table statement
173
     *
174
     * @var integer
175
     */
176
    private const DROP_COLUMN = 2;
177
178
    /**
179
     * Constant defining action for alter table statement
180
     *
181
     * @var integer
182
     */
183
    private const MODIFY_COLUMN = 3;
184
185
    /**
186
     * Constant defining action to add a constraint
187
     *
188
     * @var integer
189
     */
190
    private const ADD_CONSTRAINT = 4;
191
192
    /**
193
     * Constant defining a TRUNCATE TABLE query
194
     *
195
     * @var integer
196
     */
197
    private const TRUNCATE = 13;
198
199
    /**
200
     * The mysqli connection
201
     *
202
     * @access protected
203
     * @var mysqli
204
     */
205
    protected $_c;
206
207
    /**
208
     * To store the SQL statement
209
     *
210
     * @access private
211
     * @var string
212
     */
213
    private $_sql = '';
214
215
    /**
216
     * A variable to store the type of query that is being run
217
     *
218
     * @access private
219
     * @var int
220
     */
221
    private $_queryType = null;
222
223
    /**
224
     * The result of the query
225
     *
226
     * @access protected
227
     * @var mixed
228
     */
229
    protected $_result = null;
230
231
    /**
232
     * Log level
233
     *
234
     * @access private
235
     * @var int
236
     */
237
    private $_logLevel = Logger::ERROR;
238
239
    /**
240
     * Variable to store the logger
241
     *
242
     * @access private
243
     * @var \Monolog\Logger
244
     */
245
    private $_logger = null;
246
247
    /**
248
     * Path for the logger to log the file
249
     *
250
     * @access private
251
     * @var string
252
     */
253
    private $_logPath = null;
254
255
    /**
256
     * Variable to store the most recent insert ID from an insert query
257
     *
258
     * @access protected
259
     * @var mixed
260
     */
261
    protected $_insertId = null;
262
263
    /**
264
     * Constructor
265
     *
266
     * @param string $strLogPath
267
     *            [optional] absolute log path for the log file
268
     * @param mysqli $dbh
269
     *            [optional]
270
     *            [by ref]
271
     *            mysqli object to perform queries.
272
     * @param int $intLogLevel
273
     *            [optional] Level of the log file to output
274
     *
275
     * @uses PHP_DB_ENCRYPT bool to define if the password is encrypted
276
     * @uses PHP_DB_PWD string to store the password
277
     * @uses PHP_DB_SERVER string to store the database server name or IP
278
     * @uses PHP_DB_USER string to store the name of the user used to connect to the server
279
     * @uses PHP_DB_SCHEMA string to store the default schema to connect to
280
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
281
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
282
     */
283 131
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
284
    {
285
        // set the log file path
286 131
        $this->_logPath = $strLogPath;
287 131
        if (!file_exists($this->_logPath)) {
288
            touch($this->_logPath . "/db.log");
289
        }
290
291
        // set the log level
292 131
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
293
            $this->_logLevel = Logger::ERROR;
294 131
        } elseif (!is_null($intLogLevel)) {
295
            $this->_logLevel = $intLogLevel;
296 131
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
297 131
            $this->_logLevel = PHP_DB_LOG_LEVEL;
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_LOG_LEVEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
298
        }
299
300
        // open the log handler
301 131
        $this->_logger = new Logger('db', [
302 131
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
303
        ]);
304
305
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
306 131
        if (PHP_SAPI == 'cli' && defined('PHP_DB_CLI_LOG') && PHP_DB_CLI_LOG) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_CLI_LOG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
307
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
308
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
309
            $this->_logger->pushHandler($stream);
310
        }
311
312
        // check to see if a connection was passed and all defined constants are present to establish a connection
313 131
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
314 1
            $this->_logger->debug("Connecting through existing connection");
315 1
            $this->_c = $dbh;
316 131
        } elseif (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
317
            $this->_logger->critical("Missing essential defined constants");
318
            throw new MissingOrInvalidParam("Please create and include a constant file with the following constants defining your DB connection (PHP_DB_SERVER, PHP_DB_USER, PHP_DB_PWD, PHP_DB_SCHEMA)", E_USER_ERROR);
319 131
        } elseif (defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
320
            $this->_logger->critical("Missing essential encryption constants");
321
            throw new MissingOrInvalidParam("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
322
        }
323
324
        // check to see if the password is encrypted and decrypt if it is
325 131
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
326 131
            $this->_logger->debug("Decrypting password");
327 131
            $pwd = $this->decrypt(PHP_DB_PWD);
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_PWD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
328
        } else {
329
            $pwd = PHP_DB_PWD;
330
        }
331
332
        // open the connection
333 131
        $this->_c = new mysqli(PHP_DB_SERVER, PHP_DB_USER, $pwd, PHP_DB_SCHEMA);
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SCHEMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SERVER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
334
335
        // check for a connection error and throw an error if there is one
336 131
        if ($this->_c->connect_errno) {
337
            $this->_logger->error("Error connecting to database {$this->_c->connect_error}");
338
            throw new ConnectError("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
339
        }
340
341
        // update the logger with the connection details
342 131
        $this->_logger->info("Database connected");
343 131
        $this->_logger->debug("Connection details:", [
344 131
            'Server' => PHP_DB_SERVER,
345 131
            'User'   => PHP_DB_USER,
346 131
            'Schema' => PHP_DB_SCHEMA
347
        ]);
348
349 131
        $this->setVar("time_zone", "+00:00");
350 131
        $this->setVar("sql_mode", "");
351 131
    }
352
353
    /**
354
     * Function to make sure that the database is connected
355
     *
356
     * @return bool
357
     *
358
     * @throws ConnectError
359
     */
360 17
    public function isConnected(): bool
361
    {
362 17
        $this->_logger->info("Checking for live connection");
363
364 17
        if (is_a($this->_c, 'mysqli')) {
365 16
            $ret = $this->_c->ping();
366
        } else {
367 1
            throw new ConnectError("Connection lost");
368
        }
369
370 16
        return $ret;
371
    }
372
373
    /**
374
     * Setter function for _logger
375
     *
376
     * @param Logger $log
377
     *
378
     * @return bool
379
     */
380 1
    public function setLogger(Logger $log): bool
381
    {
382
        // set the logger
383 1
        $this->_logger->debug("Setting logger");
384 1
        $this->_logger = $log;
385 1
        return true;
386
    }
387
388
    /**
389
     * Getter function for _logLevel
390
     *
391
     * @return int
392
     */
393 1
    public function getLogLevel(): int
394
    {
395 1
        $level = $this->_logLevel;
396
397 1
        $this->_logger->debug("Getting log level ({$level})");
398 1
        return $level;
399
    }
400
401
    /**
402
     * Getter function for _queryType
403
     *
404
     * @return int
405
     */
406 1
    public function getQueryType(): int
407
    {
408 1
        $this->_logger->debug("Getting query type");
409 1
        return $this->_queryType;
410
    }
411
412
    /**
413
     * Setter function for _queryType
414
     *
415
     * @param int $qt
416
     *      Class constants that store query type
417
     *
418
     * @return Database
419
     */
420 101
    private function setQueryType(int $qt): Database
421
    {
422 101
        $this->_logger->debug("Setting query type");
423 101
        $this->_queryType = $qt;
424
425 101
        return $this;
426
    }
427
428
    /**
429
     * Magic method to convert the class to a string represented by the SQL query
430
     *
431
     * @return string|null
432
     */
433 67
    public function __toString(): string
434
    {
435 67
        $this->_logger->notice("__toString");
436 67
        $this->_logger->debug($this->_sql);
437 67
        return $this->_sql;
438
    }
439
440
    /**
441
     * Function to return the currently selected database schema
442
     *
443
     * @return string|bool
444
     *      Returns the string name of the selected schema or FALSE if none selected
445
     */
446 1
    public function getSchema()
447
    {
448 1
        if ($res = $this->_c->query("SELECT DATABASE()")) {
449 1
            $row = $res->fetch_row();
450
451 1
            $this->_logger->debug("Getting schema {$row[0]}");
452 1
            return $row[0];
453
        }
454
455
        return false;
456
    }
457
458
    /**
459
     * Function to set schema
460
     *
461
     * @param string $strSchema
462
     *
463
     * @return bool
464
     *
465
     * @throws QueryError
466
     */
467 2
    public function setSchema(string $strSchema): bool
468
    {
469 2
        $this->_logger->info("Setting schema to {$strSchema}");
470 2
        if (! $this->_c->select_db($strSchema)) {
471 1
            $this->_logger->error("Unknown schema {$strSchema}");
472 1
            throw new QueryError("Unknown database schema $strSchema");
473
        }
474 1
        return true;
475
    }
476
477
    /**
478
     * Method to set a MYSQL variable
479
     *
480
     * @param string $strName
481
     *      Name of the SQL variable to set
482
     * @param string $strVal
483
     *      Value for the variable
484
     *
485
     * @return bool
486
     */
487 131
    public function setVar(string $strName, ?string $strVal): bool
488
    {
489 131
        if (empty($strName)) {
490 1
            $this->_logger->debug("name is blank", [
491 1
                'name'  => $strName
492
            ]);
493 1
            return false;
494
        }
495
496 131
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
497
498 131
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
499 131
            $this->_logger->info("Var set");
500 131
            return true;
501
        } else {
502 1
            $this->_logger->error("Failed to set variable {$this->_c->error}");
503 1
            return false;
504
        }
505
    }
506
507
    /**
508
     * Function to execute the statement
509
     *
510
     * @param int $return
511
     *            [optional]
512
     *            MYSQLI constant to control what is returned from the mysqli_result object
513
     * @param string $strSql
514
     *            [optional]
515
     *            Optional SQL query
516
     *
517
     * @throws QueryError
518
     * @throws ConnectError
519
     *
520
     * @return mixed
521
     */
522 15
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
523
    {
524 15
        if (! is_null($strSql)) {
525 2
            $this->_sql = $strSql;
526
        }
527
528 15
        $this->_result = false;
529 15
        $this->_insertId = null;
530
531 15
        $this->isConnected();
532
533 15
        $this->_logger->info("Executing {$this->_queryType} query");
534 15
        $this->_logger->debug($this->_sql);
535
536
        try {
537 15
            if (in_array($this->_queryType, [
538 15
                self::SELECT,
539 15
                self::SELECT_COUNT
540
            ])) {
541 6
                $this->_result = $this->_c->query($this->_sql);
542 6
                if ($this->_c->error) {
543
                    $this->_logger->error("There is an error {$this->_c->error}");
544
                    $this->_logger->debug("Errored on query", [$this->_sql]);
545
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
546
                }
547
            } else {
548 9
                $this->_result = $this->_c->real_query($this->_sql);
549 9
                if ($this->_c->errno) {
550 1
                    $this->_logger->error("There was an error {$this->_c->error}");
551 1
                    $this->_logger->debug("Errored on query", [$this->_sql]);
552 1
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
553
                }
554
            }
555
556 14
            $this->_logger->debug("Checking for query results");
557 14
            $this->_result = $this->checkResults($return);
558 1
        } catch (QueryError $e) {
559 1
            $this->_logger->error($e);
560
        }
561
562 15
        return $this->_result;
563
    }
564
565
    /**
566
     * Function to check the results and return what is expected
567
     *
568
     * @param mixed $returnType
569
     *            [optional]
570
     *            Optional return mysqli_result return type
571
     *
572
     * @return mixed
573
     */
574 14
    protected function checkResults(int $returnType)
575
    {
576 14
        $res = null;
577
578
        // check the sql results and process appropriately
579 14
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
580 5
            $res = $this->_result;
581 9
        } elseif (in_array($this->_queryType, [Database::INSERT, Database::EXTENDED_INSERT, Database::DELETE, Database::UPDATE, Database::EXTENDED_UPDATE, Database::REPLACE, Database::EXTENDED_REPLACE, Database::DELETE])) {
582 2
            $res = $this->_c->affected_rows;
583
584 2
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE, Database::EXTENDED_INSERT])) {
585 2
                $this->_insertId = $this->_c->insert_id;
586
            }
587 7
        } elseif ($this->_queryType == Database::SELECT_COUNT) {
588 1
            if (! is_a($this->_result, 'mysqli_result')) {
589
                $this->_logger->error("Error with return on query");
590
                return null;
591
            }
592
593 1
            if ($this->_result->num_rows == 1) {
594 1
                $row = $this->_result->fetch_assoc();
595 1
                if (isset($row['count'])) {
596 1
                    $this->_logger->debug("Returning SELECT_COUNT query", [
597 1
                        'count' => $row['count']
598
                    ]);
599 1
                    $res = $row['count'];
600
                }
601
            } elseif ($this->_result->num_rows > 1) {
602
                $this->_logger->debug("Returning SELECT_COUNT query", [
603
                    'count' => $this->_result->num_rows
604
                ]);
605
                $res = $this->_result->num_rows;
606
            }
607
608 1
            mysqli_free_result($this->_result);
609
        } else {
610 6
            $method = "mysqli_fetch_object";
611 6
            if ($returnType == MYSQLI_ASSOC) {
612 1
                $method = "mysqli_fetch_assoc";
613 5
            } elseif ($returnType == MYSQLI_NUM) {
614 1
                $method = "mysqli_fetch_array";
615
            }
616
617 6
            if (is_a($this->_result, 'mysqli_result')) {
618 5
                if ($this->_result->num_rows > 1) {
619 3
                    $res = [];
620 3
                    while ($row = call_user_func($method, $this->_result)) {
621 3
                        $res[] = $row;
622
                    }
623
                } else {
624 2
                    $res = call_user_func($method, $this->_result);
625
                }
626
            } else {
627 1
                $this->_logger->error("Error with return on query");
628 1
                return null;
629
            }
630
        }
631
632 13
        if ($this->_c->error) {
633
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
634
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
635
            return null;
636
        }
637
638 13
        return $res;
639
    }
640
641
    /**
642
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
643
     * Nothing is escaped
644
     *
645
     * @param string $strSql
646
     *            [optional]
647
     *            Optional query to pass in and execute
648
     *
649
     * @return \mysqli_result|bool
650
     */
651 2
    public function query(?string $strSql = null)
652
    {
653 2
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
654
    }
655
656
    /**
657
     * A function to build a select query
658
     *
659
     * @param string $strTableName
660
     *            The table to query
661
     * @param array|string $fields
662
     *            [optional]
663
     *            Optional array of fields to return (defaults to '*')
664
     * @param array:DBWhere|DBWhere $arrWhere
665
     *            [optional] Where clause data
666
     * @param array $arrFlags
667
     *            [optional]
668
     *            Optional 2-dimensional array to allow other flags
669
     *
670
     * @see Database::flags()
671
     *
672
     * @throws Exception
673
     * @throws InvalidArgumentException
674
     *
675
     * @return mixed
676
     *
677
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
678
     */
679 37
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
680
    {
681 37
        $this->_sql = null;
682 37
        $this->setQueryType(self::SELECT);
683
684
        // starting building the query
685 37
        if ($this->checkTableName($strTableName)) {
686 37
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
687 37
                'fields' => $this->fields($fields)
688
            ]);
689 36
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
690
        }
691
692
        // add in any joins
693 36
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
694 1
            $this->_logger->debug("Adding joins", [
695 1
                'joins' => implode(' ', $arrFlags['joins'])
696
            ]);
697 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
698
        } else {
699 35
            $this->_logger->debug("No joins");
700
        }
701
702
        // parse the where clauses
703 36
        $where = $this->parseClause($arrWhere);
704
705 34
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
706 8
            $where_str = " WHERE";
707 8
            $this->_logger->debug("Parsing where clause and adding to query");
708 8
            foreach ($where as $x => $w) {
709 8
                if ($x > 0) {
710 1
                    $where_str .= " {$w->sqlOperator}";
711
                }
712 8
                $where_str .= $w;
713
            }
714 8
            if (strlen($where_str) > strlen(" WHERE")) {
715 8
                $this->_sql .= $where_str;
716
            }
717
        }
718
719
        // search for any other flags (order, having, group)
720 34
        if (is_array($arrFlags) && count($arrFlags)) {
721 9
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
722 9
            $this->_sql .= $this->flags($arrFlags);
723
        }
724
725 32
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
726
            return $this->execute();
727
        }
728
729 32
        return $this->_sql;
730
    }
731
732
    /**
733
     * Function to build a query to check the number of rows in a table
734
     *
735
     * @param string $strTableName
736
     *            The table to query
737
     * @param array:DBWhere|DBWhere $arrWhere
738
     *            [optional]
739
     *            Optional 2-dimensional array to build where clause
740
     * @param array $arrFlags
741
     *            [optional]
742
     *            Optional 2-dimensional array to add flags
743
     *
744
     * @see Database::flags()
745
     *
746
     * @return string|int|NULL
747
     *
748
     * @throws InvalidArgumentException
749
     *
750
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
751
     */
752 4
    public function selectCount(string $strTableName, $arrWhere = [], ?array $arrFlags = [])
753
    {
754 4
        $this->_sql = null;
755 4
        $this->setQueryType(self::SELECT_COUNT);
756
757
        // start building query
758 4
        if ($this->checkTableName($strTableName)) {
759 4
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
760
        }
761
762
        // add in any joins
763 4
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
764 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
765
        }
766
767
        // parse where clauses
768 4
        $where = $this->parseClause($arrWhere);
769
770 4
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
771 2
            $where_str = " WHERE";
772 2
            $this->_logger->debug("Parsing where clause and adding to query");
773 2
            foreach ($where as $x => $w) {
774 2
                if ($x > 0) {
775 1
                    $where_str .= " {$w->sqlOperator}";
776
                }
777 2
                $where_str .= $w;
778
            }
779 2
            if (strlen($where_str) > strlen(" WHERE")) {
780 2
                $this->_sql .= $where_str;
781
            }
782
        }
783
784
        // add in additional flags (group, having, order)
785 4
        if (is_array($arrFlags) && count($arrFlags)) {
786 1
            $this->_sql .= $this->flags($arrFlags);
787
        }
788
789 4
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
790
            return $this->execute();
791
        }
792
793 4
        return $this->_sql;
794
    }
795
796
    /**
797
     * Function to build an insert query statement
798
     *
799
     * @param string $strTableName
800
     * @param mixed $arrParams
801
     * @param bool $blnToIgnore
802
     *
803
     * @return string|NULL
804
     *
805
     * @throws InvalidArgumentException
806
     * @throws MissingInterfaceAndMethods
807
     *
808
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
809
     */
810 7
    public function insert(string $strTableName, $params, bool $blnToIgnore = false)
811
    {
812 7
        $this->_sql = null;
813 7
        $this->setQueryType(self::INSERT);
814
815
        // start building query
816 7
        if ($this->checkTableName($strTableName)) {
817 7
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
818
        }
819
820 7
        $paramType = $this->checkParamType($params);
821
822
        // add in field parameters and values
823 6
        if ($paramType == self::ARRAY) {
824 3
            $keys = array_keys($params);
825 3
            $vals = array_values($params);
826 3
            $this->_sql .= " (`" . implode("`,`", $keys) . "`)";
827 3
            $this->_sql .= " VALUES (" . implode(",", array_map([
828 3
                $this,
829 3
                '_escape'
830 3
            ], $vals)) . ")";
831 3
        } elseif ($paramType == self::STRING) {
832 1
            $this->_sql .= " {$params}";
833 2
        } elseif ($paramType == self::OBJECT) {
834 1
            $arr = $params->insert();
835 1
            $keys = array_keys($arr);
836 1
            $vals = array_values($arr);
837 1
            $this->_sql .= " (`" . implode("`,`", $keys) . "`) VALUES ";
838 1
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], $vals)) . ")";
839
        } else {
840 1
            throw new InvalidArgumentException("Invalid type passed to insert " . gettype($params));
841
        }
842
843 5
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
844
            return $this->execute();
845
        }
846
847 5
        return $this->_sql;
848
    }
849
850
    /**
851
     * Function to create an extended insert query statement
852
     *
853
     * @param string $strTableName
854
     *            The table name that the data is going to be inserted on
855
     * @param array $arrFields
856
     *            An array of field names that each value represents
857
     * @param mixed $params
858
     *            An array of array of values or a string with a SELECT statement to populate the insert with
859
     * @param bool $blnToIgnore
860
     *            [optional]
861
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
862
     *
863
     * @return NULL|string Returns the SQL if self::$autorun is set to false, else it returns the output from running.
864
     *
865
     * @throws InvalidArgumentException
866
     * @throws MissingOrInvalidParam
867
     * @throws MissingInterfaceAndMethods
868
     *
869
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
870
     */
871 8
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
872
    {
873 8
        $this->_sql = null;
874 8
        $this->setQueryType(self::EXTENDED_INSERT);
875
876
        // start building query
877 8
        if ($this->checkTableName($strTableName)) {
878 8
            $this->_sql = "INSERT ".
879 8
                ($blnToIgnore ? "IGNORE " : "").
880 8
                "INTO $strTableName ".
881 8
                "(`".implode("`,`", $arrFields)."`)";
882
        }
883
884 8
        $paramType = $this->checkParamType($params);
885 6
        $this->_sql .= " VALUES ";
886
887 6
        if ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
888 2
            foreach ($params as $p) {
889 2
                $key_value = $p->insert();
890 2
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
891
            }
892
893 2
            $this->_sql = substr($this->_sql, 0, -1);
894 4
        } elseif ($paramType == self::OBJECT) {
895
            $key_value = $params->insert();
896
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
897 4
        } elseif ($paramType == self::ARRAY) {
898 4
            foreach ($params as $p) {
899 4
                if (count($p) != count($arrFields)) {
900 2
                    $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
901 2
                        $p,
902 2
                        debug_backtrace()
903
                    ]);
904 2
                    throw new MissingOrInvalidParam("Inconsistent number of fields in fields and values in extendedInsert " . print_r($p, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($p, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

904
                    throw new MissingOrInvalidParam("Inconsistent number of fields in fields and values in extendedInsert " . /** @scrutinizer ignore-type */ print_r($p, true));
Loading history...
905
                }
906 3
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
907
908 3
                if ($p != end($params)) {
909 3
                    $this->_sql .= ",";
910
                }
911
            }
912
        } else {
913
            throw new InvalidArgumentException("Invalid param type ".gettype($params));
914
        }
915
916 4
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
917
            return $this->execute();
918
        }
919
920 4
        return $this->_sql;
921
    }
922
923
    /**
924
     * Build a statement to update a table
925
     *
926
     * @param string $strTableName
927
     *            The table name to update
928
     * @param mixed $arrParams
929
     *            Name/value pairs of the field name and value
930
     * @param array:DBWhere|DBWhere $arrWhere
931
     *            [optional]
932
     *            DBWhere clauses
933
     * @param array $arrFlags
934
     *            [optional]
935
     *            Two-dimensional array to create other flag options (joins, order, and group)
936
     *
937
     * @see Database::flags()
938
     *
939
     * @return NULL|string
940
     *
941
     * @throws InvalidArgumentException
942
     * @throws MissingOrInvalidParam
943
     * @throws MissingInterfaceAndMethods
944
     *
945
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
946
     */
947 9
    public function update(string $strTableName, $params, $arrWhere = [], ?array $arrFlags = [])
948
    {
949 9
        $this->_sql = "UPDATE ";
950 9
        $this->setQueryType(self::UPDATE);
951
952 9
        if ($this->checkTableName($strTableName)) {
953 9
            $this->_sql .= $strTableName;
954
955 9
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
956 1
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
957 1
                unset($arrFlags['joins']);
958
            }
959
960 9
            $this->_sql .= " SET ";
961
        }
962
963 9
        $paramType = $this->checkParamType($params);
964
965 7
        if ($paramType == self::ARRAY) {
966 6
            $keys = array_keys($params);
967 6
            foreach ($params as $f => $p) {
968 6
                $field = $f;
969 6
                if ((strpos($f, "`") === false) &&
970 6
                    (strpos($f, ".") === false) &&
971 5
                    (strpos($f, "*") === false) &&
972 5
                    (stripos($f, " as ") === false)
973
                ) {
974 5
                    $field = "`{$f}`";
975
                }
976
977 6
                if (! is_null($p)) {
978 5
                    $this->_sql .= "$field={$this->_escape($p)}";
979
                } else {
980 1
                    $this->_sql .= "$field=NULL";
981
                }
982
983 6
                if ($f != end($keys)) {
984 1
                    $this->_sql .= ",";
985
                }
986
            }
987 1
        } elseif ($paramType == self::OBJECT) {
988 1
            $key_value = $params->update();
989 1
            $fields = array_keys($key_value);
990 1
            $values = array_map([$this, '_escape'], array_values($key_value));
991 1
            foreach ($fields as $x => $f) {
992 1
                if ($x > 0) {
993 1
                    $this->_sql .= ",";
994
                }
995 1
                $this->_sql .= "`{$f}`={$values[$x]}";
996
            }
997
        } else {
998
            throw new MissingOrInvalidParam("No fields to update");
999
        }
1000
1001 7
        $where = $this->parseClause($arrWhere);
1002
1003 7
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
1004 3
            $where_str = " WHERE";
1005 3
            $this->_logger->debug("Parsing where clause and adding to query");
1006 3
            foreach ($where as $x => $w) {
1007 3
                if ($x > 0) {
1008 1
                    $where_str .= " {$w->sqlOperator}";
1009
                }
1010 3
                $where_str .= $w;
1011
            }
1012 3
            if (strlen($where_str) > strlen(" WHERE")) {
1013 3
                $this->_sql .= $where_str;
1014
            }
1015
        }
1016
1017 7
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
1018
            $this->_sql .= $this->flags($arrFlags);
1019
        }
1020
1021 7
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1022
            return $this->execute();
1023
        }
1024
1025 7
        return $this->_sql;
1026
    }
1027
1028
    /**
1029
     * Function to offer an extended updated functionality by using two different tables.
1030
     *
1031
     * @param string $strTableToUpdate
1032
     *            The table that you want to update (alias 'tbu' is automatically added)
1033
     * @param string $strOriginalTable
1034
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
1035
     * @param string $strLinkField
1036
     *            The common index value between them that will join the fields
1037
     * @param array|string $arrParams
1038
     *            If string only a single field is updated (tbu.$params = o.$params)
1039
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
1040
     *
1041
     * @return mixed
1042
     *
1043
     * @throws InvalidArgumentException
1044
     *
1045
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1046
     */
1047 2
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, array $arrParams)
1048
    {
1049 2
        $this->_sql = "UPDATE ";
1050 2
        $this->setQueryType(self::EXTENDED_UPDATE);
1051
1052 2
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
1053 2
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
1054
        }
1055
1056 2
        if (is_array($arrParams) && count($arrParams)) {
1057 2
            foreach ($arrParams as $param) {
1058 2
                if ($param != $strLinkField) {
1059 2
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1060
                }
1061
            }
1062 2
            $this->_sql = substr($this->_sql, 0, - 1);
1063
        } elseif (is_string($arrParams)) {
1064
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1065
        } else {
1066
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
1067
        }
1068
1069 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1070
            return $this->execute();
1071
        }
1072
1073 2
        return $this->_sql;
1074
    }
1075
1076
    /**
1077
     * Function to build a replace query
1078
     *
1079
     * @param string $strTableName
1080
     *            The table to update
1081
     * @param mixed $arrParams
1082
     *            Name/value pair to insert
1083
     *
1084
     * @return NULL|string
1085
     *
1086
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1087
     */
1088 2
    public function replace(string $strTableName, $params)
1089
    {
1090 2
        $this->_sql = null;
1091 2
        $this->setQueryType(self::REPLACE);
1092
1093 2
        if ($this->checkTableName($strTableName)) {
1094 2
            $this->_sql = "REPLACE INTO $strTableName ";
1095
        }
1096
1097 2
        $paramType = $this->checkParamType($params);
1098
1099 2
        if ($paramType == self::ARRAY) {
1100 1
            $keys = array_keys($params);
1101 1
            $vals = array_values($params);
1102
1103 1
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1104 1
            $this->_sql .= " VALUES (" . implode(",", array_map([
1105 1
                $this,
1106 1
                '_escape'
1107 1
            ], array_values($vals))) . ")";
1108 1
        } elseif ($paramType == self::OBJECT) {
1109 1
            $key_value = $params->replace();
1110 1
            $this->_sql .= "(`" . implode("`,`", array_keys($key_value)) . "`) VALUES ";
1111 1
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . ")";
1112
        } else {
1113
            throw new MissingOrInvalidParam('Arrays or Objects that implement DBInterface are the only valid types for replace');
1114
        }
1115
1116 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1117
            return $this->execute();
1118
        }
1119
1120 2
        return $this->_sql;
1121
    }
1122
1123
    /**
1124
     * Function to build an extended replace statement
1125
     *
1126
     * @param string $strTableName
1127
     *            Table name to update
1128
     * @param array $arrFields
1129
     *            Array of fields
1130
     * @param mixed $arrParams
1131
     *            Two-dimensional array of values
1132
     *
1133
     * @return NULL|string
1134
     *
1135
     * @throws InvalidArgumentException
1136
     *
1137
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1138
     */
1139 1
    public function extendedReplace(string $strTableName, array $arrFields, $params)
1140
    {
1141 1
        $this->_sql = null;
1142 1
        $this->setQueryType(self::EXTENDED_REPLACE);
1143
1144 1
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1145
            throw new InvalidArgumentException("Error with the field type");
1146
        }
1147
1148 1
        if ($this->checkTableName($strTableName)) {
1149 1
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1150
        }
1151
1152 1
        $paramType = $this->checkParamType($params);
1153 1
        $this->_sql .= " VALUES ";
1154
1155 1
        if ($paramType == self::ARRAY) {
1156 1
            foreach ($params as $p) {
1157 1
                $this->_sql .= "(" . implode(",", array_map([
1158 1
                    $this,
1159 1
                    '_escape'
1160 1
                ], array_values($p))) . ")";
1161
1162 1
                if ($p != end($params)) {
1163 1
                    $this->_sql .= ",";
1164
                }
1165
            }
1166
        } elseif ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
1167
            foreach ($params as $p) {
1168
                $key_value = $p->replace();
1169
                $this->_sql .= "(" . implode(",", array_map([
1170
                    $this,
1171
                    '_escape'
1172
                ], array_values($key_value))) . ")";
1173
1174
                if ($p != end($params)) {
1175
                    $this->_sql .= ",";
1176
                }
1177
            }
1178
        }
1179
1180 1
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1181
            return $this->execute();
1182
        }
1183
1184 1
        return $this->_sql;
1185
    }
1186
1187
    /**
1188
     * Function to build a delete statement
1189
     *
1190
     * @param string $strTableName
1191
     *            Table name to act on
1192
     * @param array $arrFields
1193
     *            [optional]
1194
     *            Optional list of fields to delete (used when including multiple tables)
1195
     * @param array:DBWhere|DBWhere $arrWhere
1196
     *            [optional]
1197
     *            Optional where clauses to use
1198
     * @param array $arrJoins
1199
     *            [optional]
1200
     *            Optional 2-dimensional array to add other flags
1201
     *
1202
     * @see Database::flags()
1203
     *
1204
     * @return string|NULL
1205
     *
1206
     * @throws InvalidArgumentException
1207
     *
1208
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1209
     */
1210 4
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1211
    {
1212 4
        $this->_sql = "DELETE";
1213 4
        $this->setQueryType(self::DELETE);
1214
1215 4
        $this->_logger->debug("Deleting table data");
1216
1217 4
        if (! is_null($arrFields) && is_array($arrFields) && count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
introduced by
The condition is_null($arrFields) is always false.
Loading history...
1218 1
            $this->_sql .= " " . implode(",", $arrFields);
1219
        }
1220
1221 4
        if ($this->checkTableName($strTableName)) {
1222 4
            $this->_sql .= " FROM $strTableName";
1223
        }
1224
1225 4
        if (! is_null($arrJoins) && is_array($arrJoins) && count($arrJoins)) {
0 ignored issues
show
introduced by
The condition is_null($arrJoins) is always false.
Loading history...
introduced by
The condition is_array($arrJoins) is always true.
Loading history...
1226 1
            $this->_sql .= " " . implode(" ", $arrJoins);
1227
        }
1228
1229 4
        $where = $this->parseClause($arrWhere);
1230
1231 4
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
1232 2
            $where_str = " WHERE";
1233 2
            $this->_logger->debug("Parsing where clause and adding to query");
1234 2
            foreach ($where as $x => $w) {
1235 2
                if ($x > 0) {
1236 1
                    $where_str .= " {$w->sqlOperator}";
1237
                }
1238 2
                $where_str .= $w;
1239
            }
1240 2
            if (strlen($where_str) > strlen(" WHERE")) {
1241 2
                $this->_sql .= $where_str;
1242
            }
1243
        }
1244
1245 4
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1246
            return $this->execute();
1247
        }
1248
1249 4
        return $this->_sql;
1250
    }
1251
1252
    /**
1253
     * Function to build a drop table statement (automatically executes)
1254
     *
1255
     * @param string $strTableName
1256
     *            Table to drop
1257
     * @param string $strType
1258
     *            [optional]
1259
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1260
     * @param bool $blnIsTemp
1261
     *            [optional]
1262
     *            Optional bool if this is a temporary table
1263
     *
1264
     * @return string|NULL
1265
     *
1266
     * @throws InvalidArgumentException
1267
     *
1268
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1269
     */
1270 5
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1271
    {
1272 5
        $this->_sql = null;
1273 5
        $this->setQueryType(self::DROP);
1274
1275 5
        switch ($strType) {
1276 5
            case 'table':
1277 4
                $strType = 'TABLE';
1278 4
                break;
1279 1
            case 'view':
1280 1
                $strType = 'VIEW';
1281 1
                break;
1282
            default:
1283
                throw new InvalidArgumentException("Invalid type " . gettype($strType), E_ERROR);
1284
        }
1285
1286 5
        if ($this->checkTableName($strTableName)) {
1287 5
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1288
        }
1289
1290 5
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1291
            return $this->execute();
1292
        }
1293
1294 5
        return $this->_sql;
1295
    }
1296
1297
    /**
1298
     * Function to build a truncate table statement (automatically executes)
1299
     *
1300
     * @param string $strTableName
1301
     *            Table to truncate
1302
     *
1303
     * @return string|NULL
1304
     *
1305
     * @throws InvalidArgumentException
1306
     *
1307
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1308
     */
1309 1
    public function truncate(string $strTableName)
1310
    {
1311 1
        $this->_sql = null;
1312 1
        $this->setQueryType(self::TRUNCATE);
1313
1314 1
        if ($this->checkTableName($strTableName)) {
1315 1
            $this->_sql = "TRUNCATE TABLE $strTableName";
1316
        }
1317
1318 1
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1319
            return $this->execute();
1320
        }
1321
1322 1
        return $this->_sql;
1323
    }
1324
1325
    /**
1326
     * Function to build a create temporary table statement
1327
     *
1328
     * @param string $strTableName
1329
     *            Name to give the table when creating
1330
     * @param bool $blnIsTemp
1331
     *            [optional]
1332
     *            Optional bool to make the table a temporary table
1333
     * @param mixed $strSelect
1334
     *            [optional]
1335
     *            Optional parameter if null uses last built statement
1336
     *            If string, will be made the SQL statement executed to create the table
1337
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1338
     *
1339
     * @return NULL|string
1340
     *
1341
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1342
     */
1343 4
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1344
    {
1345 4
        $this->setQueryType(self::CREATE_TABLE);
1346
1347 4
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1348 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1349 3
        } elseif ($this->checkTableName($strTableName) && is_string($strSelect)) {
1350 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1351 2
        } elseif ($this->checkTableName($strTableName) && is_array($strSelect)) {
1352 2
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1353
1354 2
            foreach ($strSelect as $field) {
1355 2
                $default = null;
1356 2
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1357 2
                    $this->_sql .= (string) $field . ",";
1358
                } elseif (is_array($field)) {
1359
                    if (isset($field['default'])) {
1360
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1361
                    }
1362
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1363
                }
1364
            }
1365 2
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1366
        }
1367
1368 4
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1369
            return $this->execute();
1370
        }
1371
1372 4
        return $this->_sql;
1373
    }
1374
1375
    /**
1376
     * Function to create a table using a stdClass object derived from JSON
1377
     *
1378
     * @param stdClass $json
1379
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1380
     *
1381
     * @example /examples/create_table_json.json
1382
     *
1383
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1384
     */
1385 3
    public function createTableJson($json, bool $blnDropFirst = false)
1386
    {
1387 3
        $this->setQueryType(self::CREATE_TABLE);
1388 3
        $this->_c->select_db($json->schema);
1389
1390 3
        if ($blnDropFirst) {
1391
            $this->drop($json->name);
1392
            if (!defined('PHP_DB_AUTORUN') || !PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1393
                $this->execute();
1394
            }
1395
        }
1396
1397 3
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1398 3
        foreach ($json->fields as $field) {
1399 3
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1400
1401 3
            if ($field->dataType == 'enum' && isset($field->values)) {
1402 1
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1403
            }
1404
1405 3
            if (isset($field->ai) && $field->ai) {
1406 3
                $this->_sql .= " AUTO_INCREMENT";
1407
            }
1408
1409 3
            if (isset($field->nn) && $field->nn) {
1410 3
                $this->_sql .= " NOT NULL";
1411 2
            } elseif (isset($field->default)) {
1412 2
                if (strtolower($field->default) == 'null') {
1413 1
                    $this->_sql .= " DEFAULT NULL";
1414 1
                } elseif (strlen($field->default)) {
1415 1
                    $this->_sql .= " DEFAULT '{$field->default}'";
1416
                }
1417
            }
1418
1419 3
            if ($field != end($json->fields)) {
1420 2
                $this->_sql .= ",";
1421
            }
1422
        }
1423
1424 3
        if (isset($json->index) && count($json->index)) {
1425 1
            foreach ($json->index as $ind) {
1426 1
                $ref = null;
1427 1
                if (is_array($ind->ref)) {
1428
                    $ref = "";
1429
                    foreach ($ind->ref as $r) {
1430
                        $ref .= "`{$r}` ASC,";
1431
                    }
1432
                    $ref = substr($ref, 0, -1);
1433 1
                } elseif (is_string($ind->ref)) {
1434 1
                    $ref = $ind->ref;
1435
                }
1436 1
                if (!is_null($ref)) {
1437 1
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1438
                }
1439
            }
1440
        }
1441
1442 3
        if (isset($json->constraints) && count($json->constraints)) {
1443
            foreach ($json->constraints as $con) {
1444
                $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));
1445
            }
1446
        }
1447
1448 3
        if (isset($json->unique) && count($json->unique)) {
1449 1
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1450
        }
1451
1452 3
        if (isset($json->primary_key) && count($json->primary_key)) {
1453 3
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1454
        } else {
1455
            if (substr($this->_sql, - 1) == ',') {
1456
                $this->_sql = substr($this->_sql, 0, - 1);
1457
            }
1458
1459
            $this->_sql .= ")";
1460
        }
1461
1462 3
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1463
            return $this->execute();
1464
        }
1465
1466 3
        return $this->_sql;
1467
    }
1468
1469
    /**
1470
     * Method to add a column to the database (only one at a time!)
1471
     *
1472
     * @param string $strTableName
1473
     * @param stdClass $params
1474
     *
1475
     * @return string|mixed
1476
     *
1477
     * @throws InvalidArgumentException
1478
     *
1479
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1480
     */
1481 3
    public function addColumn(string $strTableName, stdClass $params)
1482
    {
1483 3
        $this->setQueryType(self::ALTER_TABLE);
1484 3
        $this->checkTableName($strTableName);
1485
1486 3
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1487
1488 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1489 1
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1490 1
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1491
        }
1492
1493 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1494 2
        $default = null;
1495 2
        if ($params->default === null) {
1496 1
            $default = " DEFAULT NULL";
1497 1
        } elseif (strlen($params->default)) {
1498 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1499
        }
1500 2
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1501
1502 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1503
            return $this->execute();
1504
        }
1505
1506 2
        return $this->_sql;
1507
    }
1508
1509
    /**
1510
     * Method to drop a fields from a table
1511
     *
1512
     * @param string $strTableName
1513
     * @param string|array:string $params
1514
     *
1515
     * @return string|mixed
1516
     *
1517
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1518
     */
1519 3
    public function dropColumn(string $strTableName, $params)
1520
    {
1521 3
        $this->setQueryType(self::ALTER_TABLE);
1522 3
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1523
1524 3
        if (is_array($params) && count($params)) {
1525 2
            foreach ($params as $col) {
1526 2
                $this->_sql .= " `{$col->name}`";
1527
1528 2
                if ($col != end($params)) {
1529 1
                    $this->_sql .= ",";
1530
                }
1531
            }
1532 1
        } elseif (is_string($params)) {
1533 1
            $this->_sql .= " `{$params}`";
1534
        }
1535
1536 3
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1537
            return $this->execute();
1538
        }
1539
1540 3
        return $this->_sql;
1541
    }
1542
1543
    /**
1544
     * Method to modify a field to change it's datatype, name, or other parameter
1545
     *
1546
     * @param string $strTableName
1547
     * @param stdClass $params
1548
     *
1549
     * @return string|mixed
1550
     *
1551
     * @throws InvalidArgumentException
1552
     *
1553
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1554
     */
1555 3
    public function modifyColumn($strTableName, $params)
1556
    {
1557 3
        $this->setQueryType(self::ALTER_TABLE);
1558 3
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1559
1560 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1561 1
            $this->_logger->error("Missing elements to the modifyColumn method (need 'name' and 'dataType')", [$params]);
1562 1
            throw new InvalidArgumentException("Missing elements to the modifyColumn method");
1563
        }
1564
1565 2
        if (!isset($params->new_name)) {
1566 1
            $params->new_name = $params->name;
1567
        }
1568
1569 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1570 2
        $default = null;
1571 2
        if ($params->default === null) {
1572 1
            $default = " DEFAULT NULL";
1573 1
        } elseif (strlen($params->default)) {
1574 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1575
        }
1576 2
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1577
1578 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1579
            return $this->execute();
1580
        }
1581
1582 2
        return $this->_sql;
1583
    }
1584
1585
    /**
1586
     * Method to add a constraint to a table
1587
     *
1588
     * @param string $strTableName
1589
     * @param stdClass $params
1590
     *
1591
     * @return string|mixed
1592
     *
1593
     * @throws InvalidArgumentException
1594
     *
1595
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1596
     */
1597 7
    public function addConstraint($strTableName, $params)
1598
    {
1599 7
        $this->setQueryType(self::ALTER_TABLE);
1600 7
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1601
1602 7
        if (!is_a($params, 'stdClass')) {
1603 1
            $this->_logger->critical("Error in reading constraint field");
1604 1
            throw new InvalidArgumentException("Error in reading constraint field");
1605
        }
1606
1607 6
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1608 1
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1609 1
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1610
        }
1611
1612 5
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1613 1
            $this->_logger->error("Invalid action for deletion on addConstraint");
1614 1
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1615
        }
1616
1617 4
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1618 1
            $this->_logger->error("Invalid action for update on addConstraint");
1619 1
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1620
        }
1621
1622 3
        if (is_array($params->field) && is_array($params->local)) {
1623 1
            $field = "`" . implode("`,`", $params->field) . "`";
1624 1
            $local = "`" . implode("`,`", $params->local) . "`";
1625 2
        } elseif (is_string($params->field) && is_string($params->local)) {
1626 1
            $field = "`{$params->field}`";
1627 1
            $local = "`{$params->local}`";
1628
        } else {
1629 1
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1630
        }
1631 2
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1632
1633 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1634
            return $this->execute();
1635
        }
1636
1637 2
        return $this->_sql;
1638
    }
1639
1640
    /**
1641
     * Check to see if a field in a table exists
1642
     *
1643
     * @param string $strTableName
1644
     *            Table to check
1645
     * @param string $strFieldName
1646
     *            Field name to find
1647
     *
1648
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1649
     */
1650 2
    public function fieldExists(string $strTableName, string $strFieldName): bool
1651
    {
1652 2
        $fdata = $this->fieldData($strTableName);
1653
1654 2
        if (is_array($fdata) && count($fdata)) {
1655 2
            foreach ($fdata as $field) {
1656 2
                if ($field->name == $strFieldName) {
1657 1
                    return true;
1658
                }
1659
            }
1660
        }
1661
1662 1
        return false;
1663
    }
1664
1665
    /**
1666
     * Function to get the column data (datatype, flags, defaults, etc)
1667
     *
1668
     * @param string $strTableName
1669
     *            Table to query
1670
     * @param mixed $field
1671
     *            [optional]
1672
     *            Optional field to retrieve data (if null, returns data from all fields)
1673
     *
1674
     * @return mixed
1675
     */
1676 7
    public function fieldData(string $strTableName, $field = null)
1677
    {
1678 7
        if (is_null($field)) {
1679 3
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1680 5
        } elseif (is_array($field)) {
1681 5
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1682 1
        } elseif (is_string($field)) {
1683 1
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1684
        } else {
1685 1
            return null;
1686
        }
1687
1688 7
        $fields = null;
1689 7
        if (is_a($res, 'mysqli_result')) {
1690 7
            $fields = $res->fetch_fields();
1691 7
            foreach ($fields as $i => $f) {
1692 7
                $fields["{$f->name}"] = $f;
1693 7
                unset($fields[$i]);
1694
            }
1695
        }
1696
1697 7
        return $fields;
1698
    }
1699
1700
    /**
1701
     * Function to check that all field parameters are set correctly
1702
     *
1703
     * @param stdClass $field_data
1704
     * @param stdClass $check
1705
     * @param array $pks
1706
     * @param stdClass $index
1707
     *
1708
     * @return array|string
1709
     */
1710 4
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1711
    {
1712 4
        $default = null;
1713 4
        $ret = null;
1714
1715 4
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1716
1717 4
        if ($check->default === null) {
1718 1
            $default = " DEFAULT NULL";
1719 3
        } elseif (strlen($check->default)) {
1720 1
            $default = " DEFAULT '{$check->default}'";
1721
        }
1722
1723 4
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1724 3
            $this->_logger->notice("Wrong datatype", [
1725 3
                'name' => $field_data->name,
1726 3
                'datatype' => $check->dataType
1727
            ]);
1728 3
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1729 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1730 1
            $this->_logger->notice("Incorrect size", [
1731 1
                'name' => $field_data->name,
1732 1
                'current' => $field_data->length,
1733 1
                'new_size' => $check->length
1734
            ]);
1735 1
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1736
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1737
            $this->_logger->notice("Setting ENUM type", [
1738
                'name' => $field_data->name,
1739
                'values' => implode(",", $check->values)
1740
            ]);
1741
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1742
        }
1743
1744 4
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1745
            foreach ($index as $ind) {
1746
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1747
                    $this->_logger->debug("Missing index", [
1748
                        'name' => $field_data->name
1749
                    ]);
1750
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1751
                }
1752
            }
1753
        }
1754
1755 4
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1756
            $this->_logger->debug("Setting PKs", [
1757
                'keys' => implode(',', $pks)
1758
            ]);
1759
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1760
        }
1761
1762 4
        return $ret;
1763
    }
1764
1765
    /**
1766
     * Function to check for the existence of a table within a schema
1767
     *
1768
     * @param string $strSchema
1769
     *            The schema to search in
1770
     * @param string $strTableName
1771
     *            Table to search for
1772
     *
1773
     * @return int|bool Returns number of tables that match if table is found in that schema, otherwise FALSE
1774
     *
1775
     * @throws InvalidArgumentException
1776
     */
1777 5
    public function tableExists($strSchema, $strTableName)
1778
    {
1779 5
        if (! $this->_c->select_db($strSchema)) {
1780 1
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1781 1
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1782
        }
1783
1784 4
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1785 1
            $this->_logger->warning("Invalid table name {$strTableName}");
1786 1
            return false;
1787
        }
1788
1789 3
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1790
1791 3
        if ($res = $this->_c->query($sql)) {
1792 3
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1793 2
                return $res->num_rows;
1794
            }
1795
        } elseif ($this->_c->errno) {
1796
            $this->_logger->error($this->_c->error);
1797
        }
1798
1799 1
        return false;
1800
    }
1801
1802
    /**
1803
     * Function to detect if string is a JSON object or not
1804
     *
1805
     * @param string $strVal
1806
     *
1807
     * @return bool
1808
     */
1809 1
    public function isJson($strVal): bool
1810
    {
1811 1
        json_decode($strVal);
1812 1
        return (json_last_error() == JSON_ERROR_NONE);
1813
    }
1814
1815
    /**
1816
     * Function to escape SQL characters to prevent SQL injection
1817
     *
1818
     * @param mixed $val
1819
     *            Value to escape
1820
     * @param bool $blnEscape
1821
     *            Decide if we should escape or not
1822
     *
1823
     * @throws Exception
1824
     * @throws InvalidArgumentException
1825
     *
1826
     * @return string Escaped value
1827
     */
1828 131
    public function _escape($val, bool $blnEscape = true): string
1829
    {
1830 131
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1831 1
            return 'NULL';
1832 131
        } elseif (is_numeric($val) || is_string($val)) {
1833 131
            if ($blnEscape) {
1834 131
                return "'{$this->_c->real_escape_string($val)}'";
1835
            }
1836 1
            return $val;
1837 6
        } elseif (is_a($val, 'DateTime')) {
1838 1
            return "'{$val->format(MYSQL_DATETIME)}'";
1839 5
        } elseif (is_bool($val)) {
1840 1
            return $val ? "'1'" : "'0'";
1841 4
        } elseif (is_array($val)) {
1842 1
            $ret = [];
1843 1
            foreach ($val as $v) {
1844 1
                $ret[] = $this->_escape($v);
1845
            }
1846 1
            return "(" . implode(",", $ret) . ")";
1847 3
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1848 2
            $ret = call_user_func([
1849 2
                $val,
1850 2
                '_escape'
1851
            ]);
1852 2
            if ($ret !== false && is_string($ret)) {
1853 1
                return $ret;
1854
            } else {
1855 1
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1856
            }
1857
        }
1858
1859 1
        throw new InvalidArgumentException("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1860
    }
1861
1862
    /**
1863
     * Function to populate the fields for the SQL
1864
     *
1865
     * @param array|string $fields
1866
     *            [optional]
1867
     *            Optional array of fields to string together to create a field list
1868
     *
1869
     * @return string|null
1870
     *
1871
     * @throws InvalidArgumentException
1872
     */
1873 37
    protected function fields($fields = null): string
1874
    {
1875 37
        $ret = null;
1876
1877 37
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1878 3
            foreach ($fields as $field) {
1879 3
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1880 2
                    $ret .= "`$field`,";
1881
                } else {
1882 1
                    $ret .= "$field,";
1883
                }
1884
            }
1885 3
            $ret = substr($ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
0 ignored issues
show
Bug introduced by
It seems like $ret can also be of type null; however, parameter $string of substr() does only seem to accept string, 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

1885
            $ret = substr(/** @scrutinizer ignore-type */ $ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
Loading history...
1886 34
        } elseif (is_string($fields)) {
1887 4
            $ret = $fields;
1888 30
        } elseif (is_null($fields)) {
1889 29
            $ret = "*";
1890
        } else {
1891 1
            throw new InvalidArgumentException("Invalid field type");
1892
        }
1893
1894 36
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
1895
    }
1896
1897
    /**
1898
     * Function to parse the flags
1899
     *
1900
     * @param array $flags
1901
     *            Two-dimensional array to added flags
1902
     *
1903
     *            <code>
1904
     *            [
1905
     *            &nbsp;&nbsp;'group' => 'field',
1906
     *            &nbsp;&nbsp;'having' => 'field',
1907
     *            &nbsp;&nbsp;'order' => 'field',
1908
     *            &nbsp;&nbsp;'start' => 0,
1909
     *            &nbsp;&nbsp;'limit' => 0
1910
     *            ]
1911
     *            </code>
1912
     *
1913
     * @see Database::groups()
1914
     * @see Database::having()
1915
     * @see Database::order()
1916
     *
1917
     * @return string
1918
     */
1919 10
    protected function flags(array $arrFlags)
1920
    {
1921 10
        $ret = '';
1922
1923 10
        if (isset($arrFlags['group'])) {
1924 3
            $ret .= $this->groups($arrFlags['group']);
1925
        }
1926
1927 9
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1928 1
            $having = " HAVING";
1929 1
            $this->_logger->debug("Parsing where clause and adding to query");
1930 1
            foreach ($arrFlags['having'] as $x => $h) {
1931 1
                if ($x > 0) {
1932 1
                    $having .= " {$h->sqlOperator}";
1933
                }
1934 1
                $having .= $h;
1935
            }
1936 1
            if (strlen($having) > strlen(" HAVING")) {
1937 1
                $ret .= $having;
1938
            }
1939
        }
1940
1941 9
        if (isset($arrFlags['order'])) {
1942 3
            $ret .= $this->order($arrFlags['order']);
1943
        }
1944
1945 8
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1946 1
            $ret .= " LIMIT ";
1947 1
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1948 1
                $ret .= "{$arrFlags['start']},";
1949
            }
1950 1
            $ret .= "{$arrFlags['limit']}";
1951
        }
1952
1953 8
        return $ret;
1954
    }
1955
1956
    /**
1957
     * Function to parse SQL GROUP BY statements
1958
     *
1959
     * @param mixed $groups
1960
     *
1961
     * @return string
1962
     *
1963
     * @throws InvalidArgumentException
1964
     */
1965 3
    protected function groups($groups): string
1966
    {
1967 3
        $ret = '';
1968 3
        if (is_array($groups) && count($groups)) {
1969 1
            $ret .= " GROUP BY";
1970
1971 1
            foreach ($groups as $grp) {
1972 1
                $ret .= " $grp";
1973
1974 1
                if ($grp != end($groups)) {
1975 1
                    $ret .= ",";
1976
                }
1977
            }
1978 2
        } elseif (is_string($groups)) {
1979 1
            $ret .= " GROUP BY {$groups}";
1980
        } else {
1981 1
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1982
        }
1983
1984 2
        return $ret;
1985
    }
1986
1987
    /**
1988
     * Function to parse SQL ORDER BY statements
1989
     *
1990
     * @param mixed $order
1991
     *
1992
     * @return string
1993
     *
1994
     * @throws InvalidArgumentException
1995
     */
1996 3
    protected function order($order): string
1997
    {
1998 3
        $ret = '';
1999 3
        if (is_array($order)) {
2000 1
            $ret .= " ORDER BY";
2001
2002 1
            foreach ($order as $ord) {
2003 1
                $ret .= " {$ord['field']} {$ord['sort']}";
2004
2005 1
                if ($ord != end($order)) {
2006 1
                    $ret .= ",";
2007
                }
2008
            }
2009 2
        } elseif (is_string($order)) {
2010 1
            $ret .= " ORDER BY {$order}";
2011
        } else {
2012 1
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
2013
        }
2014
2015 2
        return $ret;
2016
    }
2017
2018
    /**
2019
     * Method to check if there are any invalid characters in the table name
2020
     *
2021
     * @param string $strTableName
2022
     *      Table name passed in
2023
     *
2024
     * @return bool
2025
     *      Returns FALSE if table name contains any characters that will be problematic (0-9, a-z, A-Z, $, _), TRUE otherwise
2026
     */
2027 83
    private function checkTableName(string $strTableName): bool
2028
    {
2029 83
        return !strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName) ? false : true;
2030
    }
2031
2032
    /**
2033
     * Function to see if a constraint exists
2034
     *
2035
     *
2036
     *
2037
     * @param string $strConstraintId
2038
     *
2039
     * @return bool
2040
     */
2041
    public function isConstraint($strConstraintId): bool
2042
    {
2043
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
2044
2045
        if ($res->num_rows) {
2046
            return true;
2047
        }
2048
2049
        return false;
2050
    }
2051
2052
    /**
2053
     * Method to add a where clause
2054
     *
2055
     * @param DBWhere|array:DBWhere $where
2056
     *
2057
     * @return bool|array:DBWhere
2058
     */
2059 51
    public function parseClause($where)
2060
    {
2061 51
        $ret = [];
2062 51
        $interfaces = [];
2063 51
        if (is_object($where)) {
2064 8
            $interfaces = class_implements($where);
2065
        }
2066 51
        if (is_array($where)) {
2067 42
            foreach ($where as $k => $w) {
2068 10
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
2069 1
                    return false;
2070
                }
2071 9
                $v = $this->_escape($w->value, $w->escape);
2072 9
                $where[$k]->value = $v;
2073
2074 9
                $ret[] = $where[$k];
2075
            }
2076 9
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
2077 8
            $v = $this->_escape($where->value, $where->escape);
2078 6
            $where->value = $v;
2079 6
            $ret[] = $where;
2080 2
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable([$where, 'where'])) {
2081 1
            $ret = $this->parseClause($where->where());
2082
        } else {
2083 1
            $this->_logger->warning("Failed to get where", [$where]);
2084
        }
2085
2086 48
        return $ret;
2087
    }
2088
2089
    /**
2090
     * Encryption algorithm
2091
     *
2092
     * @param string $data
2093
     * @param string $salt
2094
     *
2095
     * @return string
2096
     *
2097
     * @throws Exception
2098
     *
2099
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2100
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2101
     */
2102 1
    public static function encrypt(string $data, ?string $salt = null)
2103
    {
2104 1
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2105
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2106
        }
2107
2108
        // Remove the base64 encoding from our key
2109 1
        if (is_null($salt)) {
2110 1
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2111
        } else {
2112
            $encryption_key = base64_decode($salt);
2113
        }
2114
        // Generate an initialization vector
2115 1
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
2116
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
2117 1
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2118
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
2119 1
        return base64_encode($encrypted . '::' . $iv);
2120
    }
2121
2122
    /**
2123
     * Decryption algorithm
2124
     *
2125
     * @param string $data
2126
     *
2127
     * @return string
2128
     *
2129
     * @throws Exception
2130
     *
2131
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2132
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2133
     */
2134 131
    public static function decrypt(string $data)
2135
    {
2136 131
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2137
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2138
        }
2139
2140
        // Remove the base64 encoding from our key
2141 131
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2142
2143
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
2144 131
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
2145 131
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2146 131
        return $plaintext;
2147
    }
2148
2149
    /**
2150
     * Method to check if all required fields are available in the object
2151
     *
2152
     * @param object $object
2153
     * @param array:string $requiredFields
2154
     *
2155
     * @return bool
2156
     */
2157 12
    public static function checkObject($object, $requiredFields): bool
2158
    {
2159 12
        $haystack = array_keys(json_decode(json_encode($object), true));
2160 12
        foreach ($requiredFields as $r) {
2161 12
            if (!in_array($r, $haystack)) {
2162 3
                return false;
2163
            }
2164
        }
2165
2166 9
        return true;
2167
    }
2168
2169
    /**
2170
     * Method to retrieve the error data
2171
     *
2172
     * @return string
2173
     */
2174 1
    public function error(): string
2175
    {
2176 1
        return $this->_c->error;
2177
    }
2178
2179
    /**
2180
     * Method to check the parameter types
2181
     *
2182
     * @param mixed $param
2183
     *
2184
     * @return int
2185
     *
2186
     * @throws MissingInterfaceAndMethods
2187
     * @throws Exception
2188
     */
2189 27
    private function checkParamType($param): int
2190
    {
2191
        // check for implented object interfaces
2192 27
        $interfaces = is_object($param) ? class_implements($param) : [];
2193
2194
        // numeric is the only datatype we can't have
2195 27
        if (is_numeric($param)) {
2196 1
            throw new MissingOrInvalidParam('Numeric parameters are not valid');
2197
        }
2198
2199
        // check for a SELECT statement within an insert
2200 26
        if (is_string($param) && stripos($param, 'select') !== false) {
2201 1
            return self::STRING;
2202
        }
2203
        // param is an object, check to see if it includes the required interface
2204 25
        elseif (is_object($param) && !is_iterable($param)) {
2205 9
            if (!in_array('Godsgood33\Php_Db\DBInterface', $interfaces)) {
2206 4
                throw new MissingInterfaceAndMethods('Object does not implement DBInterface interface');
2207
            }
2208
2209
            // check to see if all required methods are callable
2210 5
            if (!is_callable([$param, 'insert']) ||
2211 5
                !is_callable([$param, 'update']) ||
2212 5
                !is_callable([$param, 'replace']) ||
2213 5
                !is_callable([$param, 'where'])
2214
            ) {
2215
                throw new MissingInterfaceAndMethods("Required DBInterface methods are not present in class ".get_class($param));
2216
            }
2217
2218 5
            return self::OBJECT;
2219
        }
2220
        // param is an array
2221 19
        elseif (is_array($param)) {
2222
            // check that there is actual data in the array
2223 17
            if (!count($param)) {
2224
                throw new Exception('Array param is empty');
2225
            }
2226
2227
            // check the first element of the array and see if it's an object then recurse through to check it
2228 17
            $first = array_shift($param);
2229 17
            if (is_object($first) && $this->checkParamType($first) == self::OBJECT) {
2230 1
                return self::ARRAY_OBJECT;
2231
            }
2232
2233 15
            return self::ARRAY;
2234
        }
2235
        // object is potentially a collection
2236 2
        elseif (is_object($param)) {
2237
            // check that collection has required interface
2238 1
            if (!in_array('IteratorAggregate', $interfaces)) {
2239
                throw new MissingInterfaceAndMethods('Object does not implement IteratorAggregate interface');
2240
            }
2241
2242
            // get the first element in the collection
2243 1
            $it = $param->getIterator();
2244 1
            $first = $it->current();
2245
2246
            // check that the first element of the collection is a valid object as defined above
2247 1
            if ($this->checkParamType($first) == self::OBJECT) {
2248 1
                return self::COLLECTION;
2249
            }
2250
        }
2251
2252 1
        return 0;
2253
    }
2254
}
2255