Failed Conditions
Push — master ( e948fc...00142c )
by Ryan
12:27
created

Database::isConstraint()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 2
eloc 4
c 2
b 2
f 0
nc 2
nop 1
dl 0
loc 9
ccs 1
cts 1
cp 1
crap 2
rs 10
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 130
     * @access private
251
     * @var string
252
     */
253 130
    private $_logPath = null;
254 130
255
    /**
256
     * Variable to store the most recent insert ID from an insert query
257
     *
258
     * @access protected
259 130
     * @var mixed
260
     */
261 130
    protected $_insertId = null;
262
263 130
    /**
264 130
     * Constructor
265
     *
266
     * @param string $strLogPath
267
     *            [optional] absolute log path for the log file
268 130
     * @param mysqli $dbh
269 130
     *            [optional]
270
     *            [by ref]
271
     *            mysqli object to perform queries.
272
     * @param int $intLogLevel
273 130
     *            [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 130
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
281 1
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
282 1
     */
283 130
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
284
    {
285
        // set the log file path
286 130
        $this->_logPath = $strLogPath;
287
        if (!file_exists($this->_logPath)) {
288
            touch($this->_logPath . "/db.log");
289
        }
290
291
        // set the log level
292 130
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
293 130
            $this->_logLevel = Logger::ERROR;
294 130
        } elseif (!is_null($intLogLevel)) {
295
            $this->_logLevel = $intLogLevel;
296
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
297
            $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 130
        // open the log handler
301
        $this->_logger = new Logger('db', [
302
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
303 130
        ]);
304
305
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
306
        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 130
            $this->_logger->pushHandler($stream);
310 130
        }
311 130
312 130
        // check to see if a connection was passed and all defined constants are present to establish a connection
313 130
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
314
            $this->_logger->debug("Connecting through existing connection");
315
            $this->_c = $dbh;
316 130
        } elseif (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
317 130
            $this->_logger->critical("Missing essential defined constants");
318 130
            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
        } 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
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
326
            $this->_logger->debug("Decrypting password");
327 17
            $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 17
            $pwd = PHP_DB_PWD;
330
        }
331 17
332 16
        // open the connection
333
        $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 1
335
        // check for a connection error and throw an error if there is one
336
        if ($this->_c->connect_errno) {
337 16
            $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
        $this->_logger->info("Database connected");
343
        $this->_logger->debug("Connection details:", [
344
            'Server' => PHP_DB_SERVER,
345
            'User'   => PHP_DB_USER,
346
            'Schema' => PHP_DB_SCHEMA
347 1
        ]);
348
349
        $this->setVar("time_zone", "+00:00");
350 1
        $this->setVar("sql_mode", "");
351 1
    }
352 1
353
    /**
354
     * Function to make sure that the database is connected
355
     *
356
     * @return bool
357
     *
358
     * @throws ConnectError
359
     */
360 1
    public function isConnected(): bool
361
    {
362 1
        $this->_logger->info("Checking for live connection");
363
364 1
        if (is_a($this->_c, 'mysqli')) {
365 1
            $ret = $this->_c->ping();
366
        } else {
367
            throw new ConnectError("Connection lost");
368
        }
369
370
        return $ret;
371
    }
372
373 1
    /**
374
     * Setter function for _logger
375 1
     *
376 1
     * @param Logger $log
377
     *
378
     * @return bool
379
     */
380
    public function setLogger(Logger $log): bool
381
    {
382
        // set the logger
383
        $this->_logger->debug("Setting logger");
384
        $this->_logger = $log;
385
        return true;
386
    }
387 100
388
    /**
389 100
     * Getter function for _logLevel
390 100
     *
391
     * @return int
392 100
     */
393
    public function getLogLevel(): int
394
    {
395
        $level = $this->_logLevel;
396
397
        $this->_logger->debug("Getting log level ({$level})");
398
        return $level;
399
    }
400 66
401
    /**
402 66
     * Getter function for _queryType
403 66
     *
404 66
     * @return int
405
     */
406
    public function getQueryType(): int
407
    {
408
        $this->_logger->debug("Getting query type");
409
        return $this->_queryType;
410
    }
411
412
    /**
413 1
     * Setter function for _queryType
414
     *
415 1
     * @param int $qt
416 1
     *      Class constants that store query type
417
     *
418 1
     * @return Database
419 1
     */
420
    private function setQueryType(int $qt): Database
