Failed Conditions
Push — master ( f973a6...f1f6aa )
by Ryan
08:12
created

Database::delete()   C

Complexity

Conditions 16
Paths 48

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 16.021

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 16
eloc 22
c 4
b 0
f 0
nc 48
nop 4
dl 0
loc 40
ccs 22
cts 23
cp 0.9565
crap 16.021
rs 5.5666

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
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\MissingParams;
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 a SELECT query
49
     *
50
     * @var integer
51
     */
52
    private const SELECT = 1;
53
54
    /**
55
     * Constant defining a SELECT COUNT query
56
     *
57
     * @var integer
58
     */
59
    private const SELECT_COUNT = 2;
60
61
    /**
62
     * Constant defining a CREATE TABLE query
63
     *
64
     * @var integer
65
     */
66
    private const CREATE_TABLE = 3;
67
68
    /**
69
     * Constant defining DROP query
70
     *
71
     * @var integer
72
     */
73
    private const DROP = 4;
74
75
    /**
76
     * Constant defining DELETE query
77
     *
78
     * @var integer
79
     */
80
    private const DELETE = 5;
81
82
    /**
83
     * Constant defining INSERT query
84
     *
85
     * @var integer
86
     */
87
    private const INSERT = 6;
88
89
    /**
90
     * Constant defining REPLACE query
91
     *
92
     * @var integer
93
     */
94
    private const REPLACE = 7;
95
96
    /**
97
     * Constant defining UPDATE query
98
     *
99
     * @var integer
100
     */
101
    private const UPDATE = 8;
102
103
    /**
104
     * Constant defining EXTENDED INSERT query
105
     *
106
     * @var integer
107
     */
108
    private const EXTENDED_INSERT = 9;
109
110
    /**
111
     * Constant defining EXTENDED REPLACE query
112
     *
113
     * @var integer
114
     */
115
    private const EXTENDED_REPLACE = 10;
116
117
    /**
118
     * Constant defining EXTENDED UPDATE query
119
     *
120
     * @var integer
121
     */
122
    private const EXTENDED_UPDATE = 11;
123
124
    /**
125
     * Constant defining ALTER TABLE query
126
     *
127
     * @var integer
128
     */
129
    private const ALTER_TABLE = 12;
130
131
    /**
132
     * Constant defining action for alter table statement
133
     *
134
     * @var integer
135
     */
136
    private const ADD_COLUMN = 1;
137
138
    /**
139
     * Constant defining action for alter table statement
140
     *
141
     * @var integer
142
     */
143
    private const DROP_COLUMN = 2;
144
145
    /**
146
     * Constant defining action for alter table statement
147
     *
148
     * @var integer
149
     */
150
    private const MODIFY_COLUMN = 3;
151
152
    /**
153
     * Constant defining action to add a constraint
154
     *
155
     * @var integer
156
     */
157
    private const ADD_CONSTRAINT = 4;
158
159
    /**
160
     * Constant defining a TRUNCATE TABLE query
161
     *
162
     * @var integer
163
     */
164
    private const TRUNCATE = 13;
165
166
    /**
167
     * The mysqli connection
168
     *
169
     * @access protected
170
     * @var mysqli
171
     */
172
    protected $_c;
173
174
    /**
175
     * To store the SQL statement
176
     *
177
     * @access private
178
     * @var string
179
     */
180
    private $_sql = null;
181
182
    /**
183
     * A variable to store the type of query that is being run
184
     *
185
     * @access private
186
     * @var int
187
     */
188
    private $_queryType = null;
189
190
    /**
191
     * The result of the query
192
     *
193
     * @access protected
194
     * @var mixed
195
     */
196
    protected $_result = null;
197
198
    /**
199
     * Log level
200
     *
201
     * @access private
202
     * @var string
203
     */
204
    private $_logLevel = Logger::ERROR;
205
206
    /**
207
     * Variable to store the logger
208
     *
209
     * @access private
210
     * @var \Monolog\Logger
211
     */
212
    private $_logger = null;
213
214
    /**
215
     * Path for the logger to log the file
216
     *
217
     * @access private
218
     * @var string
219
     */
220
    private $_logPath = null;
221
222
    /**
223
     * Variable to store the most recent insert ID from an insert query
224
     *
225
     * @access protected
226
     * @var mixed
227
     */
228
    protected $_insertId = null;
229
230
    /**
231
     * Constructor
232
     *
233
     * @param string $strLogPath
234
     *            [optional] absolute log path for the log file
235
     * @param mysqli $dbh
236
     *            [optional]
237
     *            [by ref]
238
     *            mysqli object to perform queries.
239
     * @param int $intLogLevel
240
     *            [optional] Level of the log file to output
241
     *
242
     * @uses PHP_DB_ENCRYPT bool to define if the password is encrypted
243
     * @uses PHP_DB_PWD string to store the password
244
     * @uses PHP_DB_SERVER string to store the database server name or IP
245
     * @uses PHP_DB_USER string to store the name of the user used to connect to the server
246
     * @uses PHP_DB_SCHEMA string to store the default schema to connect to
247
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
248
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
249
     */
250 130
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
251
    {
252
        // set the log file path
253 130
        $this->_logPath = $strLogPath;
254 130
        if (!file_exists($this->_logPath)) {
255
            touch($this->_logPath . "/db.log");
256
        }
257
258
        // set the log level
259 130
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
260
            $this->_logLevel = Logger::ERROR;
261 130
        } elseif (!is_null($intLogLevel)) {
262
            $this->_logLevel = $intLogLevel;
263 130
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
264 130
            $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...
265
        }
266
267
        // open the log handler
268 130
        $this->_logger = new Logger('db', [
269 130
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
270
        ]);
271
272
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
273 130
        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...
274
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
275
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
276
            $this->_logger->pushHandler($stream);
277
        }
278
279
        // check to see if a connection was passed and all defined constants are present to establish a connection
280 130
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
281 1
            $this->_logger->debug("Connecting through existing connection");
282 1
            $this->_c = $dbh;
