Issues (100)

src/Generic.php (19 issues)

1
<?php
2
/**
3
 * Generic SQL Driver Related Functionality
4
 * @author Joe Huss <[email protected]>
5
 * @copyright 2025
6
 * @package MyAdmin
7
 * @category SQL
8
 */
9
10
namespace MyDb;
11
12
/**
13
 * Class Generic
14
 */
15
abstract class Generic
16
{
17
    /* public: connection parameters */
18
    public $host = 'localhost';
19
    public $database = '';
20
    public $user = '';
21
    public $password = '';
22
    public $port = '';
23
24
    /* public: configuration parameters */
25
    public $autoStripslashes = false;
26
    public $Debug = 0; // Set to 1 for debugging messages.
27
    public $haltOnError = 'yes'; // "yes" (halt with message), "no" (ignore errors quietly), "report" (ignore error, but spit a warning)
28
29
    public $maxConnectErrors = 5;
30
    public $connectionAttempt = 0;
31
    public $maxMatches = 10000000;
32
33
    public $Type = 'mysql';
34
35
    /**
36
     * @var int
37
     */
38
    public $autoFree = 0; // Set to 1 for automatic mysql_free_result()
39
40
    /* public: result array and current row number */
41
    public $Record = [];
42
    public $Row;
43
44
    /* public: current error number and error text */
45
    public $Errno = 0;
46
    public $Error = '';
47
48
    /* public: this is an api revision, not a CVS revision. */
49
    public $type = 'generic';
50
51
    /**
52
     * @var int|object
53
     */
54
    public $linkId = 0;
55
    public $queryId = 0;
56
57
    public $characterSet = 'utf8mb4';
58
    public $collation = 'utf8mb4_unicode_ci';
59
60
    /**
61
     * Logged queries.
62
     * @var array
63
     */
64
    protected $log = [];
65
66
    /**
67
     * Constructs the db handler, can optionally specify connection parameters
68
     *
69
     * @param string $database Optional The database name
70
     * @param string $user Optional The username to connect with
71
     * @param string $password Optional The password to use
72
     * @param string $host Optional The hostname where the server is, or default to localhost
73
     * @param string $query Optional query to perform immediately
74 1
     * @param string $port optional port for the connection
75
     */
76 1
    public function __construct($database = '', $user = '', $password = '', $host = 'localhost', $query = '', $port = '')
77 1
    {
78 1
        $this->database = $database;
79 1
        $this->user = $user;
80 1
        $this->password = $password;
81 1
        $this->host = $host;
82
        $this->port = $port;
83
        if ($query != '') {
84 1
            $this->query($query);
0 ignored issues
show
The method query() does not exist on MyDb\Generic. Did you maybe mean queryId()? ( Ignorable by Annotation )

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

84
            $this->/** @scrutinizer ignore-call */ 
85
                   query($query);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
85
        }
86
        $this->connectionAttempt = 0;
87
    }
88
89
    /**
90
     * @param string $message
91
     * @param string $line
92
     * @param string $file
93 2
     * @return void
94
     */
95 2
    public function log($message, $line = '', $file = '', $level = 'info')
0 ignored issues
show
The parameter $level is not used and could be removed. ( Ignorable by Annotation )

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

95
    public function log($message, $line = '', $file = '', /** @scrutinizer ignore-unused */ $level = 'info')

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
96
    {
97
        error_log('SQL Query '.$line.' '.$file.' '.$message);
98
    }
99
100
    /**
101 2
     * @return int|object
102
     */
103 2
    public function linkId()
104
    {
105
        return $this->linkId;
106
    }
107
108
    /**
109 1
     * @return int|object
110
     */
111 1
    public function queryId()
112
    {
113
        return $this->queryId;
114
    }
115
116
    /**
117
     * @param $string
118 1
     * @return string
119
     */
120 1
    public function real_escape($string = '')
121 1
    {
122
        if ((!is_resource($this->linkId) || $this->linkId == 0) && !$this->connect()) {
0 ignored issues
show
The condition is_resource($this->linkId) is always false.
Loading history...
The method connect() does not exist on MyDb\Generic. Since it exists in all sub-types, consider adding an abstract or default implementation to MyDb\Generic. ( Ignorable by Annotation )

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

122
        if ((!is_resource($this->linkId) || $this->linkId == 0) && !$this->/** @scrutinizer ignore-call */ connect()) {
Loading history...
123
            return $this->escape($string);
124
        }
125
        return mysqli_real_escape_string($this->linkId, $string);
0 ignored issues
show
It seems like $this->linkId can also be of type integer and resource; however, parameter $mysql of mysqli_real_escape_string() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

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

125
        return mysqli_real_escape_string(/** @scrutinizer ignore-type */ $this->linkId, $string);
Loading history...
126
    }
127
128
    /**
129
     * @param $string
130 4
     * @return string
131
     */
132
    public function escape($string = '')
133
    {
134 4
        //if (function_exists('mysql_escape_string'))
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
135
        //return mysql_escape_string($string);
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
136
        return str_replace(['\\', "\0", "\n", "\r", "'", '"', "\x1a"], ['\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'], $string);
137
    }
138
139
    /**
140
     * @param mixed $str
141 3
     * @return string
142
     */
143 3
    public function dbAddslashes($str = '')
144 3
    {
145
        if (!isset($str) || $str == '') {
146 3
            return '';
147
        }
148
        return addslashes($str);
149
    }
150
151
    /**
152
     * Db::toTimestamp()
153
     * @param mixed $epoch
154 2
     * @return bool|string
155
     */
156 2
    public function toTimestamp($epoch)
157
    {
158
        return date('Y-m-d H:i:s', is_float($epoch) ? intval($epoch) : $epoch);
159
    }
160
161
    /**
162
     * Db::fromTimestamp()
163
     * @param mixed $timestamp
164 2
     * @return bool|int|mixed
165
     */
166 2
    public function fromTimestamp($timestamp)
167 2
    {
168
        if (preg_match('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', $timestamp, $parts)) {
169
            $time = mktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]);
170
        } elseif (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})/', $timestamp, $parts)) {
