SqlController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers;
  4. use PhpMyAdmin\Bookmark;
  5. use PhpMyAdmin\CheckUserPrivileges;
  6. use PhpMyAdmin\Config\PageSettings;
  7. use PhpMyAdmin\Core;
  8. use PhpMyAdmin\DatabaseInterface;
  9. use PhpMyAdmin\Html\Generator;
  10. use PhpMyAdmin\Message;
  11. use PhpMyAdmin\ParseAnalyze;
  12. use PhpMyAdmin\Response;
  13. use PhpMyAdmin\Sql;
  14. use PhpMyAdmin\Template;
  15. use PhpMyAdmin\Url;
  16. use PhpMyAdmin\Util;
  17. use const ENT_COMPAT;
  18. use function htmlentities;
  19. use function mb_strpos;
  20. use function strlen;
  21. use function strpos;
  22. use function urlencode;
  23. class SqlController extends AbstractController
  24. {
  25. /** @var Sql */
  26. private $sql;
  27. /** @var CheckUserPrivileges */
  28. private $checkUserPrivileges;
  29. /** @var DatabaseInterface */
  30. private $dbi;
  31. /**
  32. * @param Response $response
  33. * @param DatabaseInterface $dbi
  34. */
  35. public function __construct(
  36. $response,
  37. Template $template,
  38. Sql $sql,
  39. CheckUserPrivileges $checkUserPrivileges,
  40. $dbi
  41. ) {
  42. parent::__construct($response, $template);
  43. $this->sql = $sql;
  44. $this->checkUserPrivileges = $checkUserPrivileges;
  45. $this->dbi = $dbi;
  46. }
  47. public function index(): void
  48. {
  49. global $cfg, $db, $display_query, $sql_query, $table, $PMA_Theme;
  50. global $ajax_reload, $goto, $err_url, $find_real_end, $unlim_num_rows, $import_text, $disp_query;
  51. global $extra_data, $message_to_show, $sql_data, $disp_message, $complete_query;
  52. global $is_gotofile, $back, $table_from_sql;
  53. $this->checkUserPrivileges->getPrivileges();
  54. $pageSettings = new PageSettings('Browse');
  55. $this->response->addHTML($pageSettings->getErrorHTML());
  56. $this->response->addHTML($pageSettings->getHTML());
  57. $this->addScriptFiles([
  58. 'vendor/jquery/jquery.uitablefilter.js',
  59. 'table/change.js',
  60. 'indexes.js',
  61. 'vendor/stickyfill.min.js',
  62. 'gis_data_editor.js',
  63. 'multi_column_sort.js',
  64. ]);
  65. /**
  66. * Set ajax_reload in the response if it was already set
  67. */
  68. if (isset($ajax_reload) && $ajax_reload['reload'] === true) {
  69. $this->response->addJSON('ajax_reload', $ajax_reload);
  70. }
  71. /**
  72. * Defines the url to return to in case of error in a sql statement
  73. */
  74. $is_gotofile = true;
  75. if (empty($goto)) {
  76. if (empty($table)) {
  77. $goto = Util::getScriptNameForOption(
  78. $cfg['DefaultTabDatabase'],
  79. 'database'
  80. );
  81. } else {
  82. $goto = Util::getScriptNameForOption(
  83. $cfg['DefaultTabTable'],
  84. 'table'
  85. );
  86. }
  87. }
  88. if (! isset($err_url)) {
  89. $err_url = ! empty($back) ? $back : $goto;
  90. $err_url .= Url::getCommon(
  91. ['db' => $GLOBALS['db']],
  92. strpos($err_url, '?') === false ? '?' : '&'
  93. );
  94. if ((mb_strpos(' ' . $err_url, 'db_') !== 1 || mb_strpos($err_url, '?route=/database/') === false)
  95. && strlen($table) > 0
  96. ) {
  97. $err_url .= '&amp;table=' . urlencode($table);
  98. }
  99. }
  100. // Coming from a bookmark dialog
  101. if (isset($_POST['bkm_fields']['bkm_sql_query'])) {
  102. $sql_query = $_POST['bkm_fields']['bkm_sql_query'];
  103. } elseif (isset($_POST['sql_query'])) {
  104. $sql_query = $_POST['sql_query'];
  105. } elseif (isset($_GET['sql_query'], $_GET['sql_signature'])) {
  106. if (Core::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
  107. $sql_query = $_GET['sql_query'];
  108. }
  109. }
  110. // This one is just to fill $db
  111. if (isset($_POST['bkm_fields']['bkm_database'])) {
  112. $db = $_POST['bkm_fields']['bkm_database'];
  113. }
  114. // Default to browse if no query set and we have table
  115. // (needed for browsing from DefaultTabTable)
  116. if (empty($sql_query) && strlen($table) > 0 && strlen($db) > 0) {
  117. $sql_query = $this->sql->getDefaultSqlQueryForBrowse($db, $table);
  118. // set $goto to what will be displayed if query returns 0 rows
  119. $goto = '';
  120. } else {
  121. // Now we can check the parameters
  122. Util::checkParameters(['sql_query']);
  123. }
  124. /**
  125. * Parse and analyze the query
  126. */
  127. [
  128. $analyzed_sql_results,
  129. $db,
  130. $table_from_sql,
  131. ] = ParseAnalyze::sqlQuery($sql_query, $db);
  132. if ($table != $table_from_sql && ! empty($table_from_sql)) {
  133. $table = $table_from_sql;
  134. }
  135. /**
  136. * Check rights in case of DROP DATABASE
  137. *
  138. * This test may be bypassed if $is_js_confirmed = 1 (already checked with js)
  139. * but since a malicious user may pass this variable by url/form, we don't take
  140. * into account this case.
  141. */
  142. if ($this->sql->hasNoRightsToDropDatabase(
  143. $analyzed_sql_results,
  144. $cfg['AllowUserDropDatabase'],
  145. $this->dbi->isSuperUser()
  146. )) {
  147. Generator::mysqlDie(
  148. __('"DROP DATABASE" statements are disabled.'),
  149. '',
  150. false,
  151. $err_url
  152. );
  153. }
  154. /**
  155. * Need to find the real end of rows?
  156. */
  157. if (isset($find_real_end) && $find_real_end) {
  158. $unlim_num_rows = $this->sql->findRealEndOfRows($db, $table);
  159. }
  160. /**
  161. * Bookmark add
  162. */
  163. if (isset($_POST['store_bkm'])) {
  164. $this->addBookmark($goto);
  165. return;
  166. }
  167. /**
  168. * Sets or modifies the $goto variable if required
  169. */
  170. if ($goto === Url::getFromRoute('/sql')) {
  171. $is_gotofile = false;
  172. $goto = Url::getFromRoute('/sql', [
  173. 'db' => $db,
  174. 'table' => $table,
  175. 'sql_query' => $sql_query,
  176. ]);
  177. }
  178. $this->response->addHTML($this->sql->executeQueryAndSendQueryResponse(
  179. $analyzed_sql_results,
  180. $is_gotofile,
  181. $db,
  182. $table,
  183. $find_real_end ?? null,
  184. $import_text ?? null,
  185. $extra_data ?? null,
  186. $message_to_show ?? null,
  187. $sql_data ?? null,
  188. $goto,
  189. $PMA_Theme->getImgPath(),
  190. isset($disp_query) ? $display_query : null,
  191. $disp_message ?? null,
  192. $sql_query,
  193. $complete_query ?? null
  194. ));
  195. }
  196. /**
  197. * Get values for the relational columns
  198. *
  199. * During grid edit, if we have a relational field, show the dropdown for it.
  200. */
  201. public function getRelationalValues(): void
  202. {
  203. global $db, $table;
  204. $this->checkUserPrivileges->getPrivileges();
  205. $column = $_POST['column'];
  206. if ($_SESSION['tmpval']['relational_display'] === 'D'
  207. && isset($_POST['relation_key_or_display_column'])
  208. && $_POST['relation_key_or_display_column']
  209. ) {
  210. $curr_value = $_POST['relation_key_or_display_column'];
  211. } else {
  212. $curr_value = $_POST['curr_value'];
  213. }
  214. $dropdown = $this->sql->getHtmlForRelationalColumnDropdown(
  215. $db,
  216. $table,
  217. $column,
  218. $curr_value
  219. );
  220. $this->response->addJSON('dropdown', $dropdown);
  221. }
  222. /**
  223. * Get possible values for enum fields during grid edit.
  224. */
  225. public function getEnumValues(): void
  226. {
  227. global $db, $table;
  228. $this->checkUserPrivileges->getPrivileges();
  229. $column = $_POST['column'];
  230. $curr_value = $_POST['curr_value'];
  231. $values = $this->sql->getValuesForColumn($db, $table, $column);
  232. $dropdown = $this->template->render('sql/enum_column_dropdown', [
  233. 'values' => $values,
  234. 'selected_values' => [$curr_value],
  235. ]);
  236. $this->response->addJSON('dropdown', $dropdown);
  237. }
  238. /**
  239. * Get possible values for SET fields during grid edit.
  240. */
  241. public function getSetValues(): void
  242. {
  243. global $db, $table;
  244. $this->checkUserPrivileges->getPrivileges();
  245. $column = $_POST['column'];
  246. $currentValue = $_POST['curr_value'];
  247. $fullValues = $_POST['get_full_values'] ?? false;
  248. $whereClause = $_POST['where_clause'] ?? null;
  249. $values = $this->sql->getValuesForColumn($db, $table, $column);
  250. // If the $currentValue was truncated, we should fetch the correct full values from the table.
  251. if ($fullValues && ! empty($whereClause)) {
  252. $currentValue = $this->sql->getFullValuesForSetColumn(
  253. $db,
  254. $table,
  255. $column,
  256. $whereClause
  257. );
  258. }
  259. // Converts characters of $currentValue to HTML entities.
  260. $convertedCurrentValue = htmlentities($currentValue, ENT_COMPAT, 'UTF-8');
  261. $select = $this->template->render('sql/set_column', [
  262. 'values' => $values,
  263. 'current_values' => $convertedCurrentValue,
  264. ]);
  265. $this->response->addJSON('select', $select);
  266. }
  267. public function getDefaultForeignKeyCheckValue(): void
  268. {
  269. $this->checkUserPrivileges->getPrivileges();
  270. $this->response->addJSON(
  271. 'default_fk_check_value',
  272. Util::isForeignKeyCheck()
  273. );
  274. }
  275. public function setColumnOrderOrVisibility(): void
  276. {
  277. global $db, $table;
  278. $this->checkUserPrivileges->getPrivileges();
  279. $tableObject = $this->dbi->getTable($db, $table);
  280. $status = false;
  281. // set column order
  282. if (isset($_POST['col_order'])) {
  283. $status = $this->sql->setColumnProperty($tableObject, 'col_order');
  284. }
  285. // set column visibility
  286. if ($status === true && isset($_POST['col_visib'])) {
  287. $status = $this->sql->setColumnProperty($tableObject, 'col_visib');
  288. }
  289. if ($status instanceof Message) {
  290. $this->response->setRequestStatus(false);
  291. $this->response->addJSON('message', $status->getString());
  292. return;
  293. }
  294. $this->response->setRequestStatus($status === true);
  295. }
  296. private function addBookmark(string $goto): void
  297. {
  298. global $cfg;
  299. $bookmark = Bookmark::createBookmark(
  300. $this->dbi,
  301. $cfg['Server']['user'],
  302. $_POST['bkm_fields'],
  303. isset($_POST['bkm_all_users']) && $_POST['bkm_all_users'] === 'true'
  304. );
  305. $result = null;
  306. if ($bookmark instanceof Bookmark) {
  307. $result = $bookmark->save();
  308. }
  309. if (! $this->response->isAjax()) {
  310. Core::sendHeaderLocation('./' . $goto . '&label=' . $_POST['bkm_fields']['bkm_label']);
  311. return;
  312. }
  313. if ($result) {
  314. $msg = Message::success(__('Bookmark %s has been created.'));
  315. $msg->addParam($_POST['bkm_fields']['bkm_label']);
  316. $this->response->addJSON('message', $msg);
  317. return;
  318. }
  319. $msg = Message::error(__('Bookmark not created!'));
  320. $this->response->setRequestStatus(false);
  321. $this->response->addJSON('message', $msg);
  322. }
  323. }