Passed
Push — master ( da814f...2c246d )
by Ryan
05:09
created

Database::encrypt()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 3
nop 2
dl 0
loc 18
rs 9.9666
c 0
b 0
f 0
1
<?php
2
namespace Godsgood33\Php_Db;
3
4
use Monolog\Logger;
5
use Monolog\Formatter\LineFormatter;
6
use Monolog\Handler\StreamHandler;
7
use Exception;
8
use mysqli;
9
10
/**
11
 * Constant to define that we want to return an object
12
 *
13
 * @var int
14
 */
15
define('MYSQLI_OBJECT', 4);
16
17
/**
18
 * Constant to return consistent date format
19
 *
20
 * @var string
21
 */
22
define('MYSQL_DATE', 'Y-m-d');
23
24
/**
25
 * Constant to return consistent datetime format
26
 *
27
 * @var string
28
 */
29
define('MYSQL_DATETIME', 'Y-m-d H:i:s');
30
31
/**
32
 * A generic database class
33
 *
34
 * @author Ryan Prather
35
 */
36
class Database
37
{
38
39
    /**
40
     * Constant defining a SELECT query
41
     *
42
     * @var integer
43
     */
44
    const SELECT = 1;
45
46
    /**
47
     * Constant defining a SELECT COUNT query
48
     *
49
     * @var integer
50
     */
51
    const SELECT_COUNT = 2;
52
53
    /**
54
     * Constant defining a CREATE TABLE query
55
     *
56
     * @var integer
57
     */
58
    const CREATE_TABLE = 3;
59
60
    /**
61
     * Constant defining DROP query
62
     *
63
     * @var integer
64
     */
65
    const DROP = 4;
66
67
    /**
68
     * Constant defining DELETE query
69
     *
70
     * @var integer
71
     */
72
    const DELETE = 5;
73
74
    /**
75
     * Constant defining INSERT query
76
     *
77
     * @var integer
78
     */
79
    const INSERT = 6;
80
81
    /**
82
     * Constant defining REPLACE query
83
     *
84
     * @var integer
85
     */
86
    const REPLACE = 7;
87
88
    /**
89
     * Constant defining UPDATE query
90
     *
91
     * @var integer
92
     */
93
    const UPDATE = 8;
94
95
    /**
96
     * Constant defining EXTENDED INSERT query
97
     *
98
     * @var integer
99
     */
100
    const EXTENDED_INSERT = 9;
101
102
    /**
103
     * Constant defining EXTENDED REPLACE query
104
     *
105
     * @var integer
106
     */
107
    const EXTENDED_REPLACE = 10;
108
109
    /**
110
     * Constant defining EXTENDED UPDATE query
111
     *
112
     * @var integer
113
     */
114
    const EXTENDED_UPDATE = 11;
115
116
    /**
117
     * Constant defining ALTER TABLE query
118
     *
119
     * @var integer
120
     */
121
    const ALTER_TABLE = 12;
122
123
    /**
124
     * Constant defining action for alter table statement
125
     *
126
     * @var integer
127
     */
128
    const ADD_COLUMN = 1;
129
130
    /**
131
     * Constant defining action for alter table statement
132
     *
133
     * @var integer
134
     */
135
    const DROP_COLUMN = 2;
136
137
    /**
138
     * Constant defining action for alter table statement
139
     *
140
     * @var integer
141
     */
142
    const MODIFY_COLUMN = 3;
143
144
    /**
145
     * Constant defining action to add a constraint
146
     * 
147
     * @var integer
148
     */
149
    const ADD_CONSTRAINT = 4;
150
151
    /**
152
     * Constant defining a TRUNCATE TABLE query
153
     *
154
     * @var integer
155
     */
156
    const TRUNCATE = 13;
157
158
    /**
159
     * Global to represent an IN statement (e.g.
160
     * WHERE field IN (1,2))
161
     *
162
     * @var string
163
     */
164
    const IN = 'IN';
165
166
    /**
167
     * Global to represent a NOT IN statement (e.g.
168
     * WHERE field NOT IN (1,2))
169
     *
170
     * @var string
171
     */
172
    const NOT_IN = 'NOT IN';
173
174
    /**
175
     * Global to represent a BETWEEN statement (e.g.
176
     * WHERE field BETWEEN 1 and 2)
177
     *
178
     * @var string
179
     */
180
    const BETWEEN = 'BETWEEN';
181
182
    /**
183
     * Global to represent a LIKE statement (e.g.
184
     * WHERE field LIKE '%value%')
185
     *
186
     * @var string
187
     */
188
    const LIKE = 'LIKE';
189
190
    /**
191
     * Global to represent a NOT LIKE statement (e.g.
192
     * WHERE field NOT LIKE '%value%')
193
     *
194
     * @var string
195
     */
196
    const NOT_LIKE = 'NOT LIKE';
197
198
    /**
199
     * Global to represent an IS statement (e.g.
200
     * WHERE field IS NULL)
201
     *
202
     * @var string
203
     */
204
    const IS = 'IS';
205
206
    /**
207
     * Global to represent an IS NOT statement (e.g.
208
     * WHERE field IS NOT NULL)
209
     *
210
     * @var string
211
     */
212
    const IS_NOT = 'IS NOT';
213
214
    /**
215
     * The mysqli connection
216
     *
217
     * @access protected
218
     * @var \mysqli
219
     */
220
    protected $_c;
221
222
    /**
223
     * To store the SQL statement
224
     *
225
     * @access private
226
     * @var string
227
     */
228
    private $_sql = null;
229
230
    /**
231
     * A variable to store the type of query that is being run
232
     *
233
     * @access private
234
     * @var int
235
     */
236
    private $_queryType = null;
237
238
    /**
239
     * The result of the query
240
     *
241
     * @access protected
242
     * @var mixed
243
     */
244
    protected $_result = null;
245
246
    /**
247
     * Log level
248
     *
249
     * @access private
250
     * @var string
251
     */
252
    private $_logLevel = Logger::ERROR;
253
254
    /**
255
     * Variable to store the logger
256
     *
257
     * @access private
258
     * @var \Monolog\Logger
259
     */
260
    private $_logger = null;
261
262
    /**
263
     * Path for the logger to log the file
264
     *
265
     * @access private
266
     * @var string
267
     */
268
    private $_logPath = null;
269
270
    /**
271
     * Variable to store the most recent insert ID from an insert query
272
     *
273
     * @access protected
274
     * @var mixed
275
     */
276
    protected $_insertId = null;
277
278
    /**
279
     * Variable to decide if we need to automatically run the queries after generating them
280
     *
281
     * @access public
282
     * @staticvar
283
     * @var boolean
284
     */
285
    public static $autorun = false;
286
287
    /**
288
     * Variable to decide if the system should create a CLI logger in addition to the file logger
289
     *
290
     * @access public
291
     * @staticvar
292
     * @var boolean
293
     */
294
    public static $cliLog = false;
295
296
    /**
297
     * Constructor
298
     *
299
     * @param string $strLogPath
300
     *            [optional]
301
     * @param \mysqli $dbh
302
     *            [optional]
303
     *            [by ref]
304
     *            mysqli object to perform queries.
305
     * @param int $intLogLevel
306
     */
307
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
308
    {
309
        if(! is_null($dbh) && is_a($dbh, 'mysqli')) {
310
            $this->_c = $dbh;
311
        } elseif(!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
312
            throw new Exception("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);
313
        } elseif(defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
314
            throw new Exception("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
315
        }
316
        
317
        if(defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
318
            $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...
319
        } else {
320
            $pwd = PHP_DB_PWD;
321
        }
322
323
        $this->_c = new mysqli(PHP_DB_SERVER, PHP_DB_USER, $pwd, PHP_DB_SCHEMA);
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SCHEMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SERVER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
324
        
325
        if ($this->_c->connect_errno) {
326
            throw new Exception("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
327
        }
328
329
        $this->_logPath = $strLogPath;
330
        touch($this->_logPath . "/db.log");
331
332
        if(!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
333
            $this->_logLevel = Logger::ERROR;
334
        } elseif(!is_null($intLogLevel)) {
335
            $this->_logLevel = $intLogLevel;
336
        } elseif(defined('PHP_DB_LOG_LEVEL')) {
337
            $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...
338
        }
339
340
        $this->_logger = new Logger('db', [
341
            new StreamHandler(realpath($this->_logPath . "/db.log"), $this->_logLevel)
0 ignored issues
show
Bug introduced by
$this->_logLevel of type string is incompatible with the type integer expected by parameter $level of Monolog\Handler\StreamHandler::__construct(). ( Ignorable by Annotation )

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

341
            new StreamHandler(realpath($this->_logPath . "/db.log"), /** @scrutinizer ignore-type */ $this->_logLevel)
Loading history...
342
        ]);
343
344
        if (PHP_SAPI == 'cli' && self::$cliLog) {
345
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
346
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
347
            $this->_logger->pushHandler($stream);
348
        }
349
350
        $this->_logger->info("Database connected");
351
        $this->_logger->debug("Connection details:", [
352
            'Server' => PHP_DB_SERVER,
353
            'User'   => PHP_DB_USER,
354
            'Schema' => PHP_DB_SCHEMA
355
        ]);
356
357
        $this->setVar("time_zone", "+00:00");
358
        $this->setVar("sql_mode", "");
359
    }
360
361
    /**
362
     * Function to make sure that the database is connected
363
     *
364
     * @return boolean
365
     */
366
    public function isConnected()
367
    {
368
        $this->_logger->debug("Pinging server");
369
        return $this->_c->ping();
370
    }
371
372
    /**
373
     * Setter function for _logger
374
     *
375
     * @param Logger $log
376
     */
377
    public function setLogger(Logger $log)
378
    {
379
        $this->_logger->debug("Setting logger");
380
        $this->_logger = $log;
381
    }
382
383
    /**
384
     * Getter function for _logLevel
385
     *
386
     * @return string
387
     */
388
    public function getLogLevel()
389
    {
390
        $level = $this->_logLevel;
391
392
        $this->_logger->debug("Getting log level ({$level})");
393
        return $level;
394
    }
395
396
    /**
397
     * Function to set the log level just in case there needs to be a change to the default log level
398
     *
399
     * @param string $strLevel
400
     */
401
    public function setLogLevel($strLevel)
402
    {
403
        $this->_logger->debug("Setting log level to {$strLevel}");
404
        $this->_logLevel = $strLevel;
405
406
        $handles = [];
407
408
        foreach ($this->_logger->getHandlers() as $h) {
409
            $h->/** @scrutinizer ignore-call */
410
                setLevel($strLevel);
411
            $handles[] = $h;
412
        }
413
414
        $this->_logger->setHandlers($handles);
415
    }
416
417
    /**
418
     * Getter function for _queryType
419
     *
420
     * @return int
421
     */
422
    public function getQueryType()
423
    {
424
        return $this->_queryType;
425
    }
426
427
    /**
428
     * Setter function for _queryType
429
     *
430
     * @param int $qt
431
     */
432
    public function setQueryType($qt)
433
    {
434
        $this->_queryType = $qt;
435
    }
436
437
    /**
438
     * Getter function for _sql
439
     *
440
     * @return string
441
     */
442
    public function getSql()
443
    {
444
        return $this->_sql;
445
    }
446
447
    /**
448
     * Function to return the currently selected database schema
449
     *
450
     * @return string
451
     */
452
    public function getSchema()
453
    {
454
        if ($res = $this->_c->query("SELECT DATABASE()")) {
455
            $row = $res->fetch_row();
456
457
            $this->_logger->debug("Getting schema {$row[0]}");
458
            return $row[0];
459
        }
460
        return null;
461
    }
462
463
    /**
464
     * Function to set schema
465
     *
466
     * @param string $strSchema
467
     */
468
    public function setSchema($strSchema)
469
    {
470
        $this->_logger->debug("Setting schema to {$strSchema}");
471
        if (! $this->_c->select_db($strSchema)) {
472
            $this->_logger->emergency("Unknown schema {$strSchema}", [debug_backtrace()]);
473
            return false;
474
        }
475
        return true;
476
    }
477
478
    /**
479
     * Method to set a MYSQL variable
480
     *
481
     * @param string $strName
482
     * @param string $strVal
483
     *
484
     * @return boolean
485
     */
486
    public function setVar($strName, $strVal)
487
    {
488
        if (! $strName ) {
489
            $this->_logger->debug("name is blank", [
490
                'name'  => $strName
491
            ]);
492
            return false;
493
        }
494
495
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
496
497
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
498
            return true;
499
        } else {
500
            $this->_logger->error("Failed to set variable {$this->_c->error}");
501
            return false;
502
        }
503
    }
504
505
    /**
506
     * Function to execute the statement
507
     *
508
     * @param mixed $return
509
     *            [optional]
510
     *            MYSQLI constant to control what is returned from the mysqli_result object
511
     * @param string $class
512
     *            [optional]
513
     *            Class to use when returning object
514
     * @param string $strSql
515
     *            [optional]
516
     *            Optional SQL query
517
     *
518
     * @throws \Exception
519
     * @throws \InvalidArgumentException
520
     *
521
     * @return mixed
522
     */
523
    public function execute($return = MYSQLI_OBJECT, $strSql = null)
524
    {
525
        if (! is_null($strSql)) {
526
            $this->_sql = $strSql;
527
        }
528
529
        $this->_result = false;
530
        $query = 'SELECT';
531
        switch ($this->_queryType) {
532
            case self::SELECT_COUNT:
533
                $query = 'SELECT COUNT';
534
                break;
535
            case self::INSERT:
536
            case self::EXTENDED_INSERT:
537
                $query = 'INSERT';
538
                break;
539
            case self::UPDATE:
540
            case self::EXTENDED_UPDATE:
541
                $query = 'UPDATE';
542
                break;
543
            case self::REPLACE:
544
            case self::EXTENDED_REPLACE:
545
                $query = 'REPLACE';
546
                break;
547
            case self::DROP:
548
                $query = 'DROP';
549
                break;
550
            case self::DELETE:
551
                $query = 'DELETE';
552
                break;
553
            case self::CREATE_TABLE:
554
                $query = 'CREATE TABLE';
555
                break;
556
            case self::TRUNCATE:
557
                $query = 'TRUNCATE';
558
                break;
559
        }
560
561
        if (is_a($this->_c, 'mysqli')) {
562
            if (! $this->_c->ping()) {
563
                require_once 'DBConfig.php';
564
                $this->_c = null;
565
                $this->_c = new mysqli(PHP_DB_SERVER, PHP_DB_USER, PHP_DB_PWD, PHP_DB_SCHEMA);
0 ignored issues
show
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SCHEMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SERVER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_PWD 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...
566
            }
567
        } else {
568
            throw new Exception('Database was not connected', E_ERROR);
569
        }
570
571
        $this->_logger->info("Executing {$query} query");
572
        $this->_logger->debug($this->_sql);
573
574
        try {
575
            if (in_array($this->_queryType, [
576
                self::SELECT,
577
                self::SELECT_COUNT
578
            ])) {
579
                $this->_result = $this->_c->query($this->_sql);
580
                if ($this->_c->error) {
581
                    $this->_logger->error("There is an error {$this->_c->error}");
582
                    $this->_logger->debug("Errored on query", [$this->_sql]);
583
                    throw new Exception("There was an error {$this->_c->error}", E_ERROR);
584
                }
585
            } else {
586
                $this->_result = $this->_c->real_query($this->_sql);
587
                if ($this->_c->errno) {
588
                    $this->_logger->error("There was an error {$this->_c->error}");
589
                    $this->_logger->debug("Errored on query", [$this->_sql]);
590
                    throw new Exception("There was an error {$this->_c->error}", E_ERROR);
591
                }
592
            }
593
594
            $this->_logger->debug("Checking for query results");
595
            $this->_result = $this->checkResults($return);
596
        } catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
597
598
        return $this->_result;
599
    }
600
601
    /**
602
     * Function to check the results and return what is expected
603
     *
604
     * @param mixed $returnType
605
     *            [optional]
606
     *            Optional return mysqli_result return type
607
     *
608
     * @return mixed
609
     */
610
    protected function checkResults($returnType)
611
    {
612
        $res = null;
613
614
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
615
            $res = $this->_result;
616
        }
617
        elseif (in_array($this->_queryType, [Database::INSERT, Database::EXTENDED_INSERT, Database::DELETE, Database::UPDATE, Database::EXTENDED_UPDATE, Database::REPLACE, Database::EXTENDED_REPLACE])) {
618
            $res = $this->_c->affected_rows;
619
620
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE])) {
621
                $this->_insertId = $this->_c->insert_id;
622
            }
623
        }