421
    {
422
        $this->_logger->debug("Setting query type");
423
        $this->_queryType = $qt;
424
425
        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
    public function __toString(): string
434 2
    {
435
        $this->_logger->notice("__toString");
436 2
        $this->_logger->debug($this->_sql);
437 2
        return $this->_sql;
438 1
    }
439 1
440
    /**
441 1
     * 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
    public function getSchema()
447
    {
448
        if ($res = $this->_c->query("SELECT DATABASE()")) {
449
            $row = $res->fetch_row();
450
451
            $this->_logger->debug("Getting schema {$row[0]}");
452
            return $row[0];
453
        }
454 130
455
        return false;
456 130
    }
457 1
458 1
    /**
459
     * Function to set schema
460 1
     *
461
     * @param string $strSchema
462
     *
463 130
     * @return bool
464
     *
465 130
     * @throws QueryError
466 130
     */
467 130
    public function setSchema(string $strSchema): bool
468
    {
469 1
        $this->_logger->info("Setting schema to {$strSchema}");
470 1
        if (! $this->_c->select_db($strSchema)) {
471
            $this->_logger->error("Unknown schema {$strSchema}");
472
            throw new QueryError("Unknown database schema $strSchema");
473
        }
474
        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
    public function setVar(string $strName, ?string $strVal): bool
488
    {
489 15
        if (empty($strName)) {
490
            $this->_logger->debug("name is blank", [
491 15
                'name'  => $strName
492 2
            ]);
493
            return false;
494
        }
495 15
496 15
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
497
498 15
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
499
            $this->_logger->info("Var set");
500 15
            return true;
501 15
        } else {
502
            $this->_logger->error("Failed to set variable {$this->_c->error}");
503
            return false;
504 15
        }
505 15
    }
506 15
507
    /**
508 6
     * Function to execute the statement
509 6
     *
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 9
     *            Optional SQL query
516 9
     *
517 1
     * @throws QueryError
518 1
     * @throws ConnectError
519 1
     *
520
     * @return mixed
521
     */
522
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
523 14
    {
524 14
        if (! is_null($strSql)) {
525 1
            $this->_sql = $strSql;
526 1
        }
527
528
        $this->_result = false;
529 15
        $this->_insertId = null;
530
531
        $this->isConnected();
532
533
        $this->_logger->info("Executing {$this->_queryType} query");
534
        $this->_logger->debug($this->_sql);
535
536
        try {
537
            if (in_array($this->_queryType, [
538
                self::SELECT,
539
                self::SELECT_COUNT
540
            ])) {
541 14
                $this->_result = $this->_c->query($this->_sql);
542
                if ($this->_c->error) {
543 14
                    $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 14
                }
547 5
            } else {
548 9
                $this->_result = $this->_c->real_query($this->_sql);
549 2
                if ($this->_c->errno) {
550
                    $this->_logger->error("There was an error {$this->_c->error}");
551 2
                    $this->_logger->debug("Errored on query", [$this->_sql]);
552 2
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
553
                }
554 7
            }
555 1
556
            $this->_logger->debug("Checking for query results");
557
            $this->_result = $this->checkResults($return);
558
        } catch (QueryError $e) {
559
            $this->_logger->error($e);
560 1
        }
561 1
562 1
        return $this->_result;
563 1
    }
564 1
565
    /**
566 1
     * 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
    protected function checkResults(int $returnType)
575 1
    {
576
        $res = null;
577 6
578 6
        // check the sql results and process appropriately
579 1
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
580 5
            $res = $this->_result;
581 1
        } 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
            $res = $this->_c->affected_rows;
583
584 6
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE, Database::EXTENDED_INSERT])) {
585 5
                $this->_insertId = $this->_c->insert_id;
586 3
            }
587 3
        } elseif ($this->_queryType == Database::SELECT_COUNT) {
588 3
            if (! is_a($this->_result, 'mysqli_result')) {
589
                $this->_logger->error("Error with return on query");
590
                return null;
591 2
            }
592
593
            if ($this->_result->num_rows == 1) {
594 1
                $row = $this->_result->fetch_assoc();
595 1
                if (isset($row['count'])) {
596
                    $this->_logger->debug("Returning SELECT_COUNT query", [
597
                        'count' => $row['count']
598
                    ]);
599 13
                    $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 13
                $res = $this->_result->num_rows;
606
            }
607
608
            mysqli_free_result($this->_result);
609
        } else {
610
            $method = "mysqli_fetch_object";
611
            if ($returnType == MYSQLI_ASSOC) {
612
                $method = "mysqli_fetch_assoc";
613
            } elseif ($returnType == MYSQLI_NUM) {
614
                $method = "mysqli_fetch_array";
615
            }
616
617
            if (is_a($this->_result, 'mysqli_result')) {
618 2
                if ($this->_result->num_rows > 1) {
619
                    $res = [];
620 2
                    while ($row = call_user_func($method, $this->_result)) {
621
                        $res[] = $row;
622
                    }
623
                } else {
624
                    $res = call_user_func($method, $this->_result);
625
                }
626
            } else {
627
                $this->_logger->error("Error with return on query");
628
                return null;
629
            }
630
        }
631
632
        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
        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 37
     *            [optional]
647
     *            Optional query to pass in and execute
648 37
     *
649 37
     * @return \mysqli_result|bool
650
     */
651
    public function query(?string $strSql = null)
652 37
    {
653 37
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
654 37
    }
655
656 36
    /**
657
     * A function to build a select query
658
     *
659
     * @param string $strTableName
660 36
     *            The table to query
661 1
     * @param array|string $fields
662 1
     *            [optional]
663
     *            Optional array of fields to return (defaults to '*')
664 1
     * @param array:DBWhere|DBWhere $arrWhere
665
     *            [optional] Where clause data
666 35
     * @param array $arrFlags
667
     *            [optional]
668
     *            Optional 2-dimensional array to allow other flags
669
     *
670 36
     * @see Database::flags()
671
     *
672 34
     * @throws Exception
673 8
     * @throws InvalidArgumentException
674 8
     *
675 8
     * @return mixed
676 8
     *
677 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
678
     */
679 8
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
680
    {
681 8
        $this->_sql = null;
682 8
        $this->setQueryType(self::SELECT);
683
684
        // starting building the query
685
        if ($this->checkTableName($strTableName)) {
686
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
687 34
                'fields' => $this->fields($fields)
688 9
            ]);
689 9
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
690
        }
691
692 32
        // add in any joins
693
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
694
            $this->_logger->debug("Adding joins", [
695
                'joins' => implode(' ', $arrFlags['joins'])
696 32
            ]);
697
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
698
        } else {
699
            $this->_logger->debug("No joins");
700
        }
701
702
        // parse the where clauses
703
        $where = $this->parseClause($arrWhere);
704
705
        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
            $where_str = " WHERE";
707
            $this->_logger->debug("Parsing where clause and adding to query");
708
            foreach ($where as $x => $w) {
709
                if ($x > 0) {
710
                    $where_str .= " {$w->sqlOperator}";
711
                }
712
                $where_str .= $w;
713
            }
714
            if (strlen($where_str) > strlen(" WHERE")) {
715
                $this->_sql .= $where_str;
716
            }
717
        }
718
719 4
        // search for any other flags (order, having, group)
720
        if (is_array($arrFlags) && count($arrFlags)) {
721 4
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
722 4
            $this->_sql .= $this->flags($arrFlags);
723
        }
724
725 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...
726 4
            return $this->execute();
727
        }
728
729
        return $this->_sql;
730 4
    }
