Database::addColumn()   B
last analyzed

Complexity

Conditions 8
Paths 25

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 9.4924

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 8
eloc 16
c 4
b 1
f 0
nc 25
nop 2
dl 0
loc 26
ccs 5
cts 7
cp 0.7143
crap 9.4924
rs 8.4444
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
     * The mysqli connection
49
     *
50
     * @access protected
51
     * @var mysqli
52
     */
53
    protected $_c;
54
55
    /**
56
     * To store the SQL statement
57
     *
58
     * @access private
59
     * @var string
60
     */
61
    private $_sql = '';
62
63
    /**
64
     * A variable to store the type of query that is being run
65
     *
66
     * @access private
67
     * @var int
68
     */
69
    private $_queryType = null;
70
71
    /**
72
     * The result of the query
73
     *
74
     * @access protected
75
     * @var mixed
76
     */
77
    protected $_result = null;
78
79
    /**
80
     * Log level
81
     *
82
     * @access private
83
     * @var int
84
     */
85
    private $_logLevel = Logger::ERROR;
86
87
    /**
88
     * Variable to store the logger
89
     *
90
     * @access private
91
     * @var \Monolog\Logger
92
     */
93
    private $_logger = null;
94
95
    /**
96
     * Path for the logger to log the file
97
     *
98
     * @access private
99
     * @var string
100
     */
101
    private $_logPath = null;
102
103
    /**
104
     * Variable to store the most recent insert ID from an insert query
105
     *
106
     * @access protected
107
     * @var mixed
108
     */
109
    protected $_insertId = null;
110
111
    /**
112
     * Constructor
113
     *
114
     * @param string $strLogPath
115
     *            [optional] absolute log path for the log file
116
     * @param mysqli $dbh
117
     *            [optional]
118
     *            [by ref]
119
     *            mysqli object to perform queries.
120
     * @param int $intLogLevel
121
     *            [optional] Level of the log file to output
122
     *
123
     * @uses PHP_DB_ENCRYPT bool to define if the password is encrypted
124
     * @uses PHP_DB_PWD string to store the password
125
     * @uses PHP_DB_SERVER string to store the database server name or IP
126
     * @uses PHP_DB_USER string to store the name of the user used to connect to the server
127
     * @uses PHP_DB_SCHEMA string to store the default schema to connect to
128
     * @uses PHP_DB_LOG_LEVEL int to store the PSR-4 log level for the library
129
     * @uses PHP_DB_CLI_LOG bool to store if logs should be echoed to STDOUT
130
     */
131
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
132
    {
133
        $this->checkLog($strLogPath);
134
        $this->checkLogLevel($intLogLevel);
135
        $this->initLog();
136
        $this->checkRequiredParams();
137
        $this->init($dbh);
138
139
        // update the logger with the connection details
140
        $this->_logger->info("Database connected");
141
        $this->_logger->debug("Connection details:", [
142
            'Server' => PHP_DB_SERVER,
0 ignored issues
show
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...
143
            'User'   => PHP_DB_USER,
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...
144
            'Schema' => 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...
145
        ]);
146
147
        $this->setVar("time_zone", "+00:00");
148
        $this->setVar("sql_mode", "");
149
    }
150
151
    /**
152
     * Function to make sure that the database is connected
153
     *
154
     * @return bool
155
     *
156
     * @throws ConnectError
157
     */
158
    public function isConnected(): bool
159
    {
160
        $this->_logger->info("Checking for live connection");
161
162
        if (is_a($this->_c, 'mysqli')) {
163
            $ret = $this->_c->ping();
164
        } else {
165
            throw new ConnectError("Connection lost");
166
        }
167
168
        return $ret;
169
    }
170
171
    /**
172
     * Setter function for _logger
173
     *
174
     * @param Logger $log
175
     *
176
     * @return bool
177
     */
178
    public function setLogger(Logger $log): bool
179
    {
180
        // set the logger
181
        $this->_logger->debug("Setting logger");
182
        $this->_logger = $log;
183
        return true;
184
    }
185
186
    /**
187
     * Getter function for _logLevel
188
     *
189
     * @return int
190
     */
191
    public function getLogLevel(): int
192
    {
193
        $level = $this->_logLevel;
194
195
        $this->_logger->debug("Getting log level ({$level})");
196
        return $level;
197
    }
198
199
    /**
200
     * Getter function for _queryType
201
     *
202
     * @return int
203
     */
204
    public function getQueryType(): int
205
    {
206
        $this->_logger->debug("Getting query type");
207
        return $this->_queryType;
208
    }
209
210
    /**
211
     * Setter function for _queryType
212
     *
213
     * @param int $qt
214
     *      Class constants that store query type
215
     *
216
     * @return Database
217
     */
218
    private function setQueryType(int $qt): Database
219
    {
220
        $this->_logger->debug("Setting query type");
221
        $this->_queryType = $qt;
222
223
        return $this;
224
    }
225
226
    /**
227
     * Magic method to convert the class to a string represented by the SQL query
228
     *
229
     * @return string|null
230
     */
231
    public function __toString(): string
232
    {
233
        $this->_logger->notice("__toString");
234
        $this->_logger->debug($this->_sql);
235
        return $this->_sql;
236
    }
237
238
    /**
239
     * Function to return the currently selected database schema
240
     *
241
     * @return string|bool
242
     *      Returns the string name of the selected schema or FALSE if none selected
243
     */
244
    public function getSchema()
245
    {
246
        $res = $this->_c->query("SELECT DATABASE()");
247
        $row = $res->fetch_row();
248
249
        $this->_logger->debug("Getting schema {$row[0]}");
250
        return $row[0];
251
    }
252
253
    /**
254
     * Function to set schema
255
     *
256
     * @param string $strSchema
257
     *
258
     * @return bool
259
     *
260
     * @throws QueryError
261
     */
262
    public function setSchema(string $strSchema): bool
263
    {
264
        $this->_logger->info("Setting schema to {$strSchema}");
265
        if (! $this->_c->select_db($strSchema)) {
266
            $this->_logger->error("Unknown schema {$strSchema}");
267
            throw new QueryError("Unknown database schema $strSchema");
268
        }
269
        return true;
270
    }
271
272
    /**
273
     * Method to set a MYSQL variable
274
     *
275
     * @param string $strName
276
     *      Name of the SQL variable to set
277
     * @param string $strVal
278
     *      Value for the variable
279
     *
280
     * @return bool
281
     */
282
    public function setVar(string $strName, ?string $strVal): bool
283 131
    {
284
        if (empty($strName)) {
285
            $this->_logger->debug("name is blank", [
286 131
                'name'  => $strName
287 131
            ]);
288
            return false;
289
        }
290
291
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
292 131
293
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
294 131
            $this->_logger->info("Var set");
295
            return true;
296 131
        } else {
297 131
            $this->_logger->error("Failed to set variable {$this->_c->error}");
298
            return false;
299
        }
300
    }
301 131
302 131
    /**
303
     * Function to execute the statement
304
     *
305
     * @param int $return
306 131
     *            [optional]
307
     *            MYSQLI constant to control what is returned from the mysqli_result object
308
     * @param string $strSql
309
     *            [optional]
310
     *            Optional SQL query
311
     *
312
     * @throws QueryError
313 131
     * @throws ConnectError
314 1
     *
315 1
     * @return mixed
316 131
     */
317
    public function execute(int $return = MYSQLI_OBJECT, ?string $strSql = null)
318
    {
319 131
        if (! is_null($strSql)) {
320
            $this->_sql = $strSql;
321
        }
322
323
        $this->_result = false;
324
        $this->_insertId = null;
325 131
326 131
        $this->isConnected();
327 131
328
        $this->_logger->info("Executing {$this->_queryType} query");
329
        $this->_logger->debug($this->_sql);
330
331
        try {
332
            if (in_array($this->_queryType, [
333 131
                DBConst::SELECT,
334
                DBConst::SELECT_COUNT
335
            ])) {
336 131
                $this->_result = $this->_c->query($this->_sql);
337
                if ($this->_c->error) {
338
                    $this->_logger->error("There is an error {$this->_c->error}");
339
                    $this->_logger->debug("Errored on query", [$this->_sql]);
340
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
341
                }
342 131
            } else {
343 131
                $this->_result = $this->_c->real_query($this->_sql);
344 131
                if ($this->_c->errno) {
345 131
                    $this->_logger->error("There was an error {$this->_c->error}");
346 131
                    $this->_logger->debug("Errored on query", [$this->_sql]);
347
                    throw new QueryError("There was an error {$this->_c->error}", E_ERROR);
348
                }
349 131
            }
350 131
351 131
            $this->_logger->debug("Checking for query results");
352
            $this->_result = $this->checkResults($return);
353
        } catch (QueryError $e) {
354
            $this->_logger->error($e);
355
        }
356
357
        return $this->_result;
358
    }