283 130
        } elseif (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
284
            $this->_logger->critical("Missing essential defined constants");
285
            throw new MissingParams("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);
286 130
        } elseif (defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
287
            $this->_logger->critical("Missing essential encryption constants");
288
            throw new MissingParams("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
289
        }
290
291
        // check to see if the password is encrypted and decrypt if it is
292 130
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
293 130
            $this->_logger->debug("Decrypting password");
294 130
            $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...
295
        } else {
296
            $pwd = PHP_DB_PWD;
297
        }
298
299
        // open the connection
300 130
        $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_USER 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_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...
301
302
        // check for a connection error and throw an error if there is one
303 130
        if ($this->_c->connect_errno) {
304
            $this->_logger->error("Error connecting to database {$this->_c->connect_error}");
305
            throw new ConnectError("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
306
        }
307
308
        // update the logger with the connection details
309 130
        $this->_logger->info("Database connected");
310 130
        $this->_logger->debug("Connection details:", [
311 130
            'Server' => PHP_DB_SERVER,
312 130
            'User'   => PHP_DB_USER,
313 130
            'Schema' => PHP_DB_SCHEMA
314
        ]);
315
316 130
        $this->setVar("time_zone", "+00:00");
317 130
        $this->setVar("sql_mode", "");
318 130
    }
319
320
    /**
321
     * Function to make sure that the database is connected
322
     *
323
     * @return bool
324
     *
325
     * @throws ConnectError
326
     */
327 17
    public function isConnected(): bool
328
    {
329 17
        $this->_logger->info("Checking for live connection");
330
331 17
        if (is_a($this->_c, 'mysqli')) {
332 16
            $ret = $this->_c->ping();
333
        } else {
334 1
            throw new ConnectError("Connection lost");
335
        }
336
337 16
        return $ret;
338
    }
339
340
    /**
341
     * Setter function for _logger
342
     *
343
     * @param Logger $log
344
     *
345
     * @return bool
346
     */
347 1
    public function setLogger(Logger $log): bool
348
    {
349
        // set the logger
350 1
        $this->_logger->debug("Setting logger");
351 1
        $this->_logger = $log;
352 1
        return true;
353
    }
354
355
    /**
356
     * Getter function for _logLevel
357
     *
358
     * @return int
359
     */
360 1
    public function getLogLevel(): int
361
    {
362 1
        $level = $this->_logLevel;
363
364 1
        $this->_logger->debug("Getting log level ({$level})");
365 1
        return $level;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $level returns the type string which is incompatible with the type-hinted return integer.
Loading history...
366
    }
367
368
    /**
369
     * Getter function for _queryType
370
     *
371
     * @return int
372
     */
373 1
    public function getQueryType(): int
374
    {
375 1
        $this->_logger->debug("Getting query type");
376 1
        return $this->_queryType;
377
    }
378
379
    /**
380
     * Setter function for _queryType
381
     *
382
     * @param int $qt
383
     *      Class constants that store query type
384
     *
385
     * @return Database
386
     */
387 100
    private function setQueryType(int $qt): Database
388
    {
389 100
        $this->_logger->debug("Setting query type");
390 100
        $this->_queryType = $qt;
391
392 100
        return $this;
393
    }
394
395
    /**
396
     * Magic method to convert the class to a string represented by the SQL query
397
     *
398
     * @return string|null
399
     */
400 66
    public function __toString(): ?string
401
    {
402 66
        $this->_logger->notice("__toString");
403 66
        $this->_logger->debug($this->_sql);
404 66
        return $this->_sql;
405
    }
406
407
    /**
408
     * Function to return the currently selected database schema
409
     *
410
     * @return string|bool
411
     *      Returns the string name of the selected schema or FALSE if none selected
412
     */
413 1
    public function getSchema()
414
    {
415 1
        if ($res = $this->_c->query("SELECT DATABASE()")) {
416 1
            $row = $res->fetch_row();
417
418 1
            $this->_logger->debug("Getting schema {$row[0]}");
419 1
            return $row[0];
420
        }
421
422
        return false;
423
    }
424
425
    /**
426
     * Function to set schema
427
     *
428
     * @param string $strSchema
429
     *
430
     * @return bool
431
     *
432
     * @throws QueryError
433
     */
434 2
    public function setSchema(string $strSchema): bool
435
    {
436 2
        $this->_logger->info("Setting schema to {$strSchema}");
437 2
        if (! $this->_c->select_db($strSchema)) {
438 1
            $this->_logger->error("Unknown schema {$strSchema}");
439 1
            throw new QueryError("Unknown database schema $strSchema");
440
        }
441 1
        return true;
442
    }
443
444
    /**
445
     * Method to set a MYSQL variable
446
     *
447
     * @param string $strName
448
     *      Name of the SQL variable to set
449
     * @param string $strVal
450
     *      Value for the variable
451
     *
452
     * @return bool
453
     */
454 130
    public function setVar(string $strName, ?string $strVal): bool
455
    {
456 130
        if (empty($strName)) {
457 1
            $this->_logger->debug("name is blank", [
458 1
                'name'  => $strName
459
            ]);
460 1
            return false;
461
        }
462
463 130
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
464
465 130
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
466 130
            $this->_logger->info("Var set");
467 130
            return true;
468
        } else {
469 1
            $this->_logger->error("Failed to set variable {$this->_c->error}");
470 1
            return false;
471
        }
472
    }
473
474
    /**
475
     * Function to execute the statement
476
     *
477
     * @param int $return
478
     *            [optional]
479
     *            MYSQLI constant to control what is returned from the mysqli_result object
480
     * @param string $strSql
481
     *            [optional]
482
     *            Optional SQL query
483
     *
484
     * @throws QueryError
485
     * @throws ConnectError
486
     *
487
     * @return mixed
488
     */
489 15
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
490
    {
491 15
        if (! is_null($strSql)) {
492 2
            $this->_sql = $strSql;
493
        }
494
495 15
        $this->_result = false;
496 15
        $this->_insertId = null;
497
498 15
        $this->isConnected();
499
500 15
        $this->_logger->info("Executing {$this->_queryType} query");
501 15
        $this->_logger->debug($this->_sql);
502
503
        try {
504 15
            if (in_array($this->_queryType, [
505 15
                self::SELECT,
506 15
                self::SELECT_COUNT
507
            ])) {
508 6
                $this->_result = $this->_c->query($this->_sql);
509 6
                if ($this->_c->error) {
510
                    $this->_logger->error("There is an error {$this->_c->error}");
511
                    $this->_logger->debug("Errored on query", [$this->_sql]);
512
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
513
                }
514
            } else {
515 9
                $this->_result = $this->_c->real_query($this->_sql);
516 9
                if ($this->_c->errno) {
517 1
                    $this->_logger->error("There was an error {$this->_c->error}");
518 1
                    $this->_logger->debug("Errored on query", [$this->_sql]);
519 1
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
520
                }
521
            }
522
523 14
            $this->_logger->debug("Checking for query results");
524 14
            $this->_result = $this->checkResults($return);
525 1
        } catch (QueryError $e) {
526 1
            $this->_logger->error($e);
527
        }
528
529 15
        return $this->_result;
530
    }
531
532
    /**
533
     * Function to check the results and return what is expected
534
     *
535
     * @param mixed $returnType
536
     *            [optional]
537
     *            Optional return mysqli_result return type
538
     *
539
     * @return mixed
540
     */
541 14
    protected function checkResults(int $returnType)
542
    {
543 14
        $res = null;
544
545
        // check the sql results and process appropriately
546 14
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
547 5
            $res = $this->_result;
548 9
        } elseif (in_array($this->_queryType, [Database::INSERT, Database::EXTENDED_INSERT, Database::DELETE, Database::UPDATE, Database::EXTENDED_UPDATE, Database::REPLACE, Database::EXTENDED_REPLACE, Database::DELETE])) {
549 2
            $res = $this->_c->affected_rows;
550
551 2
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE, Database::EXTENDED_INSERT])) {
552 2
                $this->_insertId = $this->_c->insert_id;
553
            }
554 7
        } elseif ($this->_queryType == Database::SELECT_COUNT) {
555 1
            if (! is_a($this->_result, 'mysqli_result')) {
556
                $this->_logger->error("Error with return on query");
557
                return null;
558
            }
559
560 1
            if ($this->_result->num_rows == 1) {
561 1
                $row = $this->_result->fetch_assoc();
562 1
                if (isset($row['count'])) {
563 1
                    $this->_logger->debug("Returning SELECT_COUNT query", [
564 1
                        'count' => $row['count']
565
                    ]);
566 1
                    $res = $row['count'];
567
                }
568
            } elseif ($this->_result->num_rows > 1) {
569
                $this->_logger->debug("Returning SELECT_COUNT query", [
570
                    'count' => $this->_result->num_rows
571
                ]);
572
                $res = $this->_result->num_rows;
573
            }
574
575 1
            mysqli_free_result($this->_result);
576
        } else {
577 6
            $method = "mysqli_fetch_object";
578 6
            if ($returnType == MYSQLI_ASSOC) {
579 1
                $method = "mysqli_fetch_assoc";
580 5
            } elseif ($returnType == MYSQLI_NUM) {
581 1
                $method = "mysqli_fetch_array";
582
            }
583
584 6
            if (is_a($this->_result, 'mysqli_result')) {
585 5
                if ($this->_result->num_rows > 1) {
586 3
                    $res = [];
587 3
                    while ($row = call_user_func($method, $this->_result)) {
588 3
                        $res[] = $row;
589
                    }
590
                } else {
591 2
                    $res = call_user_func($method, $this->_result);
592
                }
593
            } else {
594 1
                $this->_logger->error("Error with return on query");
595 1
                return null;
596
            }
597
        }
