Completed
Push — master ( 7f2d14...7fc8f2 )
by Fabien
02:11
created

DatabaseConnection::admin_get_charsets()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13

Duplication

Lines 13
Ratio 100 %

Importance

Changes 0
Metric Value
dl 13
loc 13
rs 9.8333
c 0
b 0
f 0
cc 3
nc 2
nop 0
1
<?php
2
namespace Fab\Vidi\Database;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Core\Database\Connection;
18
use TYPO3\CMS\Core\Database\Query\QueryHelper;
19
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Core\Utility\StringUtility;
22
23
/**
24
 * Contains the class "DatabaseConnection" containing functions for building SQL queries
25
 * and mysqli wrappers, thus providing a foundational API to all database
26
 * interaction.
27
 * This class is instantiated globally as $TYPO3_DB in TYPO3 scripts.
28
 *
29
 * TYPO3 "database wrapper" class (new in 3.6.0)
30
 * This class contains
31
 * - abstraction functions for executing INSERT/UPDATE/DELETE/SELECT queries ("Query execution"; These are REQUIRED for all future connectivity to the database, thus ensuring DBAL compliance!)
32
 * - functions for building SQL queries (INSERT/UPDATE/DELETE/SELECT) ("Query building"); These are transitional functions for building SQL queries in a more automated way. Use these to build queries instead of doing it manually in your code!
33
 * - mysqli wrapper functions; These are transitional functions. By a simple search/replace you should be able to substitute all mysql*() calls with $GLOBALS['TYPO3_DB']->sql*() and your application will work out of the box. YOU CANNOT (legally) use any mysqli functions not found as wrapper functions in this class!
34
 * See the Project Coding Guidelines (doc_core_cgl) for more instructions on best-practise
35
 *
36
 * This class is not in itself a complete database abstraction layer but can be extended to be a DBAL (by extensions, see "dbal" for example)
37
 * ALL connectivity to the database in TYPO3 must be done through this class!
38
 * The points of this class are:
39
 * - To direct all database calls through this class so it becomes possible to implement DBAL with extensions.
40
 * - To keep it very easy to use for developers used to MySQL in PHP - and preserve as much performance as possible when TYPO3 is used with MySQL directly...
41
 * - To create an interface for DBAL implemented by extensions; (Eg. making possible escaping characters, clob/blob handling, reserved words handling)
42
 * - Benchmarking the DB bottleneck queries will become much easier; Will make it easier to find optimization possibilities.
43
 *
44
 * USE:
45
 * In all TYPO3 scripts the global variable $TYPO3_DB is an instance of this class. Use that.
46
 * Eg. $GLOBALS['TYPO3_DB']->sql_fetch_assoc()
47
 */
48
class DatabaseConnection
49
{
50
    /**
51
     * The AND constraint in where clause
52
     *
53
     * @var string
54
     */
55
    const AND_Constraint = 'AND';
56
57
    /**
58
     * The OR constraint in where clause
59
     *
60
     * @var string
61
     */
62
    const OR_Constraint = 'OR';
63
64
    /**
65
     * Set "TRUE" or "1" if you want database errors outputted. Set to "2" if you also want successful database actions outputted.
66
     *
67
     * @var bool|int
68
     */
69
    public $debugOutput = false;
70
71
    /**
72
     * Internally: Set to last built query (not necessarily executed...)
73
     *
74
     * @var string
75
     */
76
    public $debug_lastBuiltQuery = '';
77
78
    /**
79
     * Set "TRUE" if you want the last built query to be stored in $debug_lastBuiltQuery independent of $this->debugOutput
80
     *
81
     * @var bool
82
     */
83
    public $store_lastBuiltQuery = false;
84
85
    /**
86
     * Set this to 1 to get queries explained (devIPmask must match). Set the value to 2 to the same but disregarding the devIPmask.
87
     * There is an alternative option to enable explain output in the admin panel under "TypoScript", which will produce much nicer output, but only works in FE.
88
     *
89
     * @var bool
90
     */
91
    public $explainOutput = 0;
92
93
    /**
94
     * @var string Database host to connect to
95
     */
96
    protected $databaseHost = '';
97
98
    /**
99
     * @var int Database port to connect to
100
     */
101
    protected $databasePort = 3306;
102
103
    /**
104
     * @var string|NULL Database socket to connect to
105
     */
106
    protected $databaseSocket = null;
107
108
    /**
109
     * @var string Database name to connect to
110
     */
111
    protected $databaseName = '';
112
113
    /**
114
     * @var string Database user to connect with
115
     */
116
    protected $databaseUsername = '';
117
118
    /**
119
     * @var string Database password to connect with
120
     */
121
    protected $databaseUserPassword = '';
122
123
    /**
124
     * @var bool TRUE if database connection should be persistent
125
     * @see http://php.net/manual/de/mysqli.persistconns.php
126
     */
127
    protected $persistentDatabaseConnection = false;
128
129
    /**
130
     * @var bool TRUE if connection between client and sql server is compressed
131
     */
132
    protected $connectionCompression = false;
133
134
    /**
135
     * The charset for the connection; will be passed on to
136
     * mysqli_set_charset during connection initialization.
137
     *
138
     * @var string
139
     */
140
    protected $connectionCharset = 'utf8';
141
142
    /**
143
     * @var array List of commands executed after connection was established
144
     */
145
    protected $initializeCommandsAfterConnect = [];
146
147
    /**
148
     * @var bool TRUE if database connection is established
149
     */
150
    protected $isConnected = false;
151
152
    /**
153
     * @var \mysqli $link Default database link object
154
     */
155
    protected $link = null;
156
157
    /**
158
     * Default character set, applies unless character set or collation are explicitly set
159
     *
160
     * @var string
161
     */
162
    public $default_charset = 'utf8';
163
164
    /**
165
     * @var array<PostProcessQueryHookInterface>
166
     */
167
    protected $preProcessHookObjects = [];
168
169
    /**
170
     * @var array<PreProcessQueryHookInterface>
171
     */
172
    protected $postProcessHookObjects = [];
173
174
    /**
175
     * Internal property to mark if a deprecation log warning has been thrown in this request
176
     * in order to avoid a load of deprecation.
177
     * @var bool
178
     */
179
    protected $deprecationWarningThrown = false;
180
181
    /**
182
     * Initialize the database connection
183
     */
184
    public function initialize()
185
    {
186
        // Intentionally blank as this will be overloaded by DBAL
187
    }
188
189
    /************************************
190
     *
191
     * Query execution
192
     *
193
     * These functions are the RECOMMENDED DBAL functions for use in your applications
194
     * Using these functions will allow the DBAL to use alternative ways of accessing data (contrary to if a query is returned!)
195
     * They compile a query AND execute it immediately and then return the result
196
     * This principle heightens our ability to create various forms of DBAL of the functions.
197
     * Generally: We want to return a result pointer/object, never queries.
198
     * Also, having the table name together with the actual query execution allows us to direct the request to other databases.
199
     *
200
     **************************************/
201
202
    /**
203
     * Creates and executes an INSERT SQL-statement for $table from the array with field/value pairs $fields_values.
204
     * Using this function specifically allows us to handle BLOB and CLOB fields depending on DB
205
     *
206
     * @param string $table Table name
207
     * @param array $fields_values Field values as key=>value pairs. Values will be escaped internally. Typically you would fill an array like "$insertFields" with 'fieldname'=>'value' and pass it to this function as argument.
208
     * @param bool|array|string $no_quote_fields See fullQuoteArray()
209
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
210
     */
211 View Code Duplication
    public function exec_INSERTquery($table, $fields_values, $no_quote_fields = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
    {
213
        $this->logDeprecation();
214
        $res = $this->query($this->INSERTquery($table, $fields_values, $no_quote_fields));
215
        if ($this->debugOutput) {
216
            $this->debug('exec_INSERTquery');
217
        }
218
        foreach ($this->postProcessHookObjects as $hookObject) {
219
            /** @var $hookObject PostProcessQueryHookInterface */
220
            $hookObject->exec_INSERTquery_postProcessAction($table, $fields_values, $no_quote_fields, $this);
221
        }
222
        return $res;
223
    }
224
225
    /**
226
     * Creates and executes an INSERT SQL-statement for $table with multiple rows.
227
     *
228
     * @param string $table Table name
229
     * @param array $fields Field names
230
     * @param array $rows Table rows. Each row should be an array with field values mapping to $fields
231
     * @param bool|array|string $no_quote_fields See fullQuoteArray()
232
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
233
     */
234 View Code Duplication
    public function exec_INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
235
    {
236
        $this->logDeprecation();
237
        $res = $this->query($this->INSERTmultipleRows($table, $fields, $rows, $no_quote_fields));
238
        if ($this->debugOutput) {
239
            $this->debug('exec_INSERTmultipleRows');
240
        }
241
        foreach ($this->postProcessHookObjects as $hookObject) {
242
            /** @var $hookObject PostProcessQueryHookInterface */
243
            $hookObject->exec_INSERTmultipleRows_postProcessAction($table, $fields, $rows, $no_quote_fields, $this);
244
        }
245
        return $res;
246
    }
247
248
    /**
249
     * Creates and executes an UPDATE SQL-statement for $table where $where-clause (typ. 'uid=...') from the array with field/value pairs $fields_values.
250
     * Using this function specifically allow us to handle BLOB and CLOB fields depending on DB
251
     *
252
     * @param string $table Database tablename
253
     * @param string $where WHERE clause, eg. "uid=1". NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
254
     * @param array $fields_values Field values as key=>value pairs. Values will be escaped internally. Typically you would fill an array like "$updateFields" with 'fieldname'=>'value' and pass it to this function as argument.
255
     * @param bool|array|string $no_quote_fields See fullQuoteArray()
256
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
257
     */
258 View Code Duplication
    public function exec_UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
259
    {
260
        $this->logDeprecation();
261
        $res = $this->query($this->UPDATEquery($table, $where, $fields_values, $no_quote_fields));
262
        if ($this->debugOutput) {
263
            $this->debug('exec_UPDATEquery');
264
        }
265
        foreach ($this->postProcessHookObjects as $hookObject) {
266
            /** @var $hookObject PostProcessQueryHookInterface */
267
            $hookObject->exec_UPDATEquery_postProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
268
        }
269
        return $res;
270
    }
271
272
    /**
273
     * Creates and executes a DELETE SQL-statement for $table where $where-clause
274
     *
275
     * @param string $table Database tablename
276
     * @param string $where WHERE clause, eg. "uid=1". NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
277
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
278
     */
279 View Code Duplication
    public function exec_DELETEquery($table, $where)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
280
    {
281
        $this->logDeprecation();
282
        $res = $this->query($this->DELETEquery($table, $where));
283
        if ($this->debugOutput) {
284
            $this->debug('exec_DELETEquery');
285
        }
286
        foreach ($this->postProcessHookObjects as $hookObject) {
287
            /** @var $hookObject PostProcessQueryHookInterface */
288
            $hookObject->exec_DELETEquery_postProcessAction($table, $where, $this);
289
        }
290
        return $res;
291
    }
292
293
    /**
294
     * Creates and executes a SELECT SQL-statement
295
     * Using this function specifically allow us to handle the LIMIT feature independently of DB.
296
     *
297
     * @param string $select_fields List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
298
     * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
299
     * @param string $where_clause Additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
300
     * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
301
     * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
302
     * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
303
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
304
     */
305
    public function exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
306
    {
307
        $this->logDeprecation();
308
        $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
309
        $res = $this->query($query);
310
        if ($this->debugOutput) {
311
            $this->debug('exec_SELECTquery');
312
        }
313
        if ($this->explainOutput) {
314
            $this->explain($query, $from_table, $res->num_rows);
315
        }
316
        foreach ($this->postProcessHookObjects as $hookObject) {
317
            /** @var $hookObject PostProcessQueryHookInterface */
318
            $hookObject->exec_SELECTquery_postProcessAction($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $this);
0 ignored issues
show
Bug introduced by
$groupBy = '' cannot be passed to exec_selectquery_postprocessaction() as the parameter $groupBy expects a reference.
Loading history...
Bug introduced by
$orderBy = '' cannot be passed to exec_selectquery_postprocessaction() as the parameter $orderBy expects a reference.
Loading history...
Bug introduced by
$limit = '' cannot be passed to exec_selectquery_postprocessaction() as the parameter $limit expects a reference.
Loading history...
319
        }
320
        return $res;
321
    }
322
323
    /**
324
     * Creates and executes a SELECT query, selecting fields ($select) from two/three tables joined
325
     * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
326
     * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local  / [$mm_table].uid_foreign <--> [$foreign_table].uid
327
     * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
328
     *
329
     * @param string $select Field list for SELECT
330
     * @param string $local_table Tablename, local table
331
     * @param string $mm_table Tablename, relation table
332
     * @param string $foreign_table Tablename, foreign table
333
     * @param string $whereClause Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT! You have to prepend 'AND ' to this parameter yourself!
334
     * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
335
     * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
336
     * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
337
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
338
     * @see exec_SELECTquery()
339
     */
340
    public function exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
341
    {
342
        $this->logDeprecation();
343
        $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
344
        return $this->exec_SELECT_queryArray($queryParts);
345
    }
346
347
    /**
348
     * Executes a select based on input query parts array
349
     *
350
     * @param array $queryParts Query parts array
351
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
352
     * @see exec_SELECTquery()
353
     */
354
    public function exec_SELECT_queryArray($queryParts)
355
    {
356
        $this->logDeprecation();
357
        return $this->exec_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
358
    }
359
360
    /**
361
     * Creates and executes a SELECT SQL-statement AND traverse result set and returns array with records in.
362
     *
363
     * @param string $select_fields List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
364
     * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
365
     * @param string $where_clause Additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
366
     * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
367
     * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
368
     * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
369
     * @param string $uidIndexField If set, the result array will carry this field names value as index. Requires that field to be selected of course!
370
     * @return array|NULL Array of rows, or NULL in case of SQL error
371
     * @see exec_SELECTquery()
372
     * @throws \InvalidArgumentException
373
     */
374
    public function exec_SELECTgetRows($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $uidIndexField = '')
375
    {
376
        $this->logDeprecation();
377
        $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
378
        if ($this->sql_error()) {
379
            $this->sql_free_result($res);
380
            return null;
381
        }
382
        $output = [];
383
        $firstRecord = true;
384
        while ($record = $this->sql_fetch_assoc($res)) {
385
            if ($uidIndexField) {
386
                if ($firstRecord) {
387
                    $firstRecord = false;
388
                    if (!array_key_exists($uidIndexField, $record)) {
389
                        $this->sql_free_result($res);
390
                        throw new \InvalidArgumentException('The given $uidIndexField "' . $uidIndexField . '" is not available in the result.', 1432933855);
391
                    }
392
                }
393
                $output[$record[$uidIndexField]] = $record;
394
            } else {
395
                $output[] = $record;
396
            }
397
        }
398
        $this->sql_free_result($res);
399
        return $output;
400
    }
401
402
    /**
403
     * Creates and executes a SELECT SQL-statement AND gets a result set and returns an array with a single record in.
404
     * LIMIT is automatically set to 1 and can not be overridden.
405
     *
406
     * @param string $select_fields List of fields to select from the table.
407
     * @param string $from_table Table(s) from which to select.
408
     * @param string $where_clause Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
409
     * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
410
     * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
411
     * @param bool $numIndex If set, the result will be fetched with sql_fetch_row, otherwise sql_fetch_assoc will be used.
412
     * @return array|FALSE|NULL Single row, FALSE on empty result, NULL on error
413
     */
414
    public function exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $numIndex = false)
415
    {
416
        $this->logDeprecation();
417
        $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, '1');
418
        $output = null;
419
        if ($res !== false) {
420
            if ($numIndex) {
421
                $output = $this->sql_fetch_row($res);
422
            } else {
423
                $output = $this->sql_fetch_assoc($res);
424
            }
425
            $this->sql_free_result($res);
426
        }
427
        return $output;
428
    }
