TableSearchController.php 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PhpMyAdmin\Controllers\Table\TableSearchController
  5. *
  6. * @package PhpMyAdmin\Controllers
  7. */
  8. namespace PhpMyAdmin\Controllers\Table;
  9. use PhpMyAdmin\Controllers\TableController;
  10. use PhpMyAdmin\DatabaseInterface;
  11. use PhpMyAdmin\Relation;
  12. use PhpMyAdmin\Sql;
  13. use PhpMyAdmin\Template;
  14. use PhpMyAdmin\Util;
  15. /**
  16. * Class TableSearchController
  17. *
  18. * @package PhpMyAdmin\Controllers
  19. */
  20. class TableSearchController extends TableController
  21. {
  22. /**
  23. * Normal search or Zoom search
  24. *
  25. * @access private
  26. * @var string
  27. */
  28. private $_searchType;
  29. /**
  30. * Names of columns
  31. *
  32. * @access private
  33. * @var array
  34. */
  35. private $_columnNames;
  36. /**
  37. * Types of columns
  38. *
  39. * @access private
  40. * @var array
  41. */
  42. private $_columnTypes;
  43. /**
  44. * Collations of columns
  45. *
  46. * @access private
  47. * @var array
  48. */
  49. private $_columnCollations;
  50. /**
  51. * Null Flags of columns
  52. *
  53. * @access private
  54. * @var array
  55. */
  56. private $_columnNullFlags;
  57. /**
  58. * Whether a geometry column is present
  59. *
  60. * @access private
  61. * @var boolean
  62. */
  63. private $_geomColumnFlag;
  64. /**
  65. * Foreign Keys
  66. *
  67. * @access private
  68. * @var array
  69. */
  70. private $_foreigners;
  71. /**
  72. * Connection charset
  73. *
  74. * @access private
  75. * @var string
  76. */
  77. private $_connectionCharSet;
  78. protected $url_query;
  79. /**
  80. * @var Relation $relation
  81. */
  82. private $relation;
  83. /**
  84. * Constructor
  85. *
  86. * @param string $searchType Search type
  87. * @param string $url_query URL query
  88. */
  89. public function __construct(
  90. $response,
  91. $dbi,
  92. $db,
  93. $table,
  94. $searchType,
  95. $url_query
  96. ) {
  97. parent::__construct($response, $dbi, $db, $table);
  98. $this->url_query = $url_query;
  99. $this->_searchType = $searchType;
  100. $this->_columnNames = array();
  101. $this->_columnNullFlags = array();
  102. $this->_columnTypes = array();
  103. $this->_columnCollations = array();
  104. $this->_geomColumnFlag = false;
  105. $this->_foreigners = array();
  106. $this->relation = new Relation();
  107. // Loads table's information
  108. $this->_loadTableInfo();
  109. $this->_connectionCharSet = $this->dbi->fetchValue(
  110. "SELECT @@character_set_connection"
  111. );
  112. }
  113. /**
  114. * Gets all the columns of a table along with their types, collations
  115. * and whether null or not.
  116. *
  117. * @return void
  118. */
  119. private function _loadTableInfo()
  120. {
  121. // Gets the list and number of columns
  122. $columns = $this->dbi->getColumns(
  123. $this->db, $this->table, null, true
  124. );
  125. // Get details about the geometry functions
  126. $geom_types = Util::getGISDatatypes();
  127. foreach ($columns as $row) {
  128. // set column name
  129. $this->_columnNames[] = $row['Field'];
  130. $type = $row['Type'];
  131. // check whether table contains geometric columns
  132. if (in_array($type, $geom_types)) {
  133. $this->_geomColumnFlag = true;
  134. }
  135. // reformat mysql query output
  136. if (strncasecmp($type, 'set', 3) == 0
  137. || strncasecmp($type, 'enum', 4) == 0
  138. ) {
  139. $type = str_replace(',', ', ', $type);
  140. } else {
  141. // strip the "BINARY" attribute, except if we find "BINARY(" because
  142. // this would be a BINARY or VARBINARY column type
  143. if (! preg_match('@BINARY[\(]@i', $type)) {
  144. $type = preg_replace('@BINARY@i', '', $type);
  145. }
  146. $type = preg_replace('@ZEROFILL@i', '', $type);
  147. $type = preg_replace('@UNSIGNED@i', '', $type);
  148. $type = mb_strtolower($type);
  149. }
  150. if (empty($type)) {
  151. $type = '&nbsp;';
  152. }
  153. $this->_columnTypes[] = $type;
  154. $this->_columnNullFlags[] = $row['Null'];
  155. $this->_columnCollations[]
  156. = ! empty($row['Collation']) && $row['Collation'] != 'NULL'
  157. ? $row['Collation']
  158. : '';
  159. } // end for
  160. // Retrieve foreign keys
  161. $this->_foreigners = $this->relation->getForeigners($this->db, $this->table);
  162. }
  163. /**
  164. * Index action
  165. *
  166. * @return void
  167. */
  168. public function indexAction()
  169. {
  170. switch ($this->_searchType) {
  171. case 'replace':
  172. if (isset($_POST['find'])) {
  173. $this->findAction();
  174. return;
  175. }
  176. $this->response
  177. ->getHeader()
  178. ->getScripts()
  179. ->addFile('tbl_find_replace.js');
  180. if (isset($_POST['replace'])) {
  181. $this->replaceAction();
  182. }
  183. // Displays the find and replace form
  184. $this->displaySelectionFormAction();
  185. break;
  186. case 'normal':
  187. $this->response->getHeader()
  188. ->getScripts()
  189. ->addFiles(
  190. array(
  191. 'makegrid.js',
  192. 'sql.js',
  193. 'tbl_select.js',
  194. 'tbl_change.js',
  195. 'vendor/jquery/jquery.uitablefilter.js',
  196. 'gis_data_editor.js',
  197. )
  198. );
  199. if (isset($_POST['range_search'])) {
  200. $this->rangeSearchAction();
  201. return;
  202. }
  203. /**
  204. * No selection criteria received -> display the selection form
  205. */
  206. if (!isset($_POST['columnsToDisplay'])
  207. && !isset($_POST['displayAllColumns'])
  208. ) {
  209. $this->displaySelectionFormAction();
  210. } else {
  211. $this->doSelectionAction();
  212. }
  213. break;
  214. case 'zoom':
  215. $this->response->getHeader()
  216. ->getScripts()
  217. ->addFiles(
  218. array(
  219. 'makegrid.js',
  220. 'sql.js',
  221. 'vendor/jqplot/jquery.jqplot.js',
  222. 'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
  223. 'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
  224. 'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
  225. 'vendor/jqplot/plugins/jqplot.highlighter.js',
  226. 'vendor/jqplot/plugins/jqplot.cursor.js',
  227. 'tbl_zoom_plot_jqplot.js',
  228. 'tbl_change.js',
  229. )
  230. );
  231. /**
  232. * Handle AJAX request for data row on point select
  233. *
  234. * @var boolean Object containing parameters for the POST request
  235. */
  236. if (isset($_POST['get_data_row'])
  237. && $_POST['get_data_row'] == true
  238. ) {
  239. $this->getDataRowAction();
  240. return;
  241. }
  242. /**
  243. * Handle AJAX request for changing field information
  244. * (value,collation,operators,field values) in input form
  245. *
  246. * @var boolean Object containing parameters for the POST request
  247. */
  248. if (isset($_POST['change_tbl_info'])
  249. && $_POST['change_tbl_info'] == true
  250. ) {
  251. $this->changeTableInfoAction();
  252. return;
  253. }
  254. //Set default datalabel if not selected
  255. if (!isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') {
  256. $dataLabel = $this->relation->getDisplayField($this->db, $this->table);
  257. } else {
  258. $dataLabel = $_POST['dataLabel'];
  259. }
  260. // Displays the zoom search form
  261. $this->displaySelectionFormAction($dataLabel);
  262. /*
  263. * Handle the input criteria and generate the query result
  264. * Form for displaying query results
  265. */
  266. if (isset($_POST['zoom_submit'])
  267. && $_POST['criteriaColumnNames'][0] != 'pma_null'
  268. && $_POST['criteriaColumnNames'][1] != 'pma_null'
  269. && $_POST['criteriaColumnNames'][0] != $_POST['criteriaColumnNames'][1]
  270. ) {
  271. if (! isset($goto)) {
  272. $goto = Util::getScriptNameForOption(
  273. $GLOBALS['cfg']['DefaultTabTable'], 'table'
  274. );
  275. }
  276. $this->zoomSubmitAction($dataLabel, $goto);
  277. }
  278. break;
  279. }
  280. }
  281. /**
  282. * Zoom submit action
  283. *
  284. * @param string $dataLabel Data label
  285. * @param string $goto Goto
  286. *
  287. * @return void
  288. */
  289. public function zoomSubmitAction($dataLabel, $goto)
  290. {
  291. //Query generation part
  292. $sql_query = $this->_buildSqlQuery();
  293. $sql_query .= ' LIMIT ' . $_POST['maxPlotLimit'];
  294. //Query execution part
  295. $result = $this->dbi->query(
  296. $sql_query . ";",
  297. DatabaseInterface::CONNECT_USER,
  298. DatabaseInterface::QUERY_STORE
  299. );
  300. $fields_meta = $this->dbi->getFieldsMeta($result);
  301. $data = array();
  302. while ($row = $this->dbi->fetchAssoc($result)) {
  303. //Need a row with indexes as 0,1,2 for the getUniqueCondition
  304. // hence using a temporary array
  305. $tmpRow = array();
  306. foreach ($row as $val) {
  307. $tmpRow[] = $val;
  308. }
  309. //Get unique condition on each row (will be needed for row update)
  310. $uniqueCondition = Util::getUniqueCondition(
  311. $result, // handle
  312. count($this->_columnNames), // fields_cnt
  313. $fields_meta, // fields_meta
  314. $tmpRow, // row
  315. true, // force_unique
  316. false, // restrict_to_table
  317. null // analyzed_sql_results
  318. );
  319. //Append it to row array as where_clause
  320. $row['where_clause'] = $uniqueCondition[0];
  321. $tmpData = array(
  322. $_POST['criteriaColumnNames'][0] =>
  323. $row[$_POST['criteriaColumnNames'][0]],
  324. $_POST['criteriaColumnNames'][1] =>
  325. $row[$_POST['criteriaColumnNames'][1]],
  326. 'where_clause' => $uniqueCondition[0]
  327. );
  328. $tmpData[$dataLabel] = ($dataLabel) ? $row[$dataLabel] : '';
  329. $data[] = $tmpData;
  330. }
  331. unset($tmpData);
  332. //Displays form for point data and scatter plot
  333. $titles = array(
  334. 'Browse' => Util::getIcon(
  335. 'b_browse',
  336. __('Browse foreign values')
  337. )
  338. );
  339. $this->response->addHTML(
  340. Template::get('table/search/zoom_result_form')->render([
  341. 'db' => $this->db,
  342. 'table' => $this->table,
  343. 'column_names' => $this->_columnNames,
  344. 'foreigners' => $this->_foreigners,
  345. 'column_null_flags' => $this->_columnNullFlags,
  346. 'column_types' => $this->_columnTypes,
  347. 'titles' => $titles,
  348. 'goto' => $goto,
  349. 'data' => $data,
  350. 'data_json' => json_encode($data),
  351. 'zoom_submit' => isset($_POST['zoom_submit']),
  352. 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
  353. ])
  354. );
  355. }
  356. /**
  357. * Change table info action
  358. *
  359. * @return void
  360. */
  361. public function changeTableInfoAction()
  362. {
  363. $field = $_POST['field'];
  364. if ($field == 'pma_null') {
  365. $this->response->addJSON('field_type', '');
  366. $this->response->addJSON('field_collation', '');
  367. $this->response->addJSON('field_operators', '');
  368. $this->response->addJSON('field_value', '');
  369. return;
  370. }
  371. $key = array_search($field, $this->_columnNames);
  372. $search_index
  373. = ((isset($_POST['it']) && is_numeric($_POST['it']))
  374. ? intval($_POST['it']) : 0);
  375. $properties = $this->getColumnProperties($search_index, $key);
  376. $this->response->addJSON(
  377. 'field_type', htmlspecialchars($properties['type'])
  378. );
  379. $this->response->addJSON('field_collation', $properties['collation']);
  380. $this->response->addJSON('field_operators', $properties['func']);
  381. $this->response->addJSON('field_value', $properties['value']);
  382. }
  383. /**
  384. * Get data row action
  385. *
  386. * @return void
  387. */
  388. public function getDataRowAction()
  389. {
  390. $extra_data = array();
  391. $row_info_query = 'SELECT * FROM `' . $_POST['db'] . '`.`'
  392. . $_POST['table'] . '` WHERE ' . $_POST['where_clause'];
  393. $result = $this->dbi->query(
  394. $row_info_query . ";",
  395. DatabaseInterface::CONNECT_USER,
  396. DatabaseInterface::QUERY_STORE
  397. );
  398. $fields_meta = $this->dbi->getFieldsMeta($result);
  399. while ($row = $this->dbi->fetchAssoc($result)) {
  400. // for bit fields we need to convert them to printable form
  401. $i = 0;
  402. foreach ($row as $col => $val) {
  403. if ($fields_meta[$i]->type == 'bit') {
  404. $row[$col] = Util::printableBitValue(
  405. $val, $fields_meta[$i]->length
  406. );
  407. }
  408. $i++;
  409. }
  410. $extra_data['row_info'] = $row;
  411. }
  412. $this->response->addJSON($extra_data);
  413. }
  414. /**
  415. * Do selection action
  416. *
  417. * @return void
  418. */
  419. public function doSelectionAction()
  420. {
  421. /**
  422. * Selection criteria have been submitted -> do the work
  423. */
  424. $sql_query = $this->_buildSqlQuery();
  425. /**
  426. * Add this to ensure following procedures included running correctly.
  427. */
  428. $db = $this->db;
  429. $sql = new Sql();
  430. $sql->executeQueryAndSendQueryResponse(
  431. null, // analyzed_sql_results
  432. false, // is_gotofile
  433. $this->db, // db
  434. $this->table, // table
  435. null, // find_real_end
  436. null, // sql_query_for_bookmark
  437. null, // extra_data
  438. null, // message_to_show
  439. null, // message
  440. null, // sql_data
  441. $GLOBALS['goto'], // goto
  442. $GLOBALS['pmaThemeImage'], // pmaThemeImage
  443. null, // disp_query
  444. null, // disp_message
  445. null, // query_type
  446. $sql_query, // sql_query
  447. null, // selectedTables
  448. null // complete_query
  449. );
  450. }
  451. /**
  452. * Display selection form action
  453. *
  454. * @param string $dataLabel Data label
  455. *
  456. * @return void
  457. */
  458. public function displaySelectionFormAction($dataLabel = null)
  459. {
  460. $this->url_query .= '&amp;goto=tbl_select.php&amp;back=tbl_select.php';
  461. if (! isset($goto)) {
  462. $goto = Util::getScriptNameForOption(
  463. $GLOBALS['cfg']['DefaultTabTable'], 'table'
  464. );
  465. }
  466. // Displays the table search form
  467. $this->response->addHTML(
  468. Template::get('secondary_tabs')
  469. ->render(
  470. array(
  471. 'url_params' => array(
  472. 'db' => $this->db,
  473. 'table' => $this->table,
  474. ),
  475. 'sub_tabs' => $this->_getSubTabs(),
  476. )
  477. )
  478. );
  479. $this->response->addHTML(
  480. Template::get('table/search/selection_form')->render(array(
  481. 'search_type' => $this->_searchType,
  482. 'db' => $this->db,
  483. 'table' => $this->table,
  484. 'goto' => $goto,
  485. 'self' => $this,
  486. 'geom_column_flag' => $this->_geomColumnFlag,
  487. 'column_names' => $this->_columnNames,
  488. 'column_types' => $this->_columnTypes,
  489. 'column_collations' => $this->_columnCollations,
  490. 'data_label' => $dataLabel,
  491. 'criteria_column_names' => isset($_POST['criteriaColumnNames']) ? $_POST['criteriaColumnNames'] : null,
  492. 'criteria_column_types' => isset($_POST['criteriaColumnTypes']) ? $_POST['criteriaColumnTypes'] : null,
  493. 'sql_types' => $GLOBALS['dbi']->types,
  494. 'max_rows' => intval($GLOBALS['cfg']['MaxRows']),
  495. 'max_plot_limit' => ((! empty($_POST['maxPlotLimit']))
  496. ? intval($_POST['maxPlotLimit'])
  497. : intval($GLOBALS['cfg']['maxRowPlotLimit'])),
  498. ))
  499. );
  500. }
  501. /**
  502. * Range search action
  503. *
  504. * @return void
  505. */
  506. public function rangeSearchAction()
  507. {
  508. $min_max = $this->getColumnMinMax($_POST['column']);
  509. $this->response->addJSON('column_data', $min_max);
  510. }
  511. /**
  512. * Find action
  513. *
  514. * @return void
  515. */
  516. public function findAction()
  517. {
  518. $useRegex = array_key_exists('useRegex', $_POST)
  519. && $_POST['useRegex'] == 'on';
  520. $preview = $this->getReplacePreview(
  521. $_POST['columnIndex'],
  522. $_POST['find'],
  523. $_POST['replaceWith'],
  524. $useRegex,
  525. $this->_connectionCharSet
  526. );
  527. $this->response->addJSON('preview', $preview);
  528. }
  529. /**
  530. * Replace action
  531. *
  532. * @return void
  533. */
  534. public function replaceAction()
  535. {
  536. $this->replace(
  537. $_POST['columnIndex'],
  538. $_POST['findString'],
  539. $_POST['replaceWith'],
  540. $_POST['useRegex'],
  541. $this->_connectionCharSet
  542. );
  543. $this->response->addHTML(
  544. Util::getMessage(
  545. __('Your SQL query has been executed successfully.'),
  546. null, 'success'
  547. )
  548. );
  549. }
  550. /**
  551. * Returns HTML for previewing strings found and their replacements
  552. *
  553. * @param int $columnIndex index of the column
  554. * @param string $find string to find in the column
  555. * @param string $replaceWith string to replace with
  556. * @param boolean $useRegex to use Regex replace or not
  557. * @param string $charSet character set of the connection
  558. *
  559. * @return string HTML for previewing strings found and their replacements
  560. */
  561. function getReplacePreview(
  562. $columnIndex, $find, $replaceWith, $useRegex, $charSet
  563. ) {
  564. $column = $this->_columnNames[$columnIndex];
  565. if ($useRegex) {
  566. $result = $this->_getRegexReplaceRows(
  567. $columnIndex, $find, $replaceWith, $charSet
  568. );
  569. } else {
  570. $sql_query = "SELECT "
  571. . Util::backquote($column) . ","
  572. . " REPLACE("
  573. . Util::backquote($column) . ", '" . $find . "', '"
  574. . $replaceWith
  575. . "'),"
  576. . " COUNT(*)"
  577. . " FROM " . Util::backquote($this->db)
  578. . "." . Util::backquote($this->table)
  579. . " WHERE " . Util::backquote($column)
  580. . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we
  581. // change the collation of the 2nd operand to a case sensitive
  582. // binary collation to make sure that the comparison
  583. // is case sensitive
  584. $sql_query .= " GROUP BY " . Util::backquote($column)
  585. . " ORDER BY " . Util::backquote($column) . " ASC";
  586. $result = $this->dbi->fetchResult($sql_query, 0);
  587. }
  588. return Template::get('table/search/replace_preview')->render(
  589. array(
  590. 'db' => $this->db,
  591. 'table' => $this->table,
  592. 'column_index' => $columnIndex,
  593. 'find' => $find,
  594. 'replace_with' => $replaceWith,
  595. 'use_regex' => $useRegex,
  596. 'result' => $result
  597. )
  598. );
  599. }
  600. /**
  601. * Finds and returns Regex pattern and their replacements
  602. *
  603. * @param int $columnIndex index of the column
  604. * @param string $find string to find in the column
  605. * @param string $replaceWith string to replace with
  606. * @param string $charSet character set of the connection
  607. *
  608. * @return array Array containing original values, replaced values and count
  609. */
  610. private function _getRegexReplaceRows(
  611. $columnIndex, $find, $replaceWith, $charSet
  612. ) {
  613. $column = $this->_columnNames[$columnIndex];
  614. $sql_query = "SELECT "
  615. . Util::backquote($column) . ","
  616. . " 1," // to add an extra column that will have replaced value
  617. . " COUNT(*)"
  618. . " FROM " . Util::backquote($this->db)
  619. . "." . Util::backquote($this->table)
  620. . " WHERE " . Util::backquote($column)
  621. . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE "
  622. . $charSet . "_bin"; // here we
  623. // change the collation of the 2nd operand to a case sensitive
  624. // binary collation to make sure that the comparison is case sensitive
  625. $sql_query .= " GROUP BY " . Util::backquote($column)
  626. . " ORDER BY " . Util::backquote($column) . " ASC";
  627. $result = $this->dbi->fetchResult($sql_query, 0);
  628. if (is_array($result)) {
  629. /* Iterate over possible delimiters to get one */
  630. $delimiters = array('/', '@', '#', '~', '!', '$', '%', '^', '&', '_');
  631. $found = false;
  632. for ($i = 0, $l = count($delimiters); $i < $l; $i++) {
  633. if (strpos($find, $delimiters[$i]) === false) {
  634. $found = true;
  635. break;
  636. }
  637. }
  638. if (! $found) {
  639. return false;
  640. }
  641. $find = $delimiters[$i] . $find . $delimiters[$i];
  642. foreach ($result as $index=>$row) {
  643. $result[$index][1] = preg_replace(
  644. $find,
  645. $replaceWith,
  646. $row[0]
  647. );
  648. }
  649. }
  650. return $result;
  651. }
  652. /**
  653. * Replaces a given string in a column with a give replacement
  654. *
  655. * @param int $columnIndex index of the column
  656. * @param string $find string to find in the column
  657. * @param string $replaceWith string to replace with
  658. * @param boolean $useRegex to use Regex replace or not
  659. * @param string $charSet character set of the connection
  660. *
  661. * @return void
  662. */
  663. public function replace($columnIndex, $find, $replaceWith, $useRegex,
  664. $charSet
  665. ) {
  666. $column = $this->_columnNames[$columnIndex];
  667. if ($useRegex) {
  668. $toReplace = $this->_getRegexReplaceRows(
  669. $columnIndex, $find, $replaceWith, $charSet
  670. );
  671. $sql_query = "UPDATE " . Util::backquote($this->table)
  672. . " SET " . Util::backquote($column) . " = CASE";
  673. if (is_array($toReplace)) {
  674. foreach ($toReplace as $row) {
  675. $sql_query .= "\n WHEN " . Util::backquote($column)
  676. . " = '" . $GLOBALS['dbi']->escapeString($row[0])
  677. . "' THEN '" . $GLOBALS['dbi']->escapeString($row[1]) . "'";
  678. }
  679. }
  680. $sql_query .= " END"
  681. . " WHERE " . Util::backquote($column)
  682. . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE "
  683. . $charSet . "_bin"; // here we
  684. // change the collation of the 2nd operand to a case sensitive
  685. // binary collation to make sure that the comparison
  686. // is case sensitive
  687. } else {
  688. $sql_query = "UPDATE " . Util::backquote($this->table)
  689. . " SET " . Util::backquote($column) . " ="
  690. . " REPLACE("
  691. . Util::backquote($column) . ", '" . $find . "', '"
  692. . $replaceWith
  693. . "')"
  694. . " WHERE " . Util::backquote($column)
  695. . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we
  696. // change the collation of the 2nd operand to a case sensitive
  697. // binary collation to make sure that the comparison
  698. // is case sensitive
  699. }
  700. $this->dbi->query(
  701. $sql_query,
  702. DatabaseInterface::CONNECT_USER,
  703. DatabaseInterface::QUERY_STORE
  704. );
  705. $GLOBALS['sql_query'] = $sql_query;
  706. }
  707. /**
  708. * Finds minimum and maximum value of a given column.
  709. *
  710. * @param string $column Column name
  711. *
  712. * @return array
  713. */
  714. public function getColumnMinMax($column)
  715. {
  716. $sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, '
  717. . 'MAX(' . Util::backquote($column) . ') AS `max` '
  718. . 'FROM ' . Util::backquote($this->db) . '.'
  719. . Util::backquote($this->table);
  720. $result = $this->dbi->fetchSingleRow($sql_query);
  721. return $result;
  722. }
  723. /**
  724. * Returns an array with necessary configurations to create
  725. * sub-tabs in the table_select page.
  726. *
  727. * @return array Array containing configuration (icon, text, link, id, args)
  728. * of sub-tabs
  729. */
  730. private function _getSubTabs()
  731. {
  732. $subtabs = array();
  733. $subtabs['search']['icon'] = 'b_search';
  734. $subtabs['search']['text'] = __('Table search');
  735. $subtabs['search']['link'] = 'tbl_select.php';
  736. $subtabs['search']['id'] = 'tbl_search_id';
  737. $subtabs['search']['args']['pos'] = 0;
  738. $subtabs['zoom']['icon'] = 'b_select';
  739. $subtabs['zoom']['link'] = 'tbl_zoom_select.php';
  740. $subtabs['zoom']['text'] = __('Zoom search');
  741. $subtabs['zoom']['id'] = 'zoom_search_id';
  742. $subtabs['replace']['icon'] = 'b_find_replace';
  743. $subtabs['replace']['link'] = 'tbl_find_replace.php';
  744. $subtabs['replace']['text'] = __('Find and replace');
  745. $subtabs['replace']['id'] = 'find_replace_id';
  746. return $subtabs;
  747. }
  748. /**
  749. * Builds the sql search query from the post parameters
  750. *
  751. * @return string the generated SQL query
  752. */
  753. private function _buildSqlQuery()
  754. {
  755. $sql_query = 'SELECT ';
  756. // If only distinct values are needed
  757. $is_distinct = (isset($_POST['distinct'])) ? 'true' : 'false';
  758. if ($is_distinct == 'true') {
  759. $sql_query .= 'DISTINCT ';
  760. }
  761. // if all column names were selected to display, we do a 'SELECT *'
  762. // (more efficient and this helps prevent a problem in IE
  763. // if one of the rows is edited and we come back to the Select results)
  764. if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) {
  765. $sql_query .= '* ';
  766. } else {
  767. $sql_query .= implode(
  768. ', ',
  769. Util::backquote($_POST['columnsToDisplay'])
  770. );
  771. } // end if
  772. $sql_query .= ' FROM '
  773. . Util::backquote($_POST['table']);
  774. $whereClause = $this->_generateWhereClause();
  775. $sql_query .= $whereClause;
  776. // if the search results are to be ordered
  777. if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] != '--nil--') {
  778. $sql_query .= ' ORDER BY '
  779. . Util::backquote($_POST['orderByColumn'])
  780. . ' ' . $_POST['order'];
  781. } // end if
  782. return $sql_query;
  783. }
  784. /**
  785. * Provides a column's type, collation, operators list, and criteria value
  786. * to display in table search form
  787. *
  788. * @param integer $search_index Row number in table search form
  789. * @param integer $column_index Column index in ColumnNames array
  790. *
  791. * @return array Array containing column's properties
  792. */
  793. public function getColumnProperties($search_index, $column_index)
  794. {
  795. $selected_operator = (isset($_POST['criteriaColumnOperators'][$search_index])
  796. ? $_POST['criteriaColumnOperators'][$search_index] : '');
  797. $entered_value = (isset($_POST['criteriaValues'])
  798. ? $_POST['criteriaValues'] : '');
  799. $titles = array(
  800. 'Browse' => Util::getIcon(
  801. 'b_browse', __('Browse foreign values')
  802. )
  803. );
  804. //Gets column's type and collation
  805. $type = $this->_columnTypes[$column_index];
  806. $collation = $this->_columnCollations[$column_index];
  807. //Gets column's comparison operators depending on column type
  808. $typeOperators = $GLOBALS['dbi']->types->getTypeOperatorsHtml(
  809. preg_replace('@\(.*@s', '', $this->_columnTypes[$column_index]),
  810. $this->_columnNullFlags[$column_index], $selected_operator
  811. );
  812. $func = Template::get('table/search/column_comparison_operators')->render(
  813. array(
  814. 'search_index' => $search_index,
  815. 'type_operators' => $typeOperators
  816. )
  817. );
  818. //Gets link to browse foreign data(if any) and criteria inputbox
  819. $foreignData = $this->relation->getForeignData(
  820. $this->_foreigners, $this->_columnNames[$column_index], false, '', ''
  821. );
  822. $value = Template::get('table/search/input_box')->render(
  823. array(
  824. 'str' => '',
  825. 'column_type' => (string) $type,
  826. 'column_id' => 'fieldID_',
  827. 'in_zoom_search_edit' => false,
  828. 'foreigners' => $this->_foreigners,
  829. 'column_name' => $this->_columnNames[$column_index],
  830. 'column_name_hash' => md5($this->_columnNames[$column_index]),
  831. 'foreign_data' => $foreignData,
  832. 'table' => $this->table,
  833. 'column_index' => $search_index,
  834. 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
  835. 'criteria_values' => $entered_value,
  836. 'db' => $this->db,
  837. 'titles' => $titles,
  838. 'in_fbs' => true
  839. )
  840. );
  841. return array(
  842. 'type' => $type,
  843. 'collation' => $collation,
  844. 'func' => $func,
  845. 'value' => $value
  846. );
  847. }
  848. /**
  849. * Generates the where clause for the SQL search query to be executed
  850. *
  851. * @return string the generated where clause
  852. */
  853. private function _generateWhereClause()
  854. {
  855. if (isset($_POST['customWhereClause'])
  856. && trim($_POST['customWhereClause']) != ''
  857. ) {
  858. return ' WHERE ' . $_POST['customWhereClause'];
  859. }
  860. // If there are no search criteria set or no unary criteria operators,
  861. // return
  862. if (! isset($_POST['criteriaValues'])
  863. && ! isset($_POST['criteriaColumnOperators'])
  864. && ! isset($_POST['geom_func'])
  865. ) {
  866. return '';
  867. }
  868. // else continue to form the where clause from column criteria values
  869. $fullWhereClause = array();
  870. foreach ($_POST['criteriaColumnOperators'] as $column_index => $operator) {
  871. $unaryFlag = $GLOBALS['dbi']->types->isUnaryOperator($operator);
  872. $tmp_geom_func = isset($_POST['geom_func'][$column_index])
  873. ? $_POST['geom_func'][$column_index] : null;
  874. $whereClause = $this->_getWhereClause(
  875. $_POST['criteriaValues'][$column_index],
  876. $_POST['criteriaColumnNames'][$column_index],
  877. $_POST['criteriaColumnTypes'][$column_index],
  878. $operator,
  879. $unaryFlag,
  880. $tmp_geom_func
  881. );
  882. if ($whereClause) {
  883. $fullWhereClause[] = $whereClause;
  884. }
  885. } // end foreach
  886. if (!empty($fullWhereClause)) {
  887. return ' WHERE ' . implode(' AND ', $fullWhereClause);
  888. }
  889. return '';
  890. }
  891. /**
  892. * Return the where clause in case column's type is ENUM.
  893. *
  894. * @param mixed $criteriaValues Search criteria input
  895. * @param string $func_type Search function/operator
  896. *
  897. * @return string part of where clause.
  898. */
  899. private function _getEnumWhereClause($criteriaValues, $func_type)
  900. {
  901. if (! is_array($criteriaValues)) {
  902. $criteriaValues = explode(',', $criteriaValues);
  903. }
  904. $enum_selected_count = count($criteriaValues);
  905. if ($func_type == '=' && $enum_selected_count > 1) {
  906. $func_type = 'IN';
  907. $parens_open = '(';
  908. $parens_close = ')';
  909. } elseif ($func_type == '!=' && $enum_selected_count > 1) {
  910. $func_type = 'NOT IN';
  911. $parens_open = '(';
  912. $parens_close = ')';
  913. } else {
  914. $parens_open = '';
  915. $parens_close = '';
  916. }
  917. $enum_where = '\''
  918. . $GLOBALS['dbi']->escapeString($criteriaValues[0]) . '\'';
  919. for ($e = 1; $e < $enum_selected_count; $e++) {
  920. $enum_where .= ', \''
  921. . $GLOBALS['dbi']->escapeString($criteriaValues[$e]) . '\'';
  922. }
  923. return ' ' . $func_type . ' ' . $parens_open
  924. . $enum_where . $parens_close;
  925. }
  926. /**
  927. * Return the where clause for a geometrical column.
  928. *
  929. * @param mixed $criteriaValues Search criteria input
  930. * @param string $names Name of the column on which search is submitted
  931. * @param string $func_type Search function/operator
  932. * @param string $types Type of the field
  933. * @param bool $geom_func Whether geometry functions should be applied
  934. *
  935. * @return string part of where clause.
  936. */
  937. private function _getGeomWhereClause($criteriaValues, $names,
  938. $func_type, $types, $geom_func = null
  939. ) {
  940. $geom_unary_functions = array(
  941. 'IsEmpty' => 1,
  942. 'IsSimple' => 1,
  943. 'IsRing' => 1,
  944. 'IsClosed' => 1,
  945. );
  946. $where = '';
  947. // Get details about the geometry functions
  948. $geom_funcs = Util::getGISFunctions($types, true, false);
  949. // If the function takes multiple parameters
  950. if(strpos($func_type, "IS NULL") !== false || strpos($func_type, "IS NOT NULL") !== false) {
  951. $where = Util::backquote($names) . " " . $func_type;
  952. return $where;
  953. } elseif ($geom_funcs[$geom_func]['params'] > 1) {
  954. // create gis data from the criteria input
  955. $gis_data = Util::createGISData($criteriaValues);
  956. $where = $geom_func . '(' . Util::backquote($names)
  957. . ', ' . $gis_data . ')';
  958. return $where;
  959. }
  960. // New output type is the output type of the function being applied
  961. $type = $geom_funcs[$geom_func]['type'];
  962. $geom_function_applied = $geom_func
  963. . '(' . Util::backquote($names) . ')';
  964. // If the where clause is something like 'IsEmpty(`spatial_col_name`)'
  965. if (isset($geom_unary_functions[$geom_func])
  966. && trim($criteriaValues) == ''
  967. ) {
  968. $where = $geom_function_applied;
  969. } elseif (in_array($type, Util::getGISDatatypes())
  970. && ! empty($criteriaValues)
  971. ) {
  972. // create gis data from the criteria input
  973. $gis_data = Util::createGISData($criteriaValues);
  974. $where = $geom_function_applied . " " . $func_type . " " . $gis_data;
  975. } elseif (strlen($criteriaValues) > 0) {
  976. $where = $geom_function_applied . " "
  977. . $func_type . " '" . $criteriaValues . "'";
  978. }
  979. return $where;
  980. }
  981. /**
  982. * Return the where clause for query generation based on the inputs provided.
  983. *
  984. * @param mixed $criteriaValues Search criteria input
  985. * @param string $names Name of the column on which search is submitted
  986. * @param string $types Type of the field
  987. * @param string $func_type Search function/operator
  988. * @param bool $unaryFlag Whether operator unary or not
  989. * @param bool $geom_func Whether geometry functions should be applied
  990. *
  991. * @return string generated where clause.
  992. */
  993. private function _getWhereClause($criteriaValues, $names, $types,
  994. $func_type, $unaryFlag, $geom_func = null
  995. ) {
  996. // If geometry function is set
  997. if (! empty($geom_func)) {
  998. return $this->_getGeomWhereClause(
  999. $criteriaValues, $names, $func_type, $types, $geom_func
  1000. );
  1001. }
  1002. $backquoted_name = Util::backquote($names);
  1003. $where = '';
  1004. if ($unaryFlag) {
  1005. $where = $backquoted_name . ' ' . $func_type;
  1006. } elseif (strncasecmp($types, 'enum', 4) == 0 && (! empty($criteriaValues) || $criteriaValues[0] === '0')) {
  1007. $where = $backquoted_name;
  1008. $where .= $this->_getEnumWhereClause($criteriaValues, $func_type);
  1009. } elseif ($criteriaValues != '') {
  1010. // For these types we quote the value. Even if it's another type
  1011. // (like INT), for a LIKE we always quote the value. MySQL converts
  1012. // strings to numbers and numbers to strings as necessary
  1013. // during the comparison
  1014. if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types)
  1015. || mb_strpos(' ' . $func_type, 'LIKE')
  1016. ) {
  1017. $quot = '\'';
  1018. } else {
  1019. $quot = '';
  1020. }
  1021. // LIKE %...%
  1022. if ($func_type == 'LIKE %...%') {
  1023. $func_type = 'LIKE';
  1024. $criteriaValues = '%' . $criteriaValues . '%';
  1025. }
  1026. if ($func_type == 'REGEXP ^...$') {
  1027. $func_type = 'REGEXP';
  1028. $criteriaValues = '^' . $criteriaValues . '$';
  1029. }
  1030. if ('IN (...)' != $func_type
  1031. && 'NOT IN (...)' != $func_type
  1032. && 'BETWEEN' != $func_type
  1033. && 'NOT BETWEEN' != $func_type
  1034. ) {
  1035. return $backquoted_name . ' ' . $func_type . ' ' . $quot
  1036. . $GLOBALS['dbi']->escapeString($criteriaValues) . $quot;
  1037. }
  1038. $func_type = str_replace(' (...)', '', $func_type);
  1039. //Don't explode if this is already an array
  1040. //(Case for (NOT) IN/BETWEEN.)
  1041. if (is_array($criteriaValues)) {
  1042. $values = $criteriaValues;
  1043. } else {
  1044. $values = explode(',', $criteriaValues);
  1045. }
  1046. // quote values one by one
  1047. $emptyKey = false;
  1048. foreach ($values as $key => &$value) {
  1049. if ('' === $value) {
  1050. $emptyKey = $key;
  1051. $value = 'NULL';
  1052. continue;
  1053. }
  1054. $value = $quot . $GLOBALS['dbi']->escapeString(trim($value))
  1055. . $quot;
  1056. }
  1057. if ('BETWEEN' == $func_type || 'NOT BETWEEN' == $func_type) {
  1058. $where = $backquoted_name . ' ' . $func_type . ' '
  1059. . (isset($values[0]) ? $values[0] : '')
  1060. . ' AND ' . (isset($values[1]) ? $values[1] : '');
  1061. } else { //[NOT] IN
  1062. if (false !== $emptyKey) {
  1063. unset($values[$emptyKey]);
  1064. }
  1065. $wheres = array();
  1066. if (!empty($values)) {
  1067. $wheres[] = $backquoted_name . ' ' . $func_type
  1068. . ' (' . implode(',', $values) . ')';
  1069. }
  1070. if (false !== $emptyKey) {
  1071. $wheres[] = $backquoted_name . ' IS NULL';
  1072. }
  1073. $where = implode(' OR ', $wheres);
  1074. if (1 < count($wheres)) {
  1075. $where = '(' . $where . ')';
  1076. }
  1077. }
  1078. } // end if
  1079. return $where;
  1080. }
  1081. }