624
        elseif ($this->_queryType == Database::SELECT_COUNT) {
625
            if (! is_a($this->_result, 'mysqli_result')) {
626
                $this->_logger->error("Error with return on query");
627
                return null;
628
            }
629
630
            if ($this->_result->num_rows == 1) {
631
                $row = $this->_result->fetch_assoc();
632
                if (isset($row['count'])) {
633
                    $this->_logger->debug("Returning SELECT_COUNT query", [
634
                        'count' => $row['count']
635
                    ]);
636
                    $res = $row['count'];
637
                }
638
            } elseif ($this->_result->num_rows > 1) {
639
                $this->_logger->debug("Returning SELECT_COUNT query", [
640
                    'count' => $this->_result->num_rows
641
                ]);
642
                $res = $this->_result->num_rows;
643
            }
644
645
            mysqli_free_result($this->_result);
646
        }
647
        else {
648
            $method = "mysqli_fetch_object";
649
            if($returnType == MYSQLI_ASSOC) {
650
                $method = "mysqli_fetch_assoc";
651
            } elseif ($returnType == MYSQLI_NUM) {
652
                $method = "mysqli_fetch_array";
653
            }
654
655
            if (is_a($this->_result, 'mysqli_result')) {
656
                if($this->_result->num_rows > 1) {
657
                    $res = [];
658
                    while ($row = call_user_func($method, $this->_result)) {
659
                        $res[] = $row;
660
                    }
661
                } else {
662
                    $res = call_user_func($method, $this->_result);
663
                }
664
            } else {
665
                $this->_logger->error("Error with return on query");
666
                return null;
667
            }
668
        }