731 1
732
    /**
733
     * Function to build a query to check the number of rows in a table
734
     *
735 4
     * @param string $strTableName
736
     *            The table to query
737 4
     * @param array:DBWhere|DBWhere $arrWhere
738 2
     *            [optional]
739 2
     *            Optional 2-dimensional array to build where clause
740 2
     * @param array $arrFlags
741 2
     *            [optional]
742 1
     *            Optional 2-dimensional array to add flags
743
     *
744 2
     * @see Database::flags()
745
     *
746 2
     * @return string|int|NULL
747 2
     *
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 1
    {
754
        $this->_sql = null;
755
        $this->setQueryType(self::SELECT_COUNT);
756 4
757
        // start building query
758
        if ($this->checkTableName($strTableName)) {
759
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
760 4
        }
761
762
        // add in any joins
763
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
764
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
765
        }
766
767
        // parse where clauses
768
        $where = $this->parseClause($arrWhere);
769
770
        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
            $where_str = " WHERE";
772
            $this->_logger->debug("Parsing where clause and adding to query");
773
            foreach ($where as $x => $w) {
774
                if ($x > 0) {
775
                    $where_str .= " {$w->sqlOperator}";
776
                }
777 7
                $where_str .= $w;
778
            }
779 7
            if (strlen($where_str) > strlen(" WHERE")) {
780 7
                $this->_sql .= $where_str;
781
            }
782
        }
783 7
784 7
        // add in additional flags (group, having, order)
785
        if (is_array($arrFlags) && count($arrFlags)) {
786
            $this->_sql .= $this->flags($arrFlags);
787
        }
788 7
789 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...
790 3
            return $this->execute();
791
        }
792 3
793 3
        return $this->_sql;
794 3
    }
795 3
796 4
    /**
797 1
     * Function to build an insert query statement
798 3
     *
799 2
     * @param string $strTableName
800 2
     * @param mixed $arrParams
801 1
     * @param bool $blnToIgnore
802 1
     *
803 1
     * @return string|NULL
804
     *
805 1
     * @throws InvalidArgumentException
806
     * @throws MissingInterfaceAndMethods
807
     *
808 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
809
     */
810
    public function insert(string $strTableName, $params, bool $blnToIgnore = false)
811 5
    {
812
        $this->_sql = null;
813
        $this->setQueryType(self::INSERT);
814
815 5
        // start building query
816
        if ($this->checkTableName($strTableName)) {
817
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
818
        }
819
820
        $paramType = $this->checkParamType($params);
821
822
        // add in field parameters and values
823
        if ($paramType == self::ARRAY) {
824
            $keys = array_keys($params);
825
            $vals = array_values($params);
826
            $this->_sql .= " (`" . implode("`,`", $keys) . "`)";
827
            $this->_sql .= " VALUES (" . implode(",", array_map([
828
                $this,
829
                '_escape'
830
            ], $vals)) . ")";
831
        } elseif ($paramType == self::STRING) {
832
            $this->_sql .= " {$params}";
833
        } elseif ($paramType == self::OBJECT) {
834
            $arr = $params->insert();
835
            $keys = array_keys($arr);
836
            $vals = array_values($arr);
837
            $this->_sql .= " (`" . implode("`,`", $keys) . "`) VALUES ";
838
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], $vals)) . ")";
839 7
        } else {
840
            throw new InvalidArgumentException("Invalid type passed to insert " . gettype($params));
841 7
        }
842 7
843
        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 7
        }
846 7
847
        return $this->_sql;
848
    }
849 7
850 6
    /**
851 6
     * Function to create an extended insert query statement
852 4
     *
853 4
     * @param string $strTableName
854 2
     *            The table name that the data is going to be inserted on
855 2
     * @param array $arrFields
856 2
     *            An array of field names that each value represents
857
     * @param mixed $params
858 2
     *            An array of array of values or a string with a SELECT statement to populate the insert with
859
     * @param bool $blnToIgnore
860 3
     *            [optional]
861
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
862 3
     *
863 3
     * @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 2
     * @throws MissingOrInvalidParam
867 2
     * @throws MissingInterfaceAndMethods
868 2
     *
869 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
870
     */
871 1
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
872 1
    {
873
        $this->_sql = null;
874
        $this->setQueryType(self::EXTENDED_INSERT);
875 1
876 1
        // start building query
877
        if ($this->checkTableName($strTableName)) {
878 1
            $this->_sql = "INSERT ".
879
                ($blnToIgnore ? "IGNORE " : "").
880
                "INTO $strTableName ".
881
                "(`".implode("`,`", $arrFields)."`)";
882
        }
883 1
884
        $paramType = $this->checkParamType($params);
885
        $this->_sql .= " VALUES ";
886 3
887
        if ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
888
            foreach ($params as $p) {
889
                $key_value = $p->insert();
890 3
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
891
            }
892
893
            $this->_sql = substr($this->_sql, 0, -1);
894
        } elseif ($paramType == self::OBJECT) {
895
            $key_value = $params->insert();
896
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
897
        } elseif ($paramType == self::ARRAY) {
898
            foreach ($params as $p) {
899
                if (count($p) != count($arrFields)) {
900
                    $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
901
                        $p,
902
                        debug_backtrace()
903
                    ]);
904
                    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
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
907
908
                if ($p != end($params)) {
909
                    $this->_sql .= ",";
910
                }
911
            }
912
        } else {
913
            throw new InvalidArgumentException("Invalid param type ".gettype($params));
914
        }
915
916
        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 9
            return $this->execute();
918
        }
919 9
920 9
        return $this->_sql;
921
    }