359
360 17
    /**
361
     * Function to check the results and return what is expected
362 17
     *
363
     * @param mixed $returnType
364 17
     *            [optional]
365 16
     *            Optional return mysqli_result return type
366
     *
367 1
     * @return mixed
368
     */
369
    protected function checkResults(int $returnType)
370 16
    {
371
        $res = null;
372
373
        // check the sql results and process appropriately
374
        if (in_array($this->_queryType, [DBConst::CREATE_TABLE, DBConst::ALTER_TABLE, DBConst::TRUNCATE, DBConst::DROP])) {
375
            $res = $this->_result;
376
        } elseif (in_array($this->_queryType, [DBConst::INSERT, DBConst::EXTENDED_INSERT, DBConst::DELETE, DBConst::UPDATE, DBConst::EXTENDED_UPDATE, DBConst::REPLACE, DBConst::EXTENDED_REPLACE, DBConst::DELETE])) {
377
            $res = $this->_c->affected_rows;
378
379
            if (in_array($this->_queryType, [DBConst::INSERT, DBConst::REPLACE, DBConst::EXTENDED_INSERT])) {
380 1
                $this->_insertId = $this->_c->insert_id;
381
            }
382
        } elseif ($this->_queryType == DBConst::SELECT_COUNT) {
383 1
            if (! is_a($this->_result, 'mysqli_result')) {
384 1
                $this->_logger->error("Error with return on query");
385 1
                return null;
386
            }
387
388
            if ($this->_result->num_rows == 1) {
389
                $row = $this->_result->fetch_assoc();
390
                if (isset($row['count'])) {
391
                    $this->_logger->debug("Returning SELECT_COUNT query", [
392
                        'count' => $row['count']
393 1
                    ]);
394
                    $res = $row['count'];
395 1
                }
396
            } elseif ($this->_result->num_rows > 1) {
397 1
                $this->_logger->debug("Returning SELECT_COUNT query", [
398 1
                    'count' => $this->_result->num_rows
399
                ]);
400
                $res = $this->_result->num_rows;
401
            }
402
403
            mysqli_free_result($this->_result);
404
        } else {
405
            $method = "mysqli_fetch_object";
406 1
            if ($returnType == MYSQLI_ASSOC) {
407
                $method = "mysqli_fetch_assoc";
408 1
            } elseif ($returnType == MYSQLI_NUM) {
409 1
                $method = "mysqli_fetch_array";
410
            }
411
412
            if (is_a($this->_result, 'mysqli_result')) {
413
                if ($this->_result->num_rows > 1) {
414
                    $res = [];
415
                    while ($row = call_user_func($method, $this->_result)) {
416
                        $res[] = $row;
417
                    }
418
                } else {
419
                    $res = call_user_func($method, $this->_result);
420 101
                }
421
            } else {
422 101
                $this->_logger->error("Error with return on query");
423 101
                return null;
424
            }
425 101
        }
426
427
        if ($this->_c->error) {
428
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
429
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
430
            return null;
431
        }
432
433 67
        return $res;
434
    }
435 67
436 67
    /**
437 67
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
438
     * Nothing is escaped
439
     *
440
     * @param string $strSql
441
     *            [optional]
442
     *            Optional query to pass in and execute
443
     *
444
     * @return \mysqli_result|bool
445
     */
446 1
    public function query(?string $strSql = null)
447
    {
448 1
        return is_null($strSql) ? $this->_c->query($this->_sql) : $this->_c->query($strSql);
449 1
    }
450
451 1
    /**
452 1
     * A function to build a select query
453
     *
454
     * @param string $strTableName
455
     *            The table to query
456
     * @param array|string $fields
457
     *            [optional]
458
     *            Optional array of fields to return (defaults to '*')
459
     * @param array:DBWhere|DBWhere $arrWhere
460
     *            [optional] Where clause data
461
     * @param array $arrFlags
462
     *            [optional]
463
     *            Optional 2-dimensional array to allow other flags
464
     *
465
     * @see Database::flags()
466
     *
467 2
     * @throws Exception
468
     * @throws InvalidArgumentException
469 2
     *
470 2
     * @return mixed
471 1
     *
472 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
473
     */
474 1
    public function select(string $strTableName, $fields = null, $arrWhere = [], ?array $arrFlags = [])
475
    {
476
        $this->_sql = null;
477
        $this->setQueryType(DBConst::SELECT);
478
        $this->checkTableName($strTableName);
479
480
        // starting building the query
481
        $this->_logger->debug("Starting SELECT query of {$strTableName}", [
482
            'fields' => $this->fields($fields)
483
        ]);
484
        $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
485
486
        // add in any joins
487 131
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
488
            $this->_logger->debug("Adding joins", [
489 131
                'joins' => implode(' ', $arrFlags['joins'])
490 1
            ]);
491 1
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
492
        } else {
493 1
            $this->_logger->debug("No joins");
494
        }
495
496 131
        // parse the where clauses
497
        $where = $this->parseClause($arrWhere);
498 131
499 131
        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...
500 131
            $where_str = " WHERE";
501
            $this->_logger->debug("Parsing where clause and adding to query");
502 1
            foreach ($where as $x => $w) {
503 1
                if ($x > 0) {
504
                    $where_str .= " {$w->sqlOperator}";
505
                }
506
                $where_str .= $w;
507
            }
508
            if (strlen($where_str) > strlen(" WHERE")) {
509
                $this->_sql .= $where_str;
510
            }
511
        }
512
513
        // search for any other flags (order, having, group)
514
        if (is_array($arrFlags) && count($arrFlags)) {
515
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
516
            $this->_sql .= $this->flags($arrFlags);
517
        }
518
519
        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...
520
            return $this->execute();
521
        }
522 15
523
        return $this->_sql;
524 15
    }
525 2
526
    /**
527
     * Function to build a query to check the number of rows in a table
528 15
     *
529 15
     * @param string $strTableName
530
     *            The table to query
531 15
     * @param array:DBWhere|DBWhere $arrWhere
532
     *            [optional]
533 15
     *            Optional 2-dimensional array to build where clause
534 15
     * @param array $arrFlags
535
     *            [optional]
536
     *            Optional 2-dimensional array to add flags
537 15
     *
538 15
     * @see Database::flags()
539 15
     *
540
     * @return string|int|NULL
541 6
     *
542 6
     * @throws InvalidArgumentException
543
     *
544
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
545
     */
546
    public function selectCount(string $strTableName, $arrWhere = [], ?array $arrFlags = [])
547
    {
548 9
        $this->_sql = null;
549 9
        $this->setQueryType(DBConst::SELECT_COUNT);
550 1
        $this->checkTableName($strTableName);
551 1
552 1
        // start building query
553
        $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
554
555
        // add in any joins
556 14
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
557 14
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
558 1
        }
559 1
560
        // parse where clauses
561
        $where = $this->parseClause($arrWhere);
562 15
563
        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...
564
            $where_str = " WHERE";
565
            $this->_logger->debug("Parsing where clause and adding to query");
566
            foreach ($where as $x => $w) {
567
                if ($x > 0) {
568
                    $where_str .= " {$w->sqlOperator}";
569
                }
570
                $where_str .= $w;
571
            }
572
            if (strlen($where_str) > strlen(" WHERE")) {
573
                $this->_sql .= $where_str;
574 14
            }
575
        }
576 14
577
        // add in additional flags (group, having, order)
578
        if (is_array($arrFlags) && count($arrFlags)) {
579 14
            $this->_sql .= $this->flags($arrFlags);
580 5
        }
581 9
582 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...
583
            return $this->execute();
584 2
        }
585 2
586
        return $this->_sql;
587 7
    }
588 1
589
    /**
590
     * Function to build an insert query statement
591
     *
592
     * @param string $strTableName
593 1
     * @param mixed $arrParams
594 1
     * @param bool $blnToIgnore
595 1
     *
596 1
     * @return string|NULL
597 1
     *
598
     * @throws InvalidArgumentException
599 1
     * @throws MissingInterfaceAndMethods
600
     *
601
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
602
     */
603
    public function insert(string $strTableName, $params, bool $blnToIgnore = false)