598
599 13
        if ($this->_c->error) {
600
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
601
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
602
            return null;
603
        }
604
605 13
        return $res;
606
    }
607
608
    /**
609
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
610
     * Nothing is escaped
611
     *
612
     * @param string $strSql
613
     *            [optional]
614
     *            Optional query to pass in and execute
615
     *
616
     * @return mysqli_result|bool
0 ignored issues
show
Bug introduced by
The type Godsgood33\Php_Db\mysqli_result was not found. Did you mean mysqli_result? If so, make sure to prefix the type with \.
Loading history...
617
     */
618 2
    public function query(?string $strSql = null)
619
    {
620 2
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return is_null($strSql) ...his->_c->query($strSql) also could return the type mysqli_result which is incompatible with the documented return type Godsgood33\Php_Db\mysqli_result|boolean.
Loading history...
621
    }
622
623
    /**
624
     * A function to build a select query
625
     *
626
     * @param string $strTableName
627
     *            The table to query
628
     * @param array|string $fields
629
     *            [optional]
630
     *            Optional array of fields to return (defaults to '*')
631
     * @param array:DBWhere|DBWhere $arrWhere
632
     *            [optional] Where clause data
633
     * @param array $arrFlags
634
     *            [optional]
635
     *            Optional 2-dimensional array to allow other flags
636
     *
637
     * @see Database::flags()
638
     *
639
     * @throws Exception
640
     * @throws InvalidArgumentException
641
     *
642
     * @return mixed
643
     *
644
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
645
     */
646 37
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
647
    {
648 37
        $this->_sql = null;
649 37
        $this->setQueryType(self::SELECT);
650
651
        // starting building the query
652 37
        if ($this->checkTableName($strTableName)) {
653 37
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
654 37
                'fields' => $this->fields($fields)
655
            ]);
656 36
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
657
        }
658
659
        // add in any joins
660 36
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
661 1
            $this->_logger->debug("Adding joins", [
662 1
                'joins' => implode(' ', $arrFlags['joins'])
663
            ]);
664 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
665
        } else {
666 35
            $this->_logger->debug("No joins");
667
        }
668
669
        // parse the where clauses
670 36
        $where = $this->parseClause($arrWhere);
671
672 34
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_array($where) is always true.
Loading history...
introduced by
The condition is_null($where) is always false.
Loading history...
673 8
            $where_str = " WHERE";
674 8
            $this->_logger->debug("Parsing where clause and adding to query");
675 8
            foreach ($where as $x => $w) {
676 8
                if ($x > 0) {
677 1
                    $where_str .= " {$w->sqlOperator}";
678
                }
679 8
                $where_str .= $w;
680
            }
681 8
            if (strlen($where_str) > strlen(" WHERE")) {
682 8
                $this->_sql .= $where_str;
683
            }
684
        }
685
686
        // search for any other flags (order, having, group)
687 34
        if (is_array($arrFlags) && count($arrFlags)) {
688 9
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
689 9
            $this->_sql .= $this->flags($arrFlags);
690
        }
691
692 32
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
693
            return $this->execute();
694
        }
695
696 32
        return $this->_sql;
697
    }
698
699
    /**
700
     * Function to build a query to check the number of rows in a table
701
     *
702
     * @param string $strTableName
703
     *            The table to query
704
     * @param array:DBWhere|DBWhere $arrWhere
705
     *            [optional]
706
     *            Optional 2-dimensional array to build where clause
707
     * @param array $arrFlags
708
     *            [optional]
709
     *            Optional 2-dimensional array to add flags
710
     *
711
     * @see Database::flags()
712
     *
713
     * @return int|NULL
714
     *
715
     * @throws InvalidArgumentException
716
     *
717
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
718
     */
719 4
    public function selectCount(string $strTableName, $arrWhere = [], ?array $arrFlags = [])
720
    {
721 4
        $this->_sql = null;
722 4
        $this->setQueryType(self::SELECT_COUNT);
723
724
        // start building query
725 4
        if ($this->checkTableName($strTableName)) {
726 4
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
727
        }
728
729
        // add in any joins
730 4
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
731 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
732
        }
733
734
        // parse where clauses
735 4
        $where = $this->parseClause($arrWhere);
736
737 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...
738 2
            $where_str = " WHERE";
739 2
            $this->_logger->debug("Parsing where clause and adding to query");
740 2
            foreach ($where as $x => $w) {
741 2
                if ($x > 0) {
742 1
                    $where_str .= " {$w->sqlOperator}";
743
                }
744 2
                $where_str .= $w;
745
            }
746 2
            if (strlen($where_str) > strlen(" WHERE")) {
747 2
                $this->_sql .= $where_str;
748
            }
749
        }
750
751
        // add in additional flags (group, having, order)
752 4
        if (is_array($arrFlags) && count($arrFlags)) {
753 1
            $this->_sql .= $this->flags($arrFlags);
754
        }
755
756 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...
757
            return $this->execute();
758
        }
759
760 4
        return $this->_sql;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_sql also could return the type string which is incompatible with the documented return type integer|null.
Loading history...
761
    }
762
763
    /**
764
     * Function to build an insert query statement
765
     *
766
     * @param string $strTableName
767
     * @param array|string $arrParams
768
     * @param bool $blnToIgnore
769
     *
770
     * @return string|NULL
771
     *
772
     * @throws InvalidArgumentException
773
     * @throws MissingInterfaceAndMethods
774
     *
775
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
776
     */
777 7
    public function insert(string $strTableName, $arrParams = null, bool $blnToIgnore = false)
778
    {
779 7
        $this->_sql = null;
780 7
        $this->setQueryType(self::INSERT);
781
782
        // start building query
783 7
        if ($this->checkTableName($strTableName)) {
784 7
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
785
        }
786
787
        // add in field parameters and values
788 7
        if (is_array($arrParams) && count($arrParams)) {
789 3
            if (is_array($arrParams) && count($arrParams)) {
790 3
                $this->_sql .= " (`" . implode("`,`", array_keys($arrParams)) . "`)";
791
            }
792 3
            $this->_sql .= " VALUES (" . implode(",", array_map([
793 3
                $this,
794 3
                '_escape'
795 3
            ], array_values($arrParams))) . ")";
796 4
        } elseif (is_string($arrParams) && stripos($arrParams, 'select') !== false) {
797 1
            $this->_sql .= " {$arrParams}";
798 3
        } elseif (is_object($arrParams)) {
799 2
            $interfaces = class_implements($arrParams);
800 2
            if (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::insert")) {
801 1
                $params = call_user_func([$arrParams, "insert"]);
802 1
                $this->_sql .= " (`" . implode("`,`", array_keys($params)) . "`) VALUES ";
803 1
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($params))) . ")";
804
            } else {
805 1
                throw new MissingInterfaceAndMethods("Object does not implement the DBInterface interface and methods");
806
            }
807
        } else {
808 1
            throw new InvalidArgumentException("Invalid type passed to insert " . gettype($arrParams));
809
        }
810
811 5
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
812
            return $this->execute();
813
        }
814
815 5
        return $this->_sql;
816
    }
817
818
    /**
819
     * Function to create an extended insert query statement
820
     *
821
     * @param string $strTableName
822
     *            The table name that the data is going to be inserted on
823
     * @param array $arrFields
824
     *            An array of field names that each value represents
825
     * @param array|string $params
826
     *            An array of array of values or a string with a SELECT statement to populate the insert with
827
     * @param bool $blnToIgnore
828
     *            [optional]
829
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
830
     *
831
     * @return NULL|string Returns the SQL if self::$autorun is set to false, else it returns the output from running.
832
     *
833
     * @throws InvalidArgumentException
834
     * @throws MissingParams
835
     * @throws MissingInterfaceAndMethods
836
     *
837
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
838
     */
