Passed
Push — master ( b00340...9ef088 )
by Ryan
02:13
created

Database::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
crap 1
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 <[email protected]>
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
     * The mysqli connection
160
     *
161
     * @access protected
162
     * @var \mysqli
163
     */
164
    protected $_c;
165
166
    /**
167
     * To store the SQL statement
168
     *
169
     * @access private
170
     * @var string
171
     */
172
    private $_sql = null;
173
174
    /**
175
     * A variable to store the type of query that is being run
176
     *
177
     * @access private
178
     * @var int
179
     */
180
    private $_queryType = null;
181
182
    /**
183
     * The result of the query
184
     *
185
     * @access protected
186
     * @var mixed
187
     */
188
    protected $_result = null;
189
190
    /**
191
     * Log level
192
     *
193
     * @access private
194
     * @var string
195
     */
196
    private $_logLevel = Logger::ERROR;
197
198
    /**
199
     * Variable to store the logger
200
     *
201
     * @access private
202
     * @var \Monolog\Logger
203
     */
204
    private $_logger = null;
205
206
    /**
207
     * Path for the logger to log the file
208
     *
209
     * @access private
210
     * @var string
211
     */
212
    private $_logPath = null;
213
214
    /**
215
     * Variable to store the most recent insert ID from an insert query
216
     *
217
     * @access protected
218
     * @var mixed
219
     */
220
    protected $_insertId = null;
221
222
    /**
223
     * Variable to decide if we need to automatically run the queries after generating them
224
     *
225
     * @access public
226
     * @staticvar
227
     * @var boolean
228
     */
229
    public static $autorun = false;
230
231
    /**
232
     * Variable to decide if the system should create a CLI logger in addition to the file logger
233
     *
234
     * @access public
235
     * @staticvar
236
     * @var boolean
237
     */
238
    public static $cliLog = false;
239
240
    /**
241
     * Constructor
242
     *
243
     * @param string $strLogPath
244
     *            [optional]
245
     * @param \mysqli $dbh
246
     *            [optional]
247
     *            [by ref]
248
     *            mysqli object to perform queries.
249
     * @param int $intLogLevel
250
     */
251
    public function __construct($strLogPath = __DIR__, mysqli &$dbh = null, $intLogLevel = null)
252
    {
253
        if(! is_null($dbh) && is_a($dbh, 'mysqli')) {
254
            $this->_c = $dbh;
255
        } elseif(!defined('PHP_DB_SERVER') || !defined('PHP_DB_USER') || !defined('PHP_DB_PWD') || !defined('PHP_DB_SCHEMA')) {
256
            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);
257
        } elseif(defined('PHP_DB_ENCRYPT') && (!defined('PHP_DB_ENCRYPT_ALGORITHM') || !defined('PHP_DB_ENCRYPT_SALT'))) {
258
            throw new Exception("Missing required PHP_DB_ENCRYPT_ALGORITHM or PHP_DB_ENCRYPT_SALT constants");
259
        }
260
        
261
        if(defined('PHP_DB_ENCRYPT') && PHP_DB_ENCRYPT) {
262
            $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...
263
        } else {
264
            $pwd = PHP_DB_PWD;
265
        }
266
267
        $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...
268
        
269
        if ($this->_c->connect_errno) {
270
            throw new Exception("Could not create database class due to error {$this->_c->connect_error}", E_ERROR);
271
        }
272
273
        $this->_logPath = $strLogPath;
274
        touch($this->_logPath . "/db.log");
275
276
        if(!defined("PHP_DB_LOG_LEVEL") && is_null($intLogLevel)) {
277
            $this->_logLevel = Logger::ERROR;
278
        } elseif(!is_null($intLogLevel)) {
279
            $this->_logLevel = $intLogLevel;
280
        } elseif(defined('PHP_DB_LOG_LEVEL')) {
281
            $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...
282
        }
283
284
        $this->_logger = new Logger('db', [
285
            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

285
            new StreamHandler(realpath($this->_logPath . "/db.log"), /** @scrutinizer ignore-type */ $this->_logLevel)
Loading history...
286
        ]);
287
288
        if (PHP_SAPI == 'cli' && self::$cliLog) {
289
            $stream = new StreamHandler(STDOUT, $this->_logLevel);
290
            $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u"));
291
            $this->_logger->pushHandler($stream);
292
        }
293
294
        $this->_logger->info("Database connected");
295
        $this->_logger->debug("Connection details:", [
296
            'Server' => PHP_DB_SERVER,
297
            'User'   => PHP_DB_USER,
298
            'Schema' => PHP_DB_SCHEMA
299
        ]);
300
301
        $this->setVar("time_zone", "+00:00");
302
        $this->setVar("sql_mode", "");
303
    }
304
305
    /**
306
     * Function to make sure that the database is connected
307 78
     *
308
     * @return boolean
309 78
     */
310 1
    public function isConnected()
311 78
    {
312
        $this->_logger->debug("Pinging server");
313 78
        return $this->_c->ping();
314
    }
315
316
    /**
317 78
     * Setter function for _logger
318 78
     *
319
     * @param Logger $log
320
     */
321
    public function setLogger(Logger $log)
322
    {
323 78
        $this->_logger->debug("Setting logger");
324
        $this->_logger = $log;
325 78
    }
326
327
    /**
328
     * Getter function for _logLevel
329 78
     *
330 78
     * @return string
331
     */
332 78
    public function getLogLevel()
333
    {
334 78
        $level = $this->_logLevel;
335
336 78
        $this->_logger->debug("Getting log level ({$level})");
337 78
        return $level;
338
    }
339
340 78
    /**
341 78
     * Function to set the log level just in case there needs to be a change to the default log level
342
     *
343
     * @param string $strLevel
344 78
     */
345
    public function setLogLevel($strLevel)
346
    {
347
        $this->_logger->debug("Setting log level to {$strLevel}");
348
        $this->_logLevel = $strLevel;
349
350 78
        $handles = [];
351 78
352 78
        foreach ($this->_logger->getHandlers() as $h) {
353 78
            $h->/** @scrutinizer ignore-call */
354 78
                setLevel($strLevel);
355
            $handles[] = $h;
356
        }
357 78
358 78
        $this->_logger->setHandlers($handles);
359 78
    }
360
361
    /**
362
     * Getter function for _queryType
363
     *
364
     * @return int
365
     */
366 1
    public function getQueryType()
367
    {
368 1
        return $this->_queryType;
369 1
    }
370
371
    /**
372
     * Setter function for _queryType
373
     *
374
     * @param int $qt
375
     */
376
    public function setQueryType($qt)
377
    {
378
        $this->_queryType = $qt;
379
    }
380
381
    /**
382
     * Getter function for _sql
383
     *
384
     * @return string
385
     */
386
    public function getSql()
387
    {
388 1
        return $this->_sql;
389
    }
390 1
391
    /**
392 1
     * Function to return the currently selected database schema
393 1
     *
394
     * @return string
395
     */
396
    public function getSchema()
397
    {
398
        if ($res = $this->_c->query("SELECT DATABASE()")) {
399
            $row = $res->fetch_row();
400
401 1
            $this->_logger->debug("Getting schema {$row[0]}");
402
            return $row[0];
403 1
        }
404 1
    }
405
406 1
    /**
407
     * Function to set schema
408 1
     *
409
     * @param string $strSchema
410 1
     */
411 1
    public function setSchema($strSchema)
412
    {
413
        $this->_logger->debug("Setting schema to {$strSchema}");
414 1
        if (! $this->_c->select_db($strSchema)) {
415 1
            $this->_logger->emergency("Unknown schema {$strSchema}", [debug_backtrace()]);
416
            return false;
417
        }
418
        return true;
419
    }
420
421
    /**
422 2
     * Method to set a MYSQL variable
423
     *
424 2
     * @param string $strName
425
     * @param string $strVal
426
     *
427
     * @return boolean
428
     */
