TableSearchController.class.php 42 KB

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