839 7
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
840
    {
841 7
        $this->_sql = null;
842 7
        $this->setQueryType(self::EXTENDED_INSERT);
843
844
        // start building query
845 7
        if ($this->checkTableName($strTableName)) {
846 7
            $this->_sql = "INSERT " . ($blnToIgnore ? "IGNORE " : "") . "INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
847
        }
848
849 7
        if (is_array($params) && count($params)) {
850 6
            $this->_sql .= " VALUES ";
851 6
            if (isset($params[0]) && is_array($params[0])) {
852 4
                foreach ($params as $p) {
853 4
                    if (count($p) != count($arrFields)) {
854 2
                        $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
855 2
                            $p,
856 2
                            debug_backtrace()
857
                        ]);
858 2
                        throw new MissingParams("Inconsistent number of fields in fields and values in extendedInsert " . print_r($p, true));
859
                    }
860 3
                    $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
861
862 3
                    if ($p != end($params)) {
863 3
                        $this->_sql .= ",";
864
                    }
865
                }
866 2
            } elseif (isset($params[0]) && is_object($params[0])) {
867 2
                $interfaces = class_implements($params[0]);
868 2
                if (!in_array("Godsgood33\Php_Db\DBInterface", $interfaces)) {
869 1
                    throw new MissingInterfaceAndMethods("Object does not implement DBInterface interface and methods");
870
                }
871 1
                foreach ($params as $param) {
872 1
                    if (!is_callable(get_class($param) . "::insert")) {
873
                        throw new MissingInterfaceAndMethods("Cannot call insert method");
874
                    }
875 1
                    $key_value = call_user_func([$param, "insert"]);
876 1
                    $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
877
                }
878 1
                $this->_sql = substr($this->_sql, 0, -1);
879
            } else {
880
                $this->_sql .= "(" . implode("),(", array_map([$this, '_escape'], array_values($params))) . ")";
881
            }
882
        } else {
883 1
            throw new InvalidArgumentException("Invalid param type");
884
        }
885
886 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...
887
            return $this->execute();
888
        }
889
890 3
        return $this->_sql;
891
    }
892
893
    /**
894
     * Build a statement to update a table
895
     *
896
     * @param string $strTableName
897
     *            The table name to update
898
     * @param array $arrParams
899
     *            Name/value pairs of the field name and value
900
     * @param array:DBWhere|DBWhere $arrWhere
901
     *            [optional]
902
     *            DBWhere clauses
903
     * @param array $arrFlags
904
     *            [optional]
905
     *            Two-dimensional array to create other flag options (joins, order, and group)
906
     *
907
     * @see Database::flags()
908
     *
909
     * @return NULL|string
910
     *
911
     * @throws InvalidArgumentException
912
     * @throws MissingParams
913
     * @throws MissingInterfaceAndMethods
914
     *
915
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
916
     */
917 9
    public function update(string $strTableName, $arrParams, $arrWhere = [], ?array $arrFlags = [])
918
    {
919 9
        $this->_sql = "UPDATE ";
920 9
        $this->setQueryType(self::UPDATE);
921
922 9
        if ($this->checkTableName($strTableName)) {
923 9
            $this->_sql .= $strTableName;
924
925 9
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
926 1
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
927 1
                unset($arrFlags['joins']);
928
            }
929
930 9
            $this->_sql .= " SET ";
931
        }
932
933 9
        if (is_array($arrParams) && count($arrParams)) {
934 6
            $keys = array_keys($arrParams);
935 6
            foreach ($arrParams as $f => $p) {
936 6
                $field = $f;
937 6
                if ((strpos($f, "`") === false) && (strpos($f, ".") === false) && (strpos($f, "*") === false) && (stripos($f, " as ") === false)) {
938 5
                    $field = "`{$f}`";
939
                }
940
941 6
                if (! is_null($p)) {
942 5
                    $this->_sql .= "$field={$this->_escape($p)}";
943
                } else {
944 1
                    $this->_sql .= "$field=NULL";
945
                }
946
947 6
                if ($f != end($keys)) {
948 1
                    $this->_sql .= ",";
949
                }
950
            }
951 3
        } elseif (is_object($arrParams)) {
952 2
            $interfaces = class_implements($arrParams);
953 2
            if (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::update")) {
954 1
                $params = call_user_func([$arrParams, "update"]);
955 1
                $fields = array_keys($params);
956 1
                $values = array_map([$this, '_escape'], array_values($params));
957 1
                foreach ($fields as $x => $f) {
958 1
                    if ($x > 0) {
959 1
                        $this->_sql .= ",";
960
                    }
961 1
                    $this->_sql .= "`{$f}`={$values[$x]}";
962
                }
963
            } else {
964 1
                throw new MissingInterfaceAndMethods("Params is an object that doesn't implement DBInterface");
965
            }
966
        } else {
967 1
            throw new MissingParams("No fields to update");
968
        }
969
970 7
        $where = $this->parseClause($arrWhere);
971
972 7
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
973 3
            $where_str = " WHERE";
974 3
            $this->_logger->debug("Parsing where clause and adding to query");
975 3
            foreach ($where as $x => $w) {
976 3
                if ($x > 0) {
977 1
                    $where_str .= " {$w->sqlOperator}";
978
                }
979 3
                $where_str .= $w;
980
            }
981 3
            if (strlen($where_str) > strlen(" WHERE")) {
982 3
                $this->_sql .= $where_str;
983
            }
984
        }
985
986 7
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
987
            $this->_sql .= $this->flags($arrFlags);
988
        }
989
990 7
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
991
            return $this->execute();
992
        }
993
994 7
        return $this->_sql;
995
    }
996
997
    /**
998
     * Function to offer an extended updated functionality by using two different tables.
999
     *
1000
     * @param string $strTableToUpdate
1001
     *            The table that you want to update (alias 'tbu' is automatically added)
1002
     * @param string $strOriginalTable
1003
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
1004
     * @param string $strLinkField
1005
     *            The common index value between them that will join the fields
1006
     * @param array|string $arrParams
1007
     *            If string only a single field is updated (tbu.$params = o.$params)
1008
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
1009
     *
1010
     * @return mixed
1011
     *
1012
     * @throws InvalidArgumentException
1013
     *
1014
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1015
     */
1016 2
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, array $arrParams)
1017
    {
1018 2
        $this->_sql = "UPDATE ";
1019 2
        $this->setQueryType(self::EXTENDED_UPDATE);
1020
1021 2
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
1022 2
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
1023
        }
1024
1025 2
        if (is_array($arrParams) && count($arrParams)) {
1026 2
            foreach ($arrParams as $param) {
1027 2
                if ($param != $strLinkField) {
1028 2
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1029
                }
1030
            }
1031 2
            $this->_sql = substr($this->_sql, 0, - 1);
1032
        } elseif (is_string($arrParams)) {
1033
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1034
        } else {
1035
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
1036
        }
1037
1038 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...
1039
            return $this->execute();
1040
        }
1041
1042 2
        return $this->_sql;
1043
    }
1044
1045
    /**
1046
     * Function to build a replace query
1047
     *
1048
     * @param string $strTableName
1049
     *            The table to update
1050
     * @param array $arrParams
1051
     *            Name/value pair to insert
1052
     *
1053
     * @return NULL|string
1054
     *
1055
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1056
     */
1057 2
    public function replace(string $strTableName, $arrParams)