429
430
    /**
431
     * Counts the number of rows in a table.
432
     *
433
     * @param string $field Name of the field to use in the COUNT() expression (e.g. '*')
434
     * @param string $table Name of the table to count rows for
435
     * @param string $where (optional) WHERE statement of the query
436
     * @return mixed Number of rows counter (int) or FALSE if something went wrong (bool)
437
     */
438
    public function exec_SELECTcountRows($field, $table, $where = '1=1')
439
    {
440
        $this->logDeprecation();
441
        $count = false;
442
        $resultSet = $this->exec_SELECTquery('COUNT(' . $field . ')', $table, $where);
443
        if ($resultSet !== false) {
444
            list($count) = $this->sql_fetch_row($resultSet);
445
            $count = (int)$count;
446
            $this->sql_free_result($resultSet);
447
        }
448
        return $count;
449
    }
450
451
    /**
452
     * Truncates a table.
453
     *
454
     * @param string $table Database tablename
455
     * @return mixed Result from handler
456
     */
457 View Code Duplication
    public function exec_TRUNCATEquery($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
458
    {
459
        $this->logDeprecation();
460
        $res = $this->query($this->TRUNCATEquery($table));
461
        if ($this->debugOutput) {
462
            $this->debug('exec_TRUNCATEquery');
463
        }
464
        foreach ($this->postProcessHookObjects as $hookObject) {
465
            /** @var $hookObject PostProcessQueryHookInterface */
466
            $hookObject->exec_TRUNCATEquery_postProcessAction($table, $this);
467
        }
468
        return $res;
469
    }
470
471
    /**
472
     * Central query method. Also checks if there is a database connection.
473
     * Use this to execute database queries instead of directly calling $this->link->query()
474
     *
475
     * @param string $query The query to send to the database
476
     * @return bool|\mysqli_result
477
     */
478
    protected function query($query)
479
    {
480
        $this->logDeprecation();
481
        if (!$this->isConnected) {
482
            $this->connectDB();
483
        }
484
        return $this->link->query($query);
485
    }
486
487
    /**************************************
488
     *
489
     * Query building
490
     *
491
     **************************************/
492
    /**
493
     * Creates an INSERT SQL-statement for $table from the array with field/value pairs $fields_values.
494
     *
495
     * @param string $table See exec_INSERTquery()
496
     * @param array $fields_values See exec_INSERTquery()
497
     * @param bool|array|string $no_quote_fields See fullQuoteArray()
498
     * @return string|NULL Full SQL query for INSERT, NULL if $fields_values is empty
499
     */
500
    public function INSERTquery($table, $fields_values, $no_quote_fields = false)
501
    {
502
        $this->logDeprecation();
503
        // Table and fieldnames should be "SQL-injection-safe" when supplied to this
504
        // function (contrary to values in the arrays which may be insecure).
505
        if (!is_array($fields_values) || empty($fields_values)) {
506
            return null;
507
        }
508
        foreach ($this->preProcessHookObjects as $hookObject) {
509
            $hookObject->INSERTquery_preProcessAction($table, $fields_values, $no_quote_fields, $this);
0 ignored issues
show
Bug introduced by
The method INSERTquery_preProcessAction() does not seem to exist on object<Fab\Vidi\Database...cessQueryHookInterface>.

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...
510
        }
511
        // Quote and escape values
512
        $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
513
        // Build query
514
        $query = 'INSERT INTO ' . $table . ' (' . implode(',', array_keys($fields_values)) . ') VALUES ' . '(' . implode(',', $fields_values) . ')';
515
        // Return query
516
        if ($this->debugOutput || $this->store_lastBuiltQuery) {
517
            $this->debug_lastBuiltQuery = $query;
518
        }
519
        return $query;
520
    }
521
522
    /**
523
     * Creates an INSERT SQL-statement for $table with multiple rows.
524
     *
525
     * @param string $table Table name
526
     * @param array $fields Field names
527
     * @param array $rows Table rows. Each row should be an array with field values mapping to $fields
528
     * @param bool|array|string $no_quote_fields See fullQuoteArray()
529
     * @return string|NULL Full SQL query for INSERT, NULL if $rows is empty
530
     */
531
    public function INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
532
    {
533
        $this->logDeprecation();
534
        // Table and fieldnames should be "SQL-injection-safe" when supplied to this
535
        // function (contrary to values in the arrays which may be insecure).
536
        if (empty($rows)) {
537
            return null;
538
        }
539
        foreach ($this->preProcessHookObjects as $hookObject) {
540
            /** @var $hookObject PreProcessQueryHookInterface */
541
            $hookObject->INSERTmultipleRows_preProcessAction($table, $fields, $rows, $no_quote_fields, $this);
542
        }
543
        // Build query
544
        $query = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES ';
545
        $rowSQL = [];
546
        foreach ($rows as $row) {
547
            // Quote and escape values
548
            $row = $this->fullQuoteArray($row, $table, $no_quote_fields);
549
            $rowSQL[] = '(' . implode(', ', $row) . ')';
550
        }
551
        $query .= implode(', ', $rowSQL);
552
        // Return query
553
        if ($this->debugOutput || $this->store_lastBuiltQuery) {
554
            $this->debug_lastBuiltQuery = $query;
555
        }
556
        return $query;
557
    }
558
559
    /**
560
     * Creates an UPDATE SQL-statement for $table where $where-clause (typ. 'uid=...') from the array with field/value pairs $fields_values.
561
     *
562
     *
563
     * @param string $table See exec_UPDATEquery()
564
     * @param string $where See exec_UPDATEquery()
565
     * @param array $fields_values See exec_UPDATEquery()
566
     * @param bool|array|string $no_quote_fields See fullQuoteArray()
567
     * @throws \InvalidArgumentException
568
     * @return string Full SQL query for UPDATE
569
     */
570
    public function UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
571
    {
572
        $this->logDeprecation();
573
        // Table and fieldnames should be "SQL-injection-safe" when supplied to this
574
        // function (contrary to values in the arrays which may be insecure).
575
        if (is_string($where)) {
576
            foreach ($this->preProcessHookObjects as $hookObject) {
577
                /** @var $hookObject PreProcessQueryHookInterface */
578
                $hookObject->UPDATEquery_preProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
579
            }
580
            $fields = [];
581
            if (is_array($fields_values) && !empty($fields_values)) {
582
                // Quote and escape values
583
                $nArr = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
584
                foreach ($nArr as $k => $v) {
585
                    $fields[] = $k . '=' . $v;
586
                }
587
            }
588
            // Build query
589
            $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . ((string)$where !== '' ? ' WHERE ' . $where : '');
590
            if ($this->debugOutput || $this->store_lastBuiltQuery) {
591
                $this->debug_lastBuiltQuery = $query;
592
            }
593
            return $query;
594
        } else {
595
            throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !', 1270853880);
596
        }
597
    }
598
599
    /**
600
     * Creates a DELETE SQL-statement for $table where $where-clause
601
     *
602
     * @param string $table See exec_DELETEquery()
603
     * @param string $where See exec_DELETEquery()
604
     * @return string Full SQL query for DELETE
605
     * @throws \InvalidArgumentException
606
     */
607
    public function DELETEquery($table, $where)
608
    {
609
        $this->logDeprecation();
610
        if (is_string($where)) {
611
            foreach ($this->preProcessHookObjects as $hookObject) {
612
                /** @var $hookObject PreProcessQueryHookInterface */
613
                $hookObject->DELETEquery_preProcessAction($table, $where, $this);
614
            }
615
            // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
616
            $query = 'DELETE FROM ' . $table . ((string)$where !== '' ? ' WHERE ' . $where : '');
617
            if ($this->debugOutput || $this->store_lastBuiltQuery) {
618
                $this->debug_lastBuiltQuery = $query;
619
            }
620
            return $query;
621
        } else {
622
            throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !', 1270853881);
623
        }
624
    }
625
626
    /**
627
     * Creates a SELECT SQL-statement
628
     *
629
     * @param string $select_fields See exec_SELECTquery()
630
     * @param string $from_table See exec_SELECTquery()
631
     * @param string $where_clause See exec_SELECTquery()
632
     * @param string $groupBy See exec_SELECTquery()
633
     * @param string $orderBy See exec_SELECTquery()
634
     * @param string $limit See exec_SELECTquery()
635
     * @return string Full SQL query for SELECT
636
     */
637
    public function SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
638
    {
639
        $this->logDeprecation();
640
        foreach ($this->preProcessHookObjects as $hookObject) {
641
            /** @var $hookObject PreProcessQueryHookInterface */
642
            $hookObject->SELECTquery_preProcessAction($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit, $this);
643
        }
644
        // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
645
        // Build basic query
646
        $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
647
        // Group by
648
        $query .= (string)$groupBy !== '' ? ' GROUP BY ' . $groupBy : '';
649
        // Order by
650
        $query .= (string)$orderBy !== '' ? ' ORDER BY ' . $orderBy : '';
651
        // Group by
652
        $query .= (string)$limit !== '' ? ' LIMIT ' . $limit : '';
653
        // Return query
654
        if ($this->debugOutput || $this->store_lastBuiltQuery) {
655
            $this->debug_lastBuiltQuery = $query;
656
        }
657
        return $query;
658
    }
659
660
    /**
661
     * Creates a SELECT SQL-statement to be used as subquery within another query.
662
     * BEWARE: This method should not be overridden within DBAL to prevent quoting from happening.
663
     *
664
     * @param string $select_fields List of fields to select from the table.
665
     * @param string $from_table Table from which to select.
666
     * @param string $where_clause Conditional WHERE statement
667
     * @return string Full SQL query for SELECT
668
     */
669
    public function SELECTsubquery($select_fields, $from_table, $where_clause)
670
    {
671
        $this->logDeprecation();
672
        // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
673
        // Build basic query:
674
        $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
675
        // Return query
676
        if ($this->debugOutput || $this->store_lastBuiltQuery) {
677
            $this->debug_lastBuiltQuery = $query;
678
        }
679
        return $query;
680
    }
681
682
    /**
683
     * Creates a SELECT query, selecting fields ($select) from two/three tables joined
684
     * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
685
     * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local  / [$mm_table].uid_foreign <--> [$foreign_table].uid
686
     * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
687
     *
688
     * @param string $select See exec_SELECT_mm_query()
689
     * @param string $local_table See exec_SELECT_mm_query()
690
     * @param string $mm_table See exec_SELECT_mm_query()
691
     * @param string $foreign_table See exec_SELECT_mm_query()
692
     * @param string $whereClause See exec_SELECT_mm_query()
693
     * @param string $groupBy See exec_SELECT_mm_query()
694
     * @param string $orderBy See exec_SELECT_mm_query()
695
     * @param string $limit See exec_SELECT_mm_query()
696
     * @return string Full SQL query for SELECT
697
     * @see SELECTquery()
698
     */
699
    public function SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
700
    {
701
        $this->logDeprecation();
702
        $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
703
        return $this->SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
704
    }
705
706
    /**
707
     * Creates a TRUNCATE TABLE SQL-statement
708
     *
709
     * @param string $table See exec_TRUNCATEquery()
710
     * @return string Full SQL query for TRUNCATE TABLE
711
     */
712
    public function TRUNCATEquery($table)
713
    {
714
        $this->logDeprecation();
715
        foreach ($this->preProcessHookObjects as $hookObject) {
716
            /** @var $hookObject PreProcessQueryHookInterface */
717
            $hookObject->TRUNCATEquery_preProcessAction($table, $this);
718
        }
719
        // Table should be "SQL-injection-safe" when supplied to this function
720
        // Build basic query:
721
        $query = 'TRUNCATE TABLE ' . $table;
722
        // Return query:
723
        if ($this->debugOutput || $this->store_lastBuiltQuery) {
724
            $this->debug_lastBuiltQuery = $query;
725
        }
726
        return $query;
727
    }
728
729
    /**
730
     * Returns a WHERE clause that can find a value ($value) in a list field ($field)
731
     * For instance a record in the database might contain a list of numbers,
732
     * "34,234,5" (with no spaces between). This query would be able to select that
733
     * record based on the value "34", "234" or "5" regardless of their position in
734
     * the list (left, middle or right).
735
     * The value must not contain a comma (,)
736
     * Is nice to look up list-relations to records or files in TYPO3 database tables.
737
     *
738
     * @param string $field Field name
739
     * @param string $value Value to find in list
740
     * @param string $table Table in which we are searching (for DBAL detection of quoteStr() method)
741
     * @return string WHERE clause for a query
742
     * @throws \InvalidArgumentException
743
     */
744
    public function listQuery($field, $value, $table)
745
    {
746
        $this->logDeprecation();
747
        $value = (string)$value;
748
        if (strpos($value, ',') !== false) {
749
            throw new \InvalidArgumentException('$value must not contain a comma (,) in $this->listQuery() !', 1294585862);
750
        }
751
        $pattern = $this->quoteStr($value, $table);
752
        $where = 'FIND_IN_SET(\'' . $pattern . '\',' . $field . ')';
753
        return $where;
754
    }
755
756
    /**
757
     * Returns a WHERE clause which will make an AND or OR search for the words in the $searchWords array in any of the fields in array $fields.
758
     *
759
     * @param array $searchWords Array of search words
760
     * @param array $fields Array of fields
761
     * @param string $table Table in which we are searching (for DBAL detection of quoteStr() method)
762
     * @param string $constraint How multiple search words have to match ('AND' or 'OR')
763
     * @return string WHERE clause for search
764
     */
765
    public function searchQuery($searchWords, $fields, $table, $constraint = self::AND_Constraint)
766
    {
767
        $this->logDeprecation();
768
        switch ($constraint) {
769
            case self::OR_Constraint:
770
                $constraint = 'OR';
771
                break;
772
            default:
773
                $constraint = 'AND';
774
        }
775
776
        $queryParts = [];
777
        foreach ($searchWords as $sw) {
778
            $like = ' LIKE \'%' . $this->quoteStr($this->escapeStrForLike($sw, $table), $table) . '%\'';
779
            $queryParts[] = $table . '.' . implode(($like . ' OR ' . $table . '.'), $fields) . $like;
780
        }
781
        $query = '(' . implode(') ' . $constraint . ' (', $queryParts) . ')';
782
783
        return $query;
784
    }
785
786
    /**************************************
787
     *
788
     * Prepared Query Support
789
     *
790
     **************************************/
791
    /**
792
     * Creates a SELECT prepared SQL statement.
793
     *
794
     * @param string $select_fields See exec_SELECTquery()
795
     * @param string $from_table See exec_SELECTquery()
796
     * @param string $where_clause See exec_SELECTquery()
797
     * @param string $groupBy See exec_SELECTquery()
798
     * @param string $orderBy See exec_SELECTquery()
799
     * @param string $limit See exec_SELECTquery()
800
     * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as \Fab\Vidi\Database\PreparedStatement::PARAM_AUTOTYPE.
801
     * @return \Fab\Vidi\Database\PreparedStatement Prepared statement
802
     */
803
    public function prepare_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', array $input_parameters = [])
804
    {
805
        $this->logDeprecation();
806
        $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
807
        /** @var $preparedStatement \Fab\Vidi\Database\PreparedStatement */
808
        $preparedStatement = GeneralUtility::makeInstance(\Fab\Vidi\Database\PreparedStatement::class, $query, $from_table, []);
809
        // Bind values to parameters
810
        foreach ($input_parameters as $key => $value) {
811
            $preparedStatement->bindValue($key, $value, PreparedStatement::PARAM_AUTOTYPE);
812
        }
813
        // Return prepared statement
814
        return $preparedStatement;
815
    }
816
817
    /**
818
     * Creates a SELECT prepared SQL statement based on input query parts array
819
     *
820
     * @param array $queryParts Query parts array
821
     * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as \Fab\Vidi\Database\PreparedStatement::PARAM_AUTOTYPE.
822
     * @return \Fab\Vidi\Database\PreparedStatement Prepared statement
823
     */
824
    public function prepare_SELECTqueryArray(array $queryParts, array $input_parameters = [])
825
    {
826
        $this->logDeprecation();
827
        return $this->prepare_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT'], $input_parameters);
828
    }
829
830
    /**
831
     * Prepares a prepared query.
832
     *
833
     * @param string $query The query to execute
834
     * @param array $queryComponents The components of the query to execute
835
     * @return \mysqli_stmt|object MySQLi statement / DBAL object
836
     * @internal This method may only be called by \Fab\Vidi\Database\PreparedStatement
837
     */
838
    public function prepare_PREPAREDquery($query, array $queryComponents)
0 ignored issues
show
Unused Code introduced by
The parameter $queryComponents is not used and could be removed.

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

Loading history...
839
    {
840
        $this->logDeprecation();
841
        if (!$this->isConnected) {
842
            $this->connectDB();
843
        }
844
        $stmt = $this->link->stmt_init();
845
        $success = $stmt->prepare($query);
846
        if ($this->debugOutput) {
847
            $this->debug('stmt_execute', $query);
848
        }
849
        return $success ? $stmt : null;
850
    }
851
852
    /**************************************
853
     *
854
     * Various helper functions
855
     *
856
     * Functions recommended to be used for
857
     * - escaping values,
858
     * - cleaning lists of values,
859
     * - stripping of excess ORDER BY/GROUP BY keywords
860
     *
861
     **************************************/
862
    /**
863
     * Escaping and quoting values for SQL statements.
864
     *
865
     * @param string $str Input string
866
     * @param string $table Table name for which to quote string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
867
     * @param bool $allowNull Whether to allow NULL values
868
     * @return string Output string; Wrapped in single quotes and quotes in the string (" / ') and \ will be backslashed (or otherwise based on DBAL handler)
869
     * @see quoteStr()
870
     */
871
    public function fullQuoteStr($str, $table, $allowNull = false)
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

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

Loading history...
872
    {
873
        $this->logDeprecation();
874
        if (!$this->isConnected) {
875
            $this->connectDB();
876
        }
877
        if ($allowNull && $str === null) {
878
            return 'NULL';
879
        }
880
        if (is_bool($str)) {
881
            $str = (int)$str;
882
        }
883
884
        return '\'' . $this->link->real_escape_string($str) . '\'';
885
    }
886
887
    /**
888
     * Will fullquote all values in the one-dimensional array so they are ready to "implode" for an sql query.
889
     *
890
     * @param array $arr Array with values (either associative or non-associative array)
891
     * @param string $table Table name for which to quote
892
     * @param bool|array|string $noQuote List/array of keys NOT to quote (eg. SQL functions) - ONLY for associative arrays
893
     * @param bool $allowNull Whether to allow NULL values
894
     * @return array The input array with the values quoted
895
     * @see cleanIntArray()
896
     */
897
    public function fullQuoteArray($arr, $table, $noQuote = false, $allowNull = false)
898
    {
899
        $this->logDeprecation();
900
        if (is_string($noQuote)) {
901
            $noQuote = explode(',', $noQuote);
902
        } elseif (!is_array($noQuote)) {
903
            $noQuote = (bool)$noQuote;
904
        }
905
        if ($noQuote === true) {
906
            return $arr;
907
        }
908
        foreach ($arr as $k => $v) {
909
            if ($noQuote === false || !in_array($k, $noQuote)) {
910
                $arr[$k] = $this->fullQuoteStr($v, $table, $allowNull);
911
            }
912
        }
913
        return $arr;
914
    }
915
916
    /**
917
     * Substitution for PHP function "addslashes()"
918
     * Use this function instead of the PHP addslashes() function when you build queries - this will prepare your code for DBAL.
919
     * NOTICE: You must wrap the output of this function in SINGLE QUOTES to be DBAL compatible. Unless you have to apply the single quotes yourself you should rather use ->fullQuoteStr()!
920
     *
921
     * @param string $str Input string
922
     * @param string $table Table name for which to quote string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
923
     * @return string Output string; Quotes (" / ') and \ will be backslashed (or otherwise based on DBAL handler)
924
     * @see quoteStr()
925
     */
926
    public function quoteStr($str, $table)
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

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

Loading history...
927
    {
928
        $this->logDeprecation();
929
        if (!$this->isConnected) {
930
            $this->connectDB();
931
        }
932
        return $this->link->real_escape_string($str);
933
    }
934
935
    /**
936
     * Escaping values for SQL LIKE statements.
937
     *
938
     * @param string $str Input string
939
     * @param string $table Table name for which to escape string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
940
     * @return string Output string; % and _ will be escaped with \ (or otherwise based on DBAL handler)
941
     * @see quoteStr()
942
     */
943
    public function escapeStrForLike($str, $table)
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

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

Loading history...
944
    {
945
        $this->logDeprecation();
946
        return addcslashes($str, '_%');
947
    }
948
949
    /**
950
     * Will convert all values in the one-dimensional array to integers.
951
     * Useful when you want to make sure an array contains only integers before imploding them in a select-list.
952
     *
953
     * @param array $arr Array with values
954
     * @return array The input array with all values cast to (int)
955
     * @see cleanIntList()
956
     */
957
    public function cleanIntArray($arr)
958
    {
959
        $this->logDeprecation();
960
        return array_map('intval', $arr);
961
    }
962
963
    /**
964
     * Will force all entries in the input comma list to integers
965
     * Useful when you want to make sure a commalist of supposed integers really contain only integers; You want to know that when you don't trust content that could go into an SQL statement.
966
     *
967
     * @param string $list List of comma-separated values which should be integers
968
     * @return string The input list but with every value cast to (int)
969
     * @see cleanIntArray()
970
     */
971
    public function cleanIntList($list)
972
    {
973
        $this->logDeprecation();
974
        return implode(',', GeneralUtility::intExplode(',', $list));
975
    }
976
977
    /**
978
     * Removes the prefix "ORDER BY" from the input string.
979
     * This function is used when you call the exec_SELECTquery() function and want to pass the ORDER BY parameter by can't guarantee that "ORDER BY" is not prefixed.
980
     * Generally; This function provides a work-around to the situation where you cannot pass only the fields by which to order the result.
981
     *
982
     * @param string $str eg. "ORDER BY title, uid
983
     * @return string eg. "title, uid
984
     * @see exec_SELECTquery(), stripGroupBy()
985
     */
986
    public function stripOrderBy($str)
987
    {
988
        $this->logDeprecation();
989
        return preg_replace('/^(?:ORDER[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
990
    }
991
992
    /**
993
     * Removes the prefix "GROUP BY" from the input string.
994
     * This function is used when you call the SELECTquery() function and want to pass the GROUP BY parameter by can't guarantee that "GROUP BY" is not prefixed.
995
     * Generally; This function provides a work-around to the situation where you cannot pass only the fields by which to order the result.
996
     *
997
     * @param string $str eg. "GROUP BY title, uid
998
     * @return string eg. "title, uid
999
     * @see exec_SELECTquery(), stripOrderBy()
1000
     */
1001
    public function stripGroupBy($str)
1002
    {
1003
        $this->logDeprecation();
1004
        return preg_replace('/^(?:GROUP[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
1005
    }
1006
1007
    /**
1008
     * Returns the date and time formats compatible with the given database table.
1009
     *
1010
     * @param string $table Table name for which to return an empty date. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how date and time should be formatted).
1011
     * @return array
1012
     */
1013
    public function getDateTimeFormats($table)
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

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

Loading history...
1014
    {
1015
        $this->logDeprecation();
1016
        return QueryHelper::getDateTimeFormats();
1017
    }
1018
1019
    /**
1020
     * Creates SELECT query components for selecting fields ($select) from two/three tables joined
1021
     * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
1022
     * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local  / [$mm_table].uid_foreign <--> [$foreign_table].uid
1023
     * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
1024
     *
1025
     * @param string $select See exec_SELECT_mm_query()
1026
     * @param string $local_table See exec_SELECT_mm_query()
1027
     * @param string $mm_table See exec_SELECT_mm_query()
1028
     * @param string $foreign_table See exec_SELECT_mm_query()
1029
     * @param string $whereClause See exec_SELECT_mm_query()
1030
     * @param string $groupBy See exec_SELECT_mm_query()
1031
     * @param string $orderBy See exec_SELECT_mm_query()
1032
     * @param string $limit See exec_SELECT_mm_query()
1033
     * @return array SQL query components
1034
     */
1035
    protected function getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
1036
    {
1037
        $foreign_table_as = $foreign_table == $local_table ? $foreign_table . StringUtility::getUniqueId('_join') : '';
1038
        $mmWhere = $local_table ? $local_table . '.uid=' . $mm_table . '.uid_local' : '';
1039
        $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : '';
1040
        $tables = ($local_table ? $local_table . ',' : '') . $mm_table;
1041
        if ($foreign_table) {
1042
            $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
1043
            $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : '');
1044
        }
1045
        return [
1046
            'SELECT' => $select,
1047
            'FROM' => $tables,
1048
            'WHERE' => $mmWhere . ' ' . $whereClause,
1049
            'GROUPBY' => $groupBy,
1050
            'ORDERBY' => $orderBy,
1051
            'LIMIT' => $limit
1052
        ];
1053
    }
1054
1055
    /**************************************
1056
     *
1057
     * MySQL(i) wrapper functions
1058
     * (For use in your applications)
1059
     *
1060
     **************************************/
1061
    /**
1062
     * Executes query
1063
     * MySQLi query() wrapper function
1064
     * Beware: Use of this method should be avoided as it is experimentally supported by DBAL. You should consider
1065
     * using exec_SELECTquery() and similar methods instead.
1066
     *
1067
     * @param string $query Query to execute
1068
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
1069
     */
1070 View Code Duplication
    public function sql_query($query)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1071
    {
1072
        $this->logDeprecation();
1073
        $res = $this->query($query);
1074
        if ($this->debugOutput) {
1075
            $this->debug('sql_query', $query);
1076
        }
1077
        return $res;
1078
    }
1079
1080
    /**
1081
     * Returns the error status on the last query() execution
1082
     *
1083
     * @return string MySQLi error string.
1084
     */
1085
    public function sql_error()
1086
    {
1087
        $this->logDeprecation();
1088
        return $this->link->error;
1089
    }
1090
1091
    /**
1092
     * Returns the error number on the last query() execution
1093
     *
1094
     * @return int MySQLi error number
1095
     */
1096
    public function sql_errno()
1097
    {
1098
        $this->logDeprecation();
1099
        return $this->link->errno;
1100
    }
1101
1102
    /**
1103
     * Returns the number of selected rows.
1104
     *
1105
     * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1106
     * @return int Number of resulting rows
1107
     */
1108
    public function sql_num_rows($res)
1109
    {
1110
        $this->logDeprecation();
1111
        if ($this->debug_check_recordset($res)) {
1112
            return $res->num_rows;
1113
        } else {
1114
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Fab\Vidi\Database\DatabaseConnection::sql_num_rows of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1115
        }
1116
    }
1117
1118
    /**
1119
     * Returns an associative array that corresponds to the fetched row, or FALSE if there are no more rows.
1120
     * MySQLi fetch_assoc() wrapper function
1121
     *
1122
     * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1123
     * @return array|bool Associative array of result row.
1124
     */
1125 View Code Duplication
    public function sql_fetch_assoc($res)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1126
    {
1127
        $this->logDeprecation();
1128
        if ($this->debug_check_recordset($res)) {
1129
            $result = $res->fetch_assoc();
1130
            if ($result === null) {
1131
                // Needed for compatibility
1132
                $result = false;
1133
            }
1134
            return $result;
1135
        } else {
1136
            return false;
1137
        }
1138
    }
1139
1140
    /**
1141
     * Returns an array that corresponds to the fetched row, or FALSE if there are no more rows.
1142
     * The array contains the values in numerical indices.
1143
     * MySQLi fetch_row() wrapper function
1144
     *
1145
     * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1146
     * @return array|bool Array with result rows.
1147
     */
1148 View Code Duplication
    public function sql_fetch_row($res)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1149
    {
1150
        $this->logDeprecation();
1151
        if ($this->debug_check_recordset($res)) {
1152
            $result = $res->fetch_row();
1153
            if ($result === null) {
1154
                // Needed for compatibility
1155
                $result = false;
1156
            }
1157
            return $result;
1158
        } else {
1159
            return false;
1160
        }
1161
    }
1162
1163
    /**
1164
     * Free result memory
1165
     * free_result() wrapper function
1166
     *
1167
     * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1168
     * @return bool Returns TRUE on success or FALSE on failure.
1169
     */
1170
    public function sql_free_result($res)
1171
    {
1172
        $this->logDeprecation();
1173
        if ($this->debug_check_recordset($res) && is_object($res)) {
1174
            $res->free();
1175
            return true;
1176
        } else {
1177
            return false;
1178
        }
1179
    }
1180
1181
    /**
1182
     * Get the ID generated from the previous INSERT operation
1183
     *
1184
     * @return int The uid of the last inserted record.
1185
     */
1186
    public function sql_insert_id()
1187
    {
1188
        $this->logDeprecation();
1189
        return $this->link->insert_id;
1190
    }
1191
1192
    /**
1193
     * Returns the number of rows affected by the last INSERT, UPDATE or DELETE query
1194
     *
1195
     * @return int Number of rows affected by last query
1196
     */
1197
    public function sql_affected_rows()
1198
    {
1199
        return $this->link->affected_rows;
1200
    }
1201
1202
    /**
1203
     * Move internal result pointer
1204
     *
1205
     * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1206
     * @param int $seek Seek result number.
1207
     * @return bool Returns TRUE on success or FALSE on failure.
1208
     */
1209
    public function sql_data_seek($res, $seek)
1210
    {
1211
        $this->logDeprecation();
1212
        if ($this->debug_check_recordset($res)) {
1213
            return $res->data_seek($seek);
1214
        } else {
1215
            return false;
1216
        }
1217
    }
1218
1219
    /**
1220
     * Get the type of the specified field in a result
1221
     * mysql_field_type() wrapper function
1222
     *
1223
     * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1224
     * @param int $pointer Field index.
1225
     * @return string Returns the name of the specified field index, or FALSE on error
1226
     */
1227
    public function sql_field_type($res, $pointer)
1228
    {
1229
        // mysql_field_type compatibility map
1230
        // taken from: http://www.php.net/manual/en/mysqli-result.fetch-field-direct.php#89117
1231
        // Constant numbers see http://php.net/manual/en/mysqli.constants.php
1232
        $mysql_data_type_hash = [
1233
            1=>'tinyint',
1234
            2=>'smallint',
1235
            3=>'int',
1236
            4=>'float',
1237
            5=>'double',
1238
            7=>'timestamp',
1239
            8=>'bigint',
1240
            9=>'mediumint',
1241
            10=>'date',
1242
            11=>'time',
1243
            12=>'datetime',
1244
            13=>'year',
1245
            16=>'bit',
1246
            //252 is currently mapped to all text and blob types (MySQL 5.0.51a)
1247
            253=>'varchar',
1248
            254=>'char',
1249
            246=>'decimal'
1250
        ];
1251
        if ($this->debug_check_recordset($res)) {
1252
            $metaInfo = $res->fetch_field_direct($pointer);
1253
            if ($metaInfo === false) {
1254
                return false;
1255
            }
1256
            return $mysql_data_type_hash[$metaInfo->type];
1257
        } else {
1258
            return false;
1259
        }
1260
    }
1261
1262
    /**
1263
     * Open a (persistent) connection to a MySQL server
1264
     *
1265
     * @return bool|void
1266
     * @throws \RuntimeException
1267
     */
1268
    public function sql_pconnect()
1269
    {
1270
        if ($this->isConnected) {
1271
            return $this->link;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->link; (mysqli) is incompatible with the return type documented by Fab\Vidi\Database\DatabaseConnection::sql_pconnect of type boolean|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1272
        }
1273
1274
        if (!extension_loaded('mysqli')) {
1275
            throw new \RuntimeException(
1276
                'Database Error: PHP mysqli extension not loaded. This is a must have for TYPO3 CMS!',
1277
                1271492607
1278
            );
1279
        }
1280
1281
        $host = $this->persistentDatabaseConnection
1282
            ? 'p:' . $this->databaseHost
1283
            : $this->databaseHost;
1284
1285
        // We are not using the TYPO3 CMS shim here as the database parameters in this class
1286
        // are settable externally. This requires building the connection parameter array
1287
        // just in time when establishing the connection.
1288
        $connection = \Doctrine\DBAL\DriverManager::getConnection([
1289
            'driver' => 'mysqli',
1290
            'wrapperClass' => Connection::class,
1291
            'host' => $host,
1292
            'port' => (int)$this->databasePort,
1293
            'unix_socket' => $this->databaseSocket,
1294
            'user' => $this->databaseUsername,
1295
            'password' => $this->databaseUserPassword,
1296
            'charset' => $this->connectionCharset,
1297
        ]);
1298
1299
        // Mimic the previous behavior of returning false on connection errors
1300
        try {
1301
            /** @var \Doctrine\DBAL\Driver\Mysqli\MysqliConnection $mysqliConnection */
1302
            $mysqliConnection = $connection->getWrappedConnection();
1303
            $this->link = $mysqliConnection->getWrappedResourceHandle();
1304
        } catch (\Doctrine\DBAL\Exception\ConnectionException $exception) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\ConnectionException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1305
            return false;
1306
        }
1307
1308
        if ($connection->isConnected()) {
1309
            $this->isConnected = true;
1310
1311
            foreach ($this->initializeCommandsAfterConnect as $command) {
1312
                if ($this->query($command) === false) {
1313
                    GeneralUtility::sysLog(
1314
                        'Could not initialize DB connection with query "' . $command . '": ' . $this->sql_error(),
1315
                        'core',
1316
                        GeneralUtility::SYSLOG_SEVERITY_ERROR
1317
                    );
1318
                }
1319
            }
1320
            $this->checkConnectionCharset();
1321
        } else {
1322
            // @todo This should raise an exception. Would be useful especially to work during installation.
1323
            $error_msg = $this->link->connect_error;
1324
            $this->link = null;
1325
            GeneralUtility::sysLog(
1326
                'Could not connect to MySQL server ' . $host . ' with user ' . $this->databaseUsername . ': '
1327
                . $error_msg,
1328
                'core',
1329
                GeneralUtility::SYSLOG_SEVERITY_FATAL
1330
            );
1331
        }
1332
1333
        return $this->link;
1334
    }
1335
1336
    /**
1337
     * Select a SQL database
1338
     *
1339
     * @return bool Returns TRUE on success or FALSE on failure.
1340
     */
1341
    public function sql_select_db()
1342
    {
1343
        if (!$this->isConnected) {
1344
            $this->connectDB();
1345
        }
1346
1347
        $ret = $this->link->select_db($this->databaseName);
1348
        if (!$ret) {
1349
            GeneralUtility::sysLog(
1350
                'Could not select MySQL database ' . $this->databaseName . ': ' . $this->sql_error(),
1351
                'core',
1352
                GeneralUtility::SYSLOG_SEVERITY_FATAL
1353
            );
1354
        }
1355
        return $ret;
1356
    }
1357
1358
    /**************************************
1359
     *
1360
     * SQL admin functions
1361
     * (For use in the Install Tool and Extension Manager)
1362
     *
1363
     **************************************/
1364
    /**
1365
     * Listing databases from current MySQL connection. NOTICE: It WILL try to select those databases and thus break selection of current database.
1366
     * This is only used as a service function in the (1-2-3 process) of the Install Tool.
1367
     * In any case a lookup should be done in the _DEFAULT handler DBMS then.
1368
     * Use in Install Tool only!
1369
     *
1370
     * @return array Each entry represents a database name
1371
     * @throws \RuntimeException
1372
     */
1373
    public function admin_get_dbs()
1374
    {
1375
        $this->logDeprecation();
1376
        $dbArr = [];
1377
        $db_list = $this->query('SELECT SCHEMA_NAME FROM information_schema.SCHEMATA');
1378
        if ($db_list === false) {
1379
            throw new \RuntimeException(
1380
                'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
1381
                1378457171
1382
            );
1383
        } else {
1384
            while ($row = $db_list->fetch_object()) {
1385
                try {
1386
                    $this->setDatabaseName($row->SCHEMA_NAME);
1387
                    if ($this->sql_select_db()) {
1388
                        $dbArr[] = $row->SCHEMA_NAME;
1389
                    }
1390
                } catch (\RuntimeException $exception) {
1391
                    // The exception happens if we cannot connect to the database
1392
                    // (usually due to missing permissions). This is ok here.
1393
                    // We catch the exception, skip the database and continue.
1394
                }
1395
            }
1396
        }
1397
        return $dbArr;
1398
    }
1399
1400
    /**
1401
     * Returns the list of tables from the default database, TYPO3_db (quering the DBMS)
1402
     * In a DBAL this method should 1) look up all tables from the DBMS  of
1403
     * the _DEFAULT handler and then 2) add all tables *configured* to be managed by other handlers
1404
     *
1405
     * @return array Array with tablenames as key and arrays with status information as value
1406
     */
1407 View Code Duplication
    public function admin_get_tables()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1408
    {
1409
        $this->logDeprecation();
1410
        $whichTables = [];
1411
        $tables_result = $this->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
1412
        if ($tables_result !== false) {
1413
            while ($theTable = $tables_result->fetch_assoc()) {
1414
                $whichTables[$theTable['Name']] = $theTable;
1415
            }
1416
            $tables_result->free();
1417
        }
1418
        return $whichTables;
1419
    }
1420
1421
    /**
1422
     * Returns information about each field in the $table (quering the DBMS)
1423
     * In a DBAL this should look up the right handler for the table and return compatible information
1424
     * This function is important not only for the Install Tool but probably for
1425
     * DBALs as well since they might need to look up table specific information
1426
     * in order to construct correct queries. In such cases this information should
1427
     * probably be cached for quick delivery.
1428
     *
1429
     * @param string $tableName Table name
1430
     * @return array Field information in an associative array with fieldname => field row
1431
     */
1432 View Code Duplication
    public function admin_get_fields($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1433
    {
1434
        $this->logDeprecation();
1435
        $output = [];
1436
        $columns_res = $this->query('SHOW FULL COLUMNS FROM `' . $tableName . '`');
1437
        if ($columns_res !== false) {
1438
            while ($fieldRow = $columns_res->fetch_assoc()) {
1439
                $output[$fieldRow['Field']] = $fieldRow;
1440
            }
1441
            $columns_res->free();
1442
        }
1443
        return $output;
1444
    }
1445
1446
    /**
1447
     * Returns information about each index key in the $table (quering the DBMS)
1448
     * In a DBAL this should look up the right handler for the table and return compatible information
1449
     *
1450
     * @param string $tableName Table name
1451
     * @return array Key information in a numeric array
1452
     */
1453 View Code Duplication
    public function admin_get_keys($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1454
    {
1455
        $this->logDeprecation();
1456
        $output = [];
1457
        $keyRes = $this->query('SHOW KEYS FROM `' . $tableName . '`');
1458
        if ($keyRes !== false) {
1459
            while ($keyRow = $keyRes->fetch_assoc()) {
1460
                $output[] = $keyRow;
1461
            }
1462
            $keyRes->free();
1463
        }
1464
        return $output;
1465
    }
1466
1467
    /**
1468
     * Returns information about the character sets supported by the current DBM
1469
     * This function is important not only for the Install Tool but probably for
1470
     * DBALs as well since they might need to look up table specific information
1471
     * in order to construct correct queries. In such cases this information should
1472
     * probably be cached for quick delivery.
1473
     *
1474
     * This is used by the Install Tool to convert tables with non-UTF8 charsets
1475
     * Use in Install Tool only!
1476
     *
1477
     * @return array Array with Charset as key and an array of "Charset", "Description", "Default collation", "Maxlen" as values
1478
     */
1479 View Code Duplication
    public function admin_get_charsets()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1480
    {
1481
        $this->logDeprecation();
1482
        $output = [];
1483
        $columns_res = $this->query('SHOW CHARACTER SET');
1484
        if ($columns_res !== false) {
1485
            while ($row = $columns_res->fetch_assoc()) {
1486
                $output[$row['Charset']] = $row;
1487
            }
1488
            $columns_res->free();
1489
        }
1490
        return $output;
1491
    }
1492
1493
    /**
1494
     * mysqli() wrapper function, used by the Install Tool and EM for all queries regarding management of the database!
1495
     *
1496
     * @param string $query Query to execute
1497
     * @return bool|\mysqli_result|object MySQLi result object / DBAL object
1498
     */
1499 View Code Duplication
    public function admin_query($query)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1500
    {
1501
        $this->logDeprecation();
1502
        $res = $this->query($query);
1503
        if ($this->debugOutput) {
1504
            $this->debug('admin_query', $query);
1505
        }
1506
        return $res;
1507
    }
1508
1509
    /******************************
1510
     *
1511
     * Connect handling
1512
     *
1513
     ******************************/
1514
1515
    /**
1516
     * Set database host
1517
     *
1518
     * @param string $host
1519
     */
1520
    public function setDatabaseHost($host = 'localhost')
1521
    {
1522
        $this->disconnectIfConnected();
1523
        $this->databaseHost = $host;
1524
    }
1525
1526
    /**
1527
     * Set database port
1528
     *
1529
     * @param int $port
1530
     */
1531
    public function setDatabasePort($port = 3306)
1532
    {
1533
        $this->disconnectIfConnected();
1534
        $this->databasePort = (int)$port;
1535
    }
1536
1537
    /**
1538
     * Set database socket
1539
     *
1540
     * @param string|NULL $socket
1541
     */
1542
    public function setDatabaseSocket($socket = null)
1543
    {
1544
        $this->disconnectIfConnected();
1545
        $this->databaseSocket = $socket;
1546
    }
1547
1548
    /**
1549
     * Set database name
1550
     *
1551
     * @param string $name
1552
     */
1553
    public function setDatabaseName($name)
1554
    {
1555
        $this->disconnectIfConnected();
1556
        $this->databaseName = $name;
1557
    }
1558
1559
    /**
1560
     * Set database username
1561
     *
1562
     * @param string $username
1563
     */
1564
    public function setDatabaseUsername($username)
1565
    {
1566
        $this->disconnectIfConnected();
1567
        $this->databaseUsername = $username;
1568
    }
1569
1570
    /**
1571
     * Set database password
1572
     *
1573
     * @param string $password
1574
     */
1575
    public function setDatabasePassword($password)
1576
    {
1577
        $this->disconnectIfConnected();
1578
        $this->databaseUserPassword = $password;
1579
    }
1580
1581
    /**
1582
     * Set persistent database connection
1583
     *
1584
     * @param bool $persistentDatabaseConnection
1585
     * @see http://php.net/manual/de/mysqli.persistconns.php
1586
     */
1587
    public function setPersistentDatabaseConnection($persistentDatabaseConnection)
1588
    {
1589
        $this->disconnectIfConnected();
1590
        $this->persistentDatabaseConnection = (bool)$persistentDatabaseConnection;
1591
    }
1592
1593
    /**
1594
     * Set connection compression. Might be an advantage, if SQL server is not on localhost
1595
     *
1596
     * @param bool $connectionCompression TRUE if connection should be compressed
1597
     */
1598
    public function setConnectionCompression($connectionCompression)
1599
    {
1600
        $this->disconnectIfConnected();
1601
        $this->connectionCompression = (bool)$connectionCompression;
1602
    }
1603
1604
    /**
1605
     * Set commands to be fired after connection was established
1606
     *
1607
     * @param array $commands List of SQL commands to be executed after connect
1608
     */
1609
    public function setInitializeCommandsAfterConnect(array $commands)
1610
    {
1611
        $this->disconnectIfConnected();
1612
        $this->initializeCommandsAfterConnect = $commands;
1613
    }
1614
1615
    /**
1616
     * Set the charset that should be used for the MySQL connection.
1617
     * The given value will be passed on to mysqli_set_charset().
1618
     *
1619
     * The default value of this setting is utf8.
1620
     *
1621
     * @param string $connectionCharset The connection charset that will be passed on to mysqli_set_charset() when connecting the database. Default is utf8.
1622
     */
1623
    public function setConnectionCharset($connectionCharset = 'utf8')
1624
    {
1625
        $this->disconnectIfConnected();
1626
        $this->connectionCharset = $connectionCharset;
1627
    }
1628
1629
    /**
1630
     * Connects to database for TYPO3 sites:
1631
     *
1632
     * @throws \RuntimeException
1633
     * @throws \UnexpectedValueException
1634
     */
1635
    public function connectDB()
1636
    {
1637
        $this->logDeprecation();
1638
        // Early return if connected already
1639
        if ($this->isConnected) {
1640
            return;
1641
        }
1642
1643
        if (!$this->databaseName) {
1644
            throw new \RuntimeException(
1645
                'TYPO3 Fatal Error: No database selected!',
1646
                1270853882
1647
            );
1648
        }
1649
1650
        if ($this->sql_pconnect()) {
1651
            if (!$this->sql_select_db()) {
1652
                throw new \RuntimeException(
1653
                    'TYPO3 Fatal Error: Cannot connect to the current database, "' . $this->databaseName . '"!',
1654
                    1270853883
1655
                );
1656
            }
1657
        } else {
1658
            throw new \RuntimeException(
1659
                'TYPO3 Fatal Error: The current username, password or host was not accepted when the connection to the database was attempted to be established!',
1660
                1270853884
1661
            );
1662
        }
1663
1664
        // Prepare user defined objects (if any) for hooks which extend query methods
1665
        $this->preProcessHookObjects = [];
1666
        $this->postProcessHookObjects = [];
1667
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'])) {
1668
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'] as $className) {
1669
                $hookObject = GeneralUtility::makeInstance($className);
1670
                if (!(
1671
                    $hookObject instanceof PreProcessQueryHookInterface
1672
                    || $hookObject instanceof PostProcessQueryHookInterface
1673
                )) {
1674
                    throw new \UnexpectedValueException(
1675
                        '$hookObject must either implement interface TYPO3\\CMS\\Typo3DbLegacy\\Database\\PreProcessQueryHookInterface or interface TYPO3\\CMS\\Typo3DbLegacy\\Database\\PostProcessQueryHookInterface',
1676
                        1299158548
1677
                    );
1678
                }
1679
                if ($hookObject instanceof PreProcessQueryHookInterface) {
1680
                    $this->preProcessHookObjects[] = $hookObject;
1681
                }
1682
                if ($hookObject instanceof PostProcessQueryHookInterface) {
1683
                    $this->postProcessHookObjects[] = $hookObject;
1684
                }
1685
            }
1686
        }
1687
    }
1688
1689
    /**
1690
     * Checks if database is connected
1691
     *
1692
     * @return bool
1693
     */
1694
    public function isConnected()
1695
    {
1696
        // We think we're still connected
1697
        if ($this->isConnected) {
1698
            // Check if this is really the case or if the database server has gone away for some reason
1699
            // Using mysqlnd ping() does not reconnect (which we would not want anyway since charset etc would not be reinitialized that way)
1700
            $this->isConnected = $this->link->ping();
1701
        }
1702
        return $this->isConnected;
1703
    }
1704
1705
    /**
1706
     * Checks if the current connection character set has the same value
1707
     * as the connectionCharset variable.
1708
     *
1709
     * To determine the character set these MySQL session variables are
1710
     * checked: character_set_client, character_set_results and
1711
     * character_set_connection.
1712
     *
1713
     * If the character set does not match or if the session variables
1714
     * can not be read a RuntimeException is thrown.
1715
     *
1716
     * @throws \RuntimeException
1717
     */
1718
    protected function checkConnectionCharset()
1719
    {
1720
        $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
1721
1722
        if ($sessionResult === false) {
1723
            GeneralUtility::sysLog(
1724
                'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
1725
                'core',
1726
                GeneralUtility::SYSLOG_SEVERITY_ERROR
1727
            );
1728
            throw new \RuntimeException(
1729
                'TYPO3 Fatal Error: Could not determine the current charset of the database.',
1730
                1381847136
1731
            );
1732
        }
1733
1734
        $charsetVariables = [];
1735
        while (($row = $this->sql_fetch_row($sessionResult)) !== false) {
1736
            $variableName = $row[0];
1737
            $variableValue = $row[1];
1738
            $charsetVariables[$variableName] = $variableValue;
1739
        }
1740
        $this->sql_free_result($sessionResult);
1741
1742
        // These variables are set with the "Set names" command which was
1743
        // used in the past. This is why we check them.
1744
        $charsetRequiredVariables = [
1745
            'character_set_client',
1746
            'character_set_results',
1747
            'character_set_connection',
1748
        ];
1749
1750
        $hasValidCharset = true;
1751
        foreach ($charsetRequiredVariables as $variableName) {
1752
            if (empty($charsetVariables[$variableName])) {
1753
                GeneralUtility::sysLog(
1754
                    'A required session variable is missing in the current MySQL connection: ' . $variableName,
1755
                    'core',
1756
                    GeneralUtility::SYSLOG_SEVERITY_ERROR
1757
                );
1758
                throw new \RuntimeException(
1759
                    'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
1760
                    1381847779
1761
                );
1762
            }
1763
1764
            if ($charsetVariables[$variableName] !== $this->connectionCharset) {
1765
                $hasValidCharset = false;
1766
                break;
1767
            }
1768
        }
1769
1770
        if (!$hasValidCharset) {
1771
            throw new \RuntimeException(
1772
                'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
1773
                'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
1774
                $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
1775
                'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
1776
                1389697515
1777
            );
1778
        }
1779
    }
1780
1781
    /**
1782
     * Disconnect from database if connected
1783
     */
1784
    protected function disconnectIfConnected()
1785
    {
1786
        if ($this->isConnected) {
1787
            $this->link->close();
1788
            $this->isConnected = false;
1789
        }
1790
    }
1791
1792
    /**
1793
     * Returns current database handle
1794
     *
1795
     * @return \mysqli|NULL
1796
     */
1797
    public function getDatabaseHandle()
1798
    {
1799
        $this->logDeprecation();
1800
        return $this->link;
1801
    }
1802
1803
    /**
1804
     * Set current database handle, usually \mysqli
1805
     *
1806
     * @param \mysqli $handle
1807
     */
1808
    public function setDatabaseHandle($handle)
1809
    {
1810
        $this->link = $handle;
1811
    }
1812
1813
    /**
1814
     * Get the MySQL server version
1815
     *
1816
     * @return string
1817
     */
1818
    public function getServerVersion()
1819
    {
1820
        $this->logDeprecation();
1821
        return $this->link->server_info;
1822
    }
1823
1824
    /******************************
1825
     *
1826
     * Debugging
1827
     *
1828
     ******************************/
1829
    /**
1830
     * Debug function: Outputs error if any
1831
     *
1832
     * @param string $func Function calling debug()
1833
     * @param string $query Last query if not last built query
1834
     */
1835
    public function debug($func, $query = '')
1836
    {
1837
        $this->logDeprecation();
1838
        $error = $this->sql_error();
1839
        if ($error || (int)$this->debugOutput === 2) {
1840
            \TYPO3\CMS\Core\Utility\DebugUtility::debug(
1841
                [
1842
                    'caller' => \Fab\Vidi\Database\DatabaseConnection::class . '::' . $func,
1843
                    'ERROR' => $error,
1844
                    'lastBuiltQuery' => $query ? $query : $this->debug_lastBuiltQuery,
1845
                    'debug_backtrace' => \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail()
1846
                ],
1847
                $func,
1848
                is_object($GLOBALS['error']) && @is_callable([$GLOBALS['error'], 'debug'])
1849
                    ? ''
1850
                    : 'DB Error'
1851
            );
1852
        }
1853
    }
1854
1855
    /**
1856
     * Checks if record set is valid and writes debugging information into devLog if not.
1857
     *
1858
     * @param bool|\mysqli_result|object MySQLi result object / DBAL object
1859
     * @return bool TRUE if the  record set is valid, FALSE otherwise
1860
     */
1861
    public function debug_check_recordset($res)
1862
    {
1863
        $this->logDeprecation();
1864
        if ($res !== false && $res !== null) {
1865
            return true;
1866
        }
1867
        $trace = debug_backtrace(0);
1868
        array_shift($trace);
1869
        $msg = 'Invalid database result detected: function TYPO3\\CMS\\Typo3DbLegacy\\Database\\DatabaseConnection->'
1870
            . $trace[0]['function'] . ' called from file ' . substr($trace[0]['file'], (strlen(PATH_site) + 2))
1871
            . ' in line ' . $trace[0]['line'] . '.';
1872
        GeneralUtility::sysLog(
1873
            $msg . ' Use a devLog extension to get more details.',
1874
            'core',
1875
            GeneralUtility::SYSLOG_SEVERITY_ERROR
1876
        );
1877
        // Send to devLog if enabled
1878
        if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['devLog'])) {
1879
            $debugLogData = [
1880
                'SQL Error' => $this->sql_error(),
1881
                'Backtrace' => $trace
1882
            ];
1883
            if ($this->debug_lastBuiltQuery) {
1884
                $debugLogData = ['SQL Query' => $this->debug_lastBuiltQuery] + $debugLogData;
1885
            }
1886
            GeneralUtility::devLog($msg, 'Core/t3lib_db', 3, $debugLogData);
1887
        }