429
    public function setVar($strName, $strVal)
430
    {
431
        if (! $strName ) {
432 1
            $this->_logger->debug("name is blank", [
433
                'name'  => $strName
434 1
            ]);
435 1
            return false;
436
        }
437
438
        $this->_logger->debug("Setting {$strName} = '{$strVal}'");
439
440
        if ($this->_c->real_query("SET $strName = {$this->_escape($strVal)}")) {
441
            return true;
442 38
        } else {
443
            $this->_logger->error("Failed to set variable {$this->_c->error}");
444 38
            return false;
445
        }
446
    }
447
448
    /**
449
     * Function to execute the statement
450
     *
451
     * @param mixed $return
452 1
     *            [optional]
453
     *            MYSQLI constant to control what is returned from the mysqli_result object
454 1
     * @param string $class
455 1
     *            [optional]
456
     *            Class to use when returning object
457 1
     * @param string $strSql
458 1
     *            [optional]
459
     *            Optional SQL query
460
     *
461
     * @throws \Exception
462
     * @throws \InvalidArgumentException
463
     *
464
     * @return mixed
465
     */
466
    public function execute($return = MYSQLI_OBJECT, $strSql = null)
467
    {
468 2
        if (! is_null($strSql)) {
469
            $this->_sql = $strSql;
470 2
        }
471 2
472 1
        $this->_result = false;
473 1
        $query = 'SELECT';
474
        switch ($this->_queryType) {
475 1
            case self::SELECT_COUNT:
476
                $query = 'SELECT COUNT';
477
                break;
478
            case self::INSERT:
479
            case self::EXTENDED_INSERT:
480
                $query = 'INSERT';
481
                break;
482
            case self::UPDATE:
483
            case self::EXTENDED_UPDATE:
484
                $query = 'UPDATE';
485
                break;
486 78
            case self::REPLACE:
487
            case self::EXTENDED_REPLACE:
488 78
                $query = 'REPLACE';
489
                break;
490
            case self::DROP:
491
                $query = 'DROP';
492
                break;
493
            case self::DELETE:
494
                $query = 'DELETE';
495 78
                break;
496
            case self::CREATE_TABLE:
497 78
                $query = 'CREATE TABLE';
498 78
                break;
499
            case self::TRUNCATE:
500
                $query = 'TRUNCATE';
501
                break;
502
        }
503
504
        if (is_a($this->_c, 'mysqli')) {
505
            if (! $this->_c->ping()) {
506
                require_once 'DBConfig.php';
507
                $this->_c = null;
508
                $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_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_SCHEMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_SERVER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Godsgood33\Php_Db\PHP_DB_USER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
509
            }
510
        } else {
511
            throw new Exception('Database was not connected', E_ERROR);
512
        }
513
514
        $this->_logger->info("Executing {$query} query");
515
        $this->_logger->debug($this->_sql);
516
517
        try {
518
            if (in_array($this->_queryType, [
519
                self::SELECT,
520
                self::SELECT_COUNT
521
            ])) {
522
                $this->_result = $this->_c->query($this->_sql);
523 7
                if ($this->_c->error) {
524
                    $this->_logger->error("There is an error {$this->_c->error}");
525 7
                    $this->_logger->debug("Errored on query", [$this->_sql]);
526
                    throw new Exception("There was an error {$this->_c->error}", E_ERROR);
527
                }
528
            } else {
529 7
                $this->_result = $this->_c->real_query($this->_sql);
530 7
                if ($this->_c->errno) {
531 7
                    $this->_logger->error("There was an error {$this->_c->error}");
532 7
                    $this->_logger->debug("Errored on query", [$this->_sql]);
533
                    throw new Exception("There was an error {$this->_c->error}", E_ERROR);
534
                }
535 7
            }
536 7
537
            $this->_logger->debug("Checking for query results");
538
            $this->_result = $this->checkResults($return);
539 7
        } catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
540 7
541
        return $this->_result;
542
    }
543 7
544 7
    /**
545
     * Function to check the results and return what is expected
546
     *
547 7
     * @param mixed $returnType
548
     *            [optional]
549
     *            Optional return mysqli_result return type
550 7
     *
551
     * @return mixed
552
     */
553 7
    protected function checkResults($returnType)
554 3
    {
555 3
        $res = null;
556 4
557
        if (in_array($this->_queryType, [Database::CREATE_TABLE, Database::ALTER_TABLE, Database::TRUNCATE, Database::DROP])) {
558
            $res = $this->_result;
559
        }
560
        elseif (in_array($this->_queryType, [Database::INSERT, Database::EXTENDED_INSERT, Database::DELETE, Database::UPDATE, Database::EXTENDED_UPDATE, Database::REPLACE, Database::EXTENDED_REPLACE])) {
561 7
            $res = $this->_c->affected_rows;
562 7
563
            if (in_array($this->_queryType, [Database::INSERT, Database::REPLACE])) {
564
                $this->_insertId = $this->_c->insert_id;
565 7
            }
566
        }
567
        elseif ($this->_queryType == Database::SELECT_COUNT) {
568
            if (! is_a($this->_result, 'mysqli_result')) {
569
                $this->_logger->error("Error with return on query");
570
                return null;
571 7
            }
572 7
573
            if ($this->_result->num_rows == 1) {
574
                $row = $this->_result->fetch_assoc();
575 7
                if (isset($row['count'])) {
576 7
                    $this->_logger->debug("Returning SELECT_COUNT query", [
577 7
                        'count' => $row['count']
578
                    ]);
579 4
                    $res = $row['count'];
580 4
                }
581
            } elseif ($this->_result->num_rows > 1) {
582
                $this->_logger->debug("Returning SELECT_COUNT query", [
583 4
                    'count' => $this->_result->num_rows
584
                ]);
585
                $res = $this->_result->num_rows;
586 3
            }
587 3
588
            mysqli_free_result($this->_result);
589
        }
590
        else {
591
            $method = "mysqli_fetch_object";
592
            if($returnType == MYSQLI_ASSOC) {
593
                $method = "mysqli_fetch_assoc";
594 7
            } elseif ($returnType == MYSQLI_NUM) {
595 7
                $method = "mysqli_fetch_array";
596
            }
597
598 7
            if (is_a($this->_result, 'mysqli_result')) {
599
                if($this->_result->num_rows > 1) {
600
                    $res = [];
601
                    while ($row = call_user_func($method, $this->_result)) {
602
                        $res[] = $row;
603
                    }
604
                } else {
605
                    $res = call_user_func($method, $this->_result);
606
                }
607
            } else {
608
                $this->_logger->error("Error with return on query");
609
                return null;
610 7
            }
611
        }
612 7
613
        if ($this->_c->error) {
614 7
            $this->_logger->error("Encountered a SQL error", ['error' => $this->_c->error, 'list' => $this->_c->error_list]);
615 3
            $this->_logger->debug("Debug", ['debug' => debug_backtrace()]);
616
            return null;
617 4
        }
618
619
        return $res;
620
    }
621
622
    /**
623
     * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported)
624 4
     * Nothing is escaped
625
     *
626
     * @param string $strSql
627
     *            [optional]
628
     *            Optional query to pass in and execute
629
     *
630
     * @return \mysqli_result|boolean
631
     */
632
    public function query($strSql = null)
633
    {
634
        if (is_null($strSql)) {
635
            return $this->_c->query($this->_sql);
636
        } else {
637
            return $this->_c->query($strSql);
638
        }
639
    }
640
641
    /**
642
     * A function to build a select query
643
     *
644
     * @param string $strTableName
645
     *            The table to query
646
     * @param array|string $fields
647
     *            [optional]
648 4
     *            Optional array of fields to return (defaults to '*')
649 4
     * @param array $arrWhere
650 1
     *            [optional]
651 3
     *            Optional 2-dimensional array to build where clause from
652 1
     * @param array $arrFlags
653
     *            [optional]
654
     *            Optional 2-dimensional array to allow other flags
655 4
     *
656 4
     * @see Database::flags()
657 3
     *
658 3
     * @throws Exception
659 3
     *
660
     * @return mixed
661
     */