922 9
923 9
    /**
924
     * Build a statement to update a table
925 9
     *
926 1
     * @param string $strTableName
927 1
     *            The table name to update
928
     * @param mixed $arrParams
929
     *            Name/value pairs of the field name and value
930 9
     * @param array:DBWhere|DBWhere $arrWhere
931
     *            [optional]
932
     *            DBWhere clauses
933 9
     * @param array $arrFlags
934 6
     *            [optional]
935 6
     *            Two-dimensional array to create other flag options (joins, order, and group)
936 6
     *
937 6
     * @see Database::flags()
938 5
     *
939
     * @return NULL|string
940
     *
941 6
     * @throws InvalidArgumentException
942 5
     * @throws MissingOrInvalidParam
943
     * @throws MissingInterfaceAndMethods
944 1
     *
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 6
    public function update(string $strTableName, $params, $arrWhere = [], ?array $arrFlags = [])
948 1
    {
949
        $this->_sql = "UPDATE ";
950
        $this->setQueryType(self::UPDATE);
951 3
952 2
        if ($this->checkTableName($strTableName)) {
953 2
            $this->_sql .= $strTableName;
954 1
955 1
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
956 1
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
957 1
                unset($arrFlags['joins']);
958 1
            }
959 1
960
            $this->_sql .= " SET ";
961 1
        }
962
963
        $paramType = $this->checkParamType($params);
964 1
965
        if ($paramType == self::ARRAY) {
966
            $keys = array_keys($params);
967 1
            foreach ($params as $f => $p) {
968
                $field = $f;
969
                if ((strpos($f, "`") === false) &&
970 7
                    (strpos($f, ".") === false) &&
971
                    (strpos($f, "*") === false) &&
972 7
                    (stripos($f, " as ") === false)
973 3
                ) {
974 3
                    $field = "`{$f}`";
975 3
                }
976 3
977 1
                if (! is_null($p)) {
978
                    $this->_sql .= "$field={$this->_escape($p)}";
979 3
                } else {
980
                    $this->_sql .= "$field=NULL";
981 3
                }
982 3
983
                if ($f != end($keys)) {
984
                    $this->_sql .= ",";
985
                }
986 7
            }
987
        } elseif ($paramType == self::OBJECT) {
988
            $key_value = $params->update();
989
            $fields = array_keys($key_value);
990 7
            $values = array_map([$this, '_escape'], array_values($key_value));
991
            foreach ($fields as $x => $f) {
992
                if ($x > 0) {
993
                    $this->_sql .= ",";
994 7
                }
995
                $this->_sql .= "`{$f}`={$values[$x]}";
996
            }
997
        } else {
998
            throw new MissingOrInvalidParam("No fields to update");
999
        }
1000
1001
        $where = $this->parseClause($arrWhere);
1002
1003
        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
            $where_str = " WHERE";
1005
            $this->_logger->debug("Parsing where clause and adding to query");
1006
            foreach ($where as $x => $w) {
1007
                if ($x > 0) {
1008
                    $where_str .= " {$w->sqlOperator}";
1009
                }
1010
                $where_str .= $w;
1011
            }
1012
            if (strlen($where_str) > strlen(" WHERE")) {
1013
                $this->_sql .= $where_str;
1014
            }
1015
        }
1016 2
1017
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
1018 2
            $this->_sql .= $this->flags($arrFlags);
1019 2
        }
1020
1021 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...
1022 2
            return $this->execute();
1023
        }
1024
1025 2
        return $this->_sql;
1026 2
    }
1027 2
1028 2
    /**
1029
     * Function to offer an extended updated functionality by using two different tables.
1030
     *
1031 2
     * @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 2
     *            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 2
     *
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
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, array $arrParams)
1048
    {
1049
        $this->_sql = "UPDATE ";
1050
        $this->setQueryType(self::EXTENDED_UPDATE);
1051
1052
        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
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
1054
        }
1055
1056
        if (is_array($arrParams) && count($arrParams)) {
1057 2
            foreach ($arrParams as $param) {
1058
                if ($param != $strLinkField) {
1059 2
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1060 2
                }
1061
            }
1062 2
            $this->_sql = substr($this->_sql, 0, - 1);
1063 2
        } elseif (is_string($arrParams)) {
1064
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1065
        } else {
1066 2
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
1067 1
        }
1068 1
1069
        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 1
            return $this->execute();
1071 1
        }
1072 1
1073 1
        return $this->_sql;
1074 1
    }
1075 1
1076 1
    /**
1077 1
     * Function to build a replace query
1078 1
     *
1079 1
     * @param string $strTableName
1080 1
     *            The table to update
1081
     * @param mixed $arrParams
1082
     *            Name/value pair to insert
1083
     *
1084 2
     * @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
        $this->_sql = null;
1091
        $this->setQueryType(self::REPLACE);
1092
1093
        if ($this->checkTableName($strTableName)) {
1094
            $this->_sql = "REPLACE INTO $strTableName ";
1095
        }
1096
1097
        $paramType = $this->checkParamType($params);
1098
1099
        if ($paramType == self::ARRAY) {
1100
            $keys = array_keys($params);
1101
            $vals = array_values($params);
1102
1103
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1104
            $this->_sql .= " VALUES (" . implode(",", array_map([
1105
                $this,
1106
                '_escape'
1107 1
            ], array_values($vals))) . ")";
1108
        } elseif ($paramType == self::OBJECT) {
1109 1
            $key_value = $params->replace();
1110 1
            $this->_sql .= "(`" . implode("`,`", array_keys($key_value)) . "`) VALUES ";
1111
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . ")";
1112 1
        } else {
1113
            throw new MissingOrInvalidParam('Arrays or Objects that implement DBInterface are the only valid types for replace');
1114
        }
1115
1116 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...
1117 1
            return $this->execute();
1118
        }
1119
1120 1
        return $this->_sql;
1121 1
    }
1122 1
1123 1
    /**
1124 1
     * Function to build an extended replace statement
1125 1
     *
1126 1
     * @param string $strTableName
1127
     *            Table name to update
1128 1
     * @param array $arrFields
1129 1
     *            Array of fields
1130
     * @param mixed $arrParams
1131
     *            Two-dimensional array of values
1132
     *
1133
     * @return NULL|string
1134 1
     *
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 1
     */
1139
    public function extendedReplace(string $strTableName, array $arrFields, $params)
1140
    {
1141
        $this->_sql = null;
1142
        $this->setQueryType(self::EXTENDED_REPLACE);
1143
1144
        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
        if ($this->checkTableName($strTableName)) {
1149
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1150
        }
1151
1152
        $paramType = $this->checkParamType($params);
1153
        $this->_sql .= " VALUES ";
1154
1155
        if ($paramType == self::ARRAY) {
1156
            foreach ($params as $p) {
1157
                $this->_sql .= "(" . implode(",", array_map([
1158
                    $this,
1159
                    '_escape'
1160
                ], array_values($p))) . ")";
1161
1162
                if ($p != end($params)) {
1163
                    $this->_sql .= ",";
1164 4
                }
1165
            }
1166 4
        } elseif ($paramType == self::COLLECTION || $paramType == self::ARRAY_OBJECT) {
1167 4
            foreach ($params as $p) {
1168
                $key_value = $p->replace();
1169 4
                $this->_sql .= "(" . implode(",", array_map([
1170
                    $this,
1171 4
                    '_escape'
1172 1
                ], array_values($key_value))) . ")";
1173
1174
                if ($p != end($params)) {
1175 4
                    $this->_sql .= ",";
1176 4
                }
1177
            }
1178
        }
1179 4
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 4
1184
        return $this->_sql;
1185 4
    }