1058
    {
1059 2
        $this->_sql = null;
1060 2
        $this->setQueryType(self::REPLACE);
1061
1062 2
        if ($this->checkTableName($strTableName)) {
1063 2
            $this->_sql = "REPLACE INTO $strTableName ";
1064
        }
1065
1066 2
        if (is_array($arrParams) && count($arrParams)) {
1067 1
            $keys = array_keys($arrParams);
1068 1
            $vals = array_values($arrParams);
1069
1070 1
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1071 1
            $this->_sql .= " VALUES (" . implode(",", array_map([
1072 1
                $this,
1073 1
                '_escape'
1074 1
            ], array_values($vals))) . ")";
1075 1
        } elseif (is_object($arrParams)) {
1076 1
            $interfaces = class_implements($arrParams);
1077 1
            if (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::replace")) {
1078 1
                $params = call_user_func([$arrParams, "replace"]);
1079 1
                $this->_sql .= "(`" . implode("`,`", array_keys($params)) . "`) VALUES ";
1080 1
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($params))) . ")";
1081
            }
1082
        }
1083
1084 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...
1085
            return $this->execute();
1086
        }
1087
1088 2
        return $this->_sql;
1089
    }
1090
1091
    /**
1092
     * Function to build an extended replace statement
1093
     *
1094
     * @param string $strTableName
1095
     *            Table name to update
1096
     * @param array $arrFields
1097
     *            Array of fields
1098
     * @param array $arrParams
1099
     *            Two-dimensional array of values
1100
     *
1101
     * @return NULL|string
1102
     *
1103
     * @throws InvalidArgumentException
1104
     *
1105
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1106
     */
1107 1
    public function extendedReplace(string $strTableName, array $arrFields, array $arrParams)
1108
    {
1109 1
        $this->_sql = null;
1110 1
        $this->setQueryType(self::EXTENDED_REPLACE);
1111
1112 1
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1113
            throw new InvalidArgumentException("Error with the field type");
1114
        }
1115
1116 1
        if ($this->checkTableName($strTableName)) {
1117 1
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1118
        }
1119
1120 1
        if (is_array($arrParams) && count($arrParams)) {
1121 1
            $this->_sql .= " VALUES ";
1122 1
            foreach ($arrParams as $p) {
1123 1
                $this->_sql .= "(" . implode(",", array_map([
1124 1
                    $this,
1125 1
                    '_escape'
1126 1
                ], array_values($p))) . ")";
1127
1128 1
                if ($p != end($arrParams)) {
1129 1
                    $this->_sql .= ",";
1130
                }
1131
            }
1132
        }
1133
1134 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...
1135
            return $this->execute();
1136
        }
1137
1138 1
        return $this->_sql;
1139
    }
1140
1141
    /**
1142
     * Function to build a delete statement
1143
     *
1144
     * @param string $strTableName
1145
     *            Table name to act on
1146
     * @param array $arrFields
1147
     *            [optional]
1148
     *            Optional list of fields to delete (used when including multiple tables)
1149
     * @param array:DBWhere|DBWhere $arrWhere
1150
     *            [optional]
1151
     *            Optional where clauses to use
1152
     * @param array $arrJoins
1153
     *            [optional]
1154
     *            Optional 2-dimensional array to add other flags
1155
     *
1156
     * @see Database::flags()
1157
     *
1158
     * @return string|NULL
1159
     *
1160
     * @throws InvalidArgumentException
1161
     *
1162
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1163
     */
1164 4
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1165
    {
1166 4
        $this->_sql = "DELETE";
1167 4
        $this->setQueryType(self::DELETE);
1168
1169 4
        $this->_logger->debug("Deleting table data");
1170
1171 4
        if (! is_null($arrFields) && is_array($arrFields) && count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_null($arrFields) is always false.
Loading history...
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1172 1
            $this->_sql .= " " . implode(",", $arrFields);
1173
        }
1174
1175 4
        if ($this->checkTableName($strTableName)) {
1176 4
            $this->_sql .= " FROM $strTableName";
1177
        }
1178
1179 4
        if (! is_null($arrJoins) && is_array($arrJoins) && count($arrJoins)) {
0 ignored issues
show
introduced by
The condition is_array($arrJoins) is always true.
Loading history...
introduced by
The condition is_null($arrJoins) is always false.
Loading history...
1180 1
            $this->_sql .= " " . implode(" ", $arrJoins);
1181
        }
1182
1183 4
        $where = $this->parseClause($arrWhere);
1184
1185 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...
1186 2
            $where_str = " WHERE";
1187 2
            $this->_logger->debug("Parsing where clause and adding to query");
1188 2
            foreach ($where as $x => $w) {
1189 2
                if ($x > 0) {
1190 1
                    $where_str .= " {$w->sqlOperator}";
1191
                }
1192 2
                $where_str .= $w;
1193
            }
1194 2
            if (strlen($where_str) > strlen(" WHERE")) {
1195 2
                $this->_sql .= $where_str;
1196
            }
1197
        }
1198
1199 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...
1200
            return $this->execute();
1201
        }
1202
1203 4
        return $this->_sql;
1204
    }
1205
1206
    /**
1207
     * Function to build a drop table statement (automatically executes)
1208
     *
1209
     * @param string $strTableName
1210
     *            Table to drop
1211
     * @param string $strType
1212
     *            [optional]
1213
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1214
     * @param bool $blnIsTemp
1215
     *            [optional]
1216
     *            Optional bool if this is a temporary table
1217
     *
1218
     * @return string|NULL
1219
     *
1220
     * @throws InvalidArgumentException
1221
     *
1222
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1223
     */
1224 5
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1225
    {
1226 5
        $this->_sql = null;
1227 5
        $this->setQueryType(self::DROP);
1228
1229 5
        switch ($strType) {
1230 5
            case 'table':
1231 4
                $strType = 'TABLE';
1232 4
                break;
1233 1
            case 'view':
1234 1
                $strType = 'VIEW';
1235 1
                break;
1236
            default:
1237
                throw new InvalidArgumentException("Invalid type " . gettype($strType), E_ERROR);
1238
        }
1239
1240 5
        if ($this->checkTableName($strTableName)) {
1241 5
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1242
        }
1243
1244 5
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1245
            return $this->execute();
1246
        }
1247
1248 5
        return $this->_sql;
1249
    }
1250
1251
    /**
1252
     * Function to build a truncate table statement (automatically executes)
1253
     *
1254
     * @param string $strTableName
1255
     *            Table to truncate
1256
     *
1257
     * @return string|NULL
1258
     *
1259
     * @throws InvalidArgumentException
1260
     *
1261
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1262
     */
1263 1
    public function truncate(string $strTableName)
1264
    {
1265 1
        $this->_sql = null;
1266 1
        $this->setQueryType(self::TRUNCATE);
1267
1268 1
        if ($this->checkTableName($strTableName)) {
1269 1
            $this->_sql = "TRUNCATE TABLE $strTableName";
1270
        }
1271
1272 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...
1273
            return $this->execute();
1274
        }
1275
1276 1
        return $this->_sql;
1277
    }
1278
1279
    /**
1280
     * Function to build a create temporary table statement
1281
     *
1282
     * @param string $strTableName
1283
     *            Name to give the table when creating
1284
     * @param bool $blnIsTemp
1285
     *            [optional]
1286
     *            Optional bool to make the table a temporary table
1287
     * @param mixed $strSelect
1288
     *            [optional]
1289
     *            Optional parameter if null uses last built statement
1290
     *            If string, will be made the SQL statement executed to create the table
1291
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1292
     *
1293
     * @return NULL|string
1294
     *
1295
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1296
     */
1297 4
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1298
    {
1299 4
        $this->setQueryType(self::CREATE_TABLE);
1300
1301 4
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1302 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1303 3
        } elseif ($this->checkTableName($strTableName) && is_string($strSelect)) {
1304 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1305 2
        } elseif ($this->checkTableName($strTableName) && is_array($strSelect)) {
1306 2
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1307
1308 2
            foreach ($strSelect as $field) {
1309 2
                $default = null;
1310 2
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1311 2
                    $this->_sql .= (string) $field . ",";
1312
                } elseif (is_array($field)) {
1313
                    if (isset($field['default'])) {
1314
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1315
                    }
1316
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1317
                }
1318
            }
1319 2
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1320
        }
1321
1322 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...
1323
            return $this->execute();