171
            $time = mktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]);
172
        } elseif (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/', $timestamp, $parts)) {
173
            $time = mktime(1, 1, 1, $parts[2], $parts[3], $parts[1]);
174
        } elseif (is_numeric($timestamp) && $timestamp >= 943938000) {
175
            $time = $timestamp;
176
        } else {
177
            $this->log('Cannot Match Timestamp from '.$timestamp, __LINE__, __FILE__);
178 2
            $time = false;
179
        }
180
        return $time;
181
    }
182
183
    /**
184
     * perform a query with limited result set
185
     *
186
     * @param string $queryString
187
     * @param string|int $numRows
188
     * @param int $offset
189
     * @param string|int $line
190
     * @param string $file
191 1
     * @return mixed
192
     */
193 1
    public function limitQuery($queryString, $numRows = '', $offset = 0, $line = '', $file = '')
194
    {
195
        if (!$numRows) {
196 1
            $numRows = $this->maxMatches;
197 1
        }
198
        if ($offset == 0) {
199 1
            $queryString .= ' LIMIT '.(int) $numRows;
200
        } else {
201
            $queryString .= ' LIMIT '.(int) $offset.','.(int) $numRows;
202 1
        }
203
204
        if ($this->Debug) {
205
            printf("Debug: limitQuery = %s<br>offset=%d, num_rows=%d<br>\n", $queryString, $offset, $numRows);
206 1
        }
207
208
        return $this->query($queryString, $line, $file);
209
    }
210
211
    /**
212
     * db:qr()
213
     *
214
     *  alias of queryReturn()
215
     *
216
     * @param mixed $query SQL Query to be used
217
     * @param string $line optionally pass __LINE__ calling the query for logging
218
     * @param string $file optionally pass __FILE__ calling the query for logging
219
     * @return mixed FALSE if no rows, if a single row it returns that, if multiple it returns an array of rows, associative responses only
220
     */
221
    public function qr($query, $line = '', $file = '')
222
    {
223
        return $this->queryReturn($query, $line, $file);
0 ignored issues
show
The method queryReturn() does not exist on MyDb\Generic. It seems like you code against a sub-type of said class. However, the method does not exist in MyDb\Pdo\Db or MyDb\Tests\TestGeneric. Are you sure you never get one of those? ( Ignorable by Annotation )

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

223
        return $this->/** @scrutinizer ignore-call */ queryReturn($query, $line, $file);
Loading history...
224
    }