1186 2
1187 2
    /**
1188 2
     * Function to build a delete statement
1189 2
     *
1190 1
     * @param string $strTableName
1191
     *            Table name to act on
1192 2
     * @param array $arrFields
1193
     *            [optional]
1194 2
     *            Optional list of fields to delete (used when including multiple tables)
1195 2
     * @param array:DBWhere|DBWhere $arrWhere
1196
     *            [optional]
1197
     *            Optional where clauses to use
1198
     * @param array $arrJoins
1199 4
     *            [optional]
1200
     *            Optional 2-dimensional array to add other flags
1201
     *
1202
     * @see Database::flags()
1203 4
     *
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
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1211
    {
1212
        $this->_sql = "DELETE";
1213
        $this->setQueryType(self::DELETE);
1214
1215
        $this->_logger->debug("Deleting table data");
1216
1217
        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
            $this->_sql .= " " . implode(",", $arrFields);
1219
        }
1220
1221
        if ($this->checkTableName($strTableName)) {
1222
            $this->_sql .= " FROM $strTableName";
1223
        }
1224 5
1225
        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 5
            $this->_sql .= " " . implode(" ", $arrJoins);
1227 5
        }
1228
1229 5
        $where = $this->parseClause($arrWhere);
1230 5
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 4
            $where_str = " WHERE";
1233 1
            $this->_logger->debug("Parsing where clause and adding to query");
1234 1
            foreach ($where as $x => $w) {
1235 1
                if ($x > 0) {
1236
                    $where_str .= " {$w->sqlOperator}";
1237
                }
1238
                $where_str .= $w;
1239
            }
1240 5
            if (strlen($where_str) > strlen(" WHERE")) {
1241 5
                $this->_sql .= $where_str;
1242
            }
1243
        }
1244 5
1245
        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 5
1249
        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 1
     *
1264
     * @return string|NULL
1265 1
     *
1266 1
     * @throws InvalidArgumentException
1267
     *
1268 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1269 1
     */
1270
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1271
    {
1272 1
        $this->_sql = null;
1273
        $this->setQueryType(self::DROP);
1274
1275
        switch ($strType) {
1276 1
            case 'table':
1277
                $strType = 'TABLE';
1278
                break;
1279
            case 'view':
1280
                $strType = 'VIEW';
1281
                break;
1282
            default:
1283
                throw new InvalidArgumentException("Invalid type " . gettype($strType), E_ERROR);
1284
        }
1285
1286
        if ($this->checkTableName($strTableName)) {
1287
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1288
        }
1289
1290
        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
        return $this->_sql;
1295
    }
1296
1297 4
    /**
1298
     * Function to build a truncate table statement (automatically executes)
1299 4
     *
1300
     * @param string $strTableName
1301 4
     *            Table to truncate
1302 1
     *
1303 3
     * @return string|NULL
1304 1
     *
1305 2
     * @throws InvalidArgumentException
1306 2
     *
1307
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1308 2
     */
1309 2
    public function truncate(string $strTableName)
1310 2
    {
1311 2
        $this->_sql = null;
1312
        $this->setQueryType(self::TRUNCATE);
1313
1314
        if ($this->checkTableName($strTableName)) {
1315
            $this->_sql = "TRUNCATE TABLE $strTableName";
1316
        }
1317
1318
        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 2
            return $this->execute();
1320
        }
1321
1322 4
        return $this->_sql;
1323
    }
1324
1325
    /**
1326 4
     * 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 3
     * @return NULL|string
1340
     *
1341 3
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1342 3
     */
1343
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1344 3
    {
1345
        $this->setQueryType(self::CREATE_TABLE);
1346
1347
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1348
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1349
        } elseif ($this->checkTableName($strTableName) && is_string($strSelect)) {
1350
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1351 3
        } elseif ($this->checkTableName($strTableName) && is_array($strSelect)) {
1352 3
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1353 3
1354
            foreach ($strSelect as $field) {
1355 3
                $default = null;
1356 1
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1357
                    $this->_sql .= (string) $field . ",";
1358
                } elseif (is_array($field)) {
1359 3
                    if (isset($field['default'])) {
1360 3
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1361
                    }
1362
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1363 3
                }
1364 3
            }
1365 2
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1366 2
        }
1367 1
1368 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...
1369 1
            return $this->execute();
1370
        }
1371
1372
        return $this->_sql;
1373 3
    }
1374 2
1375
    /**
1376
     * Function to create a table using a stdClass object derived from JSON
1377
     *
1378 3
     * @param stdClass $json
1379 1
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1380 1
     *
1381 1
     * @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
    public function createTableJson($json, bool $blnDropFirst = false)
1386
    {
1387 1
        $this->setQueryType(self::CREATE_TABLE);
1388 1
        $this->_c->select_db($json->schema);
1389
1390 1
        if ($blnDropFirst) {
1391 1
            $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 3
1397
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1398
        foreach ($json->fields as $field) {
1399
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1400
1401
            if ($field->dataType == 'enum' && isset($field->values)) {
1402 3
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1403 1
            }
1404
1405
            if (isset($field->ai) && $field->ai) {
1406 3
                $this->_sql .= " AUTO_INCREMENT";
1407 3
            }
1408
1409
            if (isset($field->nn) && $field->nn) {
1410
                $this->_sql .= " NOT NULL";
1411
            } elseif (isset($field->default)) {
1412
                if (strtolower($field->default) == 'null') {
1413
                    $this->_sql .= " DEFAULT NULL";
1414
                } elseif (strlen($field->default)) {
1415
                    $this->_sql .= " DEFAULT '{$field->default}'";
1416 3
                }
1417
            }
1418
1419
            if ($field != end($json->fields)) {
1420 3
                $this->_sql .= ",";
1421
            }
1422
        }
1423
1424
        if (isset($json->index) && count($json->index)) {
1425
            foreach ($json->index as $ind) {
1426
                $ref = null;
1427
                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
                } elseif (is_string($ind->ref)) {
1434
                    $ref = $ind->ref;
1435 3
                }
1436
                if (!is_null($ref)) {
1437 3
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1438 3
                }
1439
            }
1440 3
        }
1441
1442 3
        if (isset($json->constraints) && count($json->constraints)) {
1443 1
            foreach ($json->constraints as $con) {
1444 1
                $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 2
1448 2
        if (isset($json->unique) && count($json->unique)) {
1449 2
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1450 1
        }
1451 1
1452 1
        if (isset($json->primary_key) && count($json->primary_key)) {
1453
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1454 2
        } else {
1455
            if (substr($this->_sql, - 1) == ',') {
1456 2
                $this->_sql = substr($this->_sql, 0, - 1);
1457
            }
1458
1459
            $this->_sql .= ")";
1460 2
        }
1461
1462
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1463
            return $this->execute();
1464
        }
1465
1466
        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 3
     * @param stdClass $params
1474
     *
1475 3
     * @return string|mixed
1476 3
     *
1477
     * @throws InvalidArgumentException
1478 3
     *
1479 2
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1480 2
     */