1888
        return false;
1889
    }
1890
1891
    /**
1892
     * Explain select queries
1893
     * If $this->explainOutput is set, SELECT queries will be explained here. Only queries with more than one possible result row will be displayed.
1894
     * The output is either printed as raw HTML output or embedded into the TS admin panel (checkbox must be enabled!)
1895
     *
1896
     * @todo Feature is not DBAL-compliant
1897
     *
1898
     * @param string $query SQL query
1899
     * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
1900
     * @param int $row_count Number of resulting rows
1901
     * @return bool TRUE if explain was run, FALSE otherwise
1902
     */
1903
    protected function explain($query, $from_table, $row_count)
1904
    {
1905
        $debugAllowedForIp = GeneralUtility::cmpIP(
1906
            GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1907
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1908
        );
1909
        if (
1910
            (int)$this->explainOutput == 1
1911
            || ((int)$this->explainOutput == 2 && $debugAllowedForIp)
1912
        ) {
1913
            // Raw HTML output
1914
            $explainMode = 1;
1915
        } elseif ((int)$this->explainOutput == 3) {
1916
            // Embed the output into the TS admin panel
1917
            $explainMode = 2;
1918
        } else {
1919
            return false;
1920
        }
1921
        $error = $this->sql_error();
1922
        $trail = \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail();
1923
        $explain_tables = [];
1924
        $explain_output = [];
1925
        $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
0 ignored issues
show
Unused Code introduced by
The call to DatabaseConnection::sql_query() has too many arguments starting with $this->link.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1926
        if (is_a($res, '\\mysqli_result')) {
1927
            while ($tempRow = $this->sql_fetch_assoc($res)) {
1928
                $explain_output[] = $tempRow;
1929
                $explain_tables[] = $tempRow['table'];
1930
            }
1931
            $this->sql_free_result($res);
1932
        }
1933
        $indices_output = [];
1934
        // Notice: Rows are skipped if there is only one result, or if no conditions are set
1935
        if ($explain_output[0]['rows'] > 1 || $explain_output[0]['type'] === 'ALL') {
1936
            // Only enable output if it's really useful
1937
            $debug = true;
1938
            foreach ($explain_tables as $table) {
1939
                $tableRes = $this->sql_query('SHOW TABLE STATUS LIKE \'' . $table . '\'');
1940
                $isTable = $this->sql_num_rows($tableRes);
1941
                if ($isTable) {
1942
                    $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
0 ignored issues
show
Unused Code introduced by
The call to DatabaseConnection::sql_query() has too many arguments starting with $this->link.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1943
                    if (is_a($res, '\\mysqli_result')) {
1944
                        while ($tempRow = $this->sql_fetch_assoc($res)) {
1945
                            $indices_output[] = $tempRow;
1946
                        }
1947
                        $this->sql_free_result($res);
1948
                    }
1949
                }
1950
                $this->sql_free_result($tableRes);
1951
            }
1952
        } else {
1953
            $debug = false;
1954
        }
1955
        if ($debug) {
1956
            if ($explainMode) {
1957
                $data = [];
1958
                $data['query'] = $query;
1959
                $data['trail'] = $trail;
1960
                $data['row_count'] = $row_count;
1961
                if ($error) {
1962
                    $data['error'] = $error;
1963
                }
1964
                if (!empty($explain_output)) {
1965
                    $data['explain'] = $explain_output;
1966
                }
1967
                if (!empty($indices_output)) {
1968
                    $data['indices'] = $indices_output;
1969
                }
1970
                if ($explainMode == 1) {
1971
                    \TYPO3\CMS\Core\Utility\DebugUtility::debug($data, 'Tables: ' . $from_table, 'DB SQL EXPLAIN');
1972
                } elseif ($explainMode == 2) {
1973
                    /** @var TimeTracker $timeTracker */
1974
                    $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
1975
                    $timeTracker->setTSselectQuery($data);
1976
                }
1977
            }
1978
            return true;
1979
        }
1980
        return false;
1981
    }