604
    {
605
        $this->_sql = null;
606
        $this->setQueryType(DBConst::INSERT);
607
        $this->checkTableName($strTableName);
608 1
609
        // start building query
610 6
        $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
611 6
612 1
        $paramType = $this->checkParamType($params);
613 5
614 1
        // add in field parameters and values
615
        if ($paramType == DBConst::ARRAY_PRIMATIVE) {
616
            $keys = array_keys($params);
617 6
            $vals = array_values($params);
618 5
            $this->_sql .= " (`" . implode("`,`", $keys) . "`)";
619 3
            $this->_sql .= " VALUES (" . implode(",", array_map([
620 3
                $this,
621 3
                '_escape'
622
            ], $vals)) . ")";
623
        } elseif ($paramType == DBConst::STRING_SELECT) {
624 2
            $this->_sql .= " {$params}";
625
        } elseif ($paramType == DBConst::OBJECT) {
626
            $arr = $params->insert();
627 1
            $keys = array_keys($arr);
628 1
            $vals = array_values($arr);
629
            $this->_sql .= " (`" . implode("`,`", $keys) . "`) VALUES ";
630
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], $vals)) . ")";
631
        } else {
632 13
            throw new InvalidArgumentException("Invalid param type");
633
        }
634
635
        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...
636
            return $this->execute();
637
        }
638 13
639
        return $this->_sql;
640
    }
641
642
    /**
643
     * Function to create an extended insert query statement
644
     *
645
     * @param string $strTableName
646
     *            The table name that the data is going to be inserted on
647
     * @param array $arrFields
648
     *            An array of field names that each value represents
649
     * @param mixed $params
650
     *            An array of array of values or a string with a SELECT statement to populate the insert with
651 2
     * @param bool $blnToIgnore
652
     *            [optional]
653 2
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
654
     *
655
     * @return NULL|string Returns the SQL if PHP_DB_AUTORUN is set to false, else it returns the output from running.
656
     *
657
     * @throws InvalidArgumentException
658
     * @throws MissingOrInvalidParam
659
     * @throws MissingInterfaceAndMethods
660
     *
661
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
662
     */
663
    public function extendedInsert(string $strTableName, array $arrFields, $params, bool $blnToIgnore = false)
664
    {
665
        $this->_sql = null;
666
        $this->setQueryType(DBConst::EXTENDED_INSERT);
667
        $this->checkTableName($strTableName);
668
669
        if ($this->checkParamType($arrFields) !== DBConst::ARRAY_PRIMATIVE) {
670
            throw new InvalidArgumentException('Invalid field list type');
671
        }
672
673
        // start building query
674
        $this->_sql = "INSERT ".
675
            ($blnToIgnore ? "IGNORE " : "").
676
            "INTO $strTableName ".
677
            "(`".implode("`,`", $arrFields)."`)";
678
679 37
        $paramType = $this->checkParamType($params);
680
        $this->_sql .= " VALUES ";
681 37
682 37
        if ($paramType == DBConst::COLLECTION || $paramType == DBConst::ARRAY_OBJECT) {
683
            foreach ($params as $p) {
684
                $key_value = $p->insert();
685 37
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . "),";
686 37
            }
687 37
688
            $this->_sql = substr($this->_sql, 0, -1);
689 36
        } elseif ($paramType == DBConst::ARRAY) {
690
            foreach ($params as $p) {
691
                if (count($p) != count($arrFields)) {
692
                    $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
693 36
                        $p,
694 1
                        debug_backtrace()
695 1
                    ]);
696
                    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

696
                    throw new MissingOrInvalidParam("Inconsistent number of fields in fields and values in extendedInsert " . /** @scrutinizer ignore-type */ print_r($p, true));
Loading history...
697 1
                }
698
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
699 35
700
                if ($p != end($params)) {
701
                    $this->_sql .= ",";
702
                }
703 36
            }
704
        } elseif ($paramType == DBConst::ARRAY_PRIMATIVE) {
705 34
            foreach ($params as $p) {
706 8
                $this->_sql .= "(".$this->_escape($p)."),";
707 8
            }
708 8
            $this->_sql = substr($this->_sql, 0, -1);
709 8
        } else {
710 1
            throw new InvalidArgumentException("Invalid param type in extendedInsert");
711
        }
712 8
713
        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...
714 8
            return $this->execute();
715 8
        }
716
717
        return $this->_sql;
718
    }
719
720 34
    /**
721 9
     * Build a statement to update a table
722 9
     *
723
     * @param string $strTableName
724
     *            The table name to update
725 32
     * @param mixed $params
726
     *            Name/value pairs of the field name and value
727
     * @param array:DBWhere|DBWhere $arrWhere
728
     *            [optional]
729 32
     *            DBWhere clauses
730
     * @param array $arrFlags
731
     *            [optional]
732
     *            Two-dimensional array to create other flag options (joins, order, and group)
733
     *
734
     * @see Database::flags()
735
     *
736
     * @return NULL|string
737
     *
738
     * @throws InvalidArgumentException
739
     * @throws MissingOrInvalidParam
740
     * @throws MissingInterfaceAndMethods
741
     *
742
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
743
     */
744
    public function update(string $strTableName, $params, $arrWhere = [], ?array $arrFlags = [])
745
    {
746
        $this->_sql = "UPDATE ";
747
        $this->setQueryType(DBConst::UPDATE);
748
        $this->checkTableName($strTableName);
749
750
        $this->_sql .= $strTableName;
751
752 4
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
753
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
754 4
            unset($arrFlags['joins']);
755 4
        }
756
757
        $this->_sql .= " SET ";
758 4
759 4
        $paramType = $this->checkParamType($params);
760
761
        if ($paramType == DBConst::ARRAY_PRIMATIVE || $paramType == DBConst::ARRAY) {
762
            $keys = array_keys($params);
763 4
            foreach ($params as $f => $p) {
764 1
                $field = $f;
765
                if ((strpos($f, "`") === false) &&
766
                    (strpos($f, ".") === false) &&
767
                    (strpos($f, "*") === false) &&
768 4
                    (stripos($f, " as ") === false)
769
                ) {
770 4
                    $field = "`{$f}`";
771 2
                }
772 2
773 2
                if (! is_null($p)) {
774 2
                    $this->_sql .= "$field={$this->_escape($p)}";
775 1
                } else {
776
                    $this->_sql .= "$field=NULL";
777 2
                }
778
779 2
                if ($f != end($keys)) {
780 2
                    $this->_sql .= ",";
781
                }
782
            }
783
        } elseif ($paramType == DBConst::OBJECT) {
784
            $key_value = $params->update();
785 4
            $fields = array_keys($key_value);
786 1
            $values = array_map([$this, '_escape'], array_values($key_value));
787
            foreach ($fields as $x => $f) {
788
                if ($x > 0) {
789 4
                    $this->_sql .= ",";
790
                }
791
                $this->_sql .= "`{$f}`={$values[$x]}";
792
            }
793 4
        }
794
795
        $where = $this->parseClause($arrWhere);
796
797
        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...
798
            $where_str = " WHERE";
799
            $this->_logger->debug("Parsing where clause and adding to query");
800
            foreach ($where as $x => $w) {
801
                if ($x > 0) {
802
                    $where_str .= " {$w->sqlOperator}";
803
                }
804
                $where_str .= $w;
805
            }
806
            if (strlen($where_str) > strlen(" WHERE")) {
807
                $this->_sql .= $where_str;
808
            }
809
        }
810 7
811
        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 7
            return $this->execute();
813 7
        }
814
815
        return $this->_sql;
816 7
    }
817 7
818
    /**
819
     * Function to offer an extended updated functionality by using two different tables.
820 7
     *
821
     * @param string $strTableToUpdate
822
     *            The table that you want to update (alias 'tbu' is automatically added)
823 6
     * @param string $strOriginalTable
824 3
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
825 3
     * @param string $strLinkField
826 3
     *            The common index value between them that will join the fields
827 3
     * @param array|string $arrParams
828 3
     *            If string only a single field is updated (tbu.$params = o.$params)
829 3
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
830 3
     *
831 3
     * @return mixed
832 1
     *
833 2
     * @throws InvalidArgumentException
834 1
     *
835 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
836 1
     */
837 1
    public function extendedUpdate(string $strTableToUpdate, string $strOriginalTable, string $strLinkField, $arrParams)
838 1
    {
839
        $this->_sql = "UPDATE ";
840 1
        $this->setQueryType(DBConst::EXTENDED_UPDATE);
841
842
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
843 5
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
844
        }
845
846
        $paramType = $this->checkParamType($arrParams);
847 5
848
        if ($paramType == DBConst::ARRAY_PRIMATIVE) {
849
            foreach ($arrParams as $param) {
850
                if ($param != $strLinkField) {
851
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
852
                }
853
            }
854
            $this->_sql = substr($this->_sql, 0, - 1);
855
        } elseif ($paramType == DBConst::STRING) {
856
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
857
        } else {
858
            throw new InvalidArgumentException("Do not understand datatype " . gettype($arrParams), E_ERROR);
859
        }
860
861
        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...
862
            return $this->execute();