225
226
    /**
227
     * gets a field
228
     *
229
     * @param mixed  $name
230
     * @param string $stripSlashes
231 2
     * @return string
232
     */
233 2
    public function f($name, $stripSlashes = '')
234
    {
235
        if (is_null($this->Record)) {
0 ignored issues
show
The condition is_null($this->Record) is always false.
Loading history...
236 2
            return null;
237
        } elseif ($stripSlashes || ($this->autoStripslashes && !$stripSlashes)) {
238
            return stripslashes($this->Record[$name]);
239
        } else {
240
            return $this->Record[$name];
241
        }
242
    }
243
244
    /**
245
     * error handling
246
     *
247
     * @param mixed $msg
248 1
     * @param string $line
249
     * @param string $file
250 1
     * @return void
251
     */
252
    public function halt($msg, $line = '', $file = '')
253
    {
254
        $this->unlock(false);
0 ignored issues
show
The method unlock() does not exist on MyDb\Generic. It seems like you code against a sub-type of said class. However, the method does not exist in MyDb\Tests\TestGeneric. Are you sure you never get one of those? ( Ignorable by Annotation )

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

254
        $this->/** @scrutinizer ignore-call */ 
255
               unlock(false);
Loading history...
255 1
        /* Just in case there is a table currently locked */
256
257
        //$this->Error = @$this->linkId->error;
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
258 1
        //$this->Errno = @$this->linkId->errno;
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
259 1
        if ($this->haltOnError == 'no') {
260
            return true;
261 1
        }
262 1
        if ($msg != '') {
263
            $this->haltmsg($msg, $line, $file);
264 1
        }
265
        if ($this->haltOnError != 'report') {
266
            echo '<p><b>Session halted.</b>';
267
            //if (isset($GLOBALS['tf']))
0 ignored issues
show
Unused Code Comprehensibility introduced by
91% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
268 1
            //$GLOBALS['tf']->terminate();
0 ignored issues
show
Unused Code Comprehensibility introduced by
89% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
269
            die();
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
270
        }
271
        return true;
272
    }
273
274
    /**
275
     * @param mixed $msg
276
     * @param string $line
277 2
     * @param string $file
278
     * @return mixed|void
279 2
     */
280 2
    public function logBackTrace($msg, $line = '', $file = '')
0 ignored issues
show
The parameter $msg is not used and could be removed. ( Ignorable by Annotation )

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

280
    public function logBackTrace(/** @scrutinizer ignore-unused */ $msg, $line = '', $file = '')

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
281 2
    {
282 2
        $backtrace = (function_exists('debug_backtrace') ? debug_backtrace() : []);
283 2
        $this->log(
284 2
            ('' !== getenv('REQUEST_URI') ? ' '.getenv('REQUEST_URI') : '').
285 2
            ((isset($_POST) && count($_POST)) ? ' POST='.json_encode($_POST) : '').
286 2
            ((isset($_GET) && count($_GET)) ? ' GET='.json_encode($_GET) : '').
287 2
            ((isset($_FILES) && count($_FILES)) ? ' FILES='.json_encode($_FILES) : '').
288 2
            ('' !== getenv('HTTP_USER_AGENT') ? ' AGENT="'.getenv('HTTP_USER_AGENT').'"' : '').
289 2
            (isset($_SERVER['REQUEST_METHOD']) ? ' METHOD="'.$_SERVER['REQUEST_METHOD'].'"'.
290 2
                ($_SERVER['REQUEST_METHOD'] === 'POST' ? ' POST="'.json_encode($_POST).'"' : '') : ''),
291
            $line,
292 2
            $file,
293 2
            'error'
294 2
        );
295 2
        for ($level = 1, $levelMax = count($backtrace); $level < $levelMax; $level++) {
296 2
            $message = (isset($backtrace[$level]['file']) ? 'File: '.$backtrace[$level]['file'] : '').
297 2
                (isset($backtrace[$level]['line']) ? ' Line: '.$backtrace[$level]['line'] : '').
298 2
                ' Function: '.(isset($backtrace[$level] ['class']) ? '(class '.$backtrace[$level] ['class'].') ' : '').
299 2
                (isset($backtrace[$level] ['type']) ? $backtrace[$level] ['type'].' ' : '').
300 2
                $backtrace[$level] ['function'].'(';
301 2
            if (isset($backtrace[$level] ['args'])) {
302
                for ($argument = 0, $argumentMax = count($backtrace[$level]['args']); $argument < $argumentMax; $argument++) {
303
                    $message .= ($argument > 0 ? ', ' : '').
304 2
                        (is_object($backtrace[$level]['args'][$argument]) ? 'class '.get_class($backtrace[$level]['args'][$argument]) : json_encode($backtrace[$level]['args'][$argument]));
305 2
                }
306
            }
307
            $message .= ')';
308
            $this->log($message, $line, $file, 'error');
309
        }
310
    }