1481
    public function addColumn(string $strTableName, stdClass $params)
1482 2
    {
1483 1
        $this->setQueryType(self::ALTER_TABLE);
1484
        $this->checkTableName($strTableName);
1485
1486 1
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1487 1
1488
        if (!self::checkObject($params, ['name', 'dataType'])) {
1489
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1490 3
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1491
        }
1492
1493
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1494 3
        $default = null;
1495
        if ($params->default === null) {
1496
            $default = " DEFAULT NULL";
1497
        } elseif (strlen($params->default)) {
1498
            $default = " DEFAULT {$this->_escape($params->default)}";
1499
        }
1500
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1501
1502
        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
        return $this->_sql;
1507
    }
1508
1509 3
    /**
1510
     * Method to drop a fields from a table
1511 3
     *
1512 3
     * @param string $strTableName
1513
     * @param string|array:string $params
1514 3
     *
1515 1
     * @return string|mixed
1516 1
     *
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 2
    public function dropColumn(string $strTableName, $params)
1520 1
    {
1521
        $this->setQueryType(self::ALTER_TABLE);
1522
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1523 2
1524 2
        if (is_array($params) && count($params)) {
1525 2
            foreach ($params as $col) {
1526 1
                $this->_sql .= " `{$col->name}`";
1527 1
1528 1
                if ($col != end($params)) {
1529
                    $this->_sql .= ",";
1530 2
                }
1531
            }
1532 2
        } elseif (is_string($params)) {
1533
            $this->_sql .= " `{$params}`";
1534
        }
1535
1536 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...
1537
            return $this->execute();
1538
        }
1539
1540
        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 7
     * @throws InvalidArgumentException
1552
     *
1553 7
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1554 7
     */
1555
    public function modifyColumn($strTableName, $params)
1556 7
    {
1557 1
        $this->setQueryType(self::ALTER_TABLE);
1558 1
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1559
1560
        if (!self::checkObject($params, ['name', 'dataType'])) {
1561 6
            $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 1
        }
1564
1565
        if (!isset($params->new_name)) {
1566 5
            $params->new_name = $params->name;
1567 1
        }
1568 1
1569
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1570
        $default = null;
1571 4
        if ($params->default === null) {
1572 1
            $default = " DEFAULT NULL";
1573 1
        } elseif (strlen($params->default)) {
1574
            $default = " DEFAULT {$this->_escape($params->default)}";
1575
        }
1576 3
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1577 1
1578 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...
1579 2
            return $this->execute();
1580 1
        }
1581 1
1582
        return $this->_sql;
1583 1
    }
1584
1585 2
    /**
1586
     * Method to add a constraint to a table
1587 2
     *
1588
     * @param string $strTableName
1589
     * @param stdClass $params
1590
     *
1591 2
     * @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
    public function addConstraint($strTableName, $params)
1598
    {
1599
        $this->setQueryType(self::ALTER_TABLE);
1600
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1601
1602
        if (!is_a($params, 'stdClass')) {
1603
            $this->_logger->critical("Error in reading constraint field");
1604 2
            throw new InvalidArgumentException("Error in reading constraint field");
1605
        }
1606 2
1607
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1608 2
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1609 2
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1610 2
        }
1611 1
1612
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1613
            $this->_logger->error("Invalid action for deletion on addConstraint");
1614
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1615
        }
1616 1
1617
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1618
            $this->_logger->error("Invalid action for update on addConstraint");
1619
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1620
        }
1621
1622
        if (is_array($params->field) && is_array($params->local)) {
1623
            $field = "`" . implode("`,`", $params->field) . "`";
1624
            $local = "`" . implode("`,`", $params->local) . "`";
1625
        } elseif (is_string($params->field) && is_string($params->local)) {
1626
            $field = "`{$params->field}`";
1627
            $local = "`{$params->local}`";
1628
        } else {
1629
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1630 7
        }
1631
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1632 7
1633 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...
1634 5
            return $this->execute();
1635 5
        }
1636 1
1637 1
        return $this->_sql;
1638
    }
1639 1
1640
    /**
1641
     * Check to see if a field in a table exists
1642 7
     *
1643 7
     * @param string $strTableName
1644 7
     *            Table to check
1645 7
     * @param string $strFieldName
1646 7
     *            Field name to find
1647 7
     *
1648
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1649
     */
1650
    public function fieldExists(string $strTableName, string $strFieldName): bool
1651 7
    {
1652
        $fdata = $this->fieldData($strTableName);
1653
1654
        if (is_array($fdata) && count($fdata)) {
1655
            foreach ($fdata as $field) {
1656
                if ($field->name == $strFieldName) {
1657
                    return true;
1658
                }
1659
            }
1660
        }
1661
1662
        return false;
1663
    }
1664 4
1665
    /**
1666 4
     * Function to get the column data (datatype, flags, defaults, etc)
1667 4
     *
1668
     * @param string $strTableName
1669 4
     *            Table to query
1670
     * @param mixed $field
1671 4
     *            [optional]
1672 1
     *            Optional field to retrieve data (if null, returns data from all fields)
1673 3
     *
1674 1
     * @return mixed
1675
     */
1676
    public function fieldData(string $strTableName, $field = null)
1677 4
    {
1678 3
        if (is_null($field)) {
1679 3
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1680 3
        } elseif (is_array($field)) {
1681
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1682 3
        } elseif (is_string($field)) {
1683 1
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1684 1
        } else {
1685 1
            return null;
1686 1
        }
1687 1
1688
        $fields = null;
1689 1
        if (is_a($res, 'mysqli_result')) {
1690
            $fields = $res->fetch_fields();
1691
            foreach ($fields as $i => $f) {
1692
                $fields["{$f->name}"] = $f;
1693
                unset($fields[$i]);
1694
            }
1695
        }
1696
1697
        return $fields;
1698 4
    }
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 4
     */