669
670
        if ($this->_c->error) {
671
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
672
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
673
            return null;
674
        }
675
676
        return $res;
677
    }
678
679
    /**
680
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
681
     * Nothing is escaped
682
     *
683
     * @param string $strSql
684
     *            [optional]
685
     *            Optional query to pass in and execute
686
     *
687
     * @return \mysqli_result|boolean
688
     */
689
    public function query($strSql = null)
690
    {
691
        if (is_null($strSql)) {
692
            return $this->_c->query($this->_sql);
693
        } else {
694
            return $this->_c->query($strSql);
695
        }
696
    }
697
698
    /**
699
     * A function to build a select query
700
     *
701
     * @param string $strTableName
702
     *            The table to query
703
     * @param array|string $fields
704
     *            [optional]
705
     *            Optional array of fields to return (defaults to '*')
706
     * @param array $arrWhere
707
     *            [optional]
708
     *            Optional 2-dimensional array to build where clause from
709
     * @param array $arrFlags
710
     *            [optional]
711
     *            Optional 2-dimensional array to allow other flags
712
     *
713
     * @see Database::where()
714
     * @see Database::flags()
715
     *
716
     * @throws Exception
717
     *
718
     * @return mixed
719
     */
720
    public function select($strTableName, $fields = null, $arrWhere = [], $arrFlags = [])
721
    {
722
        $this->_sql = null;
723
        $this->_queryType = self::SELECT;
724
725
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_string($strTableName) is always true.
Loading history...
introduced by
The condition is_null($strTableName) is always false.
Loading history...
726
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
727
                'fields' => $this->fields($fields)
728
            ]);
729
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
730
        } else {
731
            $this->_logger->emergency("Table name is invalid or wrong type", [debug_backtrace()]);
732
            throw new Exception("Table name is invalid");
733
        }
734
735
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
736
            $this->_logger->debug("Adding joins", [
737
                'joins' => implode(' ', $arrFlags['joins'])
738
            ]);
739
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
740
        } else {
741
            $this->_logger->debug("No joins");
742
        }
743
744
        if (! is_null($arrWhere) && is_array($arrWhere) && count($arrWhere)) {
0 ignored issues
show
introduced by
The condition is_array($arrWhere) is always true.
Loading history...
introduced by
The condition is_null($arrWhere) is always false.
Loading history...
745
            $where_str = " WHERE";
746
            $this->_logger->debug("Parsing where clause and adding to query");
747
            foreach ($arrWhere as $x => $w) {
748
                $where_str .= $this->parseClause($w, $x);
749
            }
750
            if (strlen($where_str) > strlen(" WHERE")) {
751
                $this->_sql .= $where_str;
752
            }
753
        }
754
755
        if (is_array($arrFlags) && count($arrFlags)) {
756
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
757
            $this->_sql .= $this->flags($arrFlags);
758
        }
759
760
        if (self::$autorun) {
761
            return $this->execute();
762
        }
763
764
        return $this->_sql;
765
    }
766
767
    /**
768
     * Function to build a query to check the number of rows in a table
769
     *
770
     * @param string $strTableName
771
     *            The table to query
772
     * @param array $arrWhere
773
     *            [optional]
774
     *            Optional 2-dimensional array to build where clause
775
     * @param array $arrFlags
776
     *            [optional]
777
     *            Optional 2-dimensional array to add flags
778
     *
779
     * @see Database::where()
780
     * @see Database::flags()
781
     *
782
     * @return string|NULL
783
     */
784
    public function selectCount($strTableName, $arrWhere = [], $arrFlags = [])
785
    {
786
        $this->_sql = null;
787
        $this->_queryType = self::SELECT_COUNT;
788
789
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_null($strTableName) is always false.
Loading history...
introduced by
The condition is_string($strTableName) is always true.
Loading history...
790
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
791
        } else {
792
            $this->_logger->emergency("Table name is invalid or wrong type", [debug_backtrace()]);
793
            throw new Exception("Table name is invalid");
794
        }
795
796
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
797
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
798
        }
799
800
        if (! is_null($arrWhere) && is_array($arrWhere) && count($arrWhere)) {
0 ignored issues
show
introduced by
The condition is_array($arrWhere) is always true.
Loading history...
introduced by
The condition is_null($arrWhere) is always false.
Loading history...
801
            $where_str = " WHERE";
802
            foreach ($arrWhere as $x => $w) {
803
                $where_str .= $this->parseClause($w, $x);
804
            }
805
            if (strlen($where_str) > strlen(" WHERE")) {
806
                $this->_sql .= $where_str;
807
            }
808
        }
809
810
        if (is_array($arrFlags) && count($arrFlags)) {
811
            $this->_sql .= $this->flags($arrFlags);
812
        }
813
814
        if (self::$autorun) {
815
            return $this->execute();
816
        }
817
818
        return $this->_sql;
819
    }
820
821
    /**
822
     * Function to build an insert query statement
823
     *
824
     * @param string $strTableName
825
     * @param array|string $arrParams
826
     * @param boolean $blnToIgnore
827
     *
828
     * @return string|NULL
829
     */
830
    public function insert($strTableName, $arrParams = null, $blnToIgnore = false)
831
    {
832
        $this->_sql = null;
833
        $this->_queryType = self::INSERT;
834
835
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_string($strTableName) is always true.
Loading history...
introduced by
The condition is_null($strTableName) is always false.
Loading history...
836
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
837
        } else {
838
            throw new Exception("Table name is invalid");
839
        }