863
        }
864
865
        return $this->_sql;
866
    }
867
868
    /**
869
     * Function to build a replace query
870
     *
871 8
     * @param string $strTableName
872
     *            The table to update
873 8
     * @param mixed $arrParams
874 8
     *            Name/value pair to insert
875
     *
876
     * @return NULL|string
877 8
     *
878 8
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
879 8
     */
880 8
    public function replace(string $strTableName, $params)
881 8
    {
882
        $this->_sql = null;
883
        $this->setQueryType(DBConst::REPLACE);
884 8
        $this->checkTableName($strTableName);
885 6
886
        $this->_sql = "REPLACE INTO $strTableName ";
887 6
888 2
        $paramType = $this->checkParamType($params);
889 2
890 2
        if ($paramType == DBConst::ARRAY || $paramType == DBConst::ARRAY_PRIMATIVE) {
891
            $keys = array_keys($params);
892
            $vals = array_values($params);
893 2
894 4
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
895
            $this->_sql .= " VALUES (" . implode(",", array_map([
896
                $this,
897 4
                '_escape'
898 4
            ], array_values($vals))) . ")";
899 4
        } elseif ($paramType == DBConst::OBJECT) {
900 2
            $key_value = $params->replace();
901 2
            $this->_sql .= "(`" . implode("`,`", array_keys($key_value)) . "`) VALUES ";
902 2
            $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($key_value))) . ")";
903
        } else {
904 2
            throw new MissingOrInvalidParam('Arrays or Objects that implement DBInterface are the only valid types for replace');
905
        }
906 3
907
        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...
908 3
            return $this->execute();
909 3
        }
910
911
        return $this->_sql;
912
    }
913
914
    /**
915
     * Function to build an extended replace statement
916 4
     *
917
     * @param string $strTableName
918
     *            Table name to update
919
     * @param array $arrFields
920 4
     *            Array of fields
921
     * @param mixed $arrParams
922
     *            Two-dimensional array of values
923
     *
924
     * @return NULL|string
925
     *
926
     * @throws InvalidArgumentException
927
     *
928
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
929
     */
930
    public function extendedReplace(string $strTableName, array $arrFields, $params)
931
    {
932
        $this->_sql = null;
933
        $this->setQueryType(DBConst::EXTENDED_REPLACE);
934
        $this->checkTableName($strTableName);
935
936
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
937
            throw new InvalidArgumentException("Error with the field type");
938
        }
939
940
        $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
941
942
        $paramType = $this->checkParamType($params);
943
        $this->_sql .= " VALUES ";
944
945
        if ($paramType == DBConst::ARRAY) {
946
            foreach ($params as $p) {
947 9
                $this->_sql .= "(" . implode(",", array_map([
948
                    $this,
949 9
                    '_escape'
950 9
                ], array_values($p))) . ")";
951
952 9
                if ($p != end($params)) {
953 9
                    $this->_sql .= ",";
954
                }
955 9
            }
956 1
        } elseif ($paramType == DBConst::COLLECTION || $paramType == DBConst::ARRAY_OBJECT) {
957 1
            if ($paramType == DBConst::COLLECTION) {
958
                $last = $params->last();
959
            } elseif ($paramType == DBConst::ARRAY_OBJECT) {
960 9
                $last = end($params);
961
            }
962
963 9
            foreach ($params as $p) {
964
                $key_value = $p->replace();
965 7
                $this->_sql .= "(" . implode(",", array_map([
966 6
                    $this,
967 6
                    '_escape'
968 6
                ], array_values($key_value))) . ")";
969 6
970 6
                if ($p != $last) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last does not seem to be defined for all execution paths leading up to this point.
Loading history...
971 5
                    $this->_sql .= ",";
972 5
                }
973
            }
974 5
        } elseif ($paramType == DBConst::ARRAY_PRIMATIVE) {
975
            foreach ($params as $p) {
976
                $this->_sql .= "(".$this->_escape($p)."),";
977 6
            }
978 5
            $this->_sql = substr($this->_sql, 0, -1);
979
        }
980 1
981
        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...
982
            return $this->execute();
983 6
        }
984 1
985
        return $this->_sql;
986
    }
987 1
988 1
    /**
989 1
     * Function to build a delete statement
990 1
     *
991 1
     * @param string $strTableName
992 1
     *            Table name to act on
993 1
     * @param array $arrFields
994
     *            [optional]
995 1
     *            Optional list of fields to delete (used when including multiple tables)
996
     * @param array:DBWhere|DBWhere $arrWhere
997
     *            [optional]
998
     *            Optional where clauses to use
999
     * @param array $arrJoins
1000
     *            [optional]
1001 7
     *            Optional 2-dimensional array to add other flags
1002
     *
1003 7
     * @see Database::flags()
1004 3
     *
1005 3
     * @return string|NULL
1006 3
     *
1007 3
     * @throws InvalidArgumentException
1008 1
     *
1009
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1010 3
     */
1011
    public function delete(string $strTableName, ?array $arrFields = [], $arrWhere = [], ?array $arrJoins = [])
1012 3
    {
1013 3
        $this->_sql = "DELETE";
1014
        $this->setQueryType(DBConst::DELETE);
1015
        $this->checkTableName($strTableName);
1016
1017 7
        $this->_logger->debug("Deleting table data");
1018
1019
        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...
1020
            $this->_sql .= " " . implode(",", $arrFields);
1021 7
        }
1022
1023
        $this->_sql .= " FROM $strTableName";
1024
1025 7
        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...
1026
            $this->_sql .= " " . implode(" ", $arrJoins);
1027
        }
1028
1029
        $where = $this->parseClause($arrWhere);
1030
1031
        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...
1032
            $where_str = " WHERE";
1033
            $this->_logger->debug("Parsing where clause and adding to query");
1034
            foreach ($where as $x => $w) {
1035
                if ($x > 0) {
1036
                    $where_str .= " {$w->sqlOperator}";
1037
                }
1038
                $where_str .= $w;
1039
            }
1040
            if (strlen($where_str) > strlen(" WHERE")) {
1041
                $this->_sql .= $where_str;
1042
            }
1043
        }
1044
1045
        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...
1046
            return $this->execute();
1047 2
        }
1048
1049 2
        return $this->_sql;
1050 2
    }
1051
1052 2
    /**
1053 2
     * Function to build a drop table statement (automatically executes)
1054
     *
1055
     * @param string $strTableName
1056 2
     *            Table to drop
1057 2
     * @param string $strType
1058 2
     *            [optional]
1059 2
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1060
     * @param bool $blnIsTemp
1061
     *            [optional]
1062 2
     *            Optional bool if this is a temporary table
1063
     *
1064
     * @return string|NULL
1065
     *
1066
     * @throws InvalidArgumentException
1067
     *
1068
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1069 2
     */
1070
    public function drop(string $strTableName, string $strType = 'table', bool $blnIsTemp = false)
1071
    {
1072
        $this->_sql = null;
1073 2
        $this->setQueryType(DBConst::DROP);
1074
        $this->checkTableName($strTableName);
1075
1076
        switch ($strType) {
1077
            case 'table':
1078
                $strType = 'TABLE';
1079
                break;
1080
            case 'view':
1081
                $strType = 'VIEW';
1082
                break;
1083
            default:
1084
                throw new InvalidArgumentException("Invalid type $strType", E_ERROR);
1085
        }
1086
1087
        $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1088 2
1089
        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...
1090 2
            return $this->execute();
1091 2
        }
1092
1093 2
        return $this->_sql;
1094 2
    }
1095
1096
    /**
1097 2
     * Function to build a truncate table statement (automatically executes)
1098
     *
1099 2
     * @param string $strTableName
1100 1
     *            Table to truncate
1101 1
     *
1102
     * @return string|NULL
1103 1
     *
1104 1
     * @throws InvalidArgumentException
1105 1
     *
1106 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1107 1
     */
1108 1
    public function truncate(string $strTableName)
1109 1
    {
1110 1
        $this->_sql = null;
1111 1
        $this->setQueryType(DBConst::TRUNCATE);
1112
        $this->checkTableName($strTableName);
1113
1114
        $this->_sql = "TRUNCATE TABLE $strTableName";
1115
1116 2
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_AUTORUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1117
            return $this->execute();
1118
        }
1119
1120 2
        return $this->_sql;
1121
    }
1122
1123
    /**
1124
     * Function to build a create temporary table statement
1125
     *
1126
     * @param string $strTableName
1127
     *            Name to give the table when creating
1128
     * @param bool $blnIsTemp
1129
     *            [optional]
1130
     *            Optional bool to make the table a temporary table
1131
     * @param mixed $strSelect
1132
     *            [optional]
1133
     *            Optional parameter if null uses last built statement
1134
     *            If string, will be made the SQL statement executed to create the table
1135
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1136
     *
1137
     * @return NULL|string
1138
     *
1139 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1140
     */
