3 declare(strict_types
=1);
5 namespace PhpMyAdmin\Controllers\Table
;
7 use PhpMyAdmin\ConfigStorage\Relation
;
8 use PhpMyAdmin\ConfigStorage\RelationCleanup
;
9 use PhpMyAdmin\Controllers\AbstractController
;
11 use PhpMyAdmin\DatabaseInterface
;
12 use PhpMyAdmin\DbTableExists
;
13 use PhpMyAdmin\Operations
;
14 use PhpMyAdmin\ResponseRenderer
;
16 use PhpMyAdmin\Table\Search
;
17 use PhpMyAdmin\Template
;
18 use PhpMyAdmin\Transformations
;
21 use PhpMyAdmin\Utils\Gis
;
23 use function in_array
;
25 use function mb_strtolower
;
27 use function preg_match
;
28 use function preg_replace
;
29 use function str_ireplace
;
30 use function str_replace
;
31 use function strncasecmp
;
32 use function strtoupper
;
35 * Handles table search tab.
37 * Display table search form, create SQL query from form data
38 * and call Sql::executeQueryAndSendQueryResponse() to execute it.
40 class SearchController
extends AbstractController
55 * Types of columns without any replacement
59 private $originalColumnTypes;
61 * Collations of columns
65 private $columnCollations;
67 * Null Flags of columns
71 private $columnNullFlags;
73 * Whether a geometry column is present
77 private $geomColumnFlag;
91 /** @var DatabaseInterface */
94 public function __construct(
95 ResponseRenderer
$response,
99 DatabaseInterface
$dbi
101 parent
::__construct($response, $template);
102 $this->search
= $search;
103 $this->relation
= $relation;
106 $this->columnNames
= [];
107 $this->columnTypes
= [];
108 $this->originalColumnTypes
= [];
109 $this->columnCollations
= [];
110 $this->columnNullFlags
= [];
111 $this->geomColumnFlag
= false;
112 $this->foreigners
= [];
113 $this->loadTableInfo();
117 * Gets all the columns of a table along with their types, collations
118 * and whether null or not.
120 private function loadTableInfo(): void
122 // Gets the list and number of columns
123 $columns = $this->dbi
->getColumns($GLOBALS['db'], $GLOBALS['table'], true);
124 // Get details about the geometry functions
125 $geom_types = Gis
::getDataTypes();
127 foreach ($columns as $row) {
129 $this->columnNames
[] = $row['Field'];
131 $type = (string) $row['Type'];
132 // before any replacement
133 $this->originalColumnTypes
[] = mb_strtolower($type);
134 // check whether table contains geometric columns
135 if (in_array($type, $geom_types)) {
136 $this->geomColumnFlag
= true;
139 // reformat mysql query output
140 if (strncasecmp($type, 'set', 3) == 0 ||
strncasecmp($type, 'enum', 4) == 0) {
141 $type = str_replace(',', ', ', $type);
143 // strip the "BINARY" attribute, except if we find "BINARY(" because
144 // this would be a BINARY or VARBINARY column type
145 if (! preg_match('@BINARY[\(]@i', $type)) {
146 $type = str_ireplace('BINARY', '', $type);
149 $type = str_ireplace('ZEROFILL', '', $type);
150 $type = str_ireplace('UNSIGNED', '', $type);
151 $type = mb_strtolower($type);
158 $this->columnTypes
[] = $type;
159 $this->columnNullFlags
[] = $row['Null'];
160 $this->columnCollations
[] = ! empty($row['Collation']) && $row['Collation'] !== 'NULL'
165 // Retrieve foreign keys
166 $this->foreigners
= $this->relation
->getForeigners($GLOBALS['db'], $GLOBALS['table']);
172 public function __invoke(): void
174 Util
::checkParameters(['db', 'table']);
176 $GLOBALS['urlParams'] = ['db' => $GLOBALS['db'], 'table' => $GLOBALS['table']];
177 $GLOBALS['errorUrl'] = Util
::getScriptNameForOption($GLOBALS['cfg']['DefaultTabTable'], 'table');
178 $GLOBALS['errorUrl'] .= Url
::getCommon($GLOBALS['urlParams'], '&');
180 DbTableExists
::check($GLOBALS['db'], $GLOBALS['table']);
182 $this->addScriptFiles([
184 'vendor/stickyfill.min.js',
188 'vendor/jquery/jquery.uitablefilter.js',
189 'gis_data_editor.js',
192 if (isset($_POST['range_search'])) {
193 $this->rangeSearchAction();
199 * No selection criteria received -> display the selection form
201 if (! isset($_POST['columnsToDisplay']) && ! isset($_POST['displayAllColumns'])) {
202 $this->displaySelectionFormAction();
204 $this->doSelectionAction();
209 * Get data row action
211 public function getDataRowAction(): void
213 if (! Core
::checkSqlQuerySignature($_POST['where_clause'], $_POST['where_clause_sign'])) {
218 $row_info_query = 'SELECT * FROM ' . Util
::backquote($_POST['db']) . '.'
219 . Util
::backquote($_POST['table']) . ' WHERE ' . $_POST['where_clause'];
220 $result = $this->dbi
->query($row_info_query . ';');
221 $fields_meta = $this->dbi
->getFieldsMeta($result);
222 while ($row = $result->fetchAssoc()) {
223 // for bit fields we need to convert them to printable form
225 foreach ($row as $col => $val) {
226 if (isset($fields_meta[$i]) && $fields_meta[$i]->isMappedTypeBit
) {
227 $row[$col] = Util
::printableBitValue((int) $val, (int) $fields_meta[$i]->length
);
233 $extra_data['row_info'] = $row;
236 $this->response
->addJSON($extra_data);
240 * Do selection action
242 public function doSelectionAction(): void
245 * Selection criteria have been submitted -> do the work
247 $sql_query = $this->search
->buildSqlQuery();
250 * Add this to ensure following procedures included running correctly.
255 new RelationCleanup($this->dbi
, $this->relation
),
256 new Operations($this->dbi
, $this->relation
),
257 new Transformations(),
261 $this->response
->addHTML($sql->executeQueryAndSendQueryResponse(
262 null, // analyzed_sql_results
263 false, // is_gotofile
264 $GLOBALS['db'], // db
265 $GLOBALS['table'], // table
266 null, // find_real_end
267 null, // sql_query_for_bookmark
269 null, // message_to_show
271 $GLOBALS['goto'], // goto
273 null, // disp_message
274 $sql_query, // sql_query
275 null // complete_query
280 * Display selection form action
282 public function displaySelectionFormAction(): void
284 if (! isset($GLOBALS['goto'])) {
285 $GLOBALS['goto'] = Util
::getScriptNameForOption($GLOBALS['cfg']['DefaultTabTable'], 'table');
288 $this->render('table/search/index', [
289 'db' => $GLOBALS['db'],
290 'table' => $GLOBALS['table'],
291 'goto' => $GLOBALS['goto'],
293 'geom_column_flag' => $this->geomColumnFlag
,
294 'column_names' => $this->columnNames
,
295 'column_types' => $this->columnTypes
,
296 'column_collations' => $this->columnCollations
,
297 'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
298 'max_rows' => intval($GLOBALS['cfg']['MaxRows']),
303 * Range search action
305 public function rangeSearchAction(): void
307 $min_max = $this->getColumnMinMax($_POST['column']);
308 $this->response
->addJSON('column_data', $min_max);
312 * Finds minimum and maximum value of a given column.
314 * @param string $column Column name
318 public function getColumnMinMax($column): ?
array
320 $sql_query = 'SELECT MIN(' . Util
::backquote($column) . ') AS `min`, '
321 . 'MAX(' . Util
::backquote($column) . ') AS `max` '
322 . 'FROM ' . Util
::backquote($GLOBALS['db']) . '.'
323 . Util
::backquote($GLOBALS['table']);
325 return $this->dbi
->fetchSingleRow($sql_query);
329 * Provides a column's type, collation, operators list, and criteria value
330 * to display in table search form
332 * @param int $search_index Row number in table search form
333 * @param int $column_index Column index in ColumnNames array
335 * @return array Array containing column's properties
337 public function getColumnProperties($search_index, $column_index)
339 $selected_operator = ($_POST['criteriaColumnOperators'][$search_index] ??
'');
340 $entered_value = ($_POST['criteriaValues'] ??
'');
341 //Gets column's type and collation
342 $type = $this->columnTypes
[$column_index];
343 $collation = $this->columnCollations
[$column_index];
344 $cleanType = preg_replace('@\(.*@s', '', $type);
345 //Gets column's comparison operators depending on column type
346 $typeOperators = $this->dbi
->types
->getTypeOperatorsHtml(
348 $this->columnNullFlags
[$column_index],
351 $func = $this->template
->render('table/search/column_comparison_operators', [
352 'search_index' => $search_index,
353 'type_operators' => $typeOperators,
355 //Gets link to browse foreign data(if any) and criteria inputbox
356 $foreignData = $this->relation
->getForeignData(
358 $this->columnNames
[$column_index],
363 $htmlAttributes = '';
364 if (in_array($cleanType, $this->dbi
->types
->getIntegerTypes())) {
365 $extractedColumnspec = Util
::extractColumnSpec($this->originalColumnTypes
[$column_index]);
366 $is_unsigned = $extractedColumnspec['unsigned'];
367 $minMaxValues = $this->dbi
->types
->getIntegerRange($cleanType, ! $is_unsigned);
368 $htmlAttributes = 'data-min="' . $minMaxValues[0] . '" '
369 . 'data-max="' . $minMaxValues[1] . '"';
372 $htmlAttributes .= ' onfocus="return '
373 . 'verifyAfterSearchFieldChange(' . $search_index . ', \'#tbl_search_form\')"';
375 $value = $this->template
->render('table/search/input_box', [
377 'column_type' => (string) $type,
378 'column_data_type' => strtoupper($cleanType),
379 'html_attributes' => $htmlAttributes,
380 'column_id' => 'fieldID_',
381 'in_zoom_search_edit' => false,
382 'foreigners' => $this->foreigners
,
383 'column_name' => $this->columnNames
[$column_index],
384 'column_name_hash' => md5($this->columnNames
[$column_index]),
385 'foreign_data' => $foreignData,
386 'table' => $GLOBALS['table'],
387 'column_index' => $search_index,
388 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
389 'criteria_values' => $entered_value,
390 'db' => $GLOBALS['db'],
396 'collation' => $collation,