840
841
        if (is_array($arrParams) && count($arrParams)) {
842
            if(is_array($arrParams) && count($arrParams)) {
843
                $this->_sql .= " (`" . implode("`,`", array_keys($arrParams)) . "`)";
844
            }
845
            $this->_sql .= " VALUES (" . implode(",", array_map([
846
                $this,
847
                '_escape'
848
            ], array_values($arrParams))) . ")";
849
        } elseif (is_string($arrParams) && strpos(strtolower($arrParams), 'select') !== false) {
850
            $this->_sql .= " {$arrParams}";
851
        } elseif (is_object($arrParams)) {
852
            $interfaces = \class_implements($arrParams);
853
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces)) {
854
                $params = \call_user_func([$arrParams, "insert"]);
855
                $this->_sql .= " (`" . implode("`,`", array_keys($params)) . "`) VALUES ";
856
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($params))) . ")";
857
            } else {
858
                throw new Exception("Object does not implement the DBInterface interface and methods");
859
            }
860
        } else {
861
            throw new Exception("Invalid type passed to insert " . gettype($arrParams));
862
        }
863
864
        if (self::$autorun) {
865
            return $this->execute();
866
        }
867
868
        return $this->_sql;
869
    }
870
871
    /**
872
     * Function to create an extended insert query statement
873
     *
874
     * @param string $strTableName
875
     *            The table name that the data is going to be inserted on
876
     * @param array $arrFields
877
     *            An array of field names that each value represents
878
     * @param array|string $params
879
     *            An array of array of values or a string with a SELECT statement to populate the insert with
880
     * @param boolean $blnToIgnore
881
     *            [optional]
882
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
883
     *
884
     * @return NULL|string Returns the SQL if self::$autorun is set to false, else it returns the output from running.
885
     */
886
    public function extendedInsert($strTableName, $arrFields, $params, $blnToIgnore = false)
887
    {
888
        $this->_sql = null;
889
        $this->_queryType = self::EXTENDED_INSERT;
890
891
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_null($strTableName) is always false.
Loading history...
introduced by
The condition is_string($strTableName) is always true.
Loading history...
892
            $this->_sql = "INSERT " . ($blnToIgnore ? "IGNORE " : "") . "INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
893
        } else {
894
            throw new Exception("Table name is invalid");
895
        }
896
897
        if (is_array($params) && count($params)) {
898
            $this->_sql .= " VALUES ";
899
            if (isset($params[0]) && is_array($params[0])) {
900
                foreach ($params as $p) {
901
                    if (count($p) != count($arrFields)) {
902
                        $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
903
                            $p,
904
                            debug_backtrace()
905
                        ]);
906
                        throw new Exception("Inconsistent number of fields in fields and values in extendedInsert " . print_r($p, true));
907
                    }
908
                    $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
909
910
                    if ($p != end($params)) {
911
                        $this->_sql .= ",";
912
                    }
913
                }
914
            } else {
915
                $this->_sql .= "(" . implode("),(", array_map([$this, '_escape'], array_values($params))) . ")";
916
            }
917
        } elseif (is_object($params)) {
0 ignored issues
show
introduced by
The condition is_object($params) is always false.
Loading history...
918
            $interfaces = \class_implements($params);
919
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($params) . "::extendedInsert")) {
920
                $this->_sql .= " VALUES " . \call_user_func([$params, "extendedInsert"]);
921
            } else {
922
                throw new Exception("Params passed are an object that do not implement DBInterface");
923
            }
924
        } else {
925
            throw new Exception("Invalid param type");
926
        }
927
928
        if (self::$autorun) {
929
            return $this->execute();
930
        }
931
932
        return $this->_sql;
933
    }
934
935
    /**
936
     * Build a statement to update a table
937
     *
938
     * @param string $strTableName
939
     *            The table name to update
940
     * @param array $arrParams
941
     *            Name/value pairs of the field name and value
942
     * @param array $arrWhere
943
     *            [optional]
944
     *            Two-dimensional array to create where clause
945
     * @param array $arrFlags
946
     *            [optional]
947
     *            Two-dimensional array to create other flag options (joins, order, and group)
948
     *
949
     * @see Database::where()
950
     * @see Database::flags()
951
     *
952
     * @return NULL|string
953
     */
954
    public function update($strTableName, $arrParams, $arrWhere = [], $arrFlags = [])
955
    {
956
        $this->_sql = "UPDATE ";
957
        $this->_queryType = self::UPDATE;
958
959
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_null($strTableName) is always false.
Loading history...
introduced by
The condition is_string($strTableName) is always true.
Loading history...
960
            $this->_sql .= $strTableName;
961
962
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
963
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
964
                unset($arrFlags['joins']);
965
            }
966
967
            $this->_sql .= " SET ";
968
        } else {
969
            throw new Exception("Table name is invalid");
970
        }
971
972
        if (is_array($arrParams) && count($arrParams)) {
973
            foreach ($arrParams as $f => $p) {
974
                if ((strpos($f, "`") === false) && (strpos($f, ".") === false) && (strpos($f, "*") === false) && (stripos($f, " as ") === false)) {
975
                    $f = "`{$f}`";
976
                }
977
978
                if (! is_null($p)) {
979
                    $this->_sql .= "$f={$this->_escape($p)}";
980
                } else {
981
                    $this->_sql .= "$f=NULL";
982
                }
983
984
                if($p != end($arrParams)) {
985
                    $this->_sql .= ",";
986
                }
987
            }
988
        } elseif (is_object($arrParams)) {
989
            $interfaces = \class_implements($arrParams);
990
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::update")) {
991
                $this->_sql .= \call_user_func([$arrParams, "update"]);
992
            } else {
993
                throw new Exception("Params is an object that doesn't implement DBInterface");
994
            }
995
        } else {
996
            throw new Exception("No fields to update");
997
        }
998
999
        if (! is_null($arrWhere) && is_array($arrWhere) && count($arrWhere)) {
0 ignored issues
show
introduced by
The condition is_null($arrWhere) is always false.
Loading history...
introduced by
The condition is_array($arrWhere) is always true.
Loading history...
1000
            $where_str = " WHERE";
1001
            foreach ($arrWhere as $x => $w) {
1002
                $where_str .= $this->parseClause($w, $x);
1003
            }
1004
            if (strlen($where_str) > strlen(" WHERE")) {
1005
                $this->_sql .= $where_str;
1006
            }
1007
        }
1008
1009
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
1010
            $this->_sql .= $this->flags($arrFlags);
1011
        }
1012
1013
        if (self::$autorun) {
1014
            return $this->execute();
1015
        }
1016
1017
        return $this->_sql;
1018
    }
1019
1020
    /**
1021
     * Function to offer an extended updated functionality by using two different tables.
1022
     *
1023
     * @param string $strTableToUpdate
1024
     *            The table that you want to update (alias 'tbu' is automatically added)
1025
     * @param string $strOriginalTable
1026
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
1027
     * @param string $strLinkField
1028
     *            The common index value between them that will join the fields
1029
     * @param array|string $arrParams
1030
     *            If string only a single field is updated (tbu.$params = o.$params)
1031
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
1032
     *
1033
     * @return mixed
1034
     */
1035
    public function extendedUpdate($strTableToUpdate, $strOriginalTable, $strLinkField, $arrParams)
1036
    {
1037
        $this->_sql = "UPDATE ";
1038
        $this->_queryType = self::EXTENDED_UPDATE;
1039
1040
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
1041
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
1042
        } else {
1043
            throw new Exception("Missing necessary fields");
1044
        }
1045
1046
        if (is_array($arrParams) && count($arrParams)) {
1047
            foreach ($arrParams as $param) {
1048
                if ($param != $strLinkField) {
1049
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1050
                }
1051
            }
1052
            $this->_sql = substr($this->_sql, 0, - 1);
1053
        } elseif (is_string($arrParams)) {
1054
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1055
        } else {
1056
            throw new Exception("Do not understand datatype " . gettype($arrParams), E_ERROR);
1057
        }
1058
1059
        if (self::$autorun) {
1060
            return $this->execute();
1061
        }
1062
1063
        return $this->_sql;
1064
    }
