Passed
Push — master ( 3bb133...b97460 )
by Maurício
07:10
created

SearchController   F

Complexity

Total Complexity 134

Size/Duplication

Total Lines 1217
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 134
eloc 620
dl 0
loc 1217
rs 1.98
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A rangeSearchAction() 0 4 1
B _loadTableInfo() 0 49 9
B displaySelectionFormAction() 0 63 10
A changeTableInfoAction() 0 23 4
A replace() 0 50 4
B zoomSubmitAction() 0 74 5
F _getWhereClause() 0 102 26
A __construct() 0 23 1
B _buildSqlQuery() 0 34 7
A findAction() 0 13 2
A getColumnProperties() 0 55 3
A _getSubTabs() 0 20 1
A replaceAction() 0 14 1
B _getGeomWhereClause() 0 49 9
A getColumnMinMax() 0 8 1
B _getRegexReplaceRows() 0 57 6
A getDataRowAction() 0 26 4
B _getEnumWhereClause() 0 27 7
A getReplacePreview() 0 44 2
A doSelectionAction() 0 32 1
B _generateWhereClause() 0 42 10
D indexAction() 0 127 20

How to fix   Complexity   

Complex Class

Complex classes like SearchController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SearchController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Holds the PhpMyAdmin\Controllers\Table\SearchController
5
 *
6
 * @package PhpMyAdmin\Controllers
7
 */
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin\Controllers\Table;
11
12
use PhpMyAdmin\Controllers\TableController;
13
use PhpMyAdmin\DatabaseInterface;
14
use PhpMyAdmin\Relation;
15
use PhpMyAdmin\Sql;
16
use PhpMyAdmin\Util;
17
18
/**
19
 * Class SearchController
20
 *
21
 * @package PhpMyAdmin\Controllers
22
 */