662 4
    public function select($strTableName, $fields = null, $arrWhere = [], $arrFlags = [])
663
    {
664
        $this->_sql = null;
665
        $this->_queryType = self::SELECT;
666
667
        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...
668
            $this->_logger->debug("Starting SELECT query of {$strTableName}", [
669
                'fields' => $this->fields($fields)
670 7
            ]);
671
            $this->_sql = "SELECT " . $this->fields($fields) . " FROM $strTableName";
672
        } else {
673
            $this->_logger->emergency("Table name is invalid or wrong type", [debug_backtrace()]);
674
            throw new Exception("Table name is invalid");
675
        }
676 7
677
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins']) && count($arrFlags['joins'])) {
678
            $this->_logger->debug("Adding joins", [
679
                'joins' => implode(' ', $arrFlags['joins'])
680
            ]);
681
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
682
        } else {
683
            $this->_logger->debug("No joins");
684
        }
685
686
        $where = $this->parseClause($arrWhere);
687
688
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
689 1
            $where_str = " WHERE";
690
            $this->_logger->debug("Parsing where clause and adding to query");
691 1
            foreach ($where as $x => $w) {
692
                if($x > 0) {
693
                    $where_str .= " {$w->sqlOperator}";
694 1
                }
695
                $where_str .= $w;
696
            }
697
            if (strlen($where_str) > strlen(" WHERE")) {
698
                $this->_sql .= $where_str;
699
            }
700
        }
701
702
        if (is_array($arrFlags) && count($arrFlags)) {
703
            $this->_logger->debug("Parsing flags and adding to query", $arrFlags);
704
            $this->_sql .= $this->flags($arrFlags);
705
        }
706
707
        if (self::$autorun) {
708
            return $this->execute();
709
        }
710
711
        return $this->_sql;
712
    }
713
714
    /**
715
     * Function to build a query to check the number of rows in a table
716
     *
717
     * @param string $strTableName
718
     *            The table to query
719
     * @param array $arrWhere
720 16
     *            [optional]
721
     *            Optional 2-dimensional array to build where clause
722 16
     * @param array $arrFlags
723 16
     *            [optional]
724
     *            Optional 2-dimensional array to add flags
725 16
     *
726 15
     * @see Database::flags()
727 15
     *
728
     * @return string|NULL
729 14
     */
730
    public function selectCount($strTableName, $arrWhere = [], $arrFlags = [])
731 1
    {
732 1
        $this->_sql = null;
733
        $this->_queryType = self::SELECT_COUNT;
734
735 14
        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...
736
            $this->_sql = "SELECT COUNT(1) AS 'count' FROM $strTableName";
737
        } else {
738
            $this->_logger->emergency("Table name is invalid or wrong type", [debug_backtrace()]);
739
            throw new Exception("Table name is invalid");
740
        }
741 14
742
        if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
743
            $this->_sql .= " " . implode(" ", $arrFlags['joins']);
744 14
        }
745 2
746 2
        $where = $this->parseClause($arrWhere);
747 2
748 2
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
749
            $where_str = " WHERE";
750 2
            $this->_logger->debug("Parsing where clause and adding to query");
751 1
            foreach ($where as $x => $w) {
752
                if($x > 0) {
753
                    $where_str .= " {$w->sqlOperator}";
754
                }
755 14
                $where_str .= $w;
756
            }
757
            if (strlen($where_str) > strlen(" WHERE")) {
758
                $this->_sql .= $where_str;
759
            }
760 14
        }
761
762
        if (is_array($arrFlags) && count($arrFlags)) {
763
            $this->_sql .= $this->flags($arrFlags);
764 14
        }
765
766
        if (self::$autorun) {
767
            return $this->execute();
768
        }
769
770
        return $this->_sql;
771
    }
772
773
    /**
774
     * Function to build an insert query statement
775
     *
776
     * @param string $strTableName
777
     * @param array|string $arrParams
778
     * @param boolean $blnToIgnore
779
     *
780
     * @return string|NULL
781
     */
782
    public function insert($strTableName, $arrParams = null, $blnToIgnore = false)
783
    {
784 3
        $this->_sql = null;
785
        $this->_queryType = self::INSERT;
786 3
787 3
        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...
788
            $this->_sql = "INSERT" . ($blnToIgnore ? " IGNORE" : "") . " INTO {$strTableName}";
789 3
        } else {
790 2
            throw new Exception("Table name is invalid");
791
        }
792 1
793 1
        if (is_array($arrParams) && count($arrParams)) {
794
            if(is_array($arrParams) && count($arrParams)) {
795
                $this->_sql .= " (`" . implode("`,`", array_keys($arrParams)) . "`)";
796 2
            }
797 1
            $this->_sql .= " VALUES (" . implode(",", array_map([
798
                $this,
799
                '_escape'
800 2
            ], array_values($arrParams))) . ")";
801 1
        } elseif (is_string($arrParams) && strpos(strtolower($arrParams), 'select') !== false) {
802 1
            $this->_sql .= " {$arrParams}";
803 1
        } elseif (is_object($arrParams)) {
804
            $interfaces = \class_implements($arrParams);
805 1
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::insert")) {
806 1
                $params = \call_user_func([$arrParams, "insert"]);
807
                $this->_sql .= " (`" . implode("`,`", array_keys($params)) . "`) VALUES ";
808
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($params))) . ")";
809
            } else {
810 2
                throw new Exception("Object does not implement the DBInterface interface and methods");
811 1
            }
812
        } else {
813
            throw new Exception("Invalid type passed to insert " . gettype($arrParams));
814 2
        }
815
816
        if (self::$autorun) {
817
            return $this->execute();
818 2
        }
819
820
        return $this->_sql;
821
    }
822
823
    /**
824
     * Function to create an extended insert query statement
825
     *
826
     * @param string $strTableName
827
     *            The table name that the data is going to be inserted on
828
     * @param array $arrFields
829
     *            An array of field names that each value represents
830 8
     * @param array|string $params
831
     *            An array of array of values or a string with a SELECT statement to populate the insert with
832 8
     * @param boolean $blnToIgnore
833 8
     *            [optional]
834
     *            Boolean to decide if we need to use the INSERT IGNORE INTO syntax
835 8
     *
836 7
     * @return NULL|string Returns the SQL if self::$autorun is set to false, else it returns the output from running.
837
     */
838 1
    public function extendedInsert($strTableName, $arrFields, $params, $blnToIgnore = false)
839
    {
840
        $this->_sql = null;
841 7
        $this->_queryType = self::EXTENDED_INSERT;
842 4
843 4
        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...
844
            $this->_sql = "INSERT " . ($blnToIgnore ? "IGNORE " : "") . "INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
845 4
        } else {
846 4
            throw new Exception("Table name is invalid");
847 4
        }
848 4
849 3
        if (is_array($params) && count($params)) {
850 1
            $this->_sql .= " VALUES ";
851 2
            if (isset($params[0]) && is_array($params[0])) {
852 2
                foreach ($params as $p) {
853 2
                    if (count($p) != count($arrFields)) {
854 1
                        $this->_logger->emergency("Inconsistent number of fields to values in extendedInsert", [
855 1
                            $p,
856 1
                            debug_backtrace()
857
                        ]);
858 2
                        throw new Exception("Inconsistent number of fields in fields and values in extendedInsert " . print_r($p, true));
859
                    }
860
                    $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($p))) . ")";
861
862
                    if ($p != end($params)) {
863
                        $this->_sql .= ",";
864 6
                    }
865
                }
866
            } else {
867
                $this->_sql .= "(" . implode("),(", array_map([$this, '_escape'], array_values($params))) . ")";
868 6
            }
869
        } else {
870
            throw new Exception("Invalid param type");
871
        }