1982
1983
    /**
1984
     * Serialize destructs current connection
1985
     *
1986
     * @return array All protected properties that should be saved
1987
     */
1988
    public function __sleep()
1989
    {
1990
        $this->disconnectIfConnected();
1991
        return [
1992
            'debugOutput',
1993
            'explainOutput',
1994
            'databaseHost',
1995
            'databasePort',
1996
            'databaseSocket',
1997
            'databaseName',
1998
            'databaseUsername',
1999
            'databaseUserPassword',
2000
            'persistentDatabaseConnection',
2001
            'connectionCompression',
2002
            'initializeCommandsAfterConnect',
2003
            'default_charset',
2004
        ];
2005
    }
2006
2007
    /**
2008
     * function to call a deprecation log entry (but only once per request / class)
2009
     */
2010
    protected function logDeprecation()
2011
    {
2012
        if (!$this->deprecationWarningThrown) {
2013
            $this->deprecationWarningThrown = true;
2014
            GeneralUtility::deprecationLog('DatabaseConnection a.k.a. $["TYPO3_DB"] has been marked as deprecated in'
2015
            . ' TYPO3 v8 and will be removed in TYPO3 v9. Please use the newly available ConnectionPool and QueryBuilder'
2016
            . ' classes.');
2017
        }
2018
    }
2019
}
2020