23
class SearchController extends TableController
24
{
25
    /**
26
     * Normal search or Zoom search
27
     *
28
     * @access private
29
     * @var string
30
     */
31
    private $_searchType;
32
    /**
33
     * Names of columns
34
     *
35
     * @access private
36
     * @var array
37
     */
38
    private $_columnNames;
39
    /**
40
     * Types of columns
41
     *
42
     * @access private
43
     * @var array
44
     */
45
    private $_columnTypes;
46
    /**
47
     * Collations of columns
48
     *
49
     * @access private
50
     * @var array
51
     */
52
    private $_columnCollations;
53
    /**
54
     * Null Flags of columns
55
     *
56
     * @access private
57
     * @var array
58
     */
59
    private $_columnNullFlags;
60
    /**
61
     * Whether a geometry column is present
62
     *
63
     * @access private
64
     * @var boolean
65
     */
66
    private $_geomColumnFlag;
67
    /**
68
     * Foreign Keys
69
     *
70
     * @access private
71
     * @var array
72
     */
73
    private $_foreigners;
74
    /**
75
     * Connection charset
76
     *
77
     * @access private
78
     * @var string
79
     */
80
    private $_connectionCharSet;
81
82
    protected $url_query;
83
84
    /**
85
     * @var Relation
86
     */
87
    private $relation;
88
89
    /**
90
     * Constructor
91
     *
92
     * @param \PhpMyAdmin\Response $response   Response object
93
     * @param DatabaseInterface    $dbi        DatabaseInterface object
94
     * @param string               $db         Database name
95
     * @param string               $table      Table name
96
     * @param string               $searchType Search type
97
     * @param string               $url_query  URL query
98
     */
99
    public function __construct(
100
        $response,
101
        $dbi,
102
        $db,
103
        $table,
104
        $searchType,
105
        $url_query
106
    ) {
107
        parent::__construct($response, $dbi, $db, $table);
108
109
        $this->url_query = $url_query;
110
        $this->_searchType = $searchType;
111
        $this->_columnNames = [];
112
        $this->_columnNullFlags = [];
113
        $this->_columnTypes = [];
114
        $this->_columnCollations = [];
115
        $this->_geomColumnFlag = false;
116
        $this->_foreigners = [];
117
        $this->relation = new Relation($dbi);
118
        // Loads table's information
119
        $this->_loadTableInfo();
120
        $this->_connectionCharSet = $this->dbi->fetchValue(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dbi->fetchValue('...racter_set_connection') can also be of type false. However, the property $_connectionCharSet is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
121
            "SELECT @@character_set_connection"
122
        );
123
    }
124
125
    /**
126
     * Gets all the columns of a table along with their types, collations
127
     * and whether null or not.
128
     *
129
     * @return void
130
     */
131
    private function _loadTableInfo()
132
    {
133
        // Gets the list and number of columns
134
        $columns = $this->dbi->getColumns(
135
            $this->db,
136
            $this->table,
137
            null,
138
            true
139
        );
140
        // Get details about the geometry functions
141
        $geom_types = Util::getGISDatatypes();
142
143
        foreach ($columns as $row) {
144
            // set column name
145
            $this->_columnNames[] = $row['Field'];
146
147
            $type = $row['Type'];
148
            // check whether table contains geometric columns
149
            if (in_array($type, $geom_types)) {
150
                $this->_geomColumnFlag = true;
151
            }
152
            // reformat mysql query output
153
            if (strncasecmp($type, 'set', 3) == 0
154
                || strncasecmp($type, 'enum', 4) == 0
155
            ) {
156
                $type = str_replace(',', ', ', $type);
157
            } else {
158
                // strip the "BINARY" attribute, except if we find "BINARY(" because
159
                // this would be a BINARY or VARBINARY column type
160
                if (! preg_match('@BINARY[\(]@i', $type)) {
161
                    $type = str_ireplace("BINARY", '', $type);
162
                }
163
                $type = preg_replace('@ZEROFILL@i', '', $type);
164
                $type = preg_replace('@UNSIGNED@i', '', $type);
165
                $type = mb_strtolower($type);
166
            }
167
            if (empty($type)) {
168
                $type = '&nbsp;';
169
            }
170
            $this->_columnTypes[] = $type;
171
            $this->_columnNullFlags[] = $row['Null'];
172
            $this->_columnCollations[]
173
                = ! empty($row['Collation']) && $row['Collation'] != 'NULL'
174
                ? $row['Collation']
175
                : '';
176
        } // end for
177
178
        // Retrieve foreign keys
179
        $this->_foreigners = $this->relation->getForeigners($this->db, $this->table);
180
    }
181
182
    /**
183
     * Index action
184
     *
185
     * @return void
186
     */
187
    public function indexAction()
188
    {
189
        global $goto;
190
        switch ($this->_searchType) {
191
            case 'replace':
192
                if (isset($_POST['find'])) {
193
                    $this->findAction();
194
195
                    return;
196
                }
197
                $this->response
198
                ->getHeader()
199
                ->getScripts()
200
                ->addFile('tbl_find_replace.js');
201
202
                if (isset($_POST['replace'])) {
203
                    $this->replaceAction();
204
                }
205
206
                // Displays the find and replace form
207
                $this->displaySelectionFormAction();
208
                break;
209
210
            case 'normal':
211
                $this->response->getHeader()
212
                ->getScripts()
213
                ->addFiles(
214
                    [
215
                        'makegrid.js',
216
                        'sql.js',
217
                        'tbl_select.js',
218
                        'tbl_change.js',
219
                        'vendor/jquery/jquery.uitablefilter.js',
220
                        'gis_data_editor.js',
221
                    ]
222
                );
223
224
                if (isset($_POST['range_search'])) {
225
                    $this->rangeSearchAction();
226
227
                    return;
228
                }
229
230
                /**
231
             * No selection criteria received -> display the selection form
232
             */
233
                if (! isset($_POST['columnsToDisplay'])
234
                && ! isset($_POST['displayAllColumns'])
235
                ) {
236
                    $this->displaySelectionFormAction();
237
                } else {
238
                    $this->doSelectionAction();
239
                }
240
                break;
241
242
            case 'zoom':
243
                $this->response->getHeader()
244
                ->getScripts()
245
                ->addFiles(
246
                    [
247
                        'makegrid.js',
248
                        'sql.js',
249
                        'vendor/jqplot/jquery.jqplot.js',
250
                        'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
251
                        'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
252
                        'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
253
                        'vendor/jqplot/plugins/jqplot.highlighter.js',
254
                        'vendor/jqplot/plugins/jqplot.cursor.js',
255
                        'tbl_zoom_plot_jqplot.js',
256
                        'tbl_change.js',
257
                    ]
258
                );
259
260
                /**
261
             * Handle AJAX request for data row on point select
262
             *
263
             * @var boolean Object containing parameters for the POST request
264
             */
265
                if (isset($_POST['get_data_row'])
266
                    && $_POST['get_data_row'] == true
267
                ) {
268
                    $this->getDataRowAction();
269
270
                    return;
271
                }
272
                /**
273
             * Handle AJAX request for changing field information
274
             * (value,collation,operators,field values) in input form
275
             *
276
             * @var boolean Object containing parameters for the POST request
277
             */
278
                if (isset($_POST['change_tbl_info'])
279
                && $_POST['change_tbl_info'] == true
280
                ) {
281
                    $this->changeTableInfoAction();
282
283
                    return;
284
                }
285
286
                //Set default datalabel if not selected
287
                if (! isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') {
288
                    $dataLabel = $this->relation->getDisplayField($this->db, $this->table);
289
                } else {
290
                    $dataLabel = $_POST['dataLabel'];
291
                }
292
293
                // Displays the zoom search form
294
                $this->displaySelectionFormAction($dataLabel);
295
296
                /*
297
                 * Handle the input criteria and generate the query result
298
                 * Form for displaying query results
299
                 */
300
                if (isset($_POST['zoom_submit'])
301
                && $_POST['criteriaColumnNames'][0] != 'pma_null'
302
                && $_POST['criteriaColumnNames'][1] != 'pma_null'
303
                && $_POST['criteriaColumnNames'][0] != $_POST['criteriaColumnNames'][1]
304
                ) {
305
                    if (! isset($goto)) {
306
                        $goto = Util::getScriptNameForOption(
307
                            $GLOBALS['cfg']['DefaultTabTable'],
308
                            'table'
309
                        );
310
                    }
311
                    $this->zoomSubmitAction($dataLabel, $goto);
312
                }
313
                break;
314
        }
315
    }
316
317
    /**
318
     * Zoom submit action
319
     *
320
     * @param string $dataLabel Data label
321
     * @param string $goto      Goto
322
     *
323
     * @return void
324
     */
325
    public function zoomSubmitAction($dataLabel, $goto)
326
    {
327
        //Query generation part
328
        $sql_query = $this->_buildSqlQuery();
329
        $sql_query .= ' LIMIT ' . $_POST['maxPlotLimit'];
330
331
        //Query execution part
332
        $result = $this->dbi->query(
333
            $sql_query . ";",
334
            DatabaseInterface::CONNECT_USER,
335
            DatabaseInterface::QUERY_STORE
336
        );
337
        $fields_meta = $this->dbi->getFieldsMeta($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::getFieldsMeta() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

337
        $fields_meta = $this->dbi->getFieldsMeta(/** @scrutinizer ignore-type */ $result);
Loading history...
338
        $data = [];
339
        while ($row = $this->dbi->fetchAssoc($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::fetchAssoc() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

339
        while ($row = $this->dbi->fetchAssoc(/** @scrutinizer ignore-type */ $result)) {
Loading history...
340
            //Need a row with indexes as 0,1,2 for the getUniqueCondition
341
            // hence using a temporary array
342
            $tmpRow = [];
343
            foreach ($row as $val) {
344
                $tmpRow[] = $val;
345
            }
346
            //Get unique condition on each row (will be needed for row update)
347
            $uniqueCondition = Util::getUniqueCondition(
348
                $result, // handle
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $handle of PhpMyAdmin\Util::getUniqueCondition() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

348
                /** @scrutinizer ignore-type */ $result, // handle
Loading history...
349
                count($this->_columnNames), // fields_cnt
350
                $fields_meta, // fields_meta
351
                $tmpRow, // row
352
                true, // force_unique
353
                false, // restrict_to_table
354
                null // analyzed_sql_results
355
            );
356
            //Append it to row array as where_clause
357
            $row['where_clause'] = $uniqueCondition[0];
358
359
            $tmpData = [
360
                $_POST['criteriaColumnNames'][0] =>
361
                    $row[$_POST['criteriaColumnNames'][0]],
362
                $_POST['criteriaColumnNames'][1] =>
363
                    $row[$_POST['criteriaColumnNames'][1]],
364
                'where_clause' => $uniqueCondition[0]
365
            ];
366
            $tmpData[$dataLabel] = $dataLabel ? $row[$dataLabel] : '';
367
            $data[] = $tmpData;
368
        }
369
        unset($tmpData);
370
371
        //Displays form for point data and scatter plot
372
        $titles = [
373
            'Browse' => Util::getIcon(
374
                'b_browse',
375
                __('Browse foreign values')
376
            ),
377
        ];
378
        $column_names_hashes = [];
379
380
        foreach ($this->_columnNames as $columnName) {
381
            $column_names_hashes[$columnName] = md5($columnName);
382
        }
383
384
        $this->response->addHTML(
385
            $this->template->render('table/search/zoom_result_form', [
386
                'db' => $this->db,
387
                'table' => $this->table,
388
                'column_names' => $this->_columnNames,
389
                'column_names_hashes' => $column_names_hashes,
390
                'foreigners' => $this->_foreigners,
391
                'column_null_flags' => $this->_columnNullFlags,
392
                'column_types' => $this->_columnTypes,
393
                'titles' => $titles,
394
                'goto' => $goto,
395
                'data' => $data,
396
                'data_json' => json_encode($data),
397
                'zoom_submit' => isset($_POST['zoom_submit']),
398
                'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
399
            ])
400
        );
401
    }
402
403
    /**
404
     * Change table info action
405
     *
406
     * @return void
407
     */
408
    public function changeTableInfoAction()
409
    {
410
        $field = $_POST['field'];
411
        if ($field == 'pma_null') {
412
            $this->response->addJSON('field_type', '');
413
            $this->response->addJSON('field_collation', '');
414
            $this->response->addJSON('field_operators', '');
415
            $this->response->addJSON('field_value', '');
416
            return;
417
        }
418
        $key = array_search($field, $this->_columnNames);
419
        $search_index
420
            = (isset($_POST['it']) && is_numeric($_POST['it'])
421
                ? intval($_POST['it']) : 0);
422
423
        $properties = $this->getColumnProperties($search_index, $key);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type false and string; however, parameter $column_index of PhpMyAdmin\Controllers\T...::getColumnProperties() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

423
        $properties = $this->getColumnProperties($search_index, /** @scrutinizer ignore-type */ $key);
Loading history...
424
        $this->response->addJSON(
425
            'field_type',
426
            htmlspecialchars($properties['type'])
427
        );
428
        $this->response->addJSON('field_collation', $properties['collation']);
429
        $this->response->addJSON('field_operators', $properties['func']);
430
        $this->response->addJSON('field_value', $properties['value']);
431
    }
432
433
    /**
434
     * Get data row action
435
     *
436
     * @return void
437
     */
438
    public function getDataRowAction()
439
    {
440
        $extra_data = [];
441
        $row_info_query = 'SELECT * FROM `' . $_POST['db'] . '`.`'
442
            . $_POST['table'] . '` WHERE ' . $_POST['where_clause'];
443
        $result = $this->dbi->query(
444
            $row_info_query . ";",
445
            DatabaseInterface::CONNECT_USER,
446
            DatabaseInterface::QUERY_STORE
447
        );
448
        $fields_meta = $this->dbi->getFieldsMeta($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::getFieldsMeta() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

448
        $fields_meta = $this->dbi->getFieldsMeta(/** @scrutinizer ignore-type */ $result);
Loading history...
449
        while ($row = $this->dbi->fetchAssoc($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::fetchAssoc() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

449
        while ($row = $this->dbi->fetchAssoc(/** @scrutinizer ignore-type */ $result)) {
Loading history...
450
            // for bit fields we need to convert them to printable form
451
            $i = 0;
452
            foreach ($row as $col => $val) {
453
                if ($fields_meta[$i]->type == 'bit') {
454
                    $row[$col] = Util::printableBitValue(
455
                        (int) $val,
456
                        (int) $fields_meta[$i]->length
457
                    );
458
                }
459
                $i++;
460
            }
461
            $extra_data['row_info'] = $row;
462
        }
463
        $this->response->addJSON($extra_data);
464
    }
465
466
    /**
467
     * Do selection action
468
     *
469
     * @return void
470
     */
471
    public function doSelectionAction()
472
    {
473
        /**
474
         * Selection criteria have been submitted -> do the work
475
         */
476
        $sql_query = $this->_buildSqlQuery();
477
478
        /**
479
         * Add this to ensure following procedures included running correctly.
480
         */
481
        $db = $this->db;
0 ignored issues
show
Unused Code introduced by
The assignment to $db is dead and can be removed.
Loading history...
482
483
        $sql = new Sql();
484
        $sql->executeQueryAndSendQueryResponse(
485
            null, // analyzed_sql_results
486
            false, // is_gotofile
487
            $this->db, // db
488
            $this->table, // table
489
            null, // find_real_end
490
            null, // sql_query_for_bookmark
491
            null, // extra_data
492
            null, // message_to_show
493
            null, // message
494
            null, // sql_data
495
            $GLOBALS['goto'], // goto
496
            $GLOBALS['pmaThemeImage'], // pmaThemeImage
497
            null, // disp_query
498
            null, // disp_message
499
            null, // query_type
500
            $sql_query, // sql_query
501
            null, // selectedTables
502
            null // complete_query
503
        );
504
    }
505
506
    /**
507
     * Display selection form action
508
     *
509
     * @param string $dataLabel Data label
510
     *
511
     * @return void
512
     */
513
    public function displaySelectionFormAction($dataLabel = null)
514
    {
515
        global $goto;
516
        $this->url_query .= '&amp;goto=tbl_select.php&amp;back=tbl_select.php';
517
        if (! isset($goto)) {
518
            $goto = Util::getScriptNameForOption(
519
                $GLOBALS['cfg']['DefaultTabTable'],
520
                'table'
521
            );
522
        }
523
        // Displays the table search form
524
        $this->response->addHTML(
525
            $this->template->render('secondary_tabs', [
526
                'url_params' => [
527
                    'db'    => $this->db,
528
                    'table' => $this->table,
529
                ],
530
                'sub_tabs'   => $this->_getSubTabs(),
531
            ])
532
        );
533
534
        $column_names = $this->_columnNames;
535
        $column_types = $this->_columnTypes;
536
        $types = [];
537
        if ($this->_searchType == 'replace') {
538
            $num_cols = count($column_names);
539
            for ($i = 0; $i < $num_cols; $i++) {
540
                $types[$column_names[$i]] = preg_replace('@\\(.*@s', '', $column_types[$i]);
541
            }
542
        }
543
544
        $criteria_column_names = isset($_POST['criteriaColumnNames']) ? $_POST['criteriaColumnNames'] : null;
545
        $keys = [];
546
        for ($i = 0; $i < 4; $i++) {
547
            if (isset($criteria_column_names[$i])) {
548
                if ($criteria_column_names[$i] != 'pma_null') {
549
                    $keys[$criteria_column_names[$i]] = array_search($criteria_column_names[$i], $column_names);
550
                }
551
            }
552
        }
553
554
        $this->response->addHTML(
555
            $this->template->render('table/search/selection_form', [
556
                'search_type' => $this->_searchType,
557
                'db' => $this->db,
558
                'table' => $this->table,
559
                'goto' => $goto,
560
                'self' => $this,
561
                'geom_column_flag' => $this->_geomColumnFlag,
562
                'column_names' => $column_names,
563
                'column_types' => $column_types,
564
                'types' => $types,
565
                'column_collations' => $this->_columnCollations,
566
                'data_label' => $dataLabel,
567
                'keys' => $keys,
568
                'criteria_column_names' => $criteria_column_names,
569
                'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
570
                'criteria_column_types' => isset($_POST['criteriaColumnTypes']) ? $_POST['criteriaColumnTypes'] : null,
571
                'sql_types' => $this->dbi->types,
572
                'max_rows' => intval($GLOBALS['cfg']['MaxRows']),
573
                'max_plot_limit' => ! empty($_POST['maxPlotLimit'])
574
                    ? intval($_POST['maxPlotLimit'])
575
                    : intval($GLOBALS['cfg']['maxRowPlotLimit']),
576
            ])
577
        );
578
    }
579
580
    /**
581
     * Range search action
582
     *
583
     * @return void
584
     */
585
    public function rangeSearchAction()
586
    {
587
        $min_max = $this->getColumnMinMax($_POST['column']);
588
        $this->response->addJSON('column_data', $min_max);
589
    }
590
591
    /**
592
     * Find action
593
     *
594
     * @return void
595
     */
596
    public function findAction()
597
    {
598
        $useRegex = array_key_exists('useRegex', $_POST)
599
            && $_POST['useRegex'] == 'on';
600
601
        $preview = $this->getReplacePreview(
602
            $_POST['columnIndex'],
603
            $_POST['find'],
604
            $_POST['replaceWith'],
605
            $useRegex,
606
            $this->_connectionCharSet
607
        );
608
        $this->response->addJSON('preview', $preview);
609
    }
610
611
    /**
612
     * Replace action
613
     *
614
     * @return void
615
     */
616
    public function replaceAction()
617
    {
618
        $this->replace(
619
            $_POST['columnIndex'],
620
            $_POST['findString'],
621
            $_POST['replaceWith'],
622
            $_POST['useRegex'],
623
            $this->_connectionCharSet
624
        );
625
        $this->response->addHTML(
626
            Util::getMessage(
627
                __('Your SQL query has been executed successfully.'),
628
                null,
629
                'success'
630
            )
631
        );
632
    }
633
634
    /**
635
     * Returns HTML for previewing strings found and their replacements
636
     *
637
     * @param int     $columnIndex index of the column
638
     * @param string  $find        string to find in the column
639
     * @param string  $replaceWith string to replace with
640
     * @param boolean $useRegex    to use Regex replace or not
641
     * @param string  $charSet     character set of the connection
642
     *
643
     * @return string HTML for previewing strings found and their replacements
644
     */
645
    public function getReplacePreview(
646
        $columnIndex,
647
        $find,
648
        $replaceWith,
649
        $useRegex,
650
        $charSet
651
    ) {
652
        $column = $this->_columnNames[$columnIndex];
653
        if ($useRegex) {
654
            $result = $this->_getRegexReplaceRows(
655
                $columnIndex,
656
                $find,
657
                $replaceWith,
658
                $charSet
659
            );
660
        } else {
661
            $sql_query = "SELECT "
662
                . Util::backquote($column) . ","
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($column) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

662
                . /** @scrutinizer ignore-type */ Util::backquote($column) . ","
Loading history...
663
                . " REPLACE("
664
                . Util::backquote($column) . ", '" . $find . "', '"
665
                . $replaceWith
666
                . "'),"
667
                . " COUNT(*)"
668
                . " FROM " . Util::backquote($this->db)
669
                . "." . Util::backquote($this->table)
670
                . " WHERE " . Util::backquote($column)
671
                . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we
672
            // change the collation of the 2nd operand to a case sensitive
673
            // binary collation to make sure that the comparison
674
            // is case sensitive
675
            $sql_query .= " GROUP BY " . Util::backquote($column)
676
                . " ORDER BY " . Util::backquote($column) . " ASC";
677
678
            $result = $this->dbi->fetchResult($sql_query, 0);
679
        }
680
681
        return $this->template->render('table/search/replace_preview', [
682
            'db' => $this->db,
683
            'table' => $this->table,
684
            'column_index' => $columnIndex,
685
            'find' => $find,
686
            'replace_with' => $replaceWith,
687
            'use_regex' => $useRegex,
688
            'result' => $result,
689
        ]);
690
    }
691
692
    /**
693
     * Finds and returns Regex pattern and their replacements
694
     *
695
     * @param int    $columnIndex index of the column
696
     * @param string $find        string to find in the column
697
     * @param string $replaceWith string to replace with
698
     * @param string $charSet     character set of the connection
699
     *
700
     * @return array Array containing original values, replaced values and count
701
     */
702
    private function _getRegexReplaceRows(
703
        $columnIndex,
704
        $find,
705
        $replaceWith,
706
        $charSet
707
    ) {
708
        $column = $this->_columnNames[$columnIndex];
709
        $sql_query = "SELECT "
710
            . Util::backquote($column) . ","
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($column) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

710
            . /** @scrutinizer ignore-type */ Util::backquote($column) . ","
Loading history...
711
            . " 1," // to add an extra column that will have replaced value
712
            . " COUNT(*)"
713
            . " FROM " . Util::backquote($this->db)
714
            . "." . Util::backquote($this->table)
715
            . " WHERE " . Util::backquote($column)
716
            . " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
717
            . $charSet . "_bin"; // here we
718
        // change the collation of the 2nd operand to a case sensitive
719
        // binary collation to make sure that the comparison is case sensitive
720
        $sql_query .= " GROUP BY " . Util::backquote($column)
721
            . " ORDER BY " . Util::backquote($column) . " ASC";
722
723
        $result = $this->dbi->fetchResult($sql_query, 0);
724
725
        if (is_array($result)) {
0 ignored issues
show
introduced by
The condition is_array($result) is always true.
Loading history...
726
            /* Iterate over possible delimiters to get one */
727
            $delimiters = [
728
                '/',
729
                '@',
730
                '#',
731
                '~',
732
                '!',
733
                '$',
734
                '%',
735
                '^',
736
                '&',
737
                '_',
738
            ];
739
            $found = false;
740
            for ($i = 0, $l = count($delimiters); $i < $l; $i++) {
741
                if (strpos($find, $delimiters[$i]) === false) {
742
                    $found = true;
743
                    break;
744
                }
745
            }
746
            if (! $found) {
747
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
748
            }
749
            $find = $delimiters[$i] . $find . $delimiters[$i];
750
            foreach ($result as $index => $row) {
751
                $result[$index][1] = preg_replace(
752
                    $find,
753
                    $replaceWith,
754
                    $row[0]
755
                );
756
            }
757
        }
758
        return $result;
759
    }
760
761
    /**
762
     * Replaces a given string in a column with a give replacement
763
     *
764
     * @param int     $columnIndex index of the column
765
     * @param string  $find        string to find in the column
766
     * @param string  $replaceWith string to replace with
767
     * @param boolean $useRegex    to use Regex replace or not
768
     * @param string  $charSet     character set of the connection
769
     *
770
     * @return void
771
     */
772
    public function replace(
773
        $columnIndex,
774
        $find,
775
        $replaceWith,
776
        $useRegex,
777
        $charSet
778
    ) {
779
        $column = $this->_columnNames[$columnIndex];
780
        if ($useRegex) {
781
            $toReplace = $this->_getRegexReplaceRows(
782
                $columnIndex,
783
                $find,
784
                $replaceWith,
785
                $charSet
786
            );
787
            $sql_query = "UPDATE " . Util::backquote($this->table)
788
                . " SET " . Util::backquote($column) . " = CASE";
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($column) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

788
                . " SET " . /** @scrutinizer ignore-type */ Util::backquote($column) . " = CASE";
Loading history...
789
            if (is_array($toReplace)) {
0 ignored issues
show
introduced by
The condition is_array($toReplace) is always true.
Loading history...
790
                foreach ($toReplace as $row) {
791
                    $sql_query .= "\n WHEN " . Util::backquote($column)
792
                        . " = '" . $this->dbi->escapeString($row[0])
793
                        . "' THEN '" . $this->dbi->escapeString($row[1]) . "'";
794
                }
795
            }
796
            $sql_query .= " END"
797
                . " WHERE " . Util::backquote($column)
798
                . " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
799
                . $charSet . "_bin"; // here we
800
            // change the collation of the 2nd operand to a case sensitive
801
            // binary collation to make sure that the comparison
802
            // is case sensitive
803
        } else {
804
            $sql_query = "UPDATE " . Util::backquote($this->table)
805
                . " SET " . Util::backquote($column) . " ="
806
                . " REPLACE("
807
                . Util::backquote($column) . ", '" . $find . "', '"
808
                . $replaceWith
809
                . "')"
810
                . " WHERE " . Util::backquote($column)
811
                . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we
812
            // change the collation of the 2nd operand to a case sensitive
813
            // binary collation to make sure that the comparison
814
            // is case sensitive
815
        }
816
        $this->dbi->query(
817
            $sql_query,
818
            DatabaseInterface::CONNECT_USER,
819
            DatabaseInterface::QUERY_STORE
820
        );
821
        $GLOBALS['sql_query'] = $sql_query;
822
    }
823
824
    /**
825
     * Finds minimum and maximum value of a given column.
826
     *
827
     * @param string $column Column name
828
     *
829
     * @return array
830
     */
831
    public function getColumnMinMax($column)
832
    {
833
        $sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, '
834
            . 'MAX(' . Util::backquote($column) . ') AS `max` '
835
            . 'FROM ' . Util::backquote($this->db) . '.'
836
            . Util::backquote($this->table);
837
838
        return $this->dbi->fetchSingleRow($sql_query);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->dbi->fetchSingleRow($sql_query) could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
839
    }
840
841
    /**
842
     * Returns an array with necessary configurations to create
843
     * sub-tabs in the table_select page.
844
     *
845
     * @return array Array containing configuration (icon, text, link, id, args)
846
     * of sub-tabs
847
     */
848
    private function _getSubTabs()
849
    {
850
        $subtabs = [];
851
        $subtabs['search']['icon'] = 'b_search';
852
        $subtabs['search']['text'] = __('Table search');
853
        $subtabs['search']['link'] = 'tbl_select.php';
854
        $subtabs['search']['id'] = 'tbl_search_id';
855
        $subtabs['search']['args']['pos'] = 0;
856
857
        $subtabs['zoom']['icon'] = 'b_select';
858
        $subtabs['zoom']['link'] = 'tbl_zoom_select.php';
859
        $subtabs['zoom']['text'] = __('Zoom search');
860
        $subtabs['zoom']['id'] = 'zoom_search_id';
861
862
        $subtabs['replace']['icon'] = 'b_find_replace';
863
        $subtabs['replace']['link'] = 'tbl_find_replace.php';
864
        $subtabs['replace']['text'] = __('Find and replace');
865
        $subtabs['replace']['id'] = 'find_replace_id';
866
867
        return $subtabs;
868
    }
869
870
    /**
871
     * Builds the sql search query from the post parameters
872
     *
873
     * @return string the generated SQL query
874
     */
875
    private function _buildSqlQuery()
876
    {
877
        $sql_query = 'SELECT ';
878
879
        // If only distinct values are needed
880
        $is_distinct = isset($_POST['distinct']) ? 'true' : 'false';
881
        if ($is_distinct == 'true') {
882
            $sql_query .= 'DISTINCT ';
883
        }
884
885
        // if all column names were selected to display, we do a 'SELECT *'
886
        // (more efficient and this helps prevent a problem in IE
887
        // if one of the rows is edited and we come back to the Select results)
888
        if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) {
889
            $sql_query .= '* ';
890
        } else {
891
            $sql_query .= implode(
892
                ', ',
893
                Util::backquote($_POST['columnsToDisplay'])
0 ignored issues
show
Bug introduced by
It seems like PhpMyAdmin\Util::backquo...ST['columnsToDisplay']) can also be of type string; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

893
                /** @scrutinizer ignore-type */ Util::backquote($_POST['columnsToDisplay'])
Loading history...
894
            );
895
        } // end if
896
897
        $sql_query .= ' FROM '
898
            . Util::backquote($_POST['table']);
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($_POST['table']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

898
            . /** @scrutinizer ignore-type */ Util::backquote($_POST['table']);
Loading history...
899
        $whereClause = $this->_generateWhereClause();
900
        $sql_query .= $whereClause;
901
902
        // if the search results are to be ordered
903
        if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] != '--nil--') {
904
            $sql_query .= ' ORDER BY '
905
                . Util::backquote($_POST['orderByColumn'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo..._POST['orderByColumn']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

905
                . /** @scrutinizer ignore-type */ Util::backquote($_POST['orderByColumn'])
Loading history...
906
                . ' ' . $_POST['order'];
907
        } // end if
908
        return $sql_query;
909
    }
910
911
    /**
912
     * Provides a column's type, collation, operators list, and criteria value
913
     * to display in table search form
914
     *
915
     * @param integer $search_index Row number in table search form
916
     * @param integer $column_index Column index in ColumnNames array
917
     *
918
     * @return array Array containing column's properties
919
     */
920
    public function getColumnProperties($search_index, $column_index)
921
    {
922
        $selected_operator = (isset($_POST['criteriaColumnOperators'][$search_index])
923
            ? $_POST['criteriaColumnOperators'][$search_index] : '');
924
        $entered_value = (isset($_POST['criteriaValues'])
925
            ? $_POST['criteriaValues'] : '');
926
        $titles = [
927
            'Browse' => Util::getIcon(
928
                'b_browse',
929
                __('Browse foreign values')
930
            ),
931
        ];
932
        //Gets column's type and collation
933
        $type = $this->_columnTypes[$column_index];
934
        $collation = $this->_columnCollations[$column_index];
935
        //Gets column's comparison operators depending on column type
936
        $typeOperators = $this->dbi->types->getTypeOperatorsHtml(
937
            preg_replace('@\(.*@s', '', $this->_columnTypes[$column_index]),
938
            $this->_columnNullFlags[$column_index],
939
            $selected_operator
940
        );
941
        $func = $this->template->render('table/search/column_comparison_operators', [
942
            'search_index' => $search_index,
943
            'type_operators' => $typeOperators,
944
        ]);
945
        //Gets link to browse foreign data(if any) and criteria inputbox
946
        $foreignData = $this->relation->getForeignData(
947
            $this->_foreigners,
948
            $this->_columnNames[$column_index],
949
            false,
950
            '',
951
            ''
952
        );
953
        $value = $this->template->render('table/search/input_box', [
954
            'str' => '',
955
            'column_type' => (string) $type,
956
            'column_id' => 'fieldID_',
957
            'in_zoom_search_edit' => false,
958
            'foreigners' => $this->_foreigners,
959
            'column_name' => $this->_columnNames[$column_index],
960
            'column_name_hash' => md5($this->_columnNames[$column_index]),
961
            'foreign_data' => $foreignData,
962
            'table' => $this->table,
963
            'column_index' => $search_index,
964
            'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
965
            'criteria_values' => $entered_value,
966
            'db' => $this->db,
967
            'titles' => $titles,
968
            'in_fbs' => true,
969
        ]);
970
        return [
971
            'type' => $type,
972
            'collation' => $collation,
973
            'func' => $func,
974
            'value' => $value
975
        ];
976
    }
977
978
    /**
979
     * Generates the where clause for the SQL search query to be executed
980
     *
981
     * @return string the generated where clause
982
     */
983
    private function _generateWhereClause()
984
    {
985
        if (isset($_POST['customWhereClause'])
986
            && trim($_POST['customWhereClause']) != ''
987
        ) {
988
            return ' WHERE ' . $_POST['customWhereClause'];
989
        }
990
991
        // If there are no search criteria set or no unary criteria operators,
992
        // return
993
        if (! isset($_POST['criteriaValues'])
994
            && ! isset($_POST['criteriaColumnOperators'])
995
            && ! isset($_POST['geom_func'])
996
        ) {
997
            return '';
998
        }
999
1000
        // else continue to form the where clause from column criteria values
1001
        $fullWhereClause = [];
1002
        foreach ($_POST['criteriaColumnOperators'] as $column_index => $operator) {
1003
            $unaryFlag =  $this->dbi->types->isUnaryOperator($operator);
1004
            $tmp_geom_func = isset($_POST['geom_func'][$column_index])
1005
                ? $_POST['geom_func'][$column_index] : null;
1006
1007
            $whereClause = $this->_getWhereClause(
1008
                $_POST['criteriaValues'][$column_index],
1009
                $_POST['criteriaColumnNames'][$column_index],
1010
                $_POST['criteriaColumnTypes'][$column_index],
1011
                $operator,
1012
                $unaryFlag,
1013
                $tmp_geom_func
1014
            );
1015
1016
            if ($whereClause) {
1017
                $fullWhereClause[] = $whereClause;
1018
            }
1019
        } // end foreach
1020
1021
        if (! empty($fullWhereClause)) {
1022
            return ' WHERE ' . implode(' AND ', $fullWhereClause);
1023
        }
1024
        return '';
1025
    }
1026
1027
    /**
1028
     * Return the where clause in case column's type is ENUM.
1029
     *
1030
     * @param mixed  $criteriaValues Search criteria input
1031
     * @param string $func_type      Search function/operator
1032
     *
1033
     * @return string part of where clause.
1034
     */
1035
    private function _getEnumWhereClause($criteriaValues, $func_type)
1036
    {
1037
        if (! is_array($criteriaValues)) {
1038
            $criteriaValues = explode(',', $criteriaValues);
1039
        }
1040
        $enum_selected_count = count($criteriaValues);
1041
        if ($func_type == '=' && $enum_selected_count > 1) {
1042
            $func_type    = 'IN';
1043
            $parens_open  = '(';
1044
            $parens_close = ')';
1045
        } elseif ($func_type == '!=' && $enum_selected_count > 1) {
1046
            $func_type    = 'NOT IN';
1047
            $parens_open  = '(';
1048
            $parens_close = ')';
1049
        } else {
1050
            $parens_open  = '';
1051
            $parens_close = '';
1052
        }
1053
        $enum_where = '\''
1054
            . $this->dbi->escapeString($criteriaValues[0]) . '\'';
1055
        for ($e = 1; $e < $enum_selected_count; $e++) {
1056
            $enum_where .= ', \''
1057
                . $this->dbi->escapeString($criteriaValues[$e]) . '\'';
1058
        }
1059
1060
        return ' ' . $func_type . ' ' . $parens_open
1061
        . $enum_where . $parens_close;
1062
    }
1063
1064
    /**
1065
     * Return the where clause for a geometrical column.
1066
     *
1067
     * @param mixed  $criteriaValues Search criteria input
1068
     * @param string $names          Name of the column on which search is submitted
1069
     * @param string $func_type      Search function/operator
1070
     * @param string $types          Type of the field
1071
     * @param bool   $geom_func      Whether geometry functions should be applied
1072
     *
1073
     * @return string part of where clause.
1074
     */
1075
    private function _getGeomWhereClause(
1076
        $criteriaValues,
1077
        $names,
1078
        $func_type,
1079
        $types,
1080
        $geom_func = null
1081
    ) {
1082
        $geom_unary_functions = [
1083
            'IsEmpty' => 1,
1084
            'IsSimple' => 1,
1085
            'IsRing' => 1,
1086
            'IsClosed' => 1,
1087
        ];
1088
        $where = '';
1089
1090
        // Get details about the geometry functions
1091
        $geom_funcs = Util::getGISFunctions($types, true, false);
1092
1093
        // If the function takes multiple parameters
1094
        if (strpos($func_type, "IS NULL") !== false || strpos($func_type, "IS NOT NULL") !== false) {
1095
            return Util::backquote($names) . " " . $func_type;
1096
        } elseif ($geom_funcs[$geom_func]['params'] > 1) {
1097
            // create gis data from the criteria input
1098
            $gis_data = Util::createGISData($criteriaValues);
1099
            return $geom_func . '(' . Util::backquote($names)
1100
                . ', ' . $gis_data . ')';
1101
        }
1102
1103
        // New output type is the output type of the function being applied
1104
        $type = $geom_funcs[$geom_func]['type'];
1105
        $geom_function_applied = $geom_func
1106
            . '(' . Util::backquote($names) . ')';
1107
1108
        // If the where clause is something like 'IsEmpty(`spatial_col_name`)'
1109
        if (isset($geom_unary_functions[$geom_func])
1110
            && trim($criteriaValues) == ''
1111
        ) {
1112
            $where = $geom_function_applied;
1113
        } elseif (in_array($type, Util::getGISDatatypes())
1114
            && ! empty($criteriaValues)
1115
        ) {
1116
            // create gis data from the criteria input
1117
            $gis_data = Util::createGISData($criteriaValues);
1118
            $where = $geom_function_applied . " " . $func_type . " " . $gis_data;
1119
        } elseif (strlen($criteriaValues) > 0) {
1120
            $where = $geom_function_applied . " "
1121
                . $func_type . " '" . $criteriaValues . "'";
1122
        }
1123
        return $where;
1124
    }
1125
1126
    /**
1127
     * Return the where clause for query generation based on the inputs provided.
1128
     *
1129
     * @param mixed  $criteriaValues Search criteria input
1130
     * @param string $names          Name of the column on which search is submitted
1131
     * @param string $types          Type of the field
1132
     * @param string $func_type      Search function/operator
1133
     * @param bool   $unaryFlag      Whether operator unary or not
1134
     * @param bool   $geom_func      Whether geometry functions should be applied
1135
     *
1136
     * @return string generated where clause.
1137
     */
1138
    private function _getWhereClause(
1139
        $criteriaValues,
1140
        $names,
1141
        $types,
1142
        $func_type,
1143
        $unaryFlag,
1144
        $geom_func = null
1145
    ) {
1146
        // If geometry function is set
1147
        if (! empty($geom_func)) {
1148
            return $this->_getGeomWhereClause(
1149
                $criteriaValues,
1150
                $names,
1151
                $func_type,
1152
                $types,
1153
                $geom_func
1154
            );
1155
        }
1156
1157
        $backquoted_name = Util::backquote($names);
1158
        $where = '';
1159
        if ($unaryFlag) {
1160
            $where = $backquoted_name . ' ' . $func_type;
1161
        } elseif (strncasecmp($types, 'enum', 4) == 0 && (! empty($criteriaValues) || $criteriaValues[0] === '0')) {
1162
            $where = $backquoted_name;
1163
            $where .= $this->_getEnumWhereClause($criteriaValues, $func_type);
1164
        } elseif ($criteriaValues != '') {
1165
            // For these types we quote the value. Even if it's another type
1166
            // (like INT), for a LIKE we always quote the value. MySQL converts
1167
            // strings to numbers and numbers to strings as necessary
1168
            // during the comparison
1169
            if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types)
1170
                || mb_strpos(' ' . $func_type, 'LIKE')
1171
            ) {
1172
                $quot = '\'';
1173
            } else {
1174
                $quot = '';
1175
            }
1176
1177
            // LIKE %...%
1178
            if ($func_type == 'LIKE %...%') {
1179
                $func_type = 'LIKE';
1180
                $criteriaValues = '%' . $criteriaValues . '%';
1181
            }
1182
            if ($func_type == 'REGEXP ^...$') {
1183
                $func_type = 'REGEXP';
1184
                $criteriaValues = '^' . $criteriaValues . '$';
1185
            }
1186
1187
            if ('IN (...)' != $func_type
1188
                && 'NOT IN (...)' != $func_type
1189
                && 'BETWEEN' != $func_type
1190
                && 'NOT BETWEEN' != $func_type
1191
            ) {
1192
                return $backquoted_name . ' ' . $func_type . ' ' . $quot
1193
                        . $this->dbi->escapeString($criteriaValues) . $quot;
1194
            }
1195
            $func_type = str_replace(' (...)', '', $func_type);
1196
1197
            //Don't explode if this is already an array
1198
            //(Case for (NOT) IN/BETWEEN.)
1199
            if (is_array($criteriaValues)) {
1200
                $values = $criteriaValues;
1201
            } else {
1202
                $values = explode(',', $criteriaValues);
1203
            }
1204
            // quote values one by one
1205
            $emptyKey = false;
1206
            foreach ($values as $key => &$value) {
1207
                if ('' === $value) {
1208
                    $emptyKey = $key;
1209
                    $value = 'NULL';
1210
                    continue;
1211
                }
1212
                $value = $quot . $this->dbi->escapeString(trim($value))
1213
                    . $quot;
1214
            }
1215
1216
            if ('BETWEEN' == $func_type || 'NOT BETWEEN' == $func_type) {
1217
                $where = $backquoted_name . ' ' . $func_type . ' '
1218
                    . (isset($values[0]) ? $values[0] : '')
1219
                    . ' AND ' . (isset($values[1]) ? $values[1] : '');
1220
            } else { //[NOT] IN
1221
                if (false !== $emptyKey) {
1222
                    unset($values[$emptyKey]);
1223
                }
1224
                $wheres = [];
1225
                if (! empty($values)) {
1226
                    $wheres[] = $backquoted_name . ' ' . $func_type
1227
                        . ' (' . implode(',', $values) . ')';
1228
                }
1229
                if (false !== $emptyKey) {
1230
                    $wheres[] = $backquoted_name . ' IS NULL';
1231
                }
1232
                $where = implode(' OR ', $wheres);
1233
                if (1 < count($wheres)) {
1234
                    $where = '(' . $where . ')';
1235
                }
1236
            }
1237
        } // end if
1238
1239
        return $where;
1240
    }
1241
}
1242