1065
1066
    /**
1067
     * Function to build a replace query
1068
     *
1069
     * @param string $strTableName
1070
     *            The table to update
1071
     * @param array $arrParams
1072
     *            Name/value pair to insert
1073
     *
1074
     * @return NULL|string
1075
     */
1076
    public function replace($strTableName, $arrParams)
1077
    {
1078
        $this->_sql = null;
1079
        $this->_queryType = self::REPLACE;
1080
1081
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_null($strTableName) is always false.
Loading history...
introduced by
The condition is_string($strTableName) is always true.
Loading history...
1082
            $this->_sql = "REPLACE INTO $strTableName ";
1083
        } else {
1084
            throw new Exception("Table name is invalid");
1085
        }
1086
1087
        if(is_array($arrParams) && count($arrParams)) {
1088
            $keys = array_keys($arrParams);
1089
            $vals = array_values($arrParams);
1090
1091
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1092
            $this->_sql .= " VALUES (" . implode(",", array_map([
1093
                $this,
1094
                '_escape'
1095
            ], array_values($vals))) . ")";
1096
        } elseif (is_object($arrParams)) {
1097
            $interfaces = class_implements($arrParams);
1098
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable($params . "::replace")) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $params seems to be never defined.
Loading history...
1099
                $params = \call_user_method("replace", $params);
0 ignored issues
show
Deprecated Code introduced by
The function call_user_method() has been deprecated: 5.3 use call_user_func() instead ( Ignorable by Annotation )

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

1099
                $params = /** @scrutinizer ignore-deprecated */ \call_user_method("replace", $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1100
                $this->_sql .= "(`" . implode("`,`", array_keys($params)) . "`) VALUES ";
1101
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($params))) . ")";
1102
            }
1103
        }
1104
1105
        if (self::$autorun) {
1106
            return $this->execute();
1107
        }
1108
1109
        return $this->_sql;
1110
    }
1111
1112
    /**
1113
     * Function to build an extended replace statement
1114
     *
1115
     * @param string $strTableName
1116
     *            Table name to update
1117
     * @param array $arrFields
1118
     *            Array of fields
1119
     * @param array $arrParams
1120
     *            Two-dimensional array of values
1121
     *
1122
     * @return NULL|string
1123
     */
1124
    public function extendedReplace($strTableName, $arrFields, $arrParams)
1125
    {
1126
        $this->_sql = null;
1127
        $this->_queryType = self::EXTENDED_REPLACE;
1128
1129
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1130
            throw new Exception("Error with the field type");
1131
        }
1132
1133
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_string($strTableName) is always true.
Loading history...
introduced by
The condition is_null($strTableName) is always false.
Loading history...
1134
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1135
        } else {
1136
            throw new Exception("Table name is invalid");
1137
        }
1138
1139
        if (is_array($arrParams) && count($arrParams)) {
1140
            $this->_sql .= " VALUES ";
1141
            foreach ($arrParams as $p) {
1142
                $this->_sql .= "(" . implode(",", array_map([
1143
                    $this,
1144
                    '_escape'
1145
                ], array_values($p))) . ")";
1146
1147
                if ($p != end($arrParams)) {
1148
                    $this->_sql .= ",";
1149
                }
1150
            }
1151
        }
1152
1153
        if (self::$autorun) {
1154
            return $this->execute();
1155
        }
1156
1157
        return $this->_sql;
1158
    }
1159
1160
    /**
1161
     * Function to build a delete statement
1162
     *
1163
     * @param string $strTableName
1164
     *            Table name to act on
1165
     * @param array $arrFields
1166
     *            [optional]
1167
     *            Optional list of fields to delete (used when including multiple tables)
1168
     * @param array $arrWhere
1169
     *            [optional]
1170
     *            Optional 2-dimensional array to build where clause from
1171
     * @param array $arrJoins
1172
     *            [optional]
1173
     *            Optional 2-dimensional array to add other flags
1174
     *
1175
     * @see Database::where()
1176
     * @see Database::flags()
1177
     *
1178
     * @return string|NULL
1179
     */
1180
    public function delete($strTableName, $arrFields = [], $arrWhere = [], $arrJoins = [])
1181
    {
1182
        $this->_sql = "DELETE";
1183
        $this->_queryType = self::DELETE;
1184
1185
        $this->_logger->debug("Deleting table data");
1186
1187
        if (! is_null($arrFields) && is_array($arrFields) && count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_null($arrFields) is always false.
Loading history...
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1188
            $this->_sql .= " " . implode(",", $arrFields);
1189
        }
1190
1191
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_string($strTableName) is always true.
Loading history...
introduced by
The condition is_null($strTableName) is always false.
Loading history...
1192
            $this->_sql .= " FROM $strTableName";
1193
        } else {
1194
            throw new Exception("Table name is invalid");
1195
        }
1196
1197
        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...
1198
            $this->_sql .= " " . implode(" ", $arrJoins);
1199
        }
1200
1201
        if (! is_null($arrWhere) && is_array($arrWhere) && count($arrWhere)) {
0 ignored issues
show
introduced by
The condition is_array($arrWhere) is always true.
Loading history...
introduced by
The condition is_null($arrWhere) is always false.
Loading history...
1202
            $where_str = " WHERE";
1203
            foreach ($arrWhere as $x => $w) {
1204
                $where_str .= $this->parseClause($w, $x);
1205
            }
1206
            if (strlen($where_str) > strlen(" WHERE")) {
1207
                $this->_sql .= $where_str;
1208
            }
1209
        }
1210
1211
        if (self::$autorun) {
1212
            return $this->execute();
1213
        }
1214
1215
        return $this->_sql;
1216
    }
1217
1218
    /**
1219
     * Function to build a drop table statement (automatically executes)
1220
     *
1221
     * @param string $strTableName
1222
     *            Table to drop
1223
     * @param string $strType
1224
     *            [optional]
1225
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1226
     * @param boolean $blnIsTemp
1227
     *            [optional]
1228
     *            Optional boolean if this is a temporary table
1229
     *
1230
     * @return string|NULL
1231
     */
1232
    public function drop($strTableName, $strType = 'table', $blnIsTemp = false)
1233
    {
1234
        $this->_sql = null;
1235
        $this->_queryType = self::DROP;
1236
1237
        switch ($strType) {
1238
            case 'table':
1239
                $strType = 'TABLE';
1240
                break;
1241
            case 'view':
1242
                $strType = 'VIEW';
1243
                break;
1244
            default:
1245
                throw new Exception("Invalid type " . gettype($strType), E_ERROR);
1246
        }
1247
1248
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_string($strTableName) is always true.
Loading history...
introduced by
The condition is_null($strTableName) is always false.
Loading history...
1249
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1250
        } else {
1251
            throw new Exception("Table name is invalid");
1252
        }
1253
1254
        if (self::$autorun) {
1255
            return $this->execute();
1256
        }
1257
1258
        return $this->_sql;
1259
    }
1260
1261
    /**
1262
     * Function to build a truncate table statement (automatically executes)
1263
     *
1264
     * @param string $strTableName
1265
     *            Table to truncate
1266
     *
1267
     * @throws Exception
1268
     *
1269
     * @return string|NULL
1270
     */
1271
    public function truncate($strTableName)
1272
    {
1273
        $this->_sql = null;
1274
        $this->_queryType = self::TRUNCATE;
1275
1276
        if (! is_null($strTableName) && is_string($strTableName)) {
0 ignored issues
show
introduced by
The condition is_null($strTableName) is always false.
Loading history...
introduced by
The condition is_string($strTableName) is always true.
Loading history...
1277
            $this->_sql = "TRUNCATE TABLE $strTableName";
1278
        } else {
1279
            throw new Exception("Table name is invalid");
1280
        }
1281
1282
        if (self::$autorun) {
1283
            return $this->execute();
1284
        }
1285
1286
        return $this->_sql;
1287
    }