872
873
        if (self::$autorun) {
874
            return $this->execute();
875
        }
876
877
        return $this->_sql;
878
    }
879
880
    /**
881
     * Build a statement to update a table
882
     *
883
     * @param string $strTableName
884
     *            The table name to update
885
     * @param array $arrParams
886 5
     *            Name/value pairs of the field name and value
887
     * @param array $arrWhere
888 5
     *            [optional]
889 5
     *            Two-dimensional array to create where clause
890
     * @param array $arrFlags
891 5
     *            [optional]
892 4
     *            Two-dimensional array to create other flag options (joins, order, and group)
893
     *
894 1
     * @see Database::flags()
895
     *
896
     * @return NULL|string
897 4
     */
898 3
    public function update($strTableName, $arrParams, $arrWhere = [], $arrFlags = [])
899 3
    {
900 3
        $this->_sql = "UPDATE ";
901 3
        $this->_queryType = self::UPDATE;
902 2
903 2
        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...
904 2
            $this->_sql .= $strTableName;
905
906 2
            if (isset($arrFlags['joins']) && is_array($arrFlags['joins'])) {
907
                $this->_sql .= " " . implode(" ", $arrFlags['joins']);
908 2
                unset($arrFlags['joins']);
909
            }
910 2
911 2
            $this->_sql .= " SET ";
912
        } else {
913
            throw new Exception("Table name is invalid");
914
        }
915 1
916
        if (is_array($arrParams) && count($arrParams)) {
917 1
            $keys = array_keys($arrParams);
918 1
            foreach ($arrParams as $f => $p) {
919 1
                $field = $f;
920 1
                if ((strpos($f, "`") === false) && (strpos($f, ".") === false) && (strpos($f, "*") === false) && (stripos($f, " as ") === false)) {
921
                    $field = "`{$f}`";
922 1
                }
923
924
                if (! is_null($p)) {
925
                    $this->_sql .= "$field={$this->_escape($p)}";
926
                } else {
927
                    $this->_sql .= "$field=NULL";
928 2
                }
929
930
                if($f != end($keys)) {
931
                    $this->_sql .= ",";
932 2
                }
933
            }
934
        } elseif (is_object($arrParams)) {
935
            $interfaces = \class_implements($arrParams);
936
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::update")) {
937
                $this->_sql .= \call_user_func([$arrParams, "update"]);
938
            } else {
939
                throw new Exception("Params is an object that doesn't implement DBInterface");
940
            }
941
        } else {
942
            throw new Exception("No fields to update");
943
        }
944
945
        $where = $this->parseClause($arrWhere);
946
        
947
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
948
            $where_str = " WHERE";
949
            $this->_logger->debug("Parsing where clause and adding to query");
950
            foreach ($where as $x => $w) {
951
                if($x > 0) {
952
                    $where_str .= " {$w->sqlOperator}";
953
                }
954 8
                $where_str .= $w;
955
            }
956 8
            if (strlen($where_str) > strlen(" WHERE")) {
957 8
                $this->_sql .= $where_str;
958
            }
959 8
        }
960 7
961
        if (! is_null($arrFlags) && is_array($arrFlags) && count($arrFlags)) {
962 7
            $this->_sql .= $this->flags($arrFlags);
963 1
        }
964 1
965
        if (self::$autorun) {
966
            return $this->execute();
967 7
        }
968
969 1
        return $this->_sql;
970
    }
971
972 7
    /**
973 4
     * Function to offer an extended updated functionality by using two different tables.
974 4
     *
975 3
     * @param string $strTableToUpdate
976
     *            The table that you want to update (alias 'tbu' is automatically added)
977
     * @param string $strOriginalTable
978 4
     *            The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added)
979 4
     * @param string $strLinkField
980
     *            The common index value between them that will join the fields
981
     * @param array|string $arrParams
982
     *            If string only a single field is updated (tbu.$params = o.$params)
983
     *            If array each element in the array is a field to be updated (tbu.$param = o.$param)
984 4
     *
985
     * @return mixed
986
     */
987
    public function extendedUpdate($strTableToUpdate, $strOriginalTable, $strLinkField, $arrParams)
988 3
    {
989 2
        $this->_sql = "UPDATE ";
990 2
        $this->_queryType = self::EXTENDED_UPDATE;
991 1
992
        if (! is_null($strTableToUpdate) && ! is_null($strOriginalTable) && ! is_null($strLinkField)) {
0 ignored issues
show
introduced by
The condition is_null($strOriginalTable) is always false.
Loading history...
introduced by
The condition is_null($strLinkField) is always false.
Loading history...
introduced by
The condition is_null($strTableToUpdate) is always false.
Loading history...
993 2
            $this->_sql .= "$strTableToUpdate tbu INNER JOIN $strOriginalTable o USING ($strLinkField) SET ";
994
        } else {
995
            throw new Exception("Missing necessary fields");
996 1
        }
997
998
        if (is_array($arrParams) && count($arrParams)) {
999 5
            foreach ($arrParams as $param) {
1000 1
                if ($param != $strLinkField) {
1001 1
                    $this->_sql .= "tbu.`$param` = o.`$param`,";
1002 1
                }
1003
            }
1004 1
            $this->_sql = substr($this->_sql, 0, - 1);
1005 1
        } elseif (is_string($arrParams)) {
1006
            $this->_sql .= "tbu.`$arrParams` = o.`$arrParams`";
1007
        } else {
1008
            throw new Exception("Do not understand datatype " . gettype($arrParams), E_ERROR);
1009 5
        }
1010 1
1011
        if (self::$autorun) {
1012
            return $this->execute();
1013 5
        }
1014
1015
        return $this->_sql;
1016
    }
1017 5
1018
    /**
1019
     * Function to build a replace query
1020
     *
1021
     * @param string $strTableName
1022
     *            The table to update
1023
     * @param array $arrParams
1024
     *            Name/value pair to insert
1025
     *
1026
     * @return NULL|string
1027
     */
1028
    public function replace($strTableName, $arrParams)
1029
    {
1030
        $this->_sql = null;
1031
        $this->_queryType = self::REPLACE;
1032
1033
        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...
1034
            $this->_sql = "REPLACE INTO $strTableName ";
1035 3
        } else {
1036
            throw new Exception("Table name is invalid");
1037 3
        }
1038 3
1039
        if(is_array($arrParams) && count($arrParams)) {
1040 3
            $keys = array_keys($arrParams);
1041 3
            $vals = array_values($arrParams);
1042
1043
            $this->_sql .= "(`" . implode("`,`", $keys) . "`)";
1044
            $this->_sql .= " VALUES (" . implode(",", array_map([
1045
                $this,
1046 3
                '_escape'
1047 1
            ], array_values($vals))) . ")";
1048 1
        } elseif (is_object($arrParams)) {
1049 1
            $interfaces = class_implements($arrParams);
1050
            if(in_array("Godsgood33\Php_Db\DBInterface", $interfaces) && is_callable(get_class($arrParams) . "::replace")) {
1051
                $params = \call_user_func([$arrParams, "replace"]);
1052 1
                $this->_sql .= "(`" . implode("`,`", array_keys($params)) . "`) VALUES ";
1053 2
                $this->_sql .= "(" . implode(",", array_map([$this, '_escape'], array_values($params))) . ")";
1054 1
            }
1055
        }
1056 1
1057
        if (self::$autorun) {
1058
            return $this->execute();
1059 2
        }
1060
1061
        return $this->_sql;
1062
    }
1063 2
1064
    /**
1065
     * Function to build an extended replace statement
1066
     *
1067
     * @param string $strTableName
1068
     *            Table name to update
1069
     * @param array $arrFields
1070
     *            Array of fields
1071
     * @param array $arrParams
1072
     *            Two-dimensional array of values
1073
     *
1074
     * @return NULL|string
1075
     */
1076 2
    public function extendedReplace($strTableName, $arrFields, $arrParams)
