DbSearch.class.php 17 KB

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