1288
1289
    /**
1290
     * Function to build a create temporary table statement
1291
     *
1292
     * @param string $strTableName
1293
     *            Name to give the table when creating
1294
     * @param boolean $blnIsTemp
1295
     *            [optional]
1296
     *            Optional boolean to make the table a temporary table
1297
     * @param mixed $strSelect
1298
     *            [optional]
1299
     *            Optional parameter if null uses last built statement
1300
     *            If string, will be made the SQL statement executed to create the table
1301
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1302
     *
1303
     * @return NULL|string
1304
     */
1305
    public function createTable($strTableName, $blnIsTemp = false, $strSelect = null)
1306
    {
1307
        $this->_queryType = self::CREATE_TABLE;
1308
1309
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1310
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1311
        } elseif (! is_null($strTableName) && is_string($strTableName) && is_string($strSelect)) {
0 ignored issues
show
introduced by
The condition is_string($strTableName) is always true.
Loading history...
introduced by
The condition is_null($strTableName) is always false.
Loading history...
1312
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1313
        } elseif (! is_null($strTableName) && is_string($strTableName) && is_array($strSelect)) {
1314
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1315
1316
            foreach ($strSelect as $field) {
1317
                $default = null;
1318
                if (isset($field['default'])) {
1319
                    $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1320
                }
1321
                $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1322
            }
1323
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1324
        }
1325
1326
        if (self::$autorun) {
1327
            return $this->execute();
1328
        }
1329
1330
        return $this->_sql;
1331
    }
1332
1333
    /**
1334
     * Function to create a table using a stdClass object derived from JSON
1335
     *
1336
     * @param \stdClass $json
1337
     *
1338
     * @example /examples/create_table_json.json
1339
     *
1340
     */
1341
    public function createTableJson($json)
1342
    {
1343
        $this->_queryType = self::CREATE_TABLE;
1344
        $this->_c->select_db($json->schema);
1345
1346
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1347
        foreach ($json->fields as $field) {
1348
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1349
1350
            if ($field->dataType == 'enum' && isset($field->values)) {
1351
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1352
            }
1353
1354
            if (isset($field->ai) && $field->ai) {
1355
                $this->_sql .= " AUTO_INCREMENT";
1356
            }
1357
1358
            if (isset($field->nn) && $field->nn) {
1359
                $this->_sql .= " NOT NULL";
1360
            } elseif(isset($field->default)) {
1361
                if (strtolower($field->default) == 'null') {
1362
                    $this->_sql .= " DEFAULT NULL";
1363
                } elseif (strlen($field->default)) {
1364
                    $this->_sql .= " DEFAULT '{$field->default}'";
1365
                }
1366
            }
1367
1368
            if ($field != end($json->fields)) {
1369
                $this->_sql .= ",";
1370
            }
1371
        }
1372
1373
        if (isset($json->index) && count($json->index)) {
1374
            foreach ($json->index as $ind) {
1375
                if(is_array($ind->ref)) {
1376
                    $ref = "";
1377
                    foreach($ind->ref as $r) {
1378
                        $ref .= "`{$r}` ASC,";
1379
                    }
1380
                    $ref = substr($ref, 0, -1);
1381
                } elseif(is_string($ind->ref)) {
1382
                    $ref = $ind->ref;
1383
                }
1384
                $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ref does not seem to be defined for all execution paths leading up to this point.
Loading history...
1385
            }
1386
        }
1387
1388
        if (isset($json->constraints) && count($json->constraints)) {
1389
            foreach ($json->constraints as $con) {
1390
                $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));
1391
            }
1392
        }
1393
1394
        if (isset($json->unique) && count($json->unique)) {
1395
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1396
        }
1397
1398
        if (isset($json->primary_key) && count($json->primary_key)) {
1399
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1400
        } else {
1401
            if (substr($this->_sql, - 1) == ',') {
1402
                $this->_sql = substr($this->_sql, 0, - 1);
1403
            }
1404
1405
            $this->_sql .= ")";
1406
        }
1407
1408
        $this->execute();
1409
    }
1410
1411
    /**
1412
     * Function to alter a existing table
1413
     *
1414
     * @param string $strTableName
1415
     *            Table to alter
1416
     * @param int $intAction
1417
     *            What action should be taken
1418
     * @param mixed $arrParams
1419
     *            For add column this is a stdClass object that has the same elements as the example json
1420
     *
1421
     * @return mixed
1422
     */
1423
    public function alterTable($strTableName, $intAction, $arrParams)
1424
    {
1425
        $this->_queryType = self::ALTER_TABLE;
1426
        $this->_sql = "ALTER TABLE $strTableName";
1427
        if ($intAction == self::ADD_COLUMN) {
1428
            $nn = ($arrParams->nn ? " NOT NULL" : "");
1429
            $default = null;
1430
            if ($arrParams->default === null) {
1431
                $default = " DEFAULT NULL";
1432
            } elseif (strlen($arrParams->default)) {
1433
                $default = " DEFAULT {$this->_escape($arrParams->default)}";
1434
            }
1435
            $this->_sql .= " ADD COLUMN `{$arrParams->name}` {$arrParams->dataType}" . $nn . $default;
1436
        } elseif ($intAction == self::DROP_COLUMN) {
1437
            $this->_sql .= " DROP COLUMN ";
1438
            foreach ($arrParams as $col) {
1439
                $this->_sql .= "`{$col->name}`";
1440
1441
                if ($col != end($arrParams)) {
1442
                    $this->_sql .= ",";
1443
                }
1444
            }
1445
        } elseif ($intAction == self::MODIFY_COLUMN) {
1446
            $this->_sql .= " MODIFY COLUMN";
1447
            $nn = ($arrParams->nn ? " NOT NULL" : "");
1448
            $default = null;
1449
            if ($arrParams->default === null) {
1450
                $default = " DEFAULT NULL";
1451
            } elseif (strlen($arrParams->default)) {
1452
                $default = " DEFAULT {$this->_escape($arrParams->default)}";
1453
            }
1454
            $this->_sql .= " `{$arrParams->name}` `{$arrParams->new_name}` {$arrParams->dataType}" . $nn . $default;
1455
        } elseif ($intAction == self::ADD_CONSTRAINT) {
1456
            if(is_array($arrParams->field) && is_array($arrParams->local)) {
1457
                $field = "`" . implode("`,`", $arrParams->field) . "`";
1458
                $local = "`" . implode("`,`", $arrParams->local) . "`";
1459
            } elseif(is_string($arrParams->field) && is_string($arrParams->local)) {
1460
                $field = "`{$arrParams->field}`";
1461
                $local = "`{$arrParams->local}`";
1462
            } else {
1463
                $this->_logger->critical("Error in reading constraint field");
1464
                throw new Exception("Error in reading constraint field");
1465
            }
1466
            $this->_sql .= " ADD CONSTRAINT `{$arrParams->id}` FOREIGN KEY ({$local}) REFERENCES `{$arrParams->schema}`.`{$arrParams->table}` ({$field}) ON DELETE {$arrParams->delete} ON UPDATE {$arrParams->update}";
1467
        }
1468
1469
        if (self::$autorun) {
1470
            return $this->execute();
1471
        }
1472
1473
        return $this->_sql;
1474
    }
1475
1476
    /**
1477
     * Check to see if a field in a table exists
1478
     *
1479
     * @param string $strTableName
1480
     *            Table to check
1481
     * @param string $strFieldName
1482
     *            Field name to find
1483
     *
1484
     * @return boolean Returns TRUE if field is found in that schema and table, otherwise FALSE
1485
     */
1486
    public function fieldExists($strTableName, $strFieldName)
1487
    {
1488
        $fdata = $this->fieldData($strTableName);
1489
1490
        if (is_array($fdata) && count($fdata)) {
1491
            foreach ($fdata as $field) {
1492
                if ($field->name == $strFieldName) {
1493
                    return true;
1494
                }
1495
            }
1496
        }
1497
1498
        return false;
1499
    }
1500
1501
    /**
1502
     * Function to get the column data (datatype, flags, defaults, etc)
1503
     *
1504
     * @param string $strTableName
1505
     *            Table to query
1506
     * @param mixed $field
1507
     *            [optional]
1508
     *            Optional field to retrieve data (if null, returns data from all fields)
1509
     *
1510
     * @return array
1511
     */