1324
        }
1325
1326 4
        return $this->_sql;
1327
    }
1328
1329
    /**
1330
     * Function to create a table using a stdClass object derived from JSON
1331
     *
1332
     * @param stdClass $json
1333
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1334
     *
1335
     * @example /examples/create_table_json.json
1336
     *
1337
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1338
     */
1339 3
    public function createTableJson($json, bool $blnDropFirst = false)
1340
    {
1341 3
        $this->setQueryType(self::CREATE_TABLE);
1342 3
        $this->_c->select_db($json->schema);
1343
1344 3
        if ($blnDropFirst) {
1345
            $this->drop($json->name);
1346
            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...
1347
                $this->execute();
1348
            }
1349
        }
1350
1351 3
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1352 3
        foreach ($json->fields as $field) {
1353 3
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1354
1355 3
            if ($field->dataType == 'enum' && isset($field->values)) {
1356 1
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1357
            }
1358
1359 3
            if (isset($field->ai) && $field->ai) {
1360 3
                $this->_sql .= " AUTO_INCREMENT";
1361
            }
1362
1363 3
            if (isset($field->nn) && $field->nn) {
1364 3
                $this->_sql .= " NOT NULL";
1365 2
            } elseif (isset($field->default)) {
1366 2
                if (strtolower($field->default) == 'null') {
1367 1
                    $this->_sql .= " DEFAULT NULL";
1368 1
                } elseif (strlen($field->default)) {
1369 1
                    $this->_sql .= " DEFAULT '{$field->default}'";
1370
                }
1371
            }
1372
1373 3
            if ($field != end($json->fields)) {
1374 2
                $this->_sql .= ",";
1375
            }
1376
        }
1377
1378 3
        if (isset($json->index) && count($json->index)) {
1379 1
            foreach ($json->index as $ind) {
1380 1
                $ref = null;
1381 1
                if (is_array($ind->ref)) {
1382
                    $ref = "";
1383
                    foreach ($ind->ref as $r) {
1384
                        $ref .= "`{$r}` ASC,";
1385
                    }
1386
                    $ref = substr($ref, 0, -1);
1387 1
                } elseif (is_string($ind->ref)) {
1388 1
                    $ref = $ind->ref;
1389
                }
1390 1
                if (!is_null($ref)) {
1391 1
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1392
                }
1393
            }
1394
        }
1395
1396 3
        if (isset($json->constraints) && count($json->constraints)) {
1397
            foreach ($json->constraints as $con) {
1398
                $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));
1399
            }
1400
        }
1401
1402 3
        if (isset($json->unique) && count($json->unique)) {
1403 1
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1404
        }
1405
1406 3
        if (isset($json->primary_key) && count($json->primary_key)) {
1407 3
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1408
        } else {
1409
            if (substr($this->_sql, - 1) == ',') {
1410
                $this->_sql = substr($this->_sql, 0, - 1);
1411
            }
1412
1413
            $this->_sql .= ")";
1414
        }
1415
1416 3
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1417
            return $this->execute();
1418
        }
1419
1420 3
        return $this->_sql;
1421
    }
1422
1423
    /**
1424
     * Method to add a column to the database (only one at a time!)
1425
     *
1426
     * @param string $strTableName
1427
     * @param stdClass $params
1428
     *
1429
     * @return string|mixed
1430
     *
1431
     * @throws InvalidArgumentException
1432
     *
1433
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1434
     */
1435 3
    public function addColumn(string $strTableName, stdClass $params)
1436
    {
1437 3
        $this->setQueryType(self::ALTER_TABLE);
1438 3
        $this->checkTableName($strTableName);
1439
1440 3
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1441
1442 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1443 1
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1444 1
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1445
        }
1446
1447 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1448 2
        $default = null;
1449 2
        if ($params->default === null) {
1450 1
            $default = " DEFAULT NULL";
1451 1
        } elseif (strlen($params->default)) {
1452 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1453
        }
1454 2
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1455
1456 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...
1457
            return $this->execute();
1458
        }
1459
1460 2
        return $this->_sql;
1461
    }
1462
1463
    /**
1464
     * Method to drop a fields from a table
1465
     *
1466
     * @param string $strTableName
1467
     * @param string|array:string $params
1468
     *
1469
     * @return string|mixed
1470
     *
1471
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1472
     */
1473 3
    public function dropColumn(string $strTableName, $params)
1474
    {
1475 3
        $this->setQueryType(self::ALTER_TABLE);
1476 3
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1477
1478 3
        if (is_array($params) && count($params)) {
1479 2
            foreach ($params as $col) {
1480 2
                $this->_sql .= " `{$col->name}`";
1481
1482 2
                if ($col != end($params)) {
1483 1
                    $this->_sql .= ",";
1484
                }
1485
            }
1486 1
        } elseif (is_string($params)) {
1487 1
            $this->_sql .= " `{$params}`";
1488
        }
1489
1490 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...
1491
            return $this->execute();
1492
        }
1493
1494 3
        return $this->_sql;
1495
    }
1496
1497
    /**
1498
     * Method to modify a field to change it's datatype, name, or other parameter
1499
     *
1500
     * @param string $strTableName
1501
     * @param stdClass $params
1502
     *
1503
     * @return string|mixed
1504
     *
1505
     * @throws InvalidArgumentException
1506
     *
1507
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1508
     */
1509 3
    public function modifyColumn($strTableName, $params)
1510
    {
1511 3
        $this->setQueryType(self::ALTER_TABLE);
1512 3
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1513
1514 3
        if (!self::checkObject($params, ['name', 'dataType'])) {
1515 1
            $this->_logger->error("Missing elements to the modifyColumn method (need 'name' and 'dataType')", [$params]);
1516 1
            throw new InvalidArgumentException("Missing elements to the modifyColumn method");
1517
        }
1518
1519 2
        if (!isset($params->new_name)) {
1520 1
            $params->new_name = $params->name;
1521
        }
1522
1523 2
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1524 2
        $default = null;
1525 2
        if ($params->default === null) {
1526 1
            $default = " DEFAULT NULL";
1527 1
        } elseif (strlen($params->default)) {
1528 1
            $default = " DEFAULT {$this->_escape($params->default)}";
1529
        }
1530 2
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1531
1532 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...
1533
            return $this->execute();
1534
        }
1535
1536 2
        return $this->_sql;
1537
    }
1538
1539
    /**
1540
     * Method to add a constraint to a table
1541
     *
1542
     * @param string $strTableName
1543
     * @param stdClass $params
1544
     *
1545
     * @return string|mixed
1546
     *
1547
     * @throws InvalidArgumentException
1548
     *
1549
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1550
     */
1551 7
    public function addConstraint($strTableName, $params)
1552
    {
1553 7
        $this->setQueryType(self::ALTER_TABLE);
1554 7
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1555
1556 7
        if (!is_a($params, 'stdClass')) {
1557 1
            $this->_logger->critical("Error in reading constraint field");
1558 1
            throw new InvalidArgumentException("Error in reading constraint field");
1559
        }
1560
1561 6
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1562 1
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1563 1
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1564
        }
1565
1566 5
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1567 1
            $this->_logger->error("Invalid action for deletion on addConstraint");
1568 1
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1569
        }
1570
1571 4
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1572 1
            $this->_logger->error("Invalid action for update on addConstraint");
1573 1
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1574
        }
1575
1576 3
        if (is_array($params->field) && is_array($params->local)) {
1577 1
            $field = "`" . implode("`,`", $params->field) . "`";
1578 1
            $local = "`" . implode("`,`", $params->local) . "`";
1579 2
        } elseif (is_string($params->field) && is_string($params->local)) {
1580 1
            $field = "`{$params->field}`";
1581 1
            $local = "`{$params->local}`";
1582
        } else {
1583 1
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1584
        }
1585 2
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1586
1587 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...
1588
            return $this->execute();
