Total Complexity | 134 |
Total Lines | 1217 |
Duplicated Lines | 0 % |
Changes | 0 |
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 |
||
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( |
||
|
|||
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 = ' '; |
||
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); |
||
338 | $data = []; |
||
339 | while ($row = $this->dbi->fetchAssoc($result)) { |
||
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 |
||
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); |
||
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); |
||
449 | while ($row = $this->dbi->fetchAssoc($result)) { |
||
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; |
||
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 .= '&goto=tbl_select.php&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() |
||
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) . "," |
||
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) . "," |
||
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)) { |
||
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; |
||
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( |
||
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); |
||
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']) |
||
894 | ); |
||
895 | } // end if |
||
896 | |||
897 | $sql_query .= ' FROM ' |
||
898 | . Util::backquote($_POST['table']); |
||
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']) |
||
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( |
||
1240 | } |
||
1241 | } |
||
1242 |
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 theid
property of an instance of theAccount
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.