1077
    {
1078 2
        $this->_sql = null;
1079 2
        $this->_queryType = self::EXTENDED_REPLACE;
1080
1081 2
        if (! is_array($arrFields) || ! count($arrFields)) {
0 ignored issues
show
introduced by
The condition is_array($arrFields) is always true.
Loading history...
1082 1
            throw new Exception("Error with the field type");
1083
        }
1084 1
1085
        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...
1086
            $this->_sql = "REPLACE INTO $strTableName " . "(`" . implode("`,`", $arrFields) . "`)";
1087 1
        } else {
1088 1
            throw new Exception("Table name is invalid");
1089 1
        }
1090
1091 1
        if (is_array($arrParams) && count($arrParams)) {
1092 1
            $this->_sql .= " VALUES ";
1093 1
            foreach ($arrParams as $p) {
1094 1
                $this->_sql .= "(" . implode(",", array_map([
1095 1
                    $this,
1096
                    '_escape'
1097
                ], array_values($p))) . ")";
1098
1099
                if ($p != end($arrParams)) {
1100
                    $this->_sql .= ",";
1101
                }
1102
            }
1103
        }
1104
1105 1
        if (self::$autorun) {
1106
            return $this->execute();
1107
        }
1108
1109 1
        return $this->_sql;
1110
    }
1111
1112
    /**
1113
     * Function to build a delete statement
1114
     *
1115
     * @param string $strTableName
1116
     *            Table name to act on
1117
     * @param array $arrFields
1118
     *            [optional]
1119
     *            Optional list of fields to delete (used when including multiple tables)
1120
     * @param array $arrWhere
1121
     *            [optional]
1122
     *            Optional 2-dimensional array to build where clause from
1123
     * @param array $arrJoins
1124 2
     *            [optional]
1125
     *            Optional 2-dimensional array to add other flags
1126 2
     *
1127 2
     * @see Database::flags()
1128
     *
1129 2
     * @return string|NULL
1130 1
     */
1131
    public function delete($strTableName, $arrFields = [], $arrWhere = [], $arrJoins = [])
1132
    {
1133 1
        $this->_sql = "DELETE";
1134 1
        $this->_queryType = self::DELETE;
1135
1136
        $this->_logger->debug("Deleting table data");
1137
1138
        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...
1139 1
            $this->_sql .= " " . implode(",", $arrFields);
1140 1
        }
1141 1
1142 1
        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...
1143 1
            $this->_sql .= " FROM $strTableName";
1144 1
        } else {
1145 1
            throw new Exception("Table name is invalid");
1146
        }
1147 1
1148 1
        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...
1149
            $this->_sql .= " " . implode(" ", $arrJoins);
1150
        }
1151
1152
        $where = $this->parseClause($arrWhere);
1153 1
1154
        if (! is_null($where) && is_array($where) && count($where)) {
0 ignored issues
show
introduced by
The condition is_null($where) is always false.
Loading history...
introduced by
The condition is_array($where) is always true.
Loading history...
1155
            $where_str = " WHERE";
1156
            $this->_logger->debug("Parsing where clause and adding to query");
1157 1
            foreach ($where as $x => $w) {
1158
                if($x > 0) {
1159
                    $where_str .= " {$w->sqlOperator}";
1160
                }
1161
                $where_str .= $w;
1162
            }
1163
            if (strlen($where_str) > strlen(" WHERE")) {
1164
                $this->_sql .= $where_str;
1165
            }
1166
        }
1167
1168
        if (self::$autorun) {
1169
            return $this->execute();
1170
        }
1171
1172
        return $this->_sql;
1173
    }
1174
1175
    /**
1176
     * Function to build a drop table statement (automatically executes)
1177
     *
1178
     * @param string $strTableName
1179
     *            Table to drop
1180 4
     * @param string $strType
1181
     *            [optional]
1182 4
     *            Type of item to drop ('table', 'view') (defaulted to 'table')
1183 4
     * @param boolean $blnIsTemp
1184
     *            [optional]
1185 4
     *            Optional boolean if this is a temporary table
1186
     *
1187 4
     * @return string|NULL
1188 1
     */
1189
    public function drop($strTableName, $strType = 'table', $blnIsTemp = false)
1190
    {
1191 4
        $this->_sql = null;
1192 3
        $this->_queryType = self::DROP;
1193
1194 1
        switch ($strType) {
1195
            case 'table':
1196
                $strType = 'TABLE';
1197 3
                break;
1198 1
            case 'view':
1199
                $strType = 'VIEW';
1200
                break;
1201 3
            default:
1202 1
                throw new Exception("Invalid type " . gettype($strType), E_ERROR);
1203 1
        }
1204 1
1205
        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...
1206 1
            $this->_sql = "DROP" . ($blnIsTemp ? " TEMPORARY" : "") . " $strType IF EXISTS `{$strTableName}`";
1207 1
        } else {
1208
            throw new Exception("Table name is invalid");
1209
        }
1210
1211 3
        if (self::$autorun) {
1212
            return $this->execute();
1213
        }
1214
1215 3
        return $this->_sql;
1216
    }
1217
1218
    /**
1219
     * Function to build a truncate table statement (automatically executes)
1220
     *
1221
     * @param string $strTableName
1222
     *            Table to truncate
1223
     *
1224
     * @throws Exception
1225
     *
1226
     * @return string|NULL
1227
     */
1228
    public function truncate($strTableName)
1229
    {
1230
        $this->_sql = null;
1231
        $this->_queryType = self::TRUNCATE;
1232 5
1233
        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...
1234 5
            $this->_sql = "TRUNCATE TABLE $strTableName";
1235 5
        } else {
1236
            throw new Exception("Table name is invalid");
1237 5
        }
1238 5
1239 3
        if (self::$autorun) {
1240 3
            return $this->execute();
1241 2
        }
1242 1
1243 1
        return $this->_sql;
1244
    }
1245 1
1246
    /**
1247
     * Function to build a create temporary table statement
1248 4
     *
1249 3
     * @param string $strTableName
1250
     *            Name to give the table when creating
1251 1
     * @param boolean $blnIsTemp
1252
     *            [optional]
1253
     *            Optional boolean to make the table a temporary table
1254 3
     * @param mixed $strSelect
1255
     *            [optional]
1256
     *            Optional parameter if null uses last built statement
1257
     *            If string, will be made the SQL statement executed to create the table
1258 3
     *            If array, 2-dimensional array with "field", "datatype" values to build table fields
1259
     *
1260
     * @return NULL|string
1261
     */
1262
    public function createTable($strTableName, $blnIsTemp = false, $strSelect = null)
1263
    {
1264
        $this->_queryType = self::CREATE_TABLE;
1265
1266
        if (is_null($strSelect) && ! is_null($this->_sql) && substr($this->_sql, 0, 6) == 'SELECT') {
1267
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($this->_sql)";
1268
        } 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...
1269
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName AS ($strSelect)";
1270
        } elseif (! is_null($strTableName) && is_string($strTableName) && is_array($strSelect)) {
1271 2
            $this->_sql = "CREATE" . ($blnIsTemp ? " TEMPORARY" : "") . " TABLE IF NOT EXISTS $strTableName (";
1272
1273 2
            foreach ($strSelect as $field) {
1274 2
                $default = null;
1275
                if (isset($field['default'])) {
1276 2
                    $default = (is_null($field['default']) ? "" : " DEFAULT '{$field['default']}'");
1277 1
                }
1278
                $this->_sql .= "`{$field['field']}` {$field['datatype']}" . $default . (isset($field['option']) ? " {$field['option']}" : '') . ",";
1279 1
            }
1280
            $this->_sql = substr($this->_sql, 0, - 1) . ")";
1281
        }
1282 1
1283
        if (self::$autorun) {
1284
            return $this->execute();
1285
        }
1286 1
1287
        return $this->_sql;
1288
    }