1141 1
    public function createTable(string $strTableName, bool $blnIsTemp = false, $strSelect = null)
1142 1
    {
1143
        $this->setQueryType(DBConst::CREATE_TABLE);
1144 1
1145
        $this->checkTableName($strTableName);
1146
1147
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1148 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1149 1
        } elseif (is_string($strSelect)) {
1150
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1151
        } elseif (is_array($strSelect)) {
1152 1
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1153 1
1154
            foreach ($strSelect as $field) {
1155 1
                $default = null;
1156 1
                if (is_a($field, 'Godsgood33\Php_Db\DBCreateTable')) {
1157 1
                    $this->_sql .= (string) $field . ",";
1158 1
                } elseif (is_array($field)) {
1159 1
                    if (isset($field['default'])) {
1160 1
                        $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1161
                    }
1162 1
                    $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1163 1
                }
1164
            }
1165
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1166
        }
1167
1168
        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...
1169
            return $this->execute();
1170
        }
1171
1172
        return $this->_sql;
1173
    }
1174
1175
    /**
1176
     * Function to create a table using a stdClass object derived from JSON
1177
     *
1178
     * @param stdClass $json
1179
     * @param bool $blnDropFirst bool to decide if you want to drop the table first
1180 1
     *
1181
     * @example /examples/create_table_json.json
1182
     *
1183
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1184 1
     */
1185
    public function createTableJson($json, bool $blnDropFirst = false)
1186
    {
1187
        $this->setQueryType(DBConst::CREATE_TABLE);
1188
        $this->_c->select_db($json->schema);
1189
1190
        if ($blnDropFirst) {
1191
            $this->drop($json->name);
1192
            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...
1193
                $this->execute();
1194
            }
1195
        }
1196
1197
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1198
        foreach ($json->fields as $field) {
1199
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1200
1201
            if ($field->dataType == 'enum' && isset($field->values)) {
1202
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1203
            }
1204
1205
            if (isset($field->ai) && $field->ai) {
1206
                $this->_sql .= " AUTO_INCREMENT";
1207
            }
1208
1209
            if (isset($field->nn) && $field->nn) {
1210 4
                $this->_sql .= " NOT NULL";
1211
            } elseif (isset($field->default)) {
1212 4
                if (strtolower($field->default) == 'null') {
1213 4
                    $this->_sql .= " DEFAULT NULL";
1214
                } elseif (strlen($field->default)) {
1215 4
                    $this->_sql .= " DEFAULT '{$field->default}'";
1216
                }
1217 4
            }
1218 1
1219
            if ($field != end($json->fields)) {
1220
                $this->_sql .= ",";
1221 4
            }
1222 4
        }
1223
1224
        if (isset($json->index) && count($json->index)) {
1225 4
            foreach ($json->index as $ind) {
1226 1
                $ref = null;
1227
                if (is_array($ind->ref)) {
1228
                    $ref = "";
1229 4
                    foreach ($ind->ref as $r) {
1230
                        $ref .= "`{$r}` ASC,";
1231 4
                    }
1232 2
                    $ref = substr($ref, 0, -1);
1233 2
                } elseif (is_string($ind->ref)) {
1234 2
                    $ref = $ind->ref;
1235 2
                }
1236 1
                if (!is_null($ref)) {
1237
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1238 2
                }
1239
            }
1240 2
        }
1241 2
1242
        if (isset($json->constraints) && count($json->constraints)) {
1243
            foreach ($json->constraints as $con) {
1244
                $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));
1245 4
            }
1246
        }
1247
1248
        if (isset($json->unique) && count($json->unique)) {
1249 4
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1250
        }
1251
1252
        if (isset($json->primary_key) && count($json->primary_key)) {
1253
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1254
        } else {
1255
            if (substr($this->_sql, - 1) == ',') {
1256
                $this->_sql = substr($this->_sql, 0, - 1);
1257
            }
1258
1259
            $this->_sql .= ")";
1260
        }
1261
1262
        if (defined("PHP_DB_AUTORUN") && PHP_DB_AUTORUN) {
1263
            return $this->execute();
1264
        }
1265
1266
        return $this->_sql;
1267
    }
1268
1269
    /**
1270 5
     * Method to add a column to the database (only one at a time!)
1271
     *
1272 5
     * @param string $strTableName
1273 5
     * @param stdClass $params
1274
     *
1275 5
     * @return string|mixed
1276 5
     *
1277 4
     * @throws InvalidArgumentException
1278 4
     *
1279 1
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1280 1
     */
1281 1
    public function addColumn(string $strTableName, stdClass $params)
1282
    {
1283
        $this->setQueryType(DBConst::ALTER_TABLE);
1284
        $this->checkTableName($strTableName);
1285
1286 5
        $this->_sql = "ALTER TABLE {$strTableName} ADD COLUMN";
1287 5
1288
        if (!self::checkObject($params, ['name', 'dataType'])) {
1289
            $this->_logger->error("Missing elements for the addColumn method (need 'name', 'dataType')", [$params]);
1290 5
            throw new InvalidArgumentException("Missing elements for the addColumn method");
1291
        }
1292
1293
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1294 5
        $default = null;
1295
        if ($params->default === null) {
1296
            $default = " DEFAULT NULL";
1297
        } elseif (strlen($params->default)) {
1298
            $default = " DEFAULT {$this->_escape($params->default)}";
1299
        }
1300
        $this->_sql .= " `{$params->name}` {$params->dataType}" . $nn . $default;
1301
1302
        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...
1303
            return $this->execute();
1304
        }
1305
1306
        return $this->_sql;
1307
    }
1308
1309 1
    /**
1310
     * Method to drop a fields from a table
1311 1
     *
1312 1
     * @param string $strTableName
1313
     * @param string|array:string $params
1314 1
     *
1315 1
     * @return string|mixed
1316
     *
1317
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1318 1
     */
1319
    public function dropColumn(string $strTableName, $params)
1320
    {
1321
        $this->setQueryType(DBConst::ALTER_TABLE);
1322 1
        $this->_sql = "ALTER TABLE {$strTableName} DROP COLUMN";
1323
1324
        if (is_array($params) && count($params)) {
1325
            foreach ($params as $col) {
1326
                $this->_sql .= " `{$col->name}`";
1327
1328
                if ($col != end($params)) {
1329
                    $this->_sql .= ",";
1330
                }
1331
            }
1332
        } elseif (is_string($params)) {
1333
            $this->_sql .= " `{$params}`";
1334
        }
1335
1336
        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...
1337
            return $this->execute();
1338
        }
1339
1340
        return $this->_sql;
1341
    }
1342
1343 4
    /**
1344
     * Method to modify a field to change it's datatype, name, or other parameter
1345 4
     *
1346
     * @param string $strTableName
1347 4
     * @param stdClass $params
1348 1
     *
1349 3
     * @return string|mixed
1350 1
     *
1351 2
     * @throws InvalidArgumentException
1352 2
     *
1353
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1354 2
     */
1355 2
    public function modifyColumn($strTableName, $params)
1356 2
    {
1357 2
        $this->setQueryType(DBConst::ALTER_TABLE);
1358
        $this->_sql = "ALTER TABLE {$strTableName} MODIFY COLUMN";
1359
1360
        if (!self::checkObject($params, ['name', 'dataType'])) {
1361
            $this->_logger->error("Missing elements to the modifyColumn method (need 'name' and 'dataType')", [$params]);
1362
            throw new InvalidArgumentException("Missing elements to the modifyColumn method");
1363
        }
1364
1365 2
        if (!isset($params->new_name)) {
1366
            $params->new_name = $params->name;
1367
        }
1368 4
1369
        $nn = (isset($params->nn) && $params->nn ? " NOT NULL" : "");
1370
        $default = null;
1371
        if ($params->default === null) {
1372 4
            $default = " DEFAULT NULL";
1373
        } elseif (strlen($params->default)) {
1374
            $default = " DEFAULT {$this->_escape($params->default)}";
1375
        }
1376
        $this->_sql .= " `{$params->name}` `{$params->new_name}` {$params->dataType}" . $nn . $default;
1377
1378
        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...
1379
            return $this->execute();
1380
        }
1381
1382
        return $this->_sql;
1383
    }
1384
1385 3
    /**
1386
     * Method to add a constraint to a table
1387 3
     *
1388 3
     * @param string $strTableName
1389
     * @param stdClass $params
1390 3
     *
1391
     * @return string|mixed
1392
     *
1393
     * @throws InvalidArgumentException
1394
     *
1395
     * @uses PHP_DB_AUTORUN bool to decide if the statement should be auto-committed and the results returned instead of the statement
1396
     */