1512
    public function fieldData($strTableName, $field = null)
1513
    {
1514
        if (is_null($field)) {
1515
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1516
        } elseif (is_array($field)) {
1517
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1518
        } elseif (is_string($field)) {
1519
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1520
        } else {
1521
            return null;
1522
        }
1523
1524
        $fields = null;
1525
        if (is_a($res, 'mysqli_result')) {
1526
            $fields = $res->fetch_fields();
1527
            foreach ($fields as $i => $f) {
1528
                $fields["{$f->name}"] = $f;
1529
                unset($fields[$i]);
1530
            }
1531
        }
1532
1533
        return $fields;
1534
    }
1535
1536
    /**
1537
     * Function to check that all field parameters are set correctly
1538
     *
1539
     * @param object $field_data
1540
     * @param object $check
1541
     * @param array $pks
1542
     * @param object $index
1543
     *
1544
     * @return array|string
1545
     */
1546
    public function fieldCheck($field_data, $check, $pks, $index)
1547
    {
1548
        $default = null;
1549
        $ret = null;
1550
1551
        $nn = ($check->nn ? " NOT NULL" : null);
1552
        if ($check->default === null) {
1553
            $default = " DEFAULT NULL";
1554
        } elseif (strlen($check->default)) {
1555
            $default = " DEFAULT '{$check->default}'";
1556
        }
1557
1558
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1559
            $this->_logger->notice("Wrong datatype", [
1560
                'name' => $field_data->name,
1561
                'datatype' => $check->dataType
1562
            ]);
1563
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1564
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1565
            $this->_logger->notice("Incorrect size", [
1566
                'name' => $field_data->name,
1567
                'current' => $field_data->length,
1568
                'new_size' => $check->length
1569
            ]);
1570
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1571
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1572
            $this->_logger->notice("Setting ENUM type", [
1573
                'name' => $field_data->name,
1574
                'values' => implode(",", $check->values)
1575
            ]);
1576
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1577
        }
1578
1579
        if (! is_null($index) && count($index)) {
1580
            foreach ($index as $ind) {
1581
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1582
                    $this->_logger->debug("Missing index", [
1583
                        'name' => $field_data->name
1584
                    ]);
1585
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1586
                }
1587
            }
1588
        }
1589
1590
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1591
            $this->_logger->debug("Setting PKs", [
1592
                'keys' => implode(',', $pks)
1593
            ]);
1594
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1595
        }
1596
1597
        return $ret;
1598
    }
1599
1600
    /**
1601
     * Function to check for the existence of a table within a schema
1602
     *
1603
     * @param string $strSchema
1604
     *            The schema to search in
1605
     * @param string $strTableName
1606
     *            Table to search for
1607
     *
1608
     * @return integer|boolean Returns number of tables that match if table is found in that schema, otherwise FALSE
1609
     */
1610
    public function tableExists($strSchema, $strTableName)
1611
    {
1612
        if (! $this->_c->select_db($strSchema)) {
1613
            fwrite(STDOUT, $this->_c->error . PHP_EOL);
1614
        }
1615
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1616
1617
        if ($res = $this->_c->query($sql)) {
1618
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1619
                return $res->num_rows;
1620
            }
1621
        } else {
1622
            if ($this->_c->errno) {
1623
                fwrite(STDOUT, $this->_c->error . PHP_EOL);
1624
            }
1625
        }
1626
1627
        return false;
1628
    }
1629
1630
    /**
1631
     * Function to detect if string is a JSON object or not
1632
     *
1633
     * @param string $strVal
1634
     *
1635
     * @return boolean
1636
     */
1637
    public function isJson($strVal)
1638
    {
1639
        json_decode($strVal);
1640
        return (json_last_error() == JSON_ERROR_NONE);
1641
    }
1642
1643
    /**
1644
     * Function to escape SQL characters to prevent SQL injection
1645
     *
1646
     * @param mixed $val
1647
     *            Value to escape
1648
     * @param boolean $blnEscape
1649
     *            Decide if we should escape or not
1650
     *
1651
     * @return string Escaped value
1652
     */
1653
    protected function _escape($val, $blnEscape = true)
1654
    {
1655
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1656
            return 'NULL';
1657
        } elseif (is_numeric($val) || is_string($val)) {
1658
            if (stripos($val, "IF(") !== false) {
1659
                return $val;
1660
            }
1661
            elseif ($blnEscape) {
1662
                return "'{$this->_c->real_escape_string($val)}'";
1663
            }
1664
            return $val;
1665
        } elseif (is_a($val, 'DateTime')) {
1666
            return "'{$val->format(MYSQL_DATETIME)}'";
1667
        } elseif (is_bool($val)) {
1668
            return $val ? "'1'" : "'0'";
1669
        } elseif (gettype($val) == 'object' && method_exists($val, '_escape')) {
1670
            $ret = call_user_func([
1671
                $val,
1672
                '_escape'
1673
            ]);
1674
            if ($ret !== false) {
1675
                return $ret;
1676
            } else {
1677
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1678
            }
1679
        } elseif (gettype($val) == 'object') {
1680
            $this->_logger->error("Unknown object to escape " . get_class($val) . " in SQL string {$this->_sql}");
1681
            return;
1682
        }
1683
1684
        throw new Exception("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1685
    }
1686
1687
    /**
1688
     * Function to retrieve all results
1689
     *
1690
     * @param int $intResultType
1691
     *
1692
     * @return mixed
1693
     */
1694
    protected function fetchAll($intResultType = MYSQLI_ASSOC)
1695
    {
1696
        $res = [];
1697
        if (method_exists('mysqli_result', 'fetch_all')) { // Compatibility layer with PHP < 5.3
1698
            $res = $this->_result->fetch_all($intResultType);
1699
        } else {
1700
            while ($tmp = $this->_result->fetch_array($intResultType)) {
1701
                $res[] = $tmp;
1702
            }
1703
        }
1704
1705
        return $res;
1706
    }
1707
1708
    /**
1709
     * Function to populate the fields for the SQL
1710
     *
1711
     * @param array|string $fields
1712
     *            [optional]
1713
     *            Optional array of fields to string together to create a field list
1714
     *
1715
     * @return string
1716
     */
1717
    protected function fields($fields = null)
1718
    {
1719
        $ret = null;
1720
1721
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1722
            foreach ($fields as $field) {
1723
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false)) {
1724
                    $ret .= "`$field`,";
1725
                } else {
1726
                    $ret .= "$field,";
1727
                }
1728
            }
1729
            $ret = substr($ret, - 1) == ',' ? substr($ret, 0, - 1) : $ret;
1730
        } elseif (is_string($fields)) {
1731
            $ret = $fields;
1732
        } elseif (is_null($fields)) {
1733
            $ret = "*";
1734
        } else {
1735
            throw new \InvalidArgumentException("Invalid field type");
1736
        }
1737
1738
        return $ret;
1739
    }
1740
1741
    /**
1742
     * Function to parse the flags
1743
     *
1744
     * @param array $flags
1745
     *            Two-dimensional array to added flags
1746
     *
1747
     *            <code>
1748
     *            [
1749
     *            &nbsp;&nbsp;'joins' => [
1750
     *            &nbsp;&nbsp;&nbsp;&nbsp;"JOIN table2 t2 ON t2.id=t1.id"
1751
     *            &nbsp;&nbsp;],
1752
     *            &nbsp;&nbsp;'group' => 'field',
1753
     *            &nbsp;&nbsp;'having' => 'field',
1754
     *            &nbsp;&nbsp;'order' => 'field',
1755
     *            &nbsp;&nbsp;'start' => 0,
1756
     *            &nbsp;&nbsp;'limit' => 0
1757
     *            ]
1758
     *            </code>
1759
     *
1760
     * @see Database::groups()
1761
     * @see Database::having()
1762
     * @see Database::order()
1763
     *
1764
     * @return string
1765
     */
1766
    protected function flags($arrFlags)