1289
1290
    /**
1291
     * Function to create a table using a stdClass object derived from JSON
1292
     *
1293
     * @param \stdClass $json
1294
     *
1295
     * @example /examples/create_table_json.json
1296
     *
1297
     */
1298
    public function createTableJson($json)
1299
    {
1300
        $this->_queryType = self::CREATE_TABLE;
1301
        $this->_c->select_db($json->schema);
1302
1303
        $this->_sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` (";
1304
        foreach ($json->fields as $field) {
1305 3
            $this->_sql .= "`{$field->name}` {$field->dataType}";
1306
1307 3
            if ($field->dataType == 'enum' && isset($field->values)) {
1308
                $this->_sql .= "('" . implode("','", $field->values) . "')";
1309 3
            }
1310 1
1311 2
            if (isset($field->ai) && $field->ai) {
1312 1
                $this->_sql .= " AUTO_INCREMENT";
1313 1
            }
1314 1
1315
            if (isset($field->nn) && $field->nn) {
1316 1
                $this->_sql .= " NOT NULL";
1317 1
            } elseif(isset($field->default)) {
1318 1
                if (strtolower($field->default) == 'null') {
1319 1
                    $this->_sql .= " DEFAULT NULL";
1320
                } elseif (strlen($field->default)) {
1321 1
                    $this->_sql .= " DEFAULT '{$field->default}'";
1322
                }
1323 1
            }
1324
1325
            if ($field != end($json->fields)) {
1326 3
                $this->_sql .= ",";
1327
            }
1328
        }
1329
1330 3
        if (isset($json->index) && count($json->index)) {
1331
            foreach ($json->index as $ind) {
1332
                $ref = null;
1333
                if(is_array($ind->ref)) {
1334
                    $ref = "";
1335
                    foreach($ind->ref as $r) {
1336
                        $ref .= "`{$r}` ASC,";
1337
                    }
1338
                    $ref = substr($ref, 0, -1);
1339
                } elseif(is_string($ind->ref)) {
1340
                    $ref = $ind->ref;
1341 3
                }
1342
                if(!is_null($ref)) {
1343 3
                    $this->_sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ref}`)";
1344 3
                }
1345
            }
1346 3
        }
1347 3
1348 3
        if (isset($json->constraints) && count($json->constraints)) {
1349
            foreach ($json->constraints as $con) {
1350 3
                $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));
1351 1
            }
1352
        }
1353
1354 3
        if (isset($json->unique) && count($json->unique)) {
1355 3
            $this->_sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)";
1356
        }
1357
1358 3
        if (isset($json->primary_key) && count($json->primary_key)) {
1359 3
            $this->_sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))";
1360 2
        } else {
1361 2
            if (substr($this->_sql, - 1) == ',') {
1362 1
                $this->_sql = substr($this->_sql, 0, - 1);
1363 1
            }
1364 1
1365
            $this->_sql .= ")";
1366
        }
1367
1368 3
        $this->execute();
1369 2
    }
1370
1371
    /**
1372
     * Function to alter a existing table
1373 3
     *
1374 1
     * @param string $strTableName
1375 1
     *            Table to alter
1376 1
     * @param int $intAction
1377
     *            What action should be taken
1378
     * @param mixed $arrParams
1379
     *            For add column this is a stdClass object that has the same elements as the example json
1380
     *
1381
     * @return mixed
1382 1
     */
1383 1
    public function alterTable($strTableName, $intAction, $arrParams)
1384
    {
1385 1
        $this->_queryType = self::ALTER_TABLE;
1386 1
        $this->_sql = "ALTER TABLE $strTableName";
1387
        if ($intAction == self::ADD_COLUMN) {
1388
            $nn = (isset($arrParams->nn) && $arrParams->nn ? " NOT NULL" : "");
1389
            $default = null;
1390
            if ($arrParams->default === null) {
1391 3
                $default = " DEFAULT NULL";
1392 1
            } elseif (strlen($arrParams->default)) {
1393 1
                $default = " DEFAULT {$this->_escape($arrParams->default)}";
1394
            }
1395
            $this->_sql .= " ADD COLUMN `{$arrParams->name}` {$arrParams->dataType}" . $nn . $default;
1396
        } elseif ($intAction == self::DROP_COLUMN) {
1397 3
            $this->_sql .= " DROP COLUMN ";
1398 1
            foreach ($arrParams as $col) {
1399
                $this->_sql .= "`{$col->name}`";
1400
1401 3
                if ($col != end($arrParams)) {
1402 3
                    $this->_sql .= ",";
1403
                }
1404
            }
1405
        } elseif ($intAction == self::MODIFY_COLUMN) {
1406
            $this->_sql .= " MODIFY COLUMN";
1407
            $nn = (isset($arrParams->nn) && $arrParams->nn ? " NOT NULL" : "");
1408
            $default = null;
1409
            if ($arrParams->default === null) {
1410
                $default = " DEFAULT NULL";
1411 3
            } elseif (strlen($arrParams->default)) {
1412 3
                $default = " DEFAULT {$this->_escape($arrParams->default)}";
1413
            }
1414
            $this->_sql .= " `{$arrParams->name}` `{$arrParams->new_name}` {$arrParams->dataType}" . $nn . $default;
1415
        } elseif ($intAction == self::ADD_CONSTRAINT) {
1416
            if(is_array($arrParams->field) && is_array($arrParams->local)) {
1417
                $field = "`" . implode("`,`", $arrParams->field) . "`";
1418
                $local = "`" . implode("`,`", $arrParams->local) . "`";
1419
            } elseif(is_string($arrParams->field) && is_string($arrParams->local)) {
1420
                $field = "`{$arrParams->field}`";
1421
                $local = "`{$arrParams->local}`";
1422
            } else {
1423
                $this->_logger->critical("Error in reading constraint field");
1424
                throw new Exception("Error in reading constraint field");
1425
            }
1426 3
            $this->_sql .= " ADD CONSTRAINT `{$arrParams->id}` FOREIGN KEY ({$local}) REFERENCES `{$arrParams->schema}`.`{$arrParams->table}` ({$field}) ON DELETE {$arrParams->delete} ON UPDATE {$arrParams->update}";
1427
        }
1428 3
1429 3
        if (self::$autorun) {
1430 3
            return $this->execute();
1431 1
        }
1432 1
1433 1
        return $this->_sql;
1434 1
    }
1435
1436
    /**
1437
     * Check to see if a field in a table exists
1438 1
     *
1439 2
     * @param string $strTableName
1440 1
     *            Table to check
1441 1
     * @param string $strFieldName
1442 1
     *            Field name to find
1443
     *
1444 1
     * @return boolean Returns TRUE if field is found in that schema and table, otherwise FALSE
1445
     */
1446
    public function fieldExists($strTableName, $strFieldName)
1447
    {
1448 1
        $fdata = $this->fieldData($strTableName);
1449 1
1450 1
        if (is_array($fdata) && count($fdata)) {
1451 1
            foreach ($fdata as $field) {
1452 1
                if ($field->name == $strFieldName) {
1453
                    return true;
1454 1
                }
1455 1
            }
1456
        }
1457 1
1458
        return false;
1459
    }
1460
1461
    /**
1462
     * Function to get the column data (datatype, flags, defaults, etc)
1463
     *
1464
     * @param string $strTableName
1465
     *            Table to query
1466
     * @param mixed $field
1467
     *            [optional]
1468
     *            Optional field to retrieve data (if null, returns data from all fields)
1469
     *
1470
     * @return array
1471
     */
1472 3
    public function fieldData($strTableName, $field = null)