1397 3
    public function addConstraint($strTableName, $params)
1398 3
    {
1399 3
        $this->setQueryType(DBConst::ALTER_TABLE);
1400
        $this->_sql = "ALTER TABLE {$strTableName} ADD CONSTRAINT";
1401 3
1402 1
        if (!is_a($params, 'stdClass')) {
1403
            $this->_logger->critical("Error in reading constraint field");
1404
            throw new InvalidArgumentException("Error in reading constraint field");
1405 3
        }
1406 3
1407
        if (!self::checkObject($params, ['id', 'local', 'schema', 'table', 'field', 'delete', 'update'])) {
1408
            $this->_logger->error("Missing elements in the addConstraint method (need 'id', 'local', 'schema', 'table', 'field', 'delete', 'update')", [$params]);
1409 3
            throw new InvalidArgumentException("There are some missing elements for the addConstraint action");
1410 3
        }
1411 2
1412 2
        if (!in_array(strtoupper($params->delete), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1413 1
            $this->_logger->error("Invalid action for deletion on addConstraint");
1414 1
            throw new InvalidArgumentException("Invalid action for deletion on addConstraint");
1415 1
        }
1416
1417
        if (!in_array(strtoupper($params->update), ['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION'])) {
1418
            $this->_logger->error("Invalid action for update on addConstraint");
1419 3
            throw new InvalidArgumentException("Invalid action for update on addConstraint");
1420 2
        }
1421
1422
        if (is_array($params->field) && is_array($params->local)) {
1423
            $field = "`" . implode("`,`", $params->field) . "`";
1424 3
            $local = "`" . implode("`,`", $params->local) . "`";
1425 1
        } elseif (is_string($params->field) && is_string($params->local)) {
1426 1
            $field = "`{$params->field}`";
1427 1
            $local = "`{$params->local}`";
1428
        } else {
1429
            throw new InvalidArgumentException("Invalid type for the field and local values both must be an array or string");
1430
        }
1431
        $this->_sql .= " `{$params->id}` FOREIGN KEY ({$local}) REFERENCES `{$params->schema}`.`{$params->table}` ({$field}) ON DELETE {$params->delete} ON UPDATE {$params->update}";
1432
1433 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...
1434 1
            return $this->execute();
1435
        }
1436 1
1437 1
        return $this->_sql;
1438
    }
1439
1440
    /**
1441
     * Check to see if a field in a table exists
1442 3
     *
1443
     * @param string $strTableName
1444
     *            Table to check
1445
     * @param string $strFieldName
1446
     *            Field name to find
1447
     *
1448 3
     * @return bool Returns TRUE if field is found in that schema and table, otherwise FALSE
1449 1
     */
1450
    public function fieldExists(string $strTableName, string $strFieldName): bool
1451
    {
1452 3
        $fdata = $this->fieldData($strTableName);
1453 3
1454
        if (is_array($fdata) && count($fdata)) {
1455
            foreach ($fdata as $field) {
1456
                if ($field->name == $strFieldName) {
1457
                    return true;
1458
                }
1459
            }
1460
        }
1461
1462 3
        return false;
1463
    }
1464
1465
    /**
1466 3
     * Function to get the column data (datatype, flags, defaults, etc)
1467
     *
1468
     * @param string $strTableName
1469
     *            Table to query
1470
     * @param mixed $field
1471
     *            [optional]
1472
     *            Optional field to retrieve data (if null, returns data from all fields)
1473
     *
1474
     * @return mixed
1475
     */
1476
    public function fieldData(string $strTableName, $field = null)
1477
    {
1478
        if (is_null($field)) {
1479
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1480
        } elseif (is_array($field)) {
1481 3
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1482
        } elseif (is_string($field)) {
1483 3
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1484 3
        } else {
1485
            return null;
1486 3
        }
1487
1488 3
        $fields = null;
1489 1
        if (is_a($res, 'mysqli_result')) {
1490 1
            $fields = $res->fetch_fields();
1491
            foreach ($fields as $i => $f) {
1492
                $fields["{$f->name}"] = $f;
1493 2
                unset($fields[$i]);
1494 2
            }
1495 2
        }
1496 1
1497 1
        return $fields;
1498 1
    }
1499
1500 2
    /**
1501
     * Function to check that all field parameters are set correctly
1502 2
     *
1503
     * @param stdClass $field_data
1504
     * @param stdClass $check
1505
     * @param array $pks
1506 2
     * @param stdClass $index
1507
     *
1508
     * @return array|string
1509
     */
1510
    public function fieldCheck(stdClass $field_data, stdClass $check, array $pks, ?array $index)
1511
    {
1512
        $default = null;
1513
        $ret = null;
1514
1515
        $nn = (isset($check->nn) && $check->nn ? " NOT NULL" : null);
1516
1517
        if ($check->default === null) {
1518
            $default = " DEFAULT NULL";
1519 3
        } elseif (strlen($check->default)) {
1520
            $default = " DEFAULT '{$check->default}'";
1521 3
        }
1522 3
1523
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1524 3
            $this->_logger->notice("Wrong datatype", [
1525 2
                'name' => $field_data->name,
1526 2
                'datatype' => $check->dataType
1527
            ]);
1528 2
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1529 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1530
            $this->_logger->notice("Incorrect size", [
1531
                'name' => $field_data->name,
1532 1
                'current' => $field_data->length,
1533 1
                'new_size' => $check->length
1534
            ]);
1535
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1536 3
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1537
            $this->_logger->notice("Setting ENUM type", [
1538
                'name' => $field_data->name,
1539
                'values' => implode(",", $check->values)
1540 3
            ]);
1541
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1542
        }
1543
1544
        if (! is_null($index) && count($index)) {
0 ignored issues
show
introduced by
The condition is_null($index) is always false.
Loading history...
1545
            foreach ($index as $ind) {
1546
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1547
                    $this->_logger->debug("Missing index", [
1548
                        'name' => $field_data->name
1549
                    ]);
1550
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1551
                }
1552
            }
1553
        }
1554
1555 3
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1556
            $this->_logger->debug("Setting PKs", [
1557 3
                'keys' => implode(',', $pks)
1558 3
            ]);
1559
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1560 3
        }
1561 1
1562 1
        return $ret;
1563
    }
1564
1565 2
    /**
1566 1
     * Function to check for the existence of a table within a schema
1567
     *
1568
     * @param string $strSchema
1569 2
     *            The schema to search in
1570 2
     * @param string $strTableName
1571 2
     *            Table to search for
1572 1
     *
1573 1
     * @return int|bool Returns number of tables that match if table is found in that schema, otherwise FALSE
1574 1
     *
1575
     * @throws InvalidArgumentException
1576 2
     */
1577
    public function tableExists($strSchema, $strTableName)
1578 2
    {
1579
        if (! $this->_c->select_db($strSchema)) {
1580
            $this->_logger->error("Schema {$strSchema} not found", [$this->_c->error]);
1581
            throw new InvalidArgumentException("Error connecting to schema {$strSchema}");
1582 2
        }
1583
1584
        if (preg_match("/[^0-9a-zA-Z\%\?\_]/", $strTableName)) {
1585
            $this->_logger->warning("Invalid table name {$strTableName}");
1586
            return false;
1587
        }
1588
1589
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1590
1591
        if ($res = $this->_c->query($sql)) {
1592
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1593
                return $res->num_rows;
1594
            }
1595
        } elseif ($this->_c->errno) {
1596
            $this->_logger->error($this->_c->error);
1597 7
        }
1598
1599 7
        return false;
1600 7
    }
1601
1602 7
    /**
1603 1
     * Function to detect if string is a JSON object or not
1604 1
     *
1605
     * @param string $strVal
1606
     *
1607 6
     * @return bool
1608 1
     */
1609 1
    public function isJson($strVal): bool
1610
    {
1611
        json_decode($strVal);
1612 5
        return (json_last_error() == JSON_ERROR_NONE);
1613 1
    }
1614 1
1615
    /**
1616
     * Function to escape SQL characters to prevent SQL injection
1617 4
     *
1618 1
     * @param mixed $val
1619 1
     *            Value to escape
1620
     * @param bool $blnEscape
1621
     *            Decide if we should escape or not
1622 3
     *
1623 1
     * @throws Exception
1624 1
     * @throws InvalidArgumentException
1625 2
     *
1626 1
     * @return string Escaped value
1627 1
     */
1628
    public function _escape($val, bool $blnEscape = true): string
