Search.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Handles Database Search
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. namespace PhpMyAdmin\Database;
  9. use PhpMyAdmin\Template;
  10. use PhpMyAdmin\Util;
  11. /**
  12. * Class to handle database search
  13. *
  14. * @package PhpMyAdmin
  15. */
  16. class Search
  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 $tablesNamesOnly;
  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. $this->searchTypes = array(
  83. '1' => __('at least one of the words'),
  84. '2' => __('all of the words'),
  85. '3' => __('the exact phrase as substring'),
  86. '4' => __('the exact phrase as whole field'),
  87. '5' => __('as regular expression'),
  88. );
  89. // Sets criteria parameters
  90. $this->setSearchParams();
  91. }
  92. /**
  93. * Sets search parameters
  94. *
  95. * @return void
  96. */
  97. private function setSearchParams()
  98. {
  99. $this->tablesNamesOnly = $GLOBALS['dbi']->getTables($this->db);
  100. if (empty($_POST['criteriaSearchType'])
  101. || ! is_string($_POST['criteriaSearchType'])
  102. || ! array_key_exists(
  103. $_POST['criteriaSearchType'],
  104. $this->searchTypes
  105. )
  106. ) {
  107. $this->criteriaSearchType = 1;
  108. unset($_POST['submit_search']);
  109. } else {
  110. $this->criteriaSearchType = (int) $_POST['criteriaSearchType'];
  111. $this->searchTypeDescription
  112. = $this->searchTypes[$_POST['criteriaSearchType']];
  113. }
  114. if (empty($_POST['criteriaSearchString'])
  115. || ! is_string($_POST['criteriaSearchString'])
  116. ) {
  117. $this->criteriaSearchString = '';
  118. unset($_POST['submit_search']);
  119. } else {
  120. $this->criteriaSearchString = $_POST['criteriaSearchString'];
  121. }
  122. $this->criteriaTables = array();
  123. if (empty($_POST['criteriaTables'])
  124. || ! is_array($_POST['criteriaTables'])
  125. ) {
  126. unset($_POST['submit_search']);
  127. } else {
  128. $this->criteriaTables = array_intersect(
  129. $_POST['criteriaTables'], $this->tablesNamesOnly
  130. );
  131. }
  132. if (empty($_POST['criteriaColumnName'])
  133. || ! is_string($_POST['criteriaColumnName'])
  134. ) {
  135. unset($this->criteriaColumnName);
  136. } else {
  137. $this->criteriaColumnName = $GLOBALS['dbi']->escapeString(
  138. $_POST['criteriaColumnName']
  139. );
  140. }
  141. }
  142. /**
  143. * Builds the SQL search query
  144. *
  145. * @param string $table The table name
  146. *
  147. * @return array 3 SQL queries (for count, display and delete results)
  148. *
  149. * @todo can we make use of fulltextsearch IN BOOLEAN MODE for this?
  150. * PMA_backquote
  151. * DatabaseInterface::freeResult
  152. * DatabaseInterface::fetchAssoc
  153. * $GLOBALS['db']
  154. * explode
  155. * count
  156. * strlen
  157. */
  158. private function getSearchSqls($table)
  159. {
  160. // Statement types
  161. $sqlstr_select = 'SELECT';
  162. $sqlstr_delete = 'DELETE';
  163. // Table to use
  164. $sqlstr_from = ' FROM '
  165. . Util::backquote($GLOBALS['db']) . '.'
  166. . Util::backquote($table);
  167. // Gets where clause for the query
  168. $where_clause = $this->getWhereClause($table);
  169. // Builds complete queries
  170. $sql = array();
  171. $sql['select_columns'] = $sqlstr_select . ' * ' . $sqlstr_from
  172. . $where_clause;
  173. // here, I think we need to still use the COUNT clause, even for
  174. // VIEWs, anyway we have a WHERE clause that should limit results
  175. $sql['select_count'] = $sqlstr_select . ' COUNT(*) AS `count`'
  176. . $sqlstr_from . $where_clause;
  177. $sql['delete'] = $sqlstr_delete . $sqlstr_from . $where_clause;
  178. return $sql;
  179. }
  180. /**
  181. * Provides where clause for building SQL query
  182. *
  183. * @param string $table The table name
  184. *
  185. * @return string The generated where clause
  186. */
  187. private function getWhereClause($table)
  188. {
  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 == 5) ? 'REGEXP' : 'LIKE');
  194. $automatic_wildcard = (($this->criteriaSearchType < 4) ? '%' : '');
  195. // For "as regular expression" (search option 5), LIKE won't be used
  196. // Usage example: If user is searching for a literal $ in a regexp search,
  197. // he should enter \$ as the value.
  198. $criteriaSearchStringEscaped = $GLOBALS['dbi']->escapeString(
  199. $this->criteriaSearchString
  200. );
  201. // Extract search words or pattern
  202. $search_words = (($this->criteriaSearchType > 2)
  203. ? array($criteriaSearchStringEscaped)
  204. : explode(' ', $criteriaSearchStringEscaped));
  205. foreach ($search_words as $search_word) {
  206. // Eliminates empty values
  207. if (strlen($search_word) === 0) {
  208. continue;
  209. }
  210. $likeClausesPerColumn = array();
  211. // for each column in the table
  212. foreach ($allColumns as $column) {
  213. if (! isset($this->criteriaColumnName)
  214. || strlen($this->criteriaColumnName) === 0
  215. || $column['Field'] == $this->criteriaColumnName
  216. ) {
  217. $column = 'CONVERT(' . Util::backquote($column['Field'])
  218. . ' USING utf8)';
  219. $likeClausesPerColumn[] = $column . ' ' . $like_or_regex . ' '
  220. . "'"
  221. . $automatic_wildcard . $search_word . $automatic_wildcard
  222. . "'";
  223. }
  224. } // end for
  225. if (count($likeClausesPerColumn) > 0) {
  226. $likeClauses[] = implode(' OR ', $likeClausesPerColumn);
  227. }
  228. } // end for
  229. // Use 'OR' if 'at least one word' is to be searched, else use 'AND'
  230. $implode_str = ($this->criteriaSearchType == 1 ? ' OR ' : ' AND ');
  231. if (empty($likeClauses)) {
  232. // this could happen when the "inside column" does not exist
  233. // in any selected tables
  234. $where_clause = ' WHERE FALSE';
  235. } else {
  236. $where_clause = ' WHERE ('
  237. . implode(') ' . $implode_str . ' (', $likeClauses)
  238. . ')';
  239. }
  240. return $where_clause;
  241. }
  242. /**
  243. * Displays database search results
  244. *
  245. * @return string HTML for search results
  246. */
  247. public function getSearchResults()
  248. {
  249. $resultTotal = 0;
  250. $rows = [];
  251. // For each table selected as search criteria
  252. foreach ($this->criteriaTables as $eachTable) {
  253. // Gets the SQL statements
  254. $newSearchSqls = $this->getSearchSqls($eachTable);
  255. // Executes the "COUNT" statement
  256. $resultCount = intval($GLOBALS['dbi']->fetchValue(
  257. $newSearchSqls['select_count']
  258. ));
  259. $resultTotal += $resultCount;
  260. // Gets the result row's HTML for a table
  261. $rows[] = [
  262. 'table' => htmlspecialchars($eachTable),
  263. 'new_search_sqls' => $newSearchSqls,
  264. 'result_count' => $resultCount,
  265. ];
  266. }
  267. return Template::get('database/search/results')->render([
  268. 'db' => $this->db,
  269. 'rows' => $rows,
  270. 'result_total' => $resultTotal,
  271. 'criteria_tables' => $this->criteriaTables,
  272. 'criteria_search_string' => htmlspecialchars($this->criteriaSearchString),
  273. 'search_type_description' => $this->searchTypeDescription,
  274. ]);
  275. }
  276. /**
  277. * Provides the main search form's html
  278. *
  279. * @return string HTML for selection form
  280. */
  281. public function getSelectionForm()
  282. {
  283. $choices = array(
  284. '1' => $this->searchTypes[1] . ' '
  285. . Util::showHint(
  286. __('Words are separated by a space character (" ").')
  287. ),
  288. '2' => $this->searchTypes[2] . ' '
  289. . Util::showHint(
  290. __('Words are separated by a space character (" ").')
  291. ),
  292. '3' => $this->searchTypes[3],
  293. '4' => $this->searchTypes[4],
  294. '5' => $this->searchTypes[5] . ' ' . Util::showMySQLDocu('Regexp')
  295. );
  296. return Template::get('database/search/selection_form')->render([
  297. 'db' => $this->db,
  298. 'choices' => $choices,
  299. 'criteria_search_string' => $this->criteriaSearchString,
  300. 'criteria_search_type' => $this->criteriaSearchType,
  301. 'criteria_tables' => $this->criteriaTables,
  302. 'tables_names_only' => $this->tablesNamesOnly,
  303. 'criteria_column_name' => isset($this->criteriaColumnName)
  304. ? $this->criteriaColumnName : null,
  305. ]);
  306. }
  307. /**
  308. * Provides div tags for browsing search results and sql query form.
  309. *
  310. * @return string div tags
  311. */
  312. public function getResultDivs()
  313. {
  314. return Template::get('database/search/result_divs')->render();
  315. }
  316. }