DbSearch.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Handles Database Search
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. /**
  12. * Class to handle database search
  13. *
  14. * @package PhpMyAdmin
  15. */
  16. class PMA_DbSearch
  17. {
  18. /**
  19. * Database name
  20. *
  21. * @access private
  22. * @var string
  23. */
  24. private $_db;
  25. /**
  26. * Table Names
  27. *
  28. * @access private
  29. * @var array
  30. */
  31. private $_tables_names_only;
  32. /**
  33. * Type of search
  34. *
  35. * @access private
  36. * @var array
  37. */
  38. private $_searchTypes;
  39. /**
  40. * Already set search type
  41. *
  42. * @access private
  43. * @var integer
  44. */
  45. private $_criteriaSearchType;
  46. /**
  47. * Already set search type's description
  48. *
  49. * @access private
  50. * @var string
  51. */
  52. private $_searchTypeDescription;
  53. /**
  54. * Search string/regexp
  55. *
  56. * @access private
  57. * @var string
  58. */
  59. private $_criteriaSearchString;
  60. /**
  61. * Criteria Tables to search in
  62. *
  63. * @access private
  64. * @var array
  65. */
  66. private $_criteriaTables;
  67. /**
  68. * Restrict the search to this column
  69. *
  70. * @access private
  71. * @var string
  72. */
  73. private $_criteriaColumnName;
  74. /**
  75. * Public Constructor
  76. *
  77. * @param string $db Database name
  78. */
  79. public function __construct($db)
  80. {
  81. $this->_db = $db;
  82. // Sets criteria parameters
  83. $this->_setSearchParams();
  84. }
  85. /**
  86. * Sets search parameters
  87. *
  88. * @return void
  89. */
  90. private function _setSearchParams()
  91. {
  92. $this->_tables_names_only = $GLOBALS['dbi']->getTables($this->_db);
  93. $this->_searchTypes = array(
  94. '1' => __('at least one of the words'),
  95. '2' => __('all words'),
  96. '3' => __('the exact phrase'),
  97. '4' => __('as regular expression'),
  98. );
  99. if (empty($_REQUEST['criteriaSearchType'])
  100. || ! is_string($_REQUEST['criteriaSearchType'])
  101. || ! array_key_exists(
  102. $_REQUEST['criteriaSearchType'],
  103. $this->_searchTypes
  104. )
  105. ) {
  106. $this->_criteriaSearchType = 1;
  107. unset($_REQUEST['submit_search']);
  108. } else {
  109. $this->_criteriaSearchType = (int) $_REQUEST['criteriaSearchType'];
  110. $this->_searchTypeDescription
  111. = $this->_searchTypes[$_REQUEST['criteriaSearchType']];
  112. }
  113. if (empty($_REQUEST['criteriaSearchString'])
  114. || ! is_string($_REQUEST['criteriaSearchString'])
  115. ) {
  116. $this->_criteriaSearchString = '';
  117. unset($_REQUEST['submit_search']);
  118. } else {
  119. $this->_criteriaSearchString = $_REQUEST['criteriaSearchString'];
  120. }
  121. $this->_criteriaTables = array();
  122. if (empty($_REQUEST['criteriaTables'])
  123. || ! is_array($_REQUEST['criteriaTables'])
  124. ) {
  125. unset($_REQUEST['submit_search']);
  126. } else {
  127. $this->_criteriaTables = array_intersect(
  128. $_REQUEST['criteriaTables'], $this->_tables_names_only
  129. );
  130. }
  131. if (empty($_REQUEST['criteriaColumnName'])
  132. || ! is_string($_REQUEST['criteriaColumnName'])
  133. ) {
  134. unset($this->_criteriaColumnName);
  135. } else {
  136. $this->_criteriaColumnName = PMA_Util::sqlAddSlashes(
  137. $_REQUEST['criteriaColumnName'], true
  138. );
  139. }
  140. }
  141. /**
  142. * Builds the SQL search query
  143. *
  144. * @param string $table The table name
  145. *
  146. * @return array 3 SQL querys (for count, display and delete results)
  147. *
  148. * @todo can we make use of fulltextsearch IN BOOLEAN MODE for this?
  149. * PMA_backquote
  150. * DatabaseInterface::freeResult
  151. * DatabaseInterface::fetchAssoc
  152. * $GLOBALS['db']
  153. * explode
  154. * count
  155. * strlen
  156. */
  157. private function _getSearchSqls($table)
  158. {
  159. // Statement types
  160. $sqlstr_select = 'SELECT';
  161. $sqlstr_delete = 'DELETE';
  162. // Table to use
  163. $sqlstr_from = ' FROM '
  164. . PMA_Util::backquote($GLOBALS['db']) . '.'
  165. . PMA_Util::backquote($table);
  166. // Gets where clause for the query
  167. $where_clause = $this->_getWhereClause($table);
  168. // Builds complete queries
  169. $sql = array();
  170. $sql['select_columns'] = $sqlstr_select . ' * ' . $sqlstr_from
  171. . $where_clause;
  172. // here, I think we need to still use the COUNT clause, even for
  173. // VIEWs, anyway we have a WHERE clause that should limit results
  174. $sql['select_count'] = $sqlstr_select . ' COUNT(*) AS `count`'
  175. . $sqlstr_from . $where_clause;
  176. $sql['delete'] = $sqlstr_delete . $sqlstr_from . $where_clause;
  177. return $sql;
  178. }
  179. /**
  180. * Provides where clause for bulding SQL query
  181. *
  182. * @param string $table The table name
  183. *
  184. * @return string The generated where clause
  185. */
  186. private function _getWhereClause($table)
  187. {
  188. $where_clause = '';
  189. // Columns to select
  190. $allColumns = $GLOBALS['dbi']->getColumns($GLOBALS['db'], $table);
  191. $likeClauses = array();
  192. // Based on search type, decide like/regex & '%'/''
  193. $like_or_regex = (($this->_criteriaSearchType == 4) ? 'REGEXP' : 'LIKE');
  194. $automatic_wildcard = (($this->_criteriaSearchType < 3) ? '%' : '');
  195. // For "as regular expression" (search option 4), LIKE won't be used
  196. // Usage example: If user is seaching for a literal $ in a regexp search,
  197. // he should enter \$ as the value.
  198. $this->_criteriaSearchString = PMA_Util::sqlAddSlashes(
  199. $this->_criteriaSearchString,
  200. ($this->_criteriaSearchType == 4 ? false : true)
  201. );
  202. // Extract search words or pattern
  203. $search_words = (($this->_criteriaSearchType > 2)
  204. ? array($this->_criteriaSearchString)
  205. : explode(' ', $this->_criteriaSearchString));
  206. foreach ($search_words as $search_word) {
  207. // Eliminates empty values
  208. if (strlen($search_word) === 0) {
  209. continue;
  210. }
  211. $likeClausesPerColumn = array();
  212. // for each column in the table
  213. foreach ($allColumns as $column) {
  214. if (! isset($this->_criteriaColumnName)
  215. || strlen($this->_criteriaColumnName) == 0
  216. || $column['Field'] == $this->_criteriaColumnName
  217. ) {
  218. // Drizzle has no CONVERT and all text columns are UTF-8
  219. $column = ((PMA_DRIZZLE)
  220. ? PMA_Util::backquote($column['Field'])
  221. : 'CONVERT(' . PMA_Util::backquote($column['Field'])
  222. . ' USING utf8)');
  223. $likeClausesPerColumn[] = $column . ' ' . $like_or_regex . ' '
  224. . "'"
  225. . $automatic_wildcard . $search_word . $automatic_wildcard
  226. . "'";
  227. }
  228. } // end for
  229. if (count($likeClausesPerColumn) > 0) {
  230. $likeClauses[] = implode(' OR ', $likeClausesPerColumn);
  231. }
  232. } // end for
  233. // Use 'OR' if 'at least one word' is to be searched, else use 'AND'
  234. $implode_str = ($this->_criteriaSearchType == 1 ? ' OR ' : ' AND ');
  235. if ( empty($likeClauses)) {
  236. // this could happen when the "inside column" does not exist
  237. // in any selected tables
  238. $where_clause = ' WHERE FALSE';
  239. } else {
  240. $where_clause = ' WHERE ('
  241. . implode(') ' . $implode_str . ' (', $likeClauses)
  242. . ')';
  243. }
  244. return $where_clause;
  245. }
  246. /**
  247. * Displays database search results
  248. *
  249. * @return string HTML for search results
  250. */
  251. public function getSearchResults()
  252. {
  253. $html_output = '';
  254. // Displays search string
  255. $html_output .= '<br />'
  256. . '<table class="data">'
  257. . '<caption class="tblHeaders">'
  258. . sprintf(
  259. __('Search results for "<i>%s</i>" %s:'),
  260. htmlspecialchars($this->_criteriaSearchString),
  261. $this->_searchTypeDescription
  262. )
  263. . '</caption>';
  264. $num_search_result_total = 0;
  265. $odd_row = true;
  266. // For each table selected as search criteria
  267. foreach ($this->_criteriaTables as $each_table) {
  268. // Gets the SQL statements
  269. $newsearchsqls = $this->_getSearchSqls($each_table);
  270. // Executes the "COUNT" statement
  271. $res_cnt = $GLOBALS['dbi']->fetchValue($newsearchsqls['select_count']);
  272. $num_search_result_total += $res_cnt;
  273. // Gets the result row's HTML for a table
  274. $html_output .= $this->_getResultsRow(
  275. $each_table, $newsearchsqls, $odd_row, $res_cnt
  276. );
  277. $odd_row = ! $odd_row;
  278. } // end for
  279. $html_output .= '</table>';
  280. // Displays total number of matches
  281. if (count($this->_criteriaTables) > 1) {
  282. $html_output .= '<p>';
  283. $html_output .= sprintf(
  284. _ngettext(
  285. '<b>Total:</b> <i>%s</i> match',
  286. '<b>Total:</b> <i>%s</i> matches',
  287. $num_search_result_total
  288. ),
  289. $num_search_result_total
  290. );
  291. $html_output .= '</p>';
  292. }
  293. return $html_output;
  294. }
  295. /**
  296. * Provides search results row with browse/delete links.
  297. * (for a table)
  298. *
  299. * @param string $each_table One of the tables on which search was performed
  300. * @param array $newsearchsqls Contains SQL queries
  301. * @param bool $odd_row For displaying contrasting table rows
  302. * @param integer $res_cnt Number of results found
  303. *
  304. * @return string HTML row
  305. */
  306. private function _getResultsRow($each_table, $newsearchsqls, $odd_row, $res_cnt)
  307. {
  308. $this_url_params = array(
  309. 'db' => $GLOBALS['db'],
  310. 'table' => $each_table,
  311. 'goto' => 'db_sql.php',
  312. 'pos' => 0,
  313. 'is_js_confirmed' => 0,
  314. );
  315. // Start forming search results row
  316. $html_output = '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
  317. // Displays results count for a table
  318. $html_output .= '<td>';
  319. $html_output .= sprintf(
  320. _ngettext(
  321. '%1$s match in <strong>%2$s</strong>',
  322. '%1$s matches in <strong>%2$s</strong>', $res_cnt
  323. ),
  324. $res_cnt, htmlspecialchars($each_table)
  325. );
  326. $html_output .= '</td>';
  327. // Displays browse/delete link if result count > 0
  328. if ($res_cnt > 0) {
  329. $this_url_params['sql_query'] = $newsearchsqls['select_columns'];
  330. $browse_result_path = 'sql.php' . PMA_URL_getCommon($this_url_params);
  331. $html_output .= '<td><a name="browse_search" class="ajax" href="'
  332. . $browse_result_path . '" onclick="loadResult(\''
  333. . $browse_result_path . '\',\'' . $each_table . '\',\''
  334. . PMA_URL_getCommon($GLOBALS['db'], $each_table) . '\''
  335. . ');return false;" >'
  336. . __('Browse') . '</a></td>';
  337. $this_url_params['sql_query'] = $newsearchsqls['delete'];
  338. $delete_result_path = 'sql.php' . PMA_URL_getCommon($this_url_params);
  339. $html_output .= '<td><a name="delete_search" class="ajax" href="'
  340. . $delete_result_path . '" onclick="deleteResult(\''
  341. . $delete_result_path . '\' , \''
  342. . sprintf(
  343. __('Delete the matches for the %s table?'),
  344. htmlspecialchars($each_table)
  345. )
  346. . '\');return false;">'
  347. . __('Delete') . '</a></td>';
  348. } else {
  349. $html_output .= '<td>&nbsp;</td>'
  350. . '<td>&nbsp;</td>';
  351. }// end if else
  352. $html_output .= '</tr>';
  353. return $html_output;
  354. }
  355. /**
  356. * Provides the main search form's html
  357. *
  358. * @param array $url_params URL parameters
  359. *
  360. * @return string HTML for selection form
  361. */
  362. public function getSelectionForm($url_params)
  363. {
  364. $html_output = '<a id="db_search"></a>';
  365. $html_output .= '<form id="db_search_form"'
  366. . ' class="ajax"'
  367. . ' method="post" action="db_search.php" name="db_search">';
  368. $html_output .= PMA_URL_getHiddenInputs($GLOBALS['db']);
  369. $html_output .= '<fieldset>';
  370. // set legend caption
  371. $html_output .= '<legend>' . __('Search in database') . '</legend>';
  372. $html_output .= '<table class="formlayout">';
  373. // inputbox for search phrase
  374. $html_output .= '<tr>';
  375. $html_output .= '<td>' . __('Words or values to search for (wildcard: "%"):')
  376. . '</td>';
  377. $html_output .= '<td><input type="text"'
  378. . ' name="criteriaSearchString" size="60"'
  379. . ' value="' . htmlspecialchars($this->_criteriaSearchString) . '" />';
  380. $html_output .= '</td>';
  381. $html_output .= '</tr>';
  382. // choices for types of search
  383. $html_output .= '<tr>';
  384. $html_output .= '<td class="right vtop">' . __('Find:') . '</td>';
  385. $html_output .= '<td>';
  386. $choices = array(
  387. '1' => __('at least one of the words')
  388. . PMA_Util::showHint(
  389. __('Words are separated by a space character (" ").')
  390. ),
  391. '2' => __('all words')
  392. . PMA_Util::showHint(
  393. __('Words are separated by a space character (" ").')
  394. ),
  395. '3' => __('the exact phrase'),
  396. '4' => __('as regular expression') . ' '
  397. . PMA_Util::showMySQLDocu('Regexp')
  398. );
  399. // 4th parameter set to true to add line breaks
  400. // 5th parameter set to false to avoid htmlspecialchars() escaping
  401. // in the label since we have some HTML in some labels
  402. $html_output .= PMA_Util::getRadioFields(
  403. 'criteriaSearchType', $choices, $this->_criteriaSearchType, true, false
  404. );
  405. $html_output .= '</td></tr>';
  406. // displays table names as select options
  407. $html_output .= '<tr>';
  408. $html_output .= '<td class="right vtop">' . __('Inside tables:') . '</td>';
  409. $html_output .= '<td rowspan="2">';
  410. $html_output .= '<select name="criteriaTables[]" size="6"'
  411. . ' multiple="multiple">';
  412. foreach ($this->_tables_names_only as $each_table) {
  413. if (in_array($each_table, $this->_criteriaTables)) {
  414. $is_selected = ' selected="selected"';
  415. } else {
  416. $is_selected = '';
  417. }
  418. $html_output .= '<option value="' . htmlspecialchars($each_table) . '"'
  419. . $is_selected . '>'
  420. . str_replace(' ', '&nbsp;', htmlspecialchars($each_table))
  421. . '</option>';
  422. } // end for
  423. $html_output .= '</select>';
  424. $html_output .= '</td></tr>';
  425. // Displays 'select all' and 'unselect all' links
  426. $alter_select = '<a href="#" '
  427. . 'onclick="setSelectOptions(\'db_search\','
  428. . ' \'criteriaTables[]\', true); return false;">'
  429. . __('Select All') . '</a> &nbsp;/&nbsp;';
  430. $alter_select .= '<a href="#" '
  431. . 'onclick="setSelectOptions(\'db_search\','
  432. . ' \'criteriaTables[]\', false); return false;">'
  433. . __('Unselect All') . '</a>';
  434. $html_output .= '<tr><td class="right vbottom">'
  435. . $alter_select . '</td></tr>';
  436. // Inputbox for column name entry
  437. $html_output .= '<tr>';
  438. $html_output .= '<td class="right">' . __('Inside column:') . '</td>';
  439. $html_output .= '<td><input type="text" name="criteriaColumnName" size="60"'
  440. . 'value="'
  441. . (! empty($this->_criteriaColumnName)
  442. ? htmlspecialchars($this->_criteriaColumnName)
  443. : '')
  444. . '" /></td>';
  445. $html_output .= '</tr>';
  446. $html_output .= '</table>';
  447. $html_output .= '</fieldset>';
  448. $html_output .= '<fieldset class="tblFooters">';
  449. $html_output .= '<input type="submit" name="submit_search" value="'
  450. . __('Go') . '" id="buttonGo" />';
  451. $html_output .= '</fieldset>';
  452. $html_output .= '</form>';
  453. $html_output .= '<div id="togglesearchformdiv">'
  454. . '<a id="togglesearchformlink"></a></div>';
  455. return $html_output;
  456. }
  457. /**
  458. * Provides div tags for browsing search results and sql query form.
  459. *
  460. * @return string div tags
  461. */
  462. public function getResultDivs()
  463. {
  464. $html_output = '<!-- These two table-image and table-link elements display'
  465. . ' the table name in browse search results -->';
  466. $html_output .= '<div id="table-info">';
  467. $html_output .= '<a class="item" id="table-link" ></a>';
  468. $html_output .= '</div>';
  469. // div for browsing results
  470. $html_output .= '<div id="browse-results">';
  471. $html_output .= '<!-- this browse-results div is used to load the browse'
  472. . ' and delete results in the db search -->';
  473. $html_output .= '</div>';
  474. $html_output .= '<br class="clearfloat" />';
  475. $html_output .= '<div id="sqlqueryform">';
  476. $html_output .= '<!-- this sqlqueryform div is used to load the delete'
  477. . ' form in the db search -->';
  478. $html_output .= '</div>';
  479. $html_output .= '<!-- toggle query box link-->';
  480. $html_output .= '<a id="togglequerybox"></a>';
  481. return $html_output;
  482. }
  483. }