1710
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1711
    {
1712
        $default = null;
1713
        $ret = null;
1714
1715
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1716 4
1717
        if ($check->default === null) {
1718
            $default = " DEFAULT NULL";
1719
        } elseif (strlen($check->default)) {
1720
            $default = " DEFAULT '{$check->default}'";
1721
        }
1722
1723
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1724
            $this->_logger->notice("Wrong datatype", [
1725
                'name' => $field_data->name,
1726
                'datatype' => $check->dataType
1727
            ]);
1728
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1729
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1730
            $this->_logger->notice("Incorrect size", [
1731 5
                'name' => $field_data->name,
1732
                'current' => $field_data->length,
1733 5
                'new_size' => $check->length
1734 1
            ]);
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 4
                'name' => $field_data->name,
1739 1
                'values' => implode(",", $check->values)
1740 1
            ]);
1741
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1742
        }
1743 3
1744
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1745 3
            foreach ($index as $ind) {
1746 3
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1747 2
                    $this->_logger->debug("Missing index", [
1748
                        'name' => $field_data->name
1749
                    ]);
1750
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1751
                }
1752
            }
1753 1
        }
1754
1755
        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
        return $ret;
1763 1
    }
1764
1765 1
    /**
1766 1
     * 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
    public function tableExists($strSchema, $strTableName)
1778
    {
1779
        if (! $this->_c->select_db($strSchema)) {
1780
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1781
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1782 130
        }
1783
1784 130
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1785 1
            $this->_logger->warning("Invalid table name {$strTableName}");
1786 130
            return false;
1787 130
        }
1788 130
1789
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1790 1
1791 6
        if ($res = $this->_c->query($sql)) {
1792 1
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1793 5
                return $res->num_rows;
1794 1
            }
1795 4
        } elseif ($this->_c->errno) {
1796 1
            $this->_logger->error($this->_c->error);
1797 1
        }
1798 1
1799
        return false;
1800 1
    }
1801 3
1802 2
    /**
1803 2
     * Function to detect if string is a JSON object or not
1804 2
     *
1805
     * @param string $strVal
1806 2
     *
1807 1
     * @return bool
1808
     */
1809 1
    public function isJson($strVal): bool
1810
    {
1811
        json_decode($strVal);
1812
        return (json_last_error() == JSON_ERROR_NONE);
1813 1
    }
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 37
     */
1828
    public function _escape($val, bool $blnEscape = true): string
1829 37
    {
1830
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1831 37
            return 'NULL';
1832 3
        } elseif (is_numeric($val) || is_string($val)) {
1833 3
            if ($blnEscape) {
1834 2
                return "'{$this->_c->real_escape_string($val)}'";
1835
            }
1836 1
            return $val;
1837
        } elseif (is_a($val, 'DateTime')) {
1838
            return "'{$val->format(MYSQL_DATETIME)}'";
1839 3
        } elseif (is_bool($val)) {
1840 34
            return $val ? "'1'" : "'0'";
1841 4
        } elseif (is_array($val)) {
1842 30
            $ret = [];
1843 29
            foreach ($val as $v) {
1844
                $ret[] = $this->_escape($v);
1845 1
            }
1846
            return "(" . implode(",", $ret) . ")";
1847
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1848 36
            $ret = call_user_func([
1849
                $val,
1850
                '_escape'
1851
            ]);
1852
            if ($ret !== false && is_string($ret)) {
1853
                return $ret;
1854
            } else {
1855
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1856
            }
1857
        }
1858
1859
        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 10
    protected function fields($fields = null): string
1874
    {
1875 10
        $ret = null;
1876
1877 10
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1878 3
            foreach ($fields as $field) {
1879
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1880
                    $ret .= "`$field`,";
1881 9
                } else {
1882 1
                    $ret .= "$field,";
1883 1
                }
1884 1
            }
1885 1
            $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 1
        } elseif (is_string($fields)) {
1887
            $ret = $fields;
1888 1
        } elseif (is_null($fields)) {
1889
            $ret = "*";
1890 1
        } else {
1891 1
            throw new InvalidArgumentException("Invalid field type");
1892
        }
1893
1894
        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 9
    }
1896 3
1897
    /**
1898
     * Function to parse the flags
1899 8
     *
1900 1
     * @param array $flags
1901 1
     *            Two-dimensional array to added flags
1902 1
     *
1903
     *            <code>
1904 1
     *            [
1905
     *            &nbsp;&nbsp;'group' => 'field',
1906
     *            &nbsp;&nbsp;'having' => 'field',
1907 8
     *            &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 3
    protected function flags(array $arrFlags)
1920
    {
1921 3
        $ret = '';
1922 3
1923 1
        if (isset($arrFlags['group'])) {
1924
            $ret .= $this->groups($arrFlags['group']);
1925 1
        }
1926 1
1927
        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
            foreach ($arrFlags['having'] as $x => $h) {
1931
                if ($x > 0) {
1932 2
                    $having .= " {$h->sqlOperator}";
1933 1
                }
1934
                $having .= $h;
1935 1
            }
1936
            if (strlen($having) > strlen(" HAVING")) {
1937
                $ret .= $having;
1938 2
            }
1939
        }
1940
1941
        if (isset($arrFlags['order'])) {
1942
            $ret .= $this->order($arrFlags['order']);
1943
        }
1944
1945
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1946
            $ret .= " LIMIT ";
1947
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1948
                $ret .= "{$arrFlags['start']},";
1949
            }
1950 3
            $ret .= "{$arrFlags['limit']}";
1951
        }
1952 3
1953 3
        return $ret;
1954 1
    }
1955
1956 1
    /**
1957 1
     * Function to parse SQL GROUP BY statements
1958
     *
1959 1
     * @param mixed $groups
1960 1
     *
1961
     * @return string
1962
     *
1963 2
     * @throws InvalidArgumentException
1964 1
     */
1965
    protected function groups($groups): string
