Merge branch 'QA_5_0'
[phpmyadmin.git] / libraries / classes / Controllers / Table / SearchController.php
blob61f81f2de696dcba4728c94c8095b3d0f7eb6254
1 <?php
2 declare(strict_types=1);
4 namespace PhpMyAdmin\Controllers\Table;
6 use PhpMyAdmin\Common;
7 use PhpMyAdmin\DatabaseInterface;
8 use PhpMyAdmin\Html\Generator;
9 use PhpMyAdmin\Relation;
10 use PhpMyAdmin\Response;
11 use PhpMyAdmin\Sql;
12 use PhpMyAdmin\Table\Search;
13 use PhpMyAdmin\Template;
14 use PhpMyAdmin\Util;
15 use function in_array;
16 use function intval;
17 use function mb_strtolower;
18 use function md5;
19 use function preg_match;
20 use function preg_replace;
21 use function str_ireplace;
22 use function str_replace;
23 use function strncasecmp;
25 /**
26 * Handles table search tab.
28 * Display table search form, create SQL query from form data
29 * and call Sql::executeQueryAndSendQueryResponse() to execute it.
31 class SearchController extends AbstractController
33 /**
34 * Names of columns
36 * @access private
37 * @var array
39 private $_columnNames;
40 /**
41 * Types of columns
43 * @access private
44 * @var array
46 private $_columnTypes;
47 /**
48 * Types of columns without any replacement
50 * @access private
51 * @var array
53 private $_originalColumnTypes;
54 /**
55 * Collations of columns
57 * @access private
58 * @var array
60 private $_columnCollations;
61 /**
62 * Null Flags of columns
64 * @access private
65 * @var array
67 private $_columnNullFlags;
68 /**
69 * Whether a geometry column is present
71 * @access private
72 * @var bool
74 private $_geomColumnFlag;
75 /**
76 * Foreign Keys
78 * @access private
79 * @var array
81 private $_foreigners;
83 /** @var Search */
84 private $search;
86 /** @var Relation */
87 private $relation;
89 /**
90 * @param Response $response Response object
91 * @param DatabaseInterface $dbi DatabaseInterface object
92 * @param Template $template Template object
93 * @param string $db Database name
94 * @param string $table Table name
95 * @param Search $search A Search instance.
96 * @param Relation $relation Relation instance
98 public function __construct(
99 $response,
100 $dbi,
101 Template $template,
102 $db,
103 $table,
104 Search $search,
105 Relation $relation
107 parent::__construct($response, $dbi, $template, $db, $table);
108 $this->search = $search;
109 $this->relation = $relation;
110 $this->_columnNames = [];
111 $this->_columnNullFlags = [];
112 $this->_columnTypes = [];
113 $this->_originalColumnTypes = [];
114 $this->_columnCollations = [];
115 $this->_geomColumnFlag = false;
116 $this->_foreigners = [];
117 $this->_loadTableInfo();
121 * Gets all the columns of a table along with their types, collations
122 * and whether null or not.
124 * @return void
126 private function _loadTableInfo()
128 // Gets the list and number of columns
129 $columns = $this->dbi->getColumns(
130 $this->db,
131 $this->table,
132 null,
133 true
135 // Get details about the geometry functions
136 $geom_types = Util::getGISDatatypes();
138 foreach ($columns as $row) {
139 // set column name
140 $this->_columnNames[] = $row['Field'];
142 $type = $row['Type'];
143 // before any replacement
144 $this->_originalColumnTypes[] = mb_strtolower($type);
145 // check whether table contains geometric columns
146 if (in_array($type, $geom_types)) {
147 $this->_geomColumnFlag = true;
149 // reformat mysql query output
150 if (strncasecmp($type, 'set', 3) == 0
151 || strncasecmp($type, 'enum', 4) == 0
153 $type = str_replace(',', ', ', $type);
154 } else {
155 // strip the "BINARY" attribute, except if we find "BINARY(" because
156 // this would be a BINARY or VARBINARY column type
157 if (! preg_match('@BINARY[\(]@i', $type)) {
158 $type = str_ireplace('BINARY', '', $type);
160 $type = str_ireplace('ZEROFILL', '', $type);
161 $type = str_ireplace('UNSIGNED', '', $type);
162 $type = mb_strtolower($type);
164 if (empty($type)) {
165 $type = '&nbsp;';
167 $this->_columnTypes[] = $type;
168 $this->_columnNullFlags[] = $row['Null'];
169 $this->_columnCollations[]
170 = ! empty($row['Collation']) && $row['Collation'] != 'NULL'
171 ? $row['Collation']
172 : '';
173 } // end for
175 // Retrieve foreign keys
176 $this->_foreigners = $this->relation->getForeigners($this->db, $this->table);
180 * Index action
182 public function index(): void
184 Common::table();
186 $header = $this->response->getHeader();
187 $scripts = $header->getScripts();
188 $scripts->addFiles([
189 'makegrid.js',
190 'sql.js',
191 'table/select.js',
192 'table/change.js',
193 'vendor/jquery/jquery.uitablefilter.js',
194 'gis_data_editor.js',
197 if (isset($_POST['range_search'])) {
198 $this->rangeSearchAction();
199 return;
203 * No selection criteria received -> display the selection form
205 if (! isset($_POST['columnsToDisplay'])
206 && ! isset($_POST['displayAllColumns'])
208 $this->displaySelectionFormAction();
209 } else {
210 $this->doSelectionAction();
215 * Get data row action
217 * @return void
219 public function getDataRowAction()
221 $extra_data = [];
222 $row_info_query = 'SELECT * FROM ' . Util::backquote($_POST['db']) . '.'
223 . Util::backquote($_POST['table']) . ' WHERE ' . $_POST['where_clause'];
224 $result = $this->dbi->query(
225 $row_info_query . ';',
226 DatabaseInterface::CONNECT_USER,
227 DatabaseInterface::QUERY_STORE
229 $fields_meta = $this->dbi->getFieldsMeta($result);
230 while ($row = $this->dbi->fetchAssoc($result)) {
231 // for bit fields we need to convert them to printable form
232 $i = 0;
233 foreach ($row as $col => $val) {
234 if ($fields_meta[$i]->type == 'bit') {
235 $row[$col] = Util::printableBitValue(
236 (int) $val,
237 (int) $fields_meta[$i]->length
240 $i++;
242 $extra_data['row_info'] = $row;
244 $this->response->addJSON($extra_data);
248 * Do selection action
250 * @return void
252 public function doSelectionAction()
255 * Selection criteria have been submitted -> do the work
257 $sql_query = $this->search->buildSqlQuery();
260 * Add this to ensure following procedures included running correctly.
262 $sql = new Sql();
263 $sql->executeQueryAndSendQueryResponse(
264 null, // analyzed_sql_results
265 false, // is_gotofile
266 $this->db, // db
267 $this->table, // table
268 null, // find_real_end
269 null, // sql_query_for_bookmark
270 null, // extra_data
271 null, // message_to_show
272 null, // message
273 null, // sql_data
274 $GLOBALS['goto'], // goto
275 $GLOBALS['pmaThemeImage'], // pmaThemeImage
276 null, // disp_query
277 null, // disp_message
278 null, // query_type
279 $sql_query, // sql_query
280 null, // selectedTables
281 null // complete_query
286 * Display selection form action
288 public function displaySelectionFormAction(): void
290 global $goto, $cfg;
292 if (! isset($goto)) {
293 $goto = Util::getScriptNameForOption(
294 $cfg['DefaultTabTable'],
295 'table'
299 $this->response->addHTML(
300 $this->template->render('table/search/index', [
301 'db' => $this->db,
302 'table' => $this->table,
303 'goto' => $goto,
304 'self' => $this,
305 'geom_column_flag' => $this->_geomColumnFlag,
306 'column_names' => $this->_columnNames,
307 'column_types' => $this->_columnTypes,
308 'column_collations' => $this->_columnCollations,
309 'default_sliders_state' => $cfg['InitialSlidersState'],
310 'max_rows' => intval($cfg['MaxRows']),
316 * Range search action
318 * @return void
320 public function rangeSearchAction()
322 $min_max = $this->getColumnMinMax($_POST['column']);
323 $this->response->addJSON('column_data', $min_max);
327 * Finds minimum and maximum value of a given column.
329 * @param string $column Column name
331 * @return array
333 public function getColumnMinMax($column)
335 $sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, '
336 . 'MAX(' . Util::backquote($column) . ') AS `max` '
337 . 'FROM ' . Util::backquote($this->db) . '.'
338 . Util::backquote($this->table);
340 return $this->dbi->fetchSingleRow($sql_query);
344 * Provides a column's type, collation, operators list, and criteria value
345 * to display in table search form
347 * @param int $search_index Row number in table search form
348 * @param int $column_index Column index in ColumnNames array
350 * @return array Array containing column's properties
352 public function getColumnProperties($search_index, $column_index)
354 $selected_operator = ($_POST['criteriaColumnOperators'][$search_index] ?? '');
355 $entered_value = ($_POST['criteriaValues'] ?? '');
356 $titles = [
357 'Browse' => Generator::getIcon(
358 'b_browse',
359 __('Browse foreign values')
362 //Gets column's type and collation
363 $type = $this->_columnTypes[$column_index];
364 $collation = $this->_columnCollations[$column_index];
365 $cleanType = preg_replace('@\(.*@s', '', $type);
366 //Gets column's comparison operators depending on column type
367 $typeOperators = $this->dbi->types->getTypeOperatorsHtml(
368 $cleanType,
369 $this->_columnNullFlags[$column_index],
370 $selected_operator
372 $func = $this->template->render('table/search/column_comparison_operators', [
373 'search_index' => $search_index,
374 'type_operators' => $typeOperators,
376 //Gets link to browse foreign data(if any) and criteria inputbox
377 $foreignData = $this->relation->getForeignData(
378 $this->_foreigners,
379 $this->_columnNames[$column_index],
380 false,
384 $htmlAttributes = '';
385 if (in_array($cleanType, $this->dbi->types->getIntegerTypes())) {
386 $extractedColumnspec = Util::extractColumnSpec(
387 $this->_originalColumnTypes[$column_index]
389 $is_unsigned = $extractedColumnspec['unsigned'];
390 $minMaxValues = $this->dbi->types->getIntegerRange(
391 $cleanType,
392 ! $is_unsigned
394 $htmlAttributes = 'min="' . $minMaxValues[0] . '" '
395 . 'max="' . $minMaxValues[1] . '"';
396 $type = 'INT';
399 $htmlAttributes .= " onchange= 'return verifyAfterSearchFieldChange(" . $column_index . ")'";
401 $value = $this->template->render('table/search/input_box', [
402 'str' => '',
403 'column_type' => (string) $type,
404 'html_attributes' => $htmlAttributes,
405 'column_id' => 'fieldID_',
406 'in_zoom_search_edit' => false,
407 'foreigners' => $this->_foreigners,
408 'column_name' => $this->_columnNames[$column_index],
409 'column_name_hash' => md5($this->_columnNames[$column_index]),
410 'foreign_data' => $foreignData,
411 'table' => $this->table,
412 'column_index' => $search_index,
413 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
414 'criteria_values' => $entered_value,
415 'db' => $this->db,
416 'titles' => $titles,
417 'in_fbs' => true,
419 return [
420 'type' => $type,
421 'collation' => $collation,
422 'func' => $func,
423 'value' => $value,