1589
        }
1590
1591 2
        return $this->_sql;
1592
    }
1593
1594
    /**
1595
     * Check to see if a field in a table exists
1596
     *
1597
     * @param string $strTableName
1598
     *            Table to check
1599
     * @param string $strFieldName
1600
     *            Field name to find
1601
     *
1602
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1603
     */
1604 2
    public function fieldExists(string $strTableName, string $strFieldName): bool
1605
    {
1606 2
        $fdata = $this->fieldData($strTableName);
1607
1608 2
        if (is_array($fdata) && count($fdata)) {
1609 2
            foreach ($fdata as $field) {
1610 2
                if ($field->name == $strFieldName) {
1611 1
                    return true;
1612
                }
1613
            }
1614
        }
1615
1616 1
        return false;
1617
    }
1618
1619
    /**
1620
     * Function to get the column data (datatype, flags, defaults, etc)
1621
     *
1622
     * @param string $strTableName
1623
     *            Table to query
1624
     * @param mixed $field
1625
     *            [optional]
1626
     *            Optional field to retrieve data (if null, returns data from all fields)
1627
     *
1628
     * @return mixed
1629
     */
1630 7
    public function fieldData(string $strTableName, $field = null)
1631
    {
1632 7
        if (is_null($field)) {
1633 3
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1634 5
        } elseif (is_array($field)) {
1635 5
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1636 1
        } elseif (is_string($field)) {
1637 1
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1638
        } else {
1639 1
            return null;
1640
        }
1641
1642 7
        $fields = null;
1643 7
        if (is_a($res, 'mysqli_result')) {
1644 7
            $fields = $res->fetch_fields();
1645 7
            foreach ($fields as $i => $f) {
1646 7
                $fields["{$f->name}"] = $f;
1647 7
                unset($fields[$i]);
1648
            }
1649
        }
1650
1651 7
        return $fields;
1652
    }
1653
1654
    /**
1655
     * Function to check that all field parameters are set correctly
1656
     *
1657
     * @param stdClass $field_data
1658
     * @param stdClass $check
1659
     * @param array $pks
1660
     * @param stdClass $index
1661
     *
1662
     * @return array|string
1663
     */
1664 4
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1665
    {
1666 4
        $default = null;
1667 4
        $ret = null;
1668
1669 4
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1670
1671 4
        if ($check->default === null) {
1672 1
            $default = " DEFAULT NULL";
1673 3
        } elseif (strlen($check->default)) {
1674 1
            $default = " DEFAULT '{$check->default}'";
1675
        }
1676
1677 4
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1678 3
            $this->_logger->notice("Wrong datatype", [
1679 3
                'name' => $field_data->name,
1680 3
                'datatype' => $check->dataType
1681
            ]);
1682 3
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1683 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1684 1
            $this->_logger->notice("Incorrect size", [
1685 1
                'name' => $field_data->name,
1686 1
                'current' => $field_data->length,
1687 1
                'new_size' => $check->length
1688
            ]);
1689 1
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1690
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1691
            $this->_logger->notice("Setting ENUM type", [
1692
                'name' => $field_data->name,
1693
                'values' => implode(",", $check->values)
1694
            ]);
1695
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1696
        }
1697
1698 4
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1699
            foreach ($index as $ind) {
1700
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1701
                    $this->_logger->debug("Missing index", [
1702
                        'name' => $field_data->name
1703
                    ]);
1704
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1705
                }
1706
            }
1707
        }
1708
1709 4
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1710
            $this->_logger->debug("Setting PKs", [
1711
                'keys' => implode(',', $pks)
1712
            ]);
1713
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1714
        }
1715
1716 4
        return $ret;
1717
    }
1718
1719
    /**
1720
     * Function to check for the existence of a table within a schema
1721
     *
1722
     * @param string $strSchema
1723
     *            The schema to search in
1724
     * @param string $strTableName
1725
     *            Table to search for
1726
     *
1727
     * @return int|bool Returns number of tables that match if table is found in that schema, otherwise FALSE
1728
     *
1729
     * @throws InvalidArgumentException
1730
     */
1731 5
    public function tableExists($strSchema, $strTableName)
1732
    {
1733 5
        if (! $this->_c->select_db($strSchema)) {
1734 1
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1735 1
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1736
        }
1737
1738 4
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1739 1
            $this->_logger->warning("Invalid table name {$strTableName}");
1740 1
            return false;
1741
        }
1742
1743 3
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1744
1745 3
        if ($res = $this->_c->query($sql)) {
1746 3
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1747 2
                return $res->num_rows;
1748
            }
1749
        } elseif ($this->_c->errno) {
1750
            $this->_logger->error($this->_c->error);
1751
        }
1752
1753 1
        return false;
1754
    }
1755
1756
    /**
1757
     * Function to detect if string is a JSON object or not
1758
     *
1759
     * @param string $strVal
1760
     *
1761
     * @return bool
1762
     */
1763 1
    public function isJson($strVal): bool
1764
    {
1765 1
        json_decode($strVal);
1766 1
        return (json_last_error() == JSON_ERROR_NONE);
1767
    }
1768
1769
    /**
1770
     * Function to escape SQL characters to prevent SQL injection
1771
     *
1772
     * @param mixed $val
1773
     *            Value to escape
1774
     * @param bool $blnEscape
1775
     *            Decide if we should escape or not
1776
     *
1777
     * @throws Exception
1778
     * @throws InvalidArgumentException
1779
     *
1780
     * @return string Escaped value
1781
     */
1782 130
    public function _escape($val, bool $blnEscape = true): string
1783
    {
1784 130
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1785 1
            return 'NULL';
1786 130
        } elseif (is_numeric($val) || is_string($val)) {
1787 130
            if ($blnEscape) {
1788 130
                return "'{$this->_c->real_escape_string($val)}'";
1789
            }
1790 1
            return $val;
1791 6
        } elseif (is_a($val, 'DateTime')) {
1792 1
            return "'{$val->format(MYSQL_DATETIME)}'";
1793 5
        } elseif (is_bool($val)) {
1794 1
            return $val ? "'1'" : "'0'";
1795 4
        } elseif (is_array($val)) {
1796 1
            $ret = [];
1797 1
            foreach ($val as $v) {
1798 1
                $ret[] = $this->_escape($v);
1799
            }
1800 1
            return "(" . implode(",", $ret) . ")";
1801 3
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1802 2
            $ret = call_user_func([
1803 2
                $val,
1804 2
                '_escape'
1805
            ]);
1806 2
            if ($ret !== false && is_string($ret)) {
1807 1
                return $ret;
1808
            } else {
1809 1
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1810
            }
1811
        }
1812
1813 1
        throw new InvalidArgumentException("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1814
    }
1815
1816
    /**
1817
     * Function to populate the fields for the SQL
1818
     *
1819
     * @param array|string $fields
1820
     *            [optional]
1821
     *            Optional array of fields to string together to create a field list
1822
     *
1823
     * @return string
1824
     *
1825
     * @throws InvalidArgumentException
1826
     */
1827 37
    protected function fields($fields = null): string
1828
    {
1829 37
        $ret = null;
1830
1831 37
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1832 3
            foreach ($fields as $field) {
1833 3
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1834 2
                    $ret .= "`$field`,";
1835
                } else {
1836 1
                    $ret .= "$field,";
1837
                }
1838
            }
1839 3
            $ret = substr($ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
1840 34
        } elseif (is_string($fields)) {
1841 4
            $ret = $fields;
1842 30
        } elseif (is_null($fields)) {
1843 29
            $ret = "*";
1844
        } else {
1845 1
            throw new InvalidArgumentException("Invalid field type");
1846
        }
1847
1848 36
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
1849
    }
