ViewCreateController.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers;
  4. use PhpMyAdmin\Controllers\Table\StructureController;
  5. use PhpMyAdmin\Core;
  6. use PhpMyAdmin\DatabaseInterface;
  7. use PhpMyAdmin\Html\Generator;
  8. use PhpMyAdmin\Message;
  9. use PhpMyAdmin\Response;
  10. use PhpMyAdmin\SqlParser\Parser;
  11. use PhpMyAdmin\SqlParser\Statements\CreateStatement;
  12. use PhpMyAdmin\SqlParser\TokensList;
  13. use PhpMyAdmin\Template;
  14. use PhpMyAdmin\Url;
  15. use PhpMyAdmin\Util;
  16. use function array_merge;
  17. use function explode;
  18. use function htmlspecialchars;
  19. use function in_array;
  20. use function is_string;
  21. use function sprintf;
  22. use function strpos;
  23. use function substr;
  24. /**
  25. * Handles creation of VIEWs.
  26. */
  27. class ViewCreateController extends AbstractController
  28. {
  29. /** @var DatabaseInterface */
  30. private $dbi;
  31. /**
  32. * @param Response $response
  33. * @param DatabaseInterface $dbi
  34. */
  35. public function __construct($response, Template $template, $dbi)
  36. {
  37. parent::__construct($response, $template);
  38. $this->dbi = $dbi;
  39. }
  40. public function index(): void
  41. {
  42. global $text_dir, $url_params, $view_algorithm_options, $view_with_options, $view_security_options;
  43. global $message, $sep, $sql_query, $arr, $view_columns, $column_map, $systemDb, $pma_transformation_data;
  44. global $containerBuilder, $new_transformations_sql, $view, $item, $parts, $db, $cfg, $err_url;
  45. Util::checkParameters(['db']);
  46. $err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
  47. $err_url .= Url::getCommon(['db' => $db], '&');
  48. if (! $this->hasDatabase()) {
  49. return;
  50. }
  51. $url_params['goto'] = Url::getFromRoute('/table/structure');
  52. $url_params['back'] = Url::getFromRoute('/view/create');
  53. $view_algorithm_options = [
  54. 'UNDEFINED',
  55. 'MERGE',
  56. 'TEMPTABLE',
  57. ];
  58. $view_with_options = [
  59. 'CASCADED',
  60. 'LOCAL',
  61. ];
  62. $view_security_options = [
  63. 'DEFINER',
  64. 'INVOKER',
  65. ];
  66. // View name is a compulsory field
  67. if (isset($_POST['view']['name'])
  68. && empty($_POST['view']['name'])
  69. ) {
  70. $message = Message::error(__('View name can not be empty!'));
  71. $this->response->addJSON(
  72. 'message',
  73. $message
  74. );
  75. $this->response->setRequestStatus(false);
  76. return;
  77. }
  78. if (isset($_POST['createview']) || isset($_POST['alterview'])) {
  79. /**
  80. * Creates the view
  81. */
  82. $sep = "\r\n";
  83. if (isset($_POST['createview'])) {
  84. $sql_query = 'CREATE';
  85. if (isset($_POST['view']['or_replace'])) {
  86. $sql_query .= ' OR REPLACE';
  87. }
  88. } else {
  89. $sql_query = 'ALTER';
  90. }
  91. if (Core::isValid($_POST['view']['algorithm'], $view_algorithm_options)) {
  92. $sql_query .= $sep . ' ALGORITHM = ' . $_POST['view']['algorithm'];
  93. }
  94. if (! empty($_POST['view']['definer'])) {
  95. if (strpos($_POST['view']['definer'], '@') === false) {
  96. $sql_query .= $sep . 'DEFINER='
  97. . Util::backquote($_POST['view']['definer']);
  98. } else {
  99. $arr = explode('@', $_POST['view']['definer']);
  100. $sql_query .= $sep . 'DEFINER=' . Util::backquote($arr[0]);
  101. $sql_query .= '@' . Util::backquote($arr[1]) . ' ';
  102. }
  103. }
  104. if (isset($_POST['view']['sql_security'])
  105. && in_array($_POST['view']['sql_security'], $view_security_options)
  106. ) {
  107. $sql_query .= $sep . ' SQL SECURITY '
  108. . $_POST['view']['sql_security'];
  109. }
  110. $sql_query .= $sep . ' VIEW '
  111. . Util::backquote($_POST['view']['name']);
  112. if (! empty($_POST['view']['column_names'])) {
  113. $sql_query .= $sep . ' (' . $_POST['view']['column_names'] . ')';
  114. }
  115. $sql_query .= $sep . ' AS ' . $_POST['view']['as'];
  116. if (isset($_POST['view']['with'])) {
  117. if (in_array($_POST['view']['with'], $view_with_options)) {
  118. $sql_query .= $sep . ' WITH ' . $_POST['view']['with']
  119. . ' CHECK OPTION';
  120. }
  121. }
  122. if (! $this->dbi->tryQuery($sql_query)) {
  123. if (! isset($_POST['ajax_dialog'])) {
  124. $message = Message::rawError((string) $this->dbi->getError());
  125. return;
  126. }
  127. $this->response->addJSON(
  128. 'message',
  129. Message::error(
  130. '<i>' . htmlspecialchars($sql_query) . '</i><br><br>'
  131. . $this->dbi->getError()
  132. )
  133. );
  134. $this->response->setRequestStatus(false);
  135. return;
  136. }
  137. // If different column names defined for VIEW
  138. $view_columns = [];
  139. if (isset($_POST['view']['column_names'])) {
  140. $view_columns = explode(',', $_POST['view']['column_names']);
  141. }
  142. $column_map = $this->dbi->getColumnMapFromSql(
  143. $_POST['view']['as'],
  144. $view_columns
  145. );
  146. $systemDb = $this->dbi->getSystemDatabase();
  147. $pma_transformation_data = $systemDb->getExistingTransformationData(
  148. $db
  149. );
  150. if ($pma_transformation_data !== false) {
  151. // SQL for store new transformation details of VIEW
  152. $new_transformations_sql = $systemDb->getNewTransformationDataSql(
  153. $pma_transformation_data,
  154. $column_map,
  155. $_POST['view']['name'],
  156. $db
  157. );
  158. // Store new transformations
  159. if ($new_transformations_sql != '') {
  160. $this->dbi->tryQuery($new_transformations_sql);
  161. }
  162. }
  163. unset($pma_transformation_data);
  164. if (! isset($_POST['ajax_dialog'])) {
  165. $message = Message::success();
  166. /** @var StructureController $controller */
  167. $controller = $containerBuilder->get(StructureController::class);
  168. $controller->index();
  169. } else {
  170. $this->response->addJSON(
  171. 'message',
  172. Generator::getMessage(
  173. Message::success(),
  174. $sql_query
  175. )
  176. );
  177. $this->response->setRequestStatus(true);
  178. }
  179. return;
  180. }
  181. $sql_query = ! empty($_POST['sql_query']) ? $_POST['sql_query'] : '';
  182. // prefill values if not already filled from former submission
  183. $view = [
  184. 'operation' => 'create',
  185. 'or_replace' => '',
  186. 'algorithm' => '',
  187. 'definer' => '',
  188. 'sql_security' => '',
  189. 'name' => '',
  190. 'column_names' => '',
  191. 'as' => $sql_query,
  192. 'with' => '',
  193. ];
  194. // Used to prefill the fields when editing a view
  195. if (isset($_GET['db'], $_GET['table'])) {
  196. $item = $this->dbi->fetchSingleRow(
  197. sprintf(
  198. "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`,
  199. `SECURITY_TYPE`
  200. FROM `INFORMATION_SCHEMA`.`VIEWS`
  201. WHERE TABLE_SCHEMA='%s'
  202. AND TABLE_NAME='%s';",
  203. $this->dbi->escapeString($_GET['db']),
  204. $this->dbi->escapeString($_GET['table'])
  205. )
  206. );
  207. $createView = $this->dbi->getTable($_GET['db'], $_GET['table'])
  208. ->showCreate();
  209. // CREATE ALGORITHM=<ALGORITHM> DE...
  210. $parts = explode(' ', substr($createView, 17));
  211. $item['ALGORITHM'] = $parts[0];
  212. $view['operation'] = 'alter';
  213. $view['definer'] = $item['DEFINER'];
  214. $view['sql_security'] = $item['SECURITY_TYPE'];
  215. $view['name'] = $_GET['table'];
  216. $view['as'] = $item['VIEW_DEFINITION'];
  217. $view['with'] = $item['CHECK_OPTION'];
  218. $view['algorithm'] = $item['ALGORITHM'];
  219. // MySQL 8.0+ - issue #16194
  220. if (empty($view['as']) && is_string($createView)) {
  221. $parser = new Parser($createView);
  222. /**
  223. * @var CreateStatement $stmt
  224. */
  225. $stmt = $parser->statements[0];
  226. $view['as'] = isset($stmt->body) ? TokensList::build($stmt->body) : $view['as'];
  227. }
  228. }
  229. if (Core::isValid($_POST['view'], 'array')) {
  230. $view = array_merge($view, $_POST['view']);
  231. }
  232. $url_params['db'] = $db;
  233. $url_params['reload'] = 1;
  234. echo $this->template->render('view_create', [
  235. 'ajax_dialog' => isset($_POST['ajax_dialog']),
  236. 'text_dir' => $text_dir,
  237. 'url_params' => $url_params,
  238. 'view' => $view,
  239. 'view_algorithm_options' => $view_algorithm_options,
  240. 'view_with_options' => $view_with_options,
  241. 'view_security_options' => $view_security_options,
  242. ]);
  243. }
  244. }