1767
    {
1768
        $ret = '';
1769
1770
        if (isset($arrFlags['group'])) {
1771
            $ret .= $this->groups($arrFlags['group']);
1772
        }
1773
1774
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1775
            $having = " HAVING";
1776
            foreach ($arrFlags['having'] as $x => $h) {
1777
                $having .= $this->parseClause($h, $x);
1778
            }
1779
            if (strlen($having) > strlen(" HAVING")) {
1780
                $ret .= $having;
1781
            }
1782
        }
1783
1784
        if (isset($arrFlags['order'])) {
1785
            $ret .= $this->order($arrFlags['order']);
1786
        }
1787
1788
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1789
            $ret .= " LIMIT ";
1790
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1791
                $ret .= "{$arrFlags['start']},";
1792
            }
1793
            $ret .= "{$arrFlags['limit']}";
1794
        }
1795
1796
        return $ret;
1797
    }
1798
1799
    /**
1800
     * Function to parse SQL GROUP BY statements
1801
     *
1802
     * @param mixed $groups
1803
     *
1804
     * @return string
1805
     */
1806
    protected function groups($groups)
1807
    {
1808
        $ret = '';
1809
        if (is_array($groups) && count($groups)) {
1810
            $ret .= " GROUP BY";
1811
1812
            foreach ($groups as $grp) {
1813
                $ret .= " $grp";
1814
1815
                if ($grp != end($groups)) {
1816
                    $ret .= ",";
1817
                }
1818
            }
1819
        } elseif (is_string($groups)) {
1820
            $ret .= " GROUP BY {$groups}";
1821
        } else {
1822
            throw (new Exception("Error in datatype for groups " . gettype($groups), E_ERROR));
1823
        }
1824
1825
        return $ret;
1826
    }
1827
1828
    /**
1829
     * Function to parse SQL ORDER BY statements
1830
     *
1831
     * @param mixed $order
1832
     *
1833
     * @return string
1834
     */
1835
    protected function order($order)
1836
    {
1837
        $ret = '';
1838
        if (is_array($order)) {
1839
            $ret .= " ORDER BY";
1840
1841
            foreach ($order as $ord) {
1842
                $ret .= " {$ord['field']} {$ord['sort']}";
1843
1844
                if ($ord != end($order)) {
1845
                    $ret .= ",";
1846
                }
1847
            }
1848
        } elseif (is_string($order)) {
1849
            $ret .= " ORDER BY {$order}";
1850
        }
1851
1852
        return $ret;
1853
    }
1854
1855
    /**
1856
     * Function to see if a constraint exists
1857
     *
1858
     * @param string $strConstraintId
1859
     *
1860
     * @return boolean
1861
     */
1862
    public function isConstraint($strConstraintId)
1863
    {
1864
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
1865
1866
        if ($res->num_rows) {
1867
            return true;
1868
        }
1869
1870
        return false;
1871
    }
1872
1873
    /**
1874
     * Function to parse where and having clauses
1875
     *
1876
     * @param array $arrClause
1877
     * @param int $intIndex
1878
     */
1879
    protected function parseClause($arrClause, $intIndex)
1880
    {
1881
        $ret = null;
1882
1883
        $this->_logger->debug("Parsing clause", $arrClause);
1884
1885
        if (! isset($arrClause['field']) && isset($arrClause['close-paren']) && $arrClause['close-paren']) {
1886
            $ret .= ")";
1887
            return $ret;
1888
        } elseif ($intIndex > 0 && ! isset($arrClause['sql_op'])) {
1889
            $this->_logger->warning("Missing sql_op field to identify how current and previous WHERE clause statements should be linked ('AND', 'OR', 'XOR', etc), skipped", [
1890
                'clause' => implode(",", $arrClause)
1891
            ]);
1892
            return;
1893
        }
1894
1895
        $op = '=';
1896
        if (isset($arrClause['op'])) {
1897
            $op = $arrClause['op'];
1898
        }
1899
1900
        switch ($op) {
1901
            case self::BETWEEN:
1902
                if (! isset($arrClause['field']) || ! isset($arrClause['low']) || ! isset($arrClause['high'])) {
1903
                    $this->_logger->warning("Missing field, low, or high for BETWEEN where clause, skipping");
1904
                    return;
1905
                }
1906
                break;
1907
            default:
1908
                if (! isset($arrClause['field']) || ! isset($arrClause['value'])) {
1909
                    $this->_logger->warning("Missing field or value for WHERE clause, skipping", $arrClause);
1910
                    return;
1911
                }
1912
        }
1913
1914
        if ($intIndex > 0) {
1915
            $ret .= " {$arrClause['sql_op']}";
1916
        }
1917
1918
        if (isset($arrClause['open-paren']) && $arrClause['open-paren']) {
1919
            $ret .= " (";
1920
        }
1921
1922
        if (isset($arrClause['backticks']) && ! $arrClause['backticks']) {
1923
            $field = $arrClause['field'];
1924
        } else {
1925
            $field = "`{$arrClause['field']}`";
1926
        }
1927
1928
        if ($op == self::IN || $op == self::NOT_IN) {
1929
            if (is_string($arrClause['value'])) {
1930
                $ret .= " {$field} {$op} " . (strpos($arrClause['value'], '(') !== false ? $arrClause['value'] : "({$arrClause['value']})");
1931
            } elseif (is_array($arrClause['value'])) {
1932
                $ret .= " {$field} {$op} (" . implode(",", array_map([
1933
                    $this,
1934
                    '_escape'
1935
                ], $arrClause['value'])) . ")";
1936
            } else {
1937
                $this->_logger->error("Invalid datatype for IN WHERE clause, only string and array allowed " . gettype($arrClause['value']), $arrClause);
1938
                throw new Exception("Invalid datatype for IN WHERE clause", E_ERROR);
1939
            }
1940
        } elseif ($op == self::BETWEEN) {
1941
            $ret .= " {$field} BETWEEN {$this->_escape($arrClause['low'])} AND {$this->_escape($arrClause['high'])}";
1942
        } else {
1943
            if (isset($arrClause['escape']) && ! $arrClause['escape']) {
1944
                $value = $arrClause['value'];
1945
            } else {
1946
                $value = $this->_escape($arrClause['value']);
1947
            }
1948
1949
            if (isset($arrClause['case_insensitive']) && $arrClause['case_insensitive']) {
1950
                $ret .= " LOWER({$field}) {$op} LOWER({$this->_escape($arrClause['value'])})";
1951
            } elseif (is_string($arrClause['value']) && preg_match("/\(SELECT/", $arrClause['value'])) {
1952
                $ret .= " {$field} {$op} {$arrClause['value']}";
1953
            } else {
1954
                $ret .= " {$field} {$op} {$value}";
1955
            }
1956
        }
1957
1958
        if (isset($arrClause['close-paren']) && $arrClause['close-paren']) {
1959
            $ret .= ")";
1960
        }
1961
1962
        return $ret;
1963
    }
1964
1965
    /**
1966
     * Encryption algorithm
1967
     *
1968
     * @param string $data
1969
     * @param string $key
1970
     * 
1971
     * @throws Exception
1972
     * 
1973
     * @return string
1974
     */
1975
    public static function encrypt($data, $salt = null)
1976
    {
1977
        if(!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1978
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1979
        }
1980
1981
        // Remove the base64 encoding from our key
1982
        if (is_null($salt)) {
1983
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1984
        } else {
1985
            $encryption_key = base64_decode($salt);
1986
        }
1987
        // Generate an initialization vector
1988
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
1989
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
1990
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1991
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
1992
        return base64_encode($encrypted . '::' . $iv);
1993
    }
1994
1995
    /**
1996
     * Decryption algorithm
1997
     *
1998
     * @param string $data
1999
     * 
2000
     * @throws Exception
2001
     * 
2002
     * @return string
2003
     */
2004
    public static function decrypt($data)
2005
    {
2006
        if(!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
2007
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
2008
        }
2009
2010
        // Remove the base64 encoding from our key
2011
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
2012
2013
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
2014
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
2015
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
2016
        return $plaintext;
2017
    }
2018
}
2019