1850
1851
    /**
1852
     * Function to parse the flags
1853
     *
1854
     * @param array $flags
1855
     *            Two-dimensional array to added flags
1856
     *
1857
     *            <code>
1858
     *            [
1859
     *            &nbsp;&nbsp;'group' => 'field',
1860
     *            &nbsp;&nbsp;'having' => 'field',
1861
     *            &nbsp;&nbsp;'order' => 'field',
1862
     *            &nbsp;&nbsp;'start' => 0,
1863
     *            &nbsp;&nbsp;'limit' => 0
1864
     *            ]
1865
     *            </code>
1866
     *
1867
     * @see Database::groups()
1868
     * @see Database::having()
1869
     * @see Database::order()
1870
     *
1871
     * @return string
1872
     */
1873 10
    protected function flags(array $arrFlags)
1874
    {
1875 10
        $ret = '';
1876
1877 10
        if (isset($arrFlags['group'])) {
1878 3
            $ret .= $this->groups($arrFlags['group']);
1879
        }
1880
1881 9
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1882 1
            $having = " HAVING";
1883 1
            $this->_logger->debug("Parsing where clause and adding to query");
1884 1
            foreach ($arrFlags['having'] as $x => $h) {
1885 1
                if ($x > 0) {
1886 1
                    $having .= " {$h->sqlOperator}";
1887
                }
1888 1
                $having .= $h;
1889
            }
1890 1
            if (strlen($having) > strlen(" HAVING")) {
1891 1
                $ret .= $having;
1892
            }
1893
        }
1894
1895 9
        if (isset($arrFlags['order'])) {
1896 3
            $ret .= $this->order($arrFlags['order']);
1897
        }
1898
1899 8
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1900 1
            $ret .= " LIMIT ";
1901 1
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1902 1
                $ret .= "{$arrFlags['start']},";
1903
            }
1904 1
            $ret .= "{$arrFlags['limit']}";
1905
        }
1906
1907 8
        return $ret;
1908
    }
1909
1910
    /**
1911
     * Function to parse SQL GROUP BY statements
1912
     *
1913
     * @param mixed $groups
1914
     *
1915
     * @return string
1916
     *
1917
     * @throws InvalidArgumentException
1918
     */
1919 3
    protected function groups($groups): string
1920
    {
1921 3
        $ret = '';
1922 3
        if (is_array($groups) && count($groups)) {
1923 1
            $ret .= " GROUP BY";
1924
1925 1
            foreach ($groups as $grp) {
1926 1
                $ret .= " $grp";
1927
1928 1
                if ($grp != end($groups)) {
1929 1
                    $ret .= ",";
1930
                }
1931
            }
1932 2
        } elseif (is_string($groups)) {
1933 1
            $ret .= " GROUP BY {$groups}";
1934
        } else {
1935 1
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1936
        }
1937
1938 2
        return $ret;
1939
    }
1940
1941
    /**
1942
     * Function to parse SQL ORDER BY statements
1943
     *
1944
     * @param mixed $order
1945
     *
1946
     * @return string
1947
     *
1948
     * @throws InvalidArgumentException
1949
     */
1950 3
    protected function order($order): string
1951
    {
1952 3
        $ret = '';
1953 3
        if (is_array($order)) {
1954 1
            $ret .= " ORDER BY";
1955
1956 1
            foreach ($order as $ord) {
1957 1
                $ret .= " {$ord['field']} {$ord['sort']}";
1958
1959 1
                if ($ord != end($order)) {
1960 1
                    $ret .= ",";
1961
                }
1962
            }
1963 2
        } elseif (is_string($order)) {
1964 1
            $ret .= " ORDER BY {$order}";
1965
        } else {
1966 1
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
1967
        }
1968
1969 2
        return $ret;
1970
    }
1971
1972
    /**
1973
     * Method to check if there are any invalid characters in the table name
1974
     *
1975
     * @param string $strTableName
1976
     *      Table name passed in
1977
     *
1978
     * @return bool
1979
     *      Returns FALSE if table name contains any characters that will be problematic (0-9, a-z, A-Z, $, _), TRUE otherwise
1980
     */
1981 82
    private function checkTableName(string $strTableName): bool
1982
    {
1983 82
        return !strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName) ? false : true;
1984
    }
1985
1986
    /**
1987
     * Function to see if a constraint exists
1988
     *
1989
     *
1990
     *
1991
     * @param string $strConstraintId
1992
     *
1993
     * @return bool
1994
     */
1995
    public function isConstraint($strConstraintId): bool
1996
    {
1997
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
1998
1999
        if ($res->num_rows) {
2000
            return true;
2001
        }
2002
2003
        return false;
2004
    }
2005
2006
    /**
2007
     * Method to add a where clause
2008
     *
2009
     * @param DBWhere|array:DBWhere $where
2010
     *
2011
     * @return bool|array:DBWhere
2012
     */
2013 51
    public function parseClause($where)
2014
    {
2015 51
        $ret = [];
2016 51
        $interfaces = [];
2017 51
        if (is_object($where)) {
2018 8
            $interfaces = class_implements($where);
2019
        }
2020 51
        if (is_array($where)) {
2021 42
            foreach ($where as $k => $w) {
2022 10
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
2023 1
                    return false;
2024
                }
2025 9
                $v = $this->_escape($w->value, $w->escape);
2026 9
                $where[$k]->value = $v;
2027
2028 9
                $ret[] = $where[$k];
2029
            }
2030 9
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
2031 8
            $v = $this->_escape($where->value, $where->escape);
2032 6
            $where->value = $v;
2033 6
            $ret[] = $where;
2034 2
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($where) . "::where")) {
2035 1
            $params = call_user_func([$where, "where"]);
2036 1
            $ret = $this->parseClause($params);
2037
        } else {
2038 1
            $this->_logger->warning("Failed to get where", [$where]);
2039
        }
2040
2041 48
        return $ret;
2042
    }
2043
2044
    /**
2045
     * Encryption algorithm
2046
     *
2047
     * @param string $data
2048
     * @param string $salt
2049
     *
2050
     * @return string
2051
     *
2052
     * @throws Exception
2053
     *
2054
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2055
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2056
     */
2057 1
    public static function encrypt(string $data, ?string $salt = null)
2058
    {
2059 1
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2060
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2061
        }
2062
2063
        // Remove the base64 encoding from our key
2064 1
        if (is_null($salt)) {
2065 1
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2066
        } else {
2067
            $encryption_key = base64_decode($salt);
2068
        }
2069
        // Generate an initialization vector
2070 1
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
2071
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
2072 1
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2073
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
2074 1
        return base64_encode($encrypted . '::' . $iv);
2075
    }
2076
2077
    /**
2078
     * Decryption algorithm
2079
     *
2080
     * @param string $data
2081
     *
2082
     * @return string
2083
     *
2084
     * @throws Exception
2085
     *
2086
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
2087
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
2088
     */
2089 130
    public static function decrypt(string $data)
2090
    {
2091 130
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2092
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2093
        }
2094
2095
        // Remove the base64 encoding from our key
2096 130
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2097
2098
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
2099 130
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
2100 130
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2101 130
        return $plaintext;
2102
    }
2103
2104
    /**
2105
     * Method to check if all required fields are available in the object
2106
     *
2107
     * @param object $object
2108
     * @param array:string $requiredFields
2109
     *
2110
     * @return bool
2111
     */
2112 12
    public static function checkObject($object, $requiredFields): bool
2113
    {
2114 12
        $haystack = array_keys(json_decode(json_encode($object), true));
2115 12
        foreach ($requiredFields as $r) {
2116 12
            if (!in_array($r, $haystack)) {
2117 3
                return false;
2118
            }
2119
        }
2120
2121 9
        return true;
2122
    }
2123
2124
    /**
2125
     * Method to retrieve the error data
2126
     *
2127
     * @return string
2128
     */
2129 1
    public function error(): string
2130
    {
2131 1
        return $this->_c->error;
2132
    }
2133
}
2134