311
312
    public function emailError($queryString, $error, $line, $file)
313
    {
314
        $subject = php_uname('n').' MySQLi Error '.$queryString;
315
        if (class_exists('\\TFSmarty')) {
316
            $smarty = new \TFSmarty();
0 ignored issues
show
The type TFSmarty was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
317
            $smarty->assign([
318
                'type' => $this->type,
319
                'queryString' => $queryString,
320
                'error' => $error,
321
                'line' => $line,
322
                'file' => $file,
323
                'request' => $_REQUEST,
324
                'server' => $_SERVER,
325
            ]);
326
            if (isset($GLOBALS['tf'])) {
327
                $smarty->assign('account_id', $GLOBALS['tf']->session->account_id);
328
            }
329
            $email = $smarty->fetch('email/admin/sql_error.tpl');
330
            (new \MyAdmin\Mail())->adminMail($subject, $email, '[email protected]', '');
0 ignored issues
show
The type MyAdmin\Mail was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
331
            (new \MyAdmin\Mail())->adminMail($subject, $email, '[email protected]', '');
332
        }
333
        $this->haltmsg('Invalid SQL: '.$queryString, $line, $file);
334
    }
335
336
    /**
337 2
     * @param mixed $msg
338
     * @param string $line
339 2
     * @param string $file
340 2
     * @return mixed|void
341
     */
342
    public function haltmsg($msg, $line = '', $file = '')
343
    {
344 2
        $email = "DB Error {$msg} {$file}:{$line}";
345
        if (class_exists('\\MyAdmin\Mail')) {
346
            \MyAdmin\Mail::failsafeMail($email, $email, ['[email protected]', '[email protected]']);
347
            return;
348
        }
349
        $this->log("Database error: $msg", $line, $file, 'error');
350 1
        if ($this->Errno != '0' || !in_array($this->Error, ['', '()'])) {
351
            $sqlstate = mysqli_sqlstate($this->linkId);
0 ignored issues
show
It seems like $this->linkId can also be of type integer; however, parameter $mysql of mysqli_sqlstate() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

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

351
            $sqlstate = mysqli_sqlstate(/** @scrutinizer ignore-type */ $this->linkId);
Loading history...
352 1
            $this->log("MySQLi SQLState: {$sqlstate}. Error: ".$this->Errno.' ('.$this->Error.')', $line, $file, 'error');
353
        }
354
        $this->logBackTrace($msg, $line, $file);
355
    }
356
357
    /**
358
     * @return array
359
     */
360
    public function indexNames()
361
    {
362
        return [];
363 11
    }
364
365
366 11
    /**
367 11
     * Add query to logged queries.
368
     * @param string $statement
369 11
     * @param float $time Elapsed seconds with microseconds
370 5
     * @param string|int $line Line Number
371
     * @param string $file File Name
372 11
     */
373 5
    public function addLog($statement, $time, $line = '', $file = '')
374
    {
375 11
        $query = [
376 1
            'statement' => $statement,
377
            'time' => $time * 1000
378 11
        ];
379 11
        if ($line != '') {
380
            $query['line'] = $line;
381
        }
382
        if ($file != '') {
383
            $query['file'] = $file;
384
        }
385
        if (!isset($GLOBALS['db_queries'])) {
386
            $GLOBALS['db_queries'] = [];
387
        }
388
        $GLOBALS['db_queries'][] = $query;
389
        array_push($this->log, $query);
390
    }
391
392
    /**
393
     * Return logged queries.
394
     * @return array Logged queries
395
     */
396
    public function getLog()
397
    {
398
        return $this->log;
399
    }
400
}
401