1966 1
    {
1967
        $ret = '';
1968
        if (is_array($groups) && count($groups)) {
1969 2
            $ret .= " GROUP BY";
1970
1971
            foreach ($groups as $grp) {
1972
                $ret .= " $grp";
1973
1974
                if ($grp != end($groups)) {
1975
                    $ret .= ",";
1976
                }
1977
            }
1978
        } elseif (is_string($groups)) {
1979
            $ret .= " GROUP BY {$groups}";
1980
        } else {
1981 82
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1982
        }
1983 82
1984
        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
    protected function order($order): string
1997
    {
1998
        $ret = '';
1999
        if (is_array($order)) {
2000
            $ret .= " ORDER BY";
2001
2002
            foreach ($order as $ord) {
2003
                $ret .= " {$ord['field']} {$ord['sort']}";
2004
2005
                if ($ord != end($order)) {
2006
                    $ret .= ",";
2007
                }
2008
            }
2009
        } elseif (is_string($order)) {
2010
            $ret .= " ORDER BY {$order}";
2011
        } else {
2012
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
2013 51
        }
2014
2015 51
        return $ret;
2016 51
    }
2017 51
2018 8
    /**
2019
     * Method to check if there are any invalid characters in the table name
2020 51
     *
2021 42
     * @param string $strTableName
2022 10
     *      Table name passed in
2023 1
     *
2024
     * @return bool
2025 9
     *      Returns FALSE if table name contains any characters that will be problematic (0-9, a-z, A-Z, $, _), TRUE otherwise
2026 9
     */
2027
    private function checkTableName(string $strTableName): bool
2028 9
    {
2029
        return !strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName) ? false : true;
2030 9
    }
2031 8
2032 6
    /**
2033 6
     * Function to see if a constraint exists
2034 2
     *
2035 1
     *
2036 1
     *
2037
     * @param string $strConstraintId
2038 1
     *
2039
     * @return bool
2040
     */
2041 48
    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 1
     * @return bool|array:DBWhere
2058
     */
2059 1
    public function parseClause($where)
2060
    {
2061
        $ret = [];
2062
        $interfaces = [];
2063
        if (is_object($where)) {
2064 1
            $interfaces = class_implements($where);
2065 1
        }
2066
        if (is_array($where)) {
2067
            foreach ($where as $k => $w) {
2068
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
2069
                    return false;
2070 1
                }
2071
                $v = $this->_escape($w->value, $w->escape);
2072 1
                $where[$k]->value = $v;
2073
2074 1
                $ret[] = $where[$k];
2075
            }
2076
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
2077
            $v = $this->_escape($where->value, $where->escape);
2078
            $where->value = $v;
2079
            $ret[] = $where;
2080
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable([$where, 'where'])) {
2081
            $ret = $this->parseClause($where->where());
2082
        } else {
2083
            $this->_logger->warning("Failed to get where", [$where]);
2084
        }
2085
2086
        return $ret;
2087
    }
2088
2089 130
    /**
2090
     * Encryption algorithm
2091 130
     *
2092
     * @param string $data
2093
     * @param string $salt
2094
     *
2095
     * @return string
2096 130
     *
2097
     * @throws Exception
2098
     *
2099 130
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2100 130
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2101 130
     */
2102
    public static function encrypt(string $data, ?string $salt = null)
2103
    {
2104
        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
        if (is_null($salt)) {
2110
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2111
        } else {
2112 12
            $encryption_key = base64_decode($salt);
2113
        }
2114 12
        // Generate an initialization vector
2115 12
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
2116 12
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
2117 3
        $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
        return base64_encode($encrypted . '::' . $iv);
2120
    }
2121 9
2122
    /**
2123
     * Decryption algorithm
2124
     *
2125
     * @param string $data
2126
     *
2127
     * @return string
2128
     *
2129 1
     * @throws Exception
2130
     *
2131 1
     * @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
    public static function decrypt(string $data)
2135
    {
2136
        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
        $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
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
2145
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2146
        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
    public static function checkObject($object, $requiredFields): bool
2158
    {
2159
        $haystack = array_keys(json_decode(json_encode($object), true));
2160
        foreach ($requiredFields as $r) {
2161
            if (!in_array($r, $haystack)) {
2162
                return false;
2163
            }
2164
        }
2165
2166
        return true;
2167
    }
2168
2169
    /**
2170
     * Method to retrieve the error data
2171
     *
2172
     * @return string
2173
     */
2174
    public function error(): string
2175
    {
2176
        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
    private function checkParamType($param): int
2190
    {
2191
        // check for implented object interfaces
2192
        $interfaces = is_object($param) ? class_implements($param) : [];
2193
2194
        // numeric is the only datatype we can't have
2195
        if (is_numeric($param)) {
2196
            throw new MissingOrInvalidParam('Numeric parameters are not valid');
2197
        }
2198
2199
        // check for a SELECT statement within an insert
2200
        if (is_string($param) && stripos($param, 'select') !== false) {
2201
            return self::STRING;
2202
        }
2203
        // param is an object, check to see if it includes the required interface
2204
        elseif (is_object($param) && !is_iterable($param)) {
2205
            if (!in_array('Godsgood33\Php_Db\DBInterface', $interfaces)) {
2206
                throw new MissingInterfaceAndMethods('Object does not implement DBInterface interface');
2207
            }
2208
2209
            // check to see if all required methods are callable
2210
            if (!is_callable([$param, 'insert']) ||
2211
                !is_callable([$param, 'update']) ||
2212
                !is_callable([$param, 'replace']) ||
2213
                !is_callable([$param, 'where'])
2214
            ) {
2215
                throw new MissingInterfaceAndMethods("Required DBInterface methods are not present in class ".get_class($param));
2216
            }
2217
2218
            return self::OBJECT;
2219
        }
2220
        // param is an array
2221
        elseif (is_array($param)) {
2222
            // check that there is actual data in the array
2223
            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
            $first = array_shift($param);
2229
            if (is_object($first) && $this->checkParamType($first) == self::OBJECT) {
2230
                return self::ARRAY_OBJECT;
2231
            }
2232
2233
            return self::ARRAY;
2234
        }
2235
        // object is potentially a collection
2236
        elseif (is_object($param)) {
2237
            // check that collection has required interface
2238
            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
            $it = $param->getIterator();
2244
            $first = $it->current();
2245
2246
            // check that the first element of the collection is a valid object as defined above
2247
            if ($this->checkParamType($first) == self::OBJECT) {
2248
                return self::COLLECTION;
2249
            }
2250
        }
2251
2252
        return 0;
2253
    }
2254
}
2255