1473
    {
1474
        if (is_null($field)) {
1475
            $res = $this->_c->query("SELECT * FROM $strTableName LIMIT 1");
1476 3
        } elseif (is_array($field)) {
1477
            $res = $this->_c->query("SELECT `" . implode("`,`", $field) . "` FROM $strTableName LIMIT 1");
1478
        } elseif (is_string($field)) {
1479
            $res = $this->_c->query("SELECT $field FROM $strTableName LIMIT 1");
1480
        } else {
1481
            return null;
1482
        }
1483
1484
        $fields = null;
1485
        if (is_a($res, 'mysqli_result')) {
1486
            $fields = $res->fetch_fields();
1487
            foreach ($fields as $i => $f) {
1488
                $fields["{$f->name}"] = $f;
1489 2
                unset($fields[$i]);
1490
            }
1491 2
        }
1492
1493 2
        return $fields;
1494 2
    }
1495 2
1496 1
    /**
1497
     * Function to check that all field parameters are set correctly
1498
     *
1499
     * @param object $field_data
1500
     * @param object $check
1501 1
     * @param array $pks
1502
     * @param object $index
1503
     *
1504
     * @return array|string
1505
     */
1506
    public function fieldCheck($field_data, $check, $pks, $index)
1507
    {
1508
        $default = null;
1509
        $ret = null;
1510
1511
        $nn = ($check->nn ? " NOT NULL" : null);
1512
        if ($check->default === null) {
1513
            $default = " DEFAULT NULL";
1514
        } elseif (strlen($check->default)) {
1515 3
            $default = " DEFAULT '{$check->default}'";
1516
        }
1517 3
1518 3
        if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) {
1519 1
            $this->_logger->notice("Wrong datatype", [
1520 1
                'name' => $field_data->name,
1521 1
                'datatype' => $check->dataType
1522 1
            ]);
1523
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1524 1
        } elseif (! is_null($check->length) && $field_data->length != $check->length) {
1525
            $this->_logger->notice("Incorrect size", [
1526
                'name' => $field_data->name,
1527 3
                'current' => $field_data->length,
1528 3
                'new_size' => $check->length
1529 3
            ]);
1530 3
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}";
1531 3
        } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) {
1532 3
            $this->_logger->notice("Setting ENUM type", [
1533
                'name' => $field_data->name,
1534
                'values' => implode(",", $check->values)
1535
            ]);
1536 3
            $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}";
1537
        }
1538
1539
        if (! is_null($index) && count($index)) {
1540
            foreach ($index as $ind) {
1541
                if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) {
1542
                    $this->_logger->debug("Missing index", [
1543
                        'name' => $field_data->name
1544
                    ]);
1545
                    $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)";
1546
                }
1547
            }
1548
        }
1549
1550
        if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) {
1551
            $this->_logger->debug("Setting PKs", [
1552
                'keys' => implode(',', $pks)
1553
            ]);
1554
            $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)";
1555
        }
1556
1557
        return $ret;
1558
    }
1559
1560
    /**
1561
     * Function to check for the existence of a table within a schema
1562
     *
1563
     * @param string $strSchema
1564
     *            The schema to search in
1565
     * @param string $strTableName
1566
     *            Table to search for
1567
     *
1568
     * @return integer|boolean Returns number of tables that match if table is found in that schema, otherwise FALSE
1569
     */
1570
    public function tableExists($strSchema, $strTableName)
1571
    {
1572
        if (! $this->_c->select_db($strSchema)) {
1573
            fwrite(STDOUT, $this->_c->error . PHP_EOL);
1574
        }
1575
        $sql = "SHOW TABLES LIKE '{$strTableName}'";
1576
1577
        if ($res = $this->_c->query($sql)) {
1578
            if (gettype($res) == 'object' && is_a($res, 'mysqli_result') && $res->num_rows) {
1579
                return $res->num_rows;
1580
            }
1581
        } else {
1582
            if ($this->_c->errno) {
1583
                fwrite(STDOUT, $this->_c->error . PHP_EOL);
1584
            }
1585
        }
1586
1587
        return false;
1588
    }
1589
1590
    /**
1591
     * Function to detect if string is a JSON object or not
1592
     *
1593
     * @param string $strVal
1594
     *
1595
     * @return boolean
1596
     */
1597
    public function isJson($strVal)
1598
    {
1599
        json_decode($strVal);
1600
        return (json_last_error() == JSON_ERROR_NONE);
1601
    }
1602
1603
    /**
1604
     * Function to escape SQL characters to prevent SQL injection
1605
     *
1606
     * @param mixed $val
1607
     *            Value to escape
1608
     * @param boolean $blnEscape
1609
     *            Decide if we should escape or not
1610
     *
1611
     * @return string Escaped value
1612
     */
1613 3
    public function _escape($val, $blnEscape = true)
1614
    {
1615 3
        if (is_null($val) || (is_string($val) && strtolower($val) == 'null')) {
1616
            return 'NULL';
1617
        } elseif (is_numeric($val) || is_string($val)) {
1618 3
            if (stripos($val, "IF(") !== false) {
1619
                return $val;
1620 3
            } elseif ($blnEscape) {
1621 3
                return "'{$this->_c->real_escape_string($val)}'";
1622 3
            }
1623
            return "'{$val}'";
1624
        } elseif (is_a($val, 'DateTime')) {
1625
            return "'{$val->format(MYSQL_DATETIME)}'";
1626
        } elseif (is_bool($val)) {
1627
            return $val ? "'1'" : "'0'";
1628
        } elseif (is_array($val)) {
1629
            $ret = [];
1630 1
            foreach($val as $v) {
1631
                $ret[] = $this->_escape($v);
1632
            }
1633
            return "(" . implode(",", $ret) . ")";
1634
        } elseif (is_object($val) && method_exists($val, '_escape')) {
1635
            $ret = call_user_func([
1636
                $val,
1637
                '_escape'
1638
            ]);
1639
            if ($ret !== false) {
1640
                return $ret;
1641
            } else {
1642
                throw new Exception("Error in return from _escape method in " . get_class($val), E_ERROR);
1643
            }
1644
        }
1645
1646
        throw new Exception("Unknown datatype to escape in SQL string {$this->_sql} " . gettype($val), E_ERROR);
1647
    }
1648
1649
    /**
1650
     * Function to retrieve all results
1651
     *
1652
     * @param int $intResultType
1653
     *
1654
     * @return mixed
1655
     */
1656 78
    protected function fetchAll($intResultType = MYSQLI_ASSOC)
1657
    {
1658 78
        $res = [];
1659
        if (method_exists('mysqli_result', 'fetch_all')) { // Compatibility layer with PHP < 5.3
1660 78
            $res = $this->_result->fetch_all($intResultType);
1661 78
        } else {
1662
            while ($tmp = $this->_result->fetch_array($intResultType)) {
1663
                $res[] = $tmp;
1664 78
            }
1665 78
        }
1666
1667
        return $res;
1668
    }
1669
1670
    /**
1671
     * Function to populate the fields for the SQL
1672
     *
1673
     * @param array|string $fields
1674
     *            [optional]
1675
     *            Optional array of fields to string together to create a field list
1676
     *
1677
     * @return string
1678
     */
1679
    protected function fields($fields = null)
1680
    {
1681
        $ret = null;
1682
1683
        if (is_array($fields) && count($fields) && isset($fields[0]) && is_string($fields[0])) {
1684
            foreach ($fields as $field) {
1685
                if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (strpos($field, 'JSON_') === false) && (stripos($field, ' as ') === false)) {
1686
                    $ret .= "`$field`,";
1687
                } else {
1688
                    $ret .= "$field,";
1689
                }
1690
            }
1691
            $ret = substr($ret, - 1) == ',' ? substr($ret, 0, - 1) : $ret;
1692
        } elseif (is_string($fields)) {
1693
            $ret = $fields;
1694
        } elseif (is_null($fields)) {
1695
            $ret = "*";
1696
        } else {
1697
            throw new \InvalidArgumentException("Invalid field type");
1698
        }
1699
1700
        return $ret;
1701
    }