1629 1
    {
1630
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1631 2
            return 'NULL';
1632
        } elseif (is_numeric($val) || is_string($val)) {
1633 2
            if ($blnEscape) {
1634
                return "'{$this->_c->real_escape_string($val)}'";
1635
            }
1636
            return $val;
1637 2
        } elseif (is_a($val, 'DateTime')) {
1638
            return "'{$val->format(MYSQL_DATETIME)}'";
1639
        } elseif (is_bool($val)) {
1640
            return $val ? "'1'" : "'0'";
1641
        } elseif (is_array($val)) {
1642
            $ret = [];
1643
            foreach ($val as $v) {
1644
                $ret[] = $this->_escape($v);
1645
            }
1646
            return "(" . implode(",", $ret) . ")";
1647
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1648
            $ret = call_user_func([
1649
                $val,
1650 2
                '_escape'
1651
            ]);
1652 2
            if ($ret !== false && is_string($ret)) {
1653
                return $ret;
1654 2
            } else {
1655 2
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1656 2
            }
1657 1
        }
1658
1659
        throw new InvalidArgumentException("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1660
    }
1661
1662 1
    /**
1663
     * Function to populate the fields for the SQL
1664
     *
1665
     * @param mixed $fields
1666
     *            [optional]
1667
     *            Optional array of fields to string together to create a field list
1668
     *
1669
     * @return string|null
1670
     *
1671
     * @throws InvalidArgumentException
1672
     */
1673
    protected function fields($fields = null): string
1674
    {
1675
        $ret = null;
1676 7
1677
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1678 7
            foreach ($fields as $field) {
1679 3
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false) && stripos($field, 'IF(') === false) {
1680 5
                    $ret .= "`$field`,";
1681 5
                } else {
1682 1
                    $ret .= "$field,";
1683 1
                }
1684
            }
1685 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

1685
            $ret = substr(/** @scrutinizer ignore-type */ $ret, -1) == ',' ? substr($ret, 0, -1) : $ret;
Loading history...
1686
        } elseif (is_a($fields, 'Godsgood33\Php_Db\DBField')) {
1687
            $ret = (string) $fields;
1688 7
        } elseif (is_array($fields) && count($fields) && isset($fields[0]) && is_a($fields[0], 'Godsgood33\Php_Db\DBField')) {
1689 7
            foreach ($fields as $f) {
1690 7
                $ret .= (string) $f.",";
1691 7
            }
1692 7
            $ret = rtrim($ret, ',');
0 ignored issues
show
Bug introduced by
It seems like $ret can also be of type null; however, parameter $string of rtrim() 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

1692
            $ret = rtrim(/** @scrutinizer ignore-type */ $ret, ',');
Loading history...
1693 7
        } elseif (is_string($fields)) {
1694
            $ret = $fields;
1695
        } elseif (is_null($fields)) {
1696
            $ret = '*';
1697 7
        } else {
1698
            throw new InvalidArgumentException("Invalid field type");
1699
        }
1700
1701
        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...
1702
    }
1703
1704
    /**
1705
     * Function to parse the flags
1706
     *
1707
     * @param array $flags
1708
     *            Two-dimensional array to added flags
1709
     *
1710 4
     *            <code>
1711
     *            [
1712 4
     *            &nbsp;&nbsp;'group' => 'field',
1713 4
     *            &nbsp;&nbsp;'having' => 'field',
1714
     *            &nbsp;&nbsp;'order' => 'field',
1715 4
     *            &nbsp;&nbsp;'start' => 0,
1716
     *            &nbsp;&nbsp;'limit' => 0
1717 4
     *            ]
1718 1
     *            </code>
1719 3
     *
1720 1
     * @see Database::groups()
1721
     * @see Database::having()
1722
     * @see Database::order()
1723 4
     *
1724 3
     * @return string
1725 3
     */
1726 3
    protected function flags(array $arrFlags)
1727
    {
1728 3
        $ret = '';
1729 1
1730 1
        if (isset($arrFlags['group'])) {
1731 1
            $ret .= $this->groups($arrFlags['group']);
1732 1
        }
1733 1
1734
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1735 1
            $having = " HAVING";
1736
            $this->_logger->debug("Parsing where clause and adding to query");
1737
            foreach ($arrFlags['having'] as $x => $h) {
1738
                if ($x > 0) {
1739
                    $having .= " {$h->sqlOperator}";
1740
                }
1741
                $having .= $h;
1742
            }
1743
            if (strlen($having) > strlen(" HAVING")) {
1744 4
                $ret .= $having;
1745
            }
1746
        }
1747
1748
        if (isset($arrFlags['order'])) {
1749
            $ret .= $this->order($arrFlags['order']);
1750
        }
1751
1752
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1753
            $ret .= " LIMIT ";
1754
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1755 4
                $ret .= "{$arrFlags['start']},";
1756
            }
1757
            $ret .= "{$arrFlags['limit']}";
1758
        }
1759
1760
        return $ret;
1761
    }
1762 4
1763
    /**
1764
     * Function to parse SQL GROUP BY statements
1765
     *
1766
     * @param mixed $groups
1767
     *
1768
     * @return string
1769
     *
1770
     * @throws InvalidArgumentException
1771
     */
1772
    protected function groups($groups): string
1773
    {
1774
        $ret = '';
1775
        if (is_array($groups) && count($groups)) {
1776
            $ret .= " GROUP BY";
1777 5
1778
            foreach ($groups as $grp) {
1779 5
                $ret .= " $grp";
1780 1
1781 1
                if ($grp != end($groups)) {
1782
                    $ret .= ",";
1783
                }
1784 4
            }
1785 1
        } elseif (is_string($groups)) {
1786 1
            $ret .= " GROUP BY {$groups}";
1787
        } else {
1788
            throw new InvalidArgumentException("Error in datatype for groups " . gettype($groups), E_ERROR);
1789 3
        }
1790
1791 3
        return $ret;
1792 3
    }
1793 2
1794
    /**
1795
     * Function to parse SQL ORDER BY statements
1796
     *
1797
     * @param mixed $order
1798
     *
1799 1
     * @return string
1800
     *
1801
     * @throws InvalidArgumentException
1802
     */
1803
    protected function order($order): string
1804
    {
1805
        $ret = '';
1806
        if (is_array($order)) {
1807
            $ret .= " ORDER BY";
1808
1809 1
            foreach ($order as $ord) {
1810
                $ret .= " {$ord['field']} {$ord['sort']}";
1811 1
1812 1
                if ($ord != end($order)) {
1813
                    $ret .= ",";
1814
                }
1815
            }
1816
        } elseif (is_string($order)) {
1817
            $ret .= " ORDER BY {$order}";
1818
        } else {
1819
            throw new InvalidArgumentException("Error in datatype for order method ".gettype($order), E_ERROR);
1820
        }
1821
1822
        return $ret;
1823
    }
1824
1825
    /**
1826
     * Method to check if there are any invalid characters in the table name
1827
     *
1828 131
     * @param string $strTableName
1829
     *      Table name passed in
1830 131
     *
1831 1
     * @throws MissingOrInvalidParam
1832 131
     *      Throws exception if there is a problem with the table name
1833 131
     */
1834 131
    private function checkTableName(string $strTableName)
1835
    {
1836 1
        if (!strlen($strTableName) || preg_match("/[^0-9a-zA-Z\$\_\ ]/", $strTableName)) {
1837 6
            throw new MissingOrInvalidParam("Invalid table name $strTableName");
1838 1
        }
1839 5
    }
1840 1
1841 4
    /**
1842 1
     * Function to see if a constraint exists
1843 1
     *
1844 1
     *
1845
     *
1846 1
     * @param string $strConstraintId
1847 3
     *
1848 2
     * @return bool
1849 2
     */
1850 2
    public function isConstraint($strConstraintId): bool
1851
    {
1852 2
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
1853 1
1854
        if ($res->num_rows) {
1855 1
            return true;
1856
        }
1857
1858
        return false;
1859 1
    }
1860
1861
    /**
1862
     * Method to add a where clause
1863
     *
1864
     * @param DBWhere|array:DBWhere $where
1865
     *
1866
     * @return bool|array:DBWhere
1867
     */
1868
    public function parseClause($where)
1869
    {
1870
        $ret = [];
1871
        $interfaces = [];
1872
        if (is_object($where)) {
1873 37
            $interfaces = class_implements($where);
1874
        }
1875 37
        if (is_array($where)) {
1876
            foreach ($where as $k => $w) {
1877 37
                if (!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
1878 3
                    return false;
1879 3
                }
1880 2
                $v = $this->_escape($w->value, $w->escape);
1881
                $where[$k]->value = $v;
1882 1
1883
                $ret[] = $where[$k];
1884
            }
1885 3
        } elseif (is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
1886 34
            $v = $this->_escape($where->value, $where->escape);
1887 4
            $where->value = $v;
1888 30
            $ret[] = $where;
1889 29
        } elseif (in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable([$where, 'where'])) {
1890
            $ret = $this->parseClause($where->where());
1891 1
        } else {
1892
            $this->_logger->warning("Failed to get where", [$where]);
1893
        }
1894 36
1895
        return $ret;
1896
    }
