TableSearchController.php 40 KB

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