1702
1703
    /**
1704
     * Function to parse the flags
1705
     *
1706
     * @param array $flags
1707
     *            Two-dimensional array to added flags
1708
     *
1709
     *            <code>
1710
     *            [
1711
     *            &nbsp;&nbsp;'joins' => [
1712
     *            &nbsp;&nbsp;&nbsp;&nbsp;"JOIN table2 t2 ON t2.id=t1.id"
1713
     *            &nbsp;&nbsp;],
1714
     *            &nbsp;&nbsp;'group' => 'field',
1715
     *            &nbsp;&nbsp;'having' => 'field',
1716
     *            &nbsp;&nbsp;'order' => 'field',
1717
     *            &nbsp;&nbsp;'start' => 0,
1718
     *            &nbsp;&nbsp;'limit' => 0
1719
     *            ]
1720 15
     *            </code>
1721
     *
1722 15
     * @see Database::groups()
1723
     * @see Database::having()
1724 15
     * @see Database::order()
1725 2
     *
1726 2
     * @return string
1727 2
     */
1728
    protected function flags($arrFlags)
1729
    {
1730
        $ret = '';
1731
1732 2
        if (isset($arrFlags['group'])) {
1733 13
            $ret .= $this->groups($arrFlags['group']);
1734 4
        }
1735 9
1736 8
        if (isset($arrFlags['having']) && is_array($arrFlags['having'])) {
1737
            $having = " HAVING";
1738 1
            foreach ($arrFlags['having'] as $x => $h) {
1739
                $having .= $this->parseClause($h, $x);
0 ignored issues
show
Unused Code introduced by
The call to Godsgood33\Php_Db\Database::parseClause() has too many arguments starting with $x. ( Ignorable by Annotation )

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

1739
                $having .= $this->/** @scrutinizer ignore-call */ parseClause($h, $x);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1740
            }
1741 14
            if (strlen($having) > strlen(" HAVING")) {
1742
                $ret .= $having;
1743
            }
1744
        }
1745
1746
        if (isset($arrFlags['order'])) {
1747
            $ret .= $this->order($arrFlags['order']);
1748
        }
1749
1750
        if (isset($arrFlags['limit']) && (is_string($arrFlags['limit']) || is_numeric($arrFlags['limit']))) {
1751
            $ret .= " LIMIT ";
1752
            if (isset($arrFlags['start']) && (is_string($arrFlags['start']) || is_numeric($arrFlags['start']))) {
1753
                $ret .= "{$arrFlags['start']},";
1754
            }
1755
            $ret .= "{$arrFlags['limit']}";
1756
        }
1757
1758
        return $ret;
1759
    }
1760
1761
    /**
1762
     * Function to parse SQL GROUP BY statements
1763
     *
1764
     * @param mixed $groups
1765
     *
1766
     * @return string
1767
     */
1768
    protected function groups($groups)
1769 2
    {
1770
        $ret = '';
1771 2
        if (is_array($groups) && count($groups)) {
1772
            $ret .= " GROUP BY";
1773 2
1774
            foreach ($groups as $grp) {
1775
                $ret .= " $grp";
1776
1777 2
                if ($grp != end($groups)) {
1778
                    $ret .= ",";
1779
                }
1780
            }
1781
        } elseif (is_string($groups)) {
1782
            $ret .= " GROUP BY {$groups}";
1783
        } else {
1784
            throw (new Exception("Error in datatype for groups " . gettype($groups), E_ERROR));
1785
        }
1786
1787 2
        return $ret;
1788
    }
1789
1790
    /**
1791 2
     * Function to parse SQL ORDER BY statements
1792 1
     *
1793 1
     * @param mixed $order
1794
     *
1795
     * @return string
1796 1
     */
1797
    protected function order($order)
1798
    {
1799 2
        $ret = '';
1800
        if (is_array($order)) {
1801
            $ret .= " ORDER BY";
1802
1803
            foreach ($order as $ord) {
1804
                $ret .= " {$ord['field']} {$ord['sort']}";
1805
1806
                if ($ord != end($order)) {
1807
                    $ret .= ",";
1808
                }
1809
            }
1810
        } elseif (is_string($order)) {
1811
            $ret .= " ORDER BY {$order}";
1812
        }
1813
1814
        return $ret;
1815
    }
1816
1817
    /**
1818
     * Function to see if a constraint exists
1819
     *
1820
     * @param string $strConstraintId
1821
     *
1822
     * @return boolean
1823
     */
1824
    public function isConstraint($strConstraintId)
1825
    {
1826
        $res = $this->_c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '{$strConstraintId}'");
1827
1828
        if ($res->num_rows) {
1829
            return true;
1830
        }
1831
1832
        return false;
1833
    }
1834
1835
    /**
1836
     * Method to add a where clause
1837
     * 
1838
     * @param DBWhere|array:DBWhere $where
0 ignored issues
show
Documentation Bug introduced by
The doc comment $where at position 0 could not be parsed: Unknown type name '$where' at position 0 in $where.
Loading history...
1839
     * 
1840
     * @return boolean|array:DBWhere
1841
     */
1842
    public function parseClause($where) 
1843
    {
1844
        $ret = [];
1845
        if(is_array($where)) {
1846
            foreach($where as $k => $w) {
1847
                if(!is_a($w, 'Godsgood33\Php_Db\DBWhere')) {
1848
                    return false;
1849
                }
1850
                $v = $this->_escape($w->value, $w->escape);
0 ignored issues
show
Bug Best Practice introduced by
The property escape does not exist on Godsgood33\Php_Db\DBWhere. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property value does not exist on Godsgood33\Php_Db\DBWhere. Since you implemented __get, consider adding a @property annotation.
Loading history...
1851
                $where[$k]->value = $v;
1852
1853
                $ret[] = $where[$k];
1854
            }
1855
        } elseif(is_a($where, 'Godsgood33\Php_Db\DBWhere')) {
1856
            $v = $this->_escape($where->value, $where->escape);
1857
            $where->value = $v;
0 ignored issues
show
Bug Best Practice introduced by
The property value does not exist on Godsgood33\Php_Db\DBWhere. Since you implemented __set, consider adding a @property annotation.
Loading history...
1858
            $ret[] = $where;
1859
        }
1860
1861
        return $ret;
1862
    }
1863
1864
    /**
1865
     * Encryption algorithm
1866
     *
1867
     * @param string $data
1868
     * @param string $key
1869
     * 
1870
     * @throws Exception
1871
     * 
1872
     * @return string
1873
     */
1874
    public static function encrypt($data, $salt = null)
1875
    {
1876
        if(!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1877
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1878
        }
1879
1880
        // Remove the base64 encoding from our key
1881
        if (is_null($salt)) {
1882 5
            $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1883
        } else {
1884 5
            $encryption_key = base64_decode($salt);
1885
        }
1886 5
        // Generate an initialization vector
1887
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(PHP_DB_ENCRYPT_ALGORITHM));
1888 5
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
1889
        $encrypted = openssl_encrypt($data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1890
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
1891 5
        return base64_encode($encrypted . '::' . $iv);
1892
    }
1893
1894
    /**
1895
     * Decryption algorithm
1896
     *
1897
     * @param string $data
1898 5
     * 
1899 5
     * @throws Exception
1900
     * 
1901
     * @return string
1902
     */
1903
    public static function decrypt($data)
1904 5
    {
1905
        if(!defined('PHP_DB_ENCRYPT_SALT') || !defined('PHP_DB_ENCRYPT_ALGORITHM')) {
1906
            throw new Exception("Need to declare and populate PHP_DB_ENCRYPT_SALT and PHP_DB_ENCRYPT_ALGORITHM");
1907
        }
1908
1909
        // Remove the base64 encoding from our key
1910
        $encryption_key = base64_decode(PHP_DB_ENCRYPT_SALT);
1911 5
1912 1
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
1913 1
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
1914
        $plaintext = openssl_decrypt($encrypted_data, PHP_DB_ENCRYPT_ALGORITHM, $encryption_key, 0, $iv);
1915
        return $plaintext;
1916
    }
1917
}
1918