Search.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Table;
  4. use PhpMyAdmin\DatabaseInterface;
  5. use PhpMyAdmin\Util;
  6. use function count;
  7. use function explode;
  8. use function implode;
  9. use function in_array;
  10. use function is_array;
  11. use function mb_strpos;
  12. use function preg_match;
  13. use function str_replace;
  14. use function strlen;
  15. use function strncasecmp;
  16. use function strpos;
  17. use function trim;
  18. final class Search
  19. {
  20. /** @var DatabaseInterface */
  21. private $dbi;
  22. /**
  23. * @param DatabaseInterface $dbi A DatabaseInterface instance.
  24. */
  25. public function __construct($dbi)
  26. {
  27. $this->dbi = $dbi;
  28. }
  29. /**
  30. * Builds the sql search query from the post parameters
  31. *
  32. * @return string the generated SQL query
  33. */
  34. public function buildSqlQuery(): string
  35. {
  36. $sql_query = 'SELECT ';
  37. // If only distinct values are needed
  38. $is_distinct = isset($_POST['distinct']) ? 'true' : 'false';
  39. if ($is_distinct === 'true') {
  40. $sql_query .= 'DISTINCT ';
  41. }
  42. // if all column names were selected to display, we do a 'SELECT *'
  43. // (more efficient and this helps prevent a problem in IE
  44. // if one of the rows is edited and we come back to the Select results)
  45. if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) {
  46. $sql_query .= '* ';
  47. } else {
  48. $sql_query .= implode(
  49. ', ',
  50. Util::backquote($_POST['columnsToDisplay'])
  51. );
  52. }
  53. $sql_query .= ' FROM '
  54. . Util::backquote($_POST['table']);
  55. $whereClause = $this->generateWhereClause();
  56. $sql_query .= $whereClause;
  57. // if the search results are to be ordered
  58. if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] !== '--nil--') {
  59. $sql_query .= ' ORDER BY '
  60. . Util::backquote($_POST['orderByColumn'])
  61. . ' ' . $_POST['order'];
  62. }
  63. return $sql_query;
  64. }
  65. /**
  66. * Generates the where clause for the SQL search query to be executed
  67. *
  68. * @return string the generated where clause
  69. */
  70. private function generateWhereClause(): string
  71. {
  72. if (isset($_POST['customWhereClause'])
  73. && trim($_POST['customWhereClause']) != ''
  74. ) {
  75. return ' WHERE ' . $_POST['customWhereClause'];
  76. }
  77. // If there are no search criteria set or no unary criteria operators,
  78. // return
  79. if (! isset($_POST['criteriaValues'])
  80. && ! isset($_POST['criteriaColumnOperators'])
  81. && ! isset($_POST['geom_func'])
  82. ) {
  83. return '';
  84. }
  85. // else continue to form the where clause from column criteria values
  86. $fullWhereClause = [];
  87. foreach ($_POST['criteriaColumnOperators'] as $column_index => $operator) {
  88. $unaryFlag = $this->dbi->types->isUnaryOperator($operator);
  89. $tmp_geom_func = $_POST['geom_func'][$column_index] ?? null;
  90. $whereClause = $this->getWhereClause(
  91. $_POST['criteriaValues'][$column_index],
  92. $_POST['criteriaColumnNames'][$column_index],
  93. $_POST['criteriaColumnTypes'][$column_index],
  94. $operator,
  95. $unaryFlag,
  96. $tmp_geom_func
  97. );
  98. if (! $whereClause) {
  99. continue;
  100. }
  101. $fullWhereClause[] = $whereClause;
  102. }
  103. if (! empty($fullWhereClause)) {
  104. return ' WHERE ' . implode(' AND ', $fullWhereClause);
  105. }
  106. return '';
  107. }
  108. /**
  109. * Return the where clause for query generation based on the inputs provided.
  110. *
  111. * @param mixed $criteriaValues Search criteria input
  112. * @param string $names Name of the column on which search is submitted
  113. * @param string $types Type of the field
  114. * @param string $func_type Search function/operator
  115. * @param bool $unaryFlag Whether operator unary or not
  116. * @param string|null $geom_func Whether geometry functions should be applied
  117. *
  118. * @return string generated where clause.
  119. */
  120. private function getWhereClause(
  121. $criteriaValues,
  122. $names,
  123. $types,
  124. $func_type,
  125. $unaryFlag,
  126. $geom_func = null
  127. ): string {
  128. // If geometry function is set
  129. if (! empty($geom_func)) {
  130. return $this->getGeomWhereClause(
  131. $criteriaValues,
  132. $names,
  133. $func_type,
  134. $types,
  135. $geom_func
  136. );
  137. }
  138. $backquoted_name = Util::backquote($names);
  139. $where = '';
  140. if ($unaryFlag) {
  141. $where = $backquoted_name . ' ' . $func_type;
  142. } elseif (strncasecmp($types, 'enum', 4) == 0 && (! empty($criteriaValues) || $criteriaValues[0] === '0')) {
  143. $where = $backquoted_name;
  144. $where .= $this->getEnumWhereClause($criteriaValues, $func_type);
  145. } elseif ($criteriaValues != '') {
  146. // For these types we quote the value. Even if it's another type
  147. // (like INT), for a LIKE we always quote the value. MySQL converts
  148. // strings to numbers and numbers to strings as necessary
  149. // during the comparison
  150. if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types)
  151. || mb_strpos(' ' . $func_type, 'LIKE')
  152. ) {
  153. $quot = '\'';
  154. } else {
  155. $quot = '';
  156. }
  157. // LIKE %...%
  158. if ($func_type === 'LIKE %...%') {
  159. $func_type = 'LIKE';
  160. $criteriaValues = '%' . $criteriaValues . '%';
  161. }
  162. if ($func_type === 'REGEXP ^...$') {
  163. $func_type = 'REGEXP';
  164. $criteriaValues = '^' . $criteriaValues . '$';
  165. }
  166. if ($func_type !== 'IN (...)'
  167. && $func_type !== 'NOT IN (...)'
  168. && $func_type !== 'BETWEEN'
  169. && $func_type !== 'NOT BETWEEN'
  170. ) {
  171. return $backquoted_name . ' ' . $func_type . ' ' . $quot
  172. . $this->dbi->escapeString($criteriaValues) . $quot;
  173. }
  174. $func_type = str_replace(' (...)', '', $func_type);
  175. //Don't explode if this is already an array
  176. //(Case for (NOT) IN/BETWEEN.)
  177. if (is_array($criteriaValues)) {
  178. $values = $criteriaValues;
  179. } else {
  180. $values = explode(',', $criteriaValues);
  181. }
  182. // quote values one by one
  183. $emptyKey = false;
  184. foreach ($values as $key => &$value) {
  185. if ($value === '') {
  186. $emptyKey = $key;
  187. $value = 'NULL';
  188. continue;
  189. }
  190. $value = $quot . $this->dbi->escapeString(trim($value))
  191. . $quot;
  192. }
  193. if ($func_type === 'BETWEEN' || $func_type === 'NOT BETWEEN') {
  194. $where = $backquoted_name . ' ' . $func_type . ' '
  195. . ($values[0] ?? '')
  196. . ' AND ' . ($values[1] ?? '');
  197. } else { //[NOT] IN
  198. if ($emptyKey !== false) {
  199. unset($values[$emptyKey]);
  200. }
  201. $wheres = [];
  202. if (! empty($values)) {
  203. $wheres[] = $backquoted_name . ' ' . $func_type
  204. . ' (' . implode(',', $values) . ')';
  205. }
  206. if ($emptyKey !== false) {
  207. $wheres[] = $backquoted_name . ' IS NULL';
  208. }
  209. $where = implode(' OR ', $wheres);
  210. if (1 < count($wheres)) {
  211. $where = '(' . $where . ')';
  212. }
  213. }
  214. }
  215. return $where;
  216. }
  217. /**
  218. * Return the where clause for a geometrical column.
  219. *
  220. * @param mixed $criteriaValues Search criteria input
  221. * @param string $names Name of the column on which search is submitted
  222. * @param string $func_type Search function/operator
  223. * @param string $types Type of the field
  224. * @param string|null $geom_func Whether geometry functions should be applied
  225. *
  226. * @return string part of where clause.
  227. */
  228. private function getGeomWhereClause(
  229. $criteriaValues,
  230. $names,
  231. $func_type,
  232. $types,
  233. $geom_func = null
  234. ): string {
  235. $geom_unary_functions = [
  236. 'IsEmpty' => 1,
  237. 'IsSimple' => 1,
  238. 'IsRing' => 1,
  239. 'IsClosed' => 1,
  240. ];
  241. $where = '';
  242. // Get details about the geometry functions
  243. $geom_funcs = Util::getGISFunctions($types, true, false);
  244. // If the function takes multiple parameters
  245. if (strpos($func_type, 'IS NULL') !== false || strpos($func_type, 'IS NOT NULL') !== false) {
  246. return Util::backquote($names) . ' ' . $func_type;
  247. }
  248. if ($geom_funcs[$geom_func]['params'] > 1) {
  249. // create gis data from the criteria input
  250. $gis_data = Util::createGISData($criteriaValues, $this->dbi->getVersion());
  251. return $geom_func . '(' . Util::backquote($names)
  252. . ', ' . $gis_data . ')';
  253. }
  254. // New output type is the output type of the function being applied
  255. $type = $geom_funcs[$geom_func]['type'];
  256. $geom_function_applied = $geom_func
  257. . '(' . Util::backquote($names) . ')';
  258. // If the where clause is something like 'IsEmpty(`spatial_col_name`)'
  259. if (isset($geom_unary_functions[$geom_func])
  260. && trim($criteriaValues) == ''
  261. ) {
  262. $where = $geom_function_applied;
  263. } elseif (in_array($type, Util::getGISDatatypes())
  264. && ! empty($criteriaValues)
  265. ) {
  266. // create gis data from the criteria input
  267. $gis_data = Util::createGISData($criteriaValues, $this->dbi->getVersion());
  268. $where = $geom_function_applied . ' ' . $func_type . ' ' . $gis_data;
  269. } elseif (strlen($criteriaValues) > 0) {
  270. $where = $geom_function_applied . ' '
  271. . $func_type . " '" . $criteriaValues . "'";
  272. }
  273. return $where;
  274. }
  275. /**
  276. * Return the where clause in case column's type is ENUM.
  277. *
  278. * @param mixed $criteriaValues Search criteria input
  279. * @param string $func_type Search function/operator
  280. *
  281. * @return string part of where clause.
  282. */
  283. private function getEnumWhereClause($criteriaValues, $func_type): string
  284. {
  285. if (! is_array($criteriaValues)) {
  286. $criteriaValues = explode(',', $criteriaValues);
  287. }
  288. $enum_selected_count = count($criteriaValues);
  289. if ($func_type === '=' && $enum_selected_count > 1) {
  290. $func_type = 'IN';
  291. $parens_open = '(';
  292. $parens_close = ')';
  293. } elseif ($func_type === '!=' && $enum_selected_count > 1) {
  294. $func_type = 'NOT IN';
  295. $parens_open = '(';
  296. $parens_close = ')';
  297. } else {
  298. $parens_open = '';
  299. $parens_close = '';
  300. }
  301. $enum_where = '\''
  302. . $this->dbi->escapeString($criteriaValues[0]) . '\'';
  303. for ($e = 1; $e < $enum_selected_count; $e++) {
  304. $enum_where .= ', \''
  305. . $this->dbi->escapeString($criteriaValues[$e]) . '\'';
  306. }
  307. return ' ' . $func_type . ' ' . $parens_open
  308. . $enum_where . $parens_close;
  309. }
  310. }