1897
1898
    /**
1899
     * Encryption algorithm
1900
     *
1901
     * @param string $data
1902
     * @param string $salt
1903
     *
1904
     * @return string
1905
     *
1906
     * @throws Exception
1907
     *
1908
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
1909
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
1910
     */
1911
    public static function encrypt(string $data, ?string $salt = null)
1912
    {
1913
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1914
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1915
        }
1916
1917
        // Remove the base64 encoding from our key
1918
        if (is_null($salt)) {
1919 10
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1920
        } else {
1921 10
            $encryption_key = base64_decode($salt);
1922
        }
1923 10
        // Generate an initialization vector
1924 3
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
1925
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
1926
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1927 9
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
1928 1
        return base64_encode($encrypted . '::' . $iv);
1929 1
    }
1930 1
1931 1
    /**
1932 1
     * Decryption algorithm
1933
     *
1934 1
     * @param string $data
1935
     *
1936 1
     * @return string
1937 1
     *
1938
     * @throws Exception
1939
     *
1940
     * @uses PHP_DB_ENCRYPT_SALT string the salt used in the encryption algorithm
1941 9
     * @uses PHP_DB_ENCRYPT_ALGORITHM string the encryption algorithm used
1942 3
     */
1943
    public static function decrypt(string $data)
1944
    {
1945 8
        if (!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1946 1
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1947 1
        }
1948 1
1949
        // Remove the base64 encoding from our key
1950 1
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1951
1952
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
1953 8
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
1954
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1955
        return $plaintext;
1956
    }
1957
1958
    /**
1959
     * Method to check if all required fields are available in the object
1960
     *
1961
     * @param object $object
1962
     * @param array:string $requiredFields
1963
     *
1964
     * @return bool
1965 3
     */
1966
    public static function checkObject($object, $requiredFields): bool
1967 3
    {
1968 3
        $haystack = array_keys(json_decode(json_encode($object), true));
1969 1
        foreach ($requiredFields as $r) {
1970
            if (!in_array($r, $haystack)) {
1971 1
                return false;
1972 1
            }
1973
        }
1974 1
1975 1
        return true;
1976
    }
1977
1978 2
    /**
1979 1
     * Method to retrieve the error data
1980
     *
1981 1
     * @return string
1982
     */
1983
    public function error(): string
1984 2
    {
1985
        return $this->_c->error;
1986
    }
1987
1988
    /**
1989
     * Method to check the parameter types
1990
     *
1991
     * @param mixed $param
1992
     *
1993
     * @return int
1994
     *
1995
     * @throws MissingInterfaceAndMethods
1996 3
     * @throws Exception
1997
     */
1998 3
    private function checkParamType($param): int
1999 3
    {
2000 1
        // check for implented object interfaces
2001
        $interfaces = is_object($param) ? class_implements($param) : [];
2002 1
2003 1
        // numeric is the only datatype we can't have
2004
        if (is_numeric($param)) {
2005 1
            throw new MissingOrInvalidParam('Numeric parameters are not valid');
2006 1
        } elseif (is_resource($param)) {
2007
            throw new MissingOrInvalidParam('Resources are invalid parameters');
2008
        }
2009 2
2010 1
        // check for a SELECT statement within an insert
2011
        if (is_string($param) && stripos($param, 'select') !== false) {
2012 1
            return DBConst::STRING_SELECT;
2013
        } elseif (is_string($param)) {
2014
            return DBConst::STRING;
2015 2
        }
2016
        // param is an object, check to see if it includes the required interface
2017
        elseif (is_object($param) && !is_iterable($param)) {
2018
            if (!in_array('Godsgood33\Php_Db\DBInterface', $interfaces)) {
2019
                throw new MissingInterfaceAndMethods('Object does not implement DBInterface interface');
2020
            }
2021
2022
            return DBConst::OBJECT;
2023
        }
2024
        // param is an array
2025
        elseif (is_array($param)) {
2026
            // check that there is actual data in the array
2027 83
            if (!count($param)) {
2028
                throw new Exception('Array param is empty');
2029 83
            }
2030
2031
            // check the first element of the array and see if it's an object then recurse through to check it
2032
            $first = array_shift($param);
2033
            if (is_object($first) && $this->checkParamType($first) == DBConst::OBJECT) {
2034
                return DBConst::ARRAY_OBJECT;
2035
            } elseif (is_numeric($first) || is_string($first)) {
2036
                return DBConst::ARRAY_PRIMATIVE;
2037
            }
2038
2039
            return DBConst::ARRAY;
2040
        }
2041
        // object is potentially a collection
2042
        elseif (is_object($param)) {
2043
            // check that collection has required interface
2044
            if (!in_array('IteratorAggregate', $interfaces)) {
2045
                throw new MissingInterfaceAndMethods('Object does not implement IteratorAggregate interface');
2046
            }
2047
2048
            // get the first element in the collection
2049
            $it = $param->getIterator();
2050
            $first = $it->current();
2051
2052
            // check that the first element of the collection is a valid object as defined above
2053
            if ($this->checkParamType($first) == DBConst::OBJECT) {
2054
                return DBConst::COLLECTION;
2055
            }
2056
        }
2057
2058
        return 0;
2059 51
    }
2060
2061 51
    /**
2062 51
     * Method to check the log path
2063 51
     *
2064 8
     * @param string $strLogPath
2065
     *      The absolute file path of the log file
2066 51
     */
2067 42
    private function checkLog(string $strLogPath)
2068 10
    {
2069 1
        // set the log file path
2070
        $this->_logPath = $strLogPath;
2071 9
        if (!file_exists($this->_logPath)) {
2072 9
            touch($this->_logPath . "/db.log");
2073
        }
2074 9
    }
2075
2076 9
    /**
2077 8
     * Method to check the log level
2078 6
     *
2079 6
     * @param int $intLogLevel
2080 2
     */
2081 1
    private function checkLogLevel(?int $intLogLevel)
2082
    {
2083 1
        // set the log level
2084
        if (!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
2085
            $this->_logLevel = Logger::ERROR;
2086 48
        } elseif (!is_null($intLogLevel)) {
2087
            $this->_logLevel = $intLogLevel;
2088
        } elseif (defined('PHP_DB_LOG_LEVEL')) {
2089
            $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...
2090
        }
2091
    }
2092
2093
    /**
2094
     * Method to initialize the log stream
2095
     */
2096
    private function initLog()
2097
    {
2098
        // open the log handler
2099
        $this->_logger = new Logger('db', [
2100
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
2101
        ]);
2102 1
2103
        // check to see if we are operating in a CLI and if the user wants log data output to the terminal
2104 1
        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...
2105
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
2106
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
2107
            $this->_logger->pushHandler($stream);
2108
        }
2109 1
    }
2110 1
2111
    /**
2112
     * Method to check the required params for opening the db connection
2113
     */
2114
    private function checkRequiredParams()
2115 1
    {
2116
        // check to see if a connection was passed and all defined constants are present to establish a connection
2117 1
        if (!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
2118
            $this->_logger->critical("Missing essential defined constants");
2119 1
            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);
2120
        } elseif (defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
2121
            $this->_logger->critical("Missing essential encryption constants");
2122
            throw new MissingOrInvalidParam("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
2123
        }
2124
    }
2125
2126
    /**
2127
     * Method to init the connection
2128
     *
2129
     * @param mysqli $dbh
2130
     */
2131
    private function init(?mysqli $dbh)
2132
    {
2133
        // check to see if a connection was passed and all defined constants are present to establish a connection
2134 131
        if (! is_null($dbh) && is_a($dbh, 'mysqli')) {
2135
            $this->_logger->debug("Connecting through existing connection");
2136 131
            $this->_c = $dbh;
2137
            return;
2138
        }
2139
2140
        // check to see if the password is encrypted and decrypt if it is
2141 131
        if (defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
2142
            $this->_logger->debug("Decrypting password");
2143
            $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...
2144 131
        } else {
2145 131
            $pwd = PHP_DB_PWD;
2146 131
        }
2147
2148
        // open the connection
2149
        $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_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_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_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2150
2151
        // check for a connection error and throw an error if there is one
2152
        if ($this->_c->connect_errno) {
2153
            $this->_logger->error("Error connecting to database {$this->_c->connect_error}");
2154
            throw new ConnectError("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
2155
        }
2156
    }
2157
}
2158