ColumnsDefinition.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Table;
  4. use PhpMyAdmin\Charsets;
  5. use PhpMyAdmin\Charsets\Charset;
  6. use PhpMyAdmin\Charsets\Collation;
  7. use PhpMyAdmin\DatabaseInterface;
  8. use PhpMyAdmin\Partition;
  9. use PhpMyAdmin\Relation;
  10. use PhpMyAdmin\StorageEngine;
  11. use PhpMyAdmin\Table;
  12. use PhpMyAdmin\TablePartitionDefinition;
  13. use PhpMyAdmin\Transformations;
  14. use PhpMyAdmin\Url;
  15. use PhpMyAdmin\Util;
  16. use function array_merge;
  17. use function bin2hex;
  18. use function count;
  19. use function explode;
  20. use function in_array;
  21. use function intval;
  22. use function is_array;
  23. use function is_iterable;
  24. use function mb_strtoupper;
  25. use function preg_quote;
  26. use function preg_replace;
  27. use function rtrim;
  28. use function stripcslashes;
  29. use function substr;
  30. use function trim;
  31. /**
  32. * Displays the form used to define the structure of the table
  33. */
  34. final class ColumnsDefinition
  35. {
  36. /**
  37. * @param Transformations $transformations Transformations
  38. * @param Relation $relation Relation
  39. * @param DatabaseInterface $dbi Database Interface instance
  40. * @param string $action Action
  41. * @param int $num_fields The number of fields
  42. * @param string|null $regenerate Use regeneration
  43. * @param array|null $selected Selected
  44. * @param array|null $fields_meta Fields meta
  45. * @param array|null $field_fulltext Fields full text
  46. *
  47. * @return array<string, mixed>
  48. */
  49. public static function displayForm(
  50. Transformations $transformations,
  51. Relation $relation,
  52. DatabaseInterface $dbi,
  53. string $action,
  54. $num_fields = 0,
  55. $regenerate = null,
  56. $selected = null,
  57. $fields_meta = null,
  58. $field_fulltext = null
  59. ): array {
  60. global $db, $table, $cfg, $col_priv, $is_reload_priv, $mime_map;
  61. Util::checkParameters([
  62. 'server',
  63. 'db',
  64. 'table',
  65. 'action',
  66. 'num_fields',
  67. ]);
  68. $length_values_input_size = 8;
  69. $content_cells = [];
  70. $form_params = ['db' => $db];
  71. if ($action == Url::getFromRoute('/table/create')) {
  72. $form_params['reload'] = 1;
  73. } else {
  74. if ($action == Url::getFromRoute('/table/add-field')) {
  75. $form_params = array_merge(
  76. $form_params,
  77. [
  78. 'field_where' => Util::getValueByKey($_POST, 'field_where'),
  79. ]
  80. );
  81. if (isset($_POST['field_where'])) {
  82. $form_params['after_field'] = $_POST['after_field'];
  83. }
  84. }
  85. $form_params['table'] = $table;
  86. }
  87. $form_params['orig_num_fields'] = $num_fields;
  88. $form_params = array_merge(
  89. $form_params,
  90. [
  91. 'orig_field_where' => Util::getValueByKey($_POST, 'field_where'),
  92. 'orig_after_field' => Util::getValueByKey($_POST, 'after_field'),
  93. ]
  94. );
  95. if (isset($selected) && is_array($selected)) {
  96. foreach ($selected as $o_fld_nr => $o_fld_val) {
  97. $form_params['selected[' . $o_fld_nr . ']'] = $o_fld_val;
  98. }
  99. }
  100. $is_backup = ($action != Url::getFromRoute('/table/create')
  101. && $action != Url::getFromRoute('/table/add-field'));
  102. $cfgRelation = $relation->getRelationsParam();
  103. $comments_map = $relation->getComments($db, $table);
  104. $move_columns = [];
  105. if (isset($fields_meta)) {
  106. $move_columns = $dbi->getTable($db, $table)->getColumnsMeta();
  107. }
  108. $available_mime = [];
  109. if ($cfgRelation['mimework'] && $cfg['BrowseMIME']) {
  110. $mime_map = $transformations->getMime($db, $table);
  111. $available_mime = $transformations->getAvailableMimeTypes();
  112. }
  113. $mime_types = [
  114. 'input_transformation',
  115. 'transformation',
  116. ];
  117. foreach ($mime_types as $mime_type) {
  118. if (! isset($available_mime[$mime_type]) || ! is_iterable($available_mime[$mime_type])) {
  119. continue;
  120. }
  121. foreach ($available_mime[$mime_type] as $mimekey => $transform) {
  122. $available_mime[$mime_type . '_file_quoted'][$mimekey] = preg_quote(
  123. $available_mime[$mime_type . '_file'][$mimekey],
  124. '@'
  125. );
  126. }
  127. }
  128. // workaround for field_fulltext, because its submitted indices contain
  129. // the index as a value, not a key. Inserted here for easier maintenance
  130. // and less code to change in existing files.
  131. if (isset($field_fulltext) && is_array($field_fulltext)) {
  132. foreach ($field_fulltext as $fulltext_nr => $fulltext_indexkey) {
  133. $submit_fulltext[$fulltext_indexkey] = $fulltext_indexkey;
  134. }
  135. }
  136. if (isset($_POST['submit_num_fields'])
  137. || isset($_POST['submit_partition_change'])
  138. ) {
  139. //if adding new fields, set regenerate to keep the original values
  140. $regenerate = 1;
  141. }
  142. $foreigners = $relation->getForeigners($db, $table, '', 'foreign');
  143. $child_references = null;
  144. // From MySQL 5.6.6 onwards columns with foreign keys can be renamed.
  145. // Hence, no need to get child references
  146. if ($dbi->getVersion() < 50606) {
  147. $child_references = $relation->getChildReferences($db, $table);
  148. }
  149. for ($columnNumber = 0; $columnNumber < $num_fields; $columnNumber++) {
  150. $type = '';
  151. $length = '';
  152. $columnMeta = [];
  153. $submit_attribute = null;
  154. $extracted_columnspec = [];
  155. if (! empty($regenerate)) {
  156. $columnMeta = array_merge(
  157. $columnMeta,
  158. [
  159. 'Field' => Util::getValueByKey(
  160. $_POST,
  161. "field_name.${columnNumber}",
  162. null
  163. ),
  164. 'Type' => Util::getValueByKey(
  165. $_POST,
  166. "field_type.${columnNumber}",
  167. null
  168. ),
  169. 'Collation' => Util::getValueByKey(
  170. $_POST,
  171. "field_collation.${columnNumber}",
  172. ''
  173. ),
  174. 'Null' => Util::getValueByKey(
  175. $_POST,
  176. "field_null.${columnNumber}",
  177. ''
  178. ),
  179. 'DefaultType' => Util::getValueByKey(
  180. $_POST,
  181. "field_default_type.${columnNumber}",
  182. 'NONE'
  183. ),
  184. 'DefaultValue' => Util::getValueByKey(
  185. $_POST,
  186. "field_default_value.${columnNumber}",
  187. ''
  188. ),
  189. 'Extra' => Util::getValueByKey(
  190. $_POST,
  191. "field_extra.${columnNumber}",
  192. null
  193. ),
  194. 'Virtuality' => Util::getValueByKey(
  195. $_POST,
  196. "field_virtuality.${columnNumber}",
  197. ''
  198. ),
  199. 'Expression' => Util::getValueByKey(
  200. $_POST,
  201. "field_expression.${columnNumber}",
  202. ''
  203. ),
  204. ]
  205. );
  206. $columnMeta['Key'] = '';
  207. $parts = explode(
  208. '_',
  209. Util::getValueByKey($_POST, "field_key.${columnNumber}", ''),
  210. 2
  211. );
  212. if (count($parts) === 2 && $parts[1] == $columnNumber) {
  213. $columnMeta['Key'] = Util::getValueByKey(
  214. [
  215. 'primary' => 'PRI',
  216. 'index' => 'MUL',
  217. 'unique' => 'UNI',
  218. 'fulltext' => 'FULLTEXT',
  219. 'spatial' => 'SPATIAL',
  220. ],
  221. $parts[0],
  222. ''
  223. );
  224. }
  225. $columnMeta['Comment']
  226. = isset($submit_fulltext[$columnNumber])
  227. && ($submit_fulltext[$columnNumber] == $columnNumber)
  228. ? 'FULLTEXT' : false;
  229. switch ($columnMeta['DefaultType']) {
  230. case 'NONE':
  231. $columnMeta['Default'] = null;
  232. break;
  233. case 'USER_DEFINED':
  234. $columnMeta['Default'] = $columnMeta['DefaultValue'];
  235. break;
  236. case 'NULL':
  237. case 'CURRENT_TIMESTAMP':
  238. case 'current_timestamp()':
  239. $columnMeta['Default'] = $columnMeta['DefaultType'];
  240. break;
  241. }
  242. $length = Util::getValueByKey($_POST, "field_length.${columnNumber}", $length);
  243. $submit_attribute = Util::getValueByKey(
  244. $_POST,
  245. "field_attribute.${columnNumber}",
  246. false
  247. );
  248. $comments_map[$columnMeta['Field']] = Util::getValueByKey(
  249. $_POST,
  250. "field_comments.${columnNumber}"
  251. );
  252. $mime_map[$columnMeta['Field']] = array_merge(
  253. $mime_map[$columnMeta['Field']] ?? [],
  254. [
  255. 'mimetype' => Util::getValueByKey($_POST, "field_mimetype.${columnNumber}"),
  256. 'transformation' => Util::getValueByKey(
  257. $_POST,
  258. "field_transformation.${columnNumber}"
  259. ),
  260. 'transformation_options' => Util::getValueByKey(
  261. $_POST,
  262. "field_transformation_options.${columnNumber}"
  263. ),
  264. ]
  265. );
  266. } elseif (isset($fields_meta[$columnNumber])) {
  267. $columnMeta = $fields_meta[$columnNumber];
  268. $virtual = [
  269. 'VIRTUAL',
  270. 'PERSISTENT',
  271. 'VIRTUAL GENERATED',
  272. 'STORED GENERATED',
  273. ];
  274. if (in_array($columnMeta['Extra'], $virtual)) {
  275. $tableObj = new Table($table, $db);
  276. $expressions = $tableObj->getColumnGenerationExpression(
  277. $columnMeta['Field']
  278. );
  279. $columnMeta['Expression'] = is_array($expressions) ? $expressions[$columnMeta['Field']] : null;
  280. }
  281. switch ($columnMeta['Default']) {
  282. case null:
  283. if ($columnMeta['Default'] === null) {
  284. if ($columnMeta['Null'] === 'YES') {
  285. $columnMeta['DefaultType'] = 'NULL';
  286. $columnMeta['DefaultValue'] = '';
  287. } else {
  288. $columnMeta['DefaultType'] = 'NONE';
  289. $columnMeta['DefaultValue'] = '';
  290. }
  291. } else { // empty
  292. $columnMeta['DefaultType'] = 'USER_DEFINED';
  293. $columnMeta['DefaultValue'] = $columnMeta['Default'];
  294. }
  295. break;
  296. case 'CURRENT_TIMESTAMP':
  297. case 'current_timestamp()':
  298. $columnMeta['DefaultType'] = 'CURRENT_TIMESTAMP';
  299. $columnMeta['DefaultValue'] = '';
  300. break;
  301. default:
  302. $columnMeta['DefaultType'] = 'USER_DEFINED';
  303. $columnMeta['DefaultValue'] = $columnMeta['Default'];
  304. if (substr($columnMeta['Type'], -4) === 'text') {
  305. $textDefault = substr($columnMeta['Default'], 1, -1);
  306. $columnMeta['Default'] = stripcslashes($textDefault);
  307. }
  308. break;
  309. }
  310. }
  311. if (isset($columnMeta['Type'])) {
  312. $extracted_columnspec = Util::extractColumnSpec(
  313. $columnMeta['Type']
  314. );
  315. if ($extracted_columnspec['type'] === 'bit') {
  316. $columnMeta['Default']
  317. = Util::convertBitDefaultValue($columnMeta['Default']);
  318. }
  319. $type = $extracted_columnspec['type'];
  320. if ($length == '') {
  321. $length = $extracted_columnspec['spec_in_brackets'];
  322. }
  323. } else {
  324. // creating a column
  325. $columnMeta['Type'] = '';
  326. }
  327. // Variable tell if current column is bound in a foreign key constraint or not.
  328. // MySQL version from 5.6.6 allow renaming columns with foreign keys
  329. if (isset($columnMeta['Field'], $form_params['table']) && $dbi->getVersion() < 50606) {
  330. $columnMeta['column_status'] = $relation->checkChildForeignReferences(
  331. $form_params['db'],
  332. $form_params['table'],
  333. $columnMeta['Field'],
  334. $foreigners,
  335. $child_references
  336. );
  337. }
  338. // some types, for example longtext, are reported as
  339. // "longtext character set latin7" when their charset and / or collation
  340. // differs from the ones of the corresponding database.
  341. // rtrim the type, for cases like "float unsigned"
  342. $type = rtrim(
  343. preg_replace('/[\s]character set[\s][\S]+/', '', $type)
  344. );
  345. /**
  346. * old column attributes
  347. */
  348. if ($is_backup) {
  349. // old column name
  350. if (isset($columnMeta['Field'])) {
  351. $form_params['field_orig[' . $columnNumber . ']']
  352. = $columnMeta['Field'];
  353. if (isset($columnMeta['column_status'])
  354. && ! $columnMeta['column_status']['isEditable']
  355. ) {
  356. $form_params['field_name[' . $columnNumber . ']']
  357. = $columnMeta['Field'];
  358. }
  359. } else {
  360. $form_params['field_orig[' . $columnNumber . ']'] = '';
  361. }
  362. // old column type
  363. if (isset($columnMeta['Type'])) {
  364. // keep in uppercase because the new type will be in uppercase
  365. $form_params['field_type_orig[' . $columnNumber . ']'] = mb_strtoupper($type);
  366. if (isset($columnMeta['column_status'])
  367. && ! $columnMeta['column_status']['isEditable']
  368. ) {
  369. $form_params['field_type[' . $columnNumber . ']'] = mb_strtoupper($type);
  370. }
  371. } else {
  372. $form_params['field_type_orig[' . $columnNumber . ']'] = '';
  373. }
  374. // old column length
  375. $form_params['field_length_orig[' . $columnNumber . ']'] = $length;
  376. // old column default
  377. $form_params = array_merge(
  378. $form_params,
  379. [
  380. "field_default_value_orig[${columnNumber}]" => Util::getValueByKey(
  381. $columnMeta,
  382. 'Default',
  383. ''
  384. ),
  385. "field_default_type_orig[${columnNumber}]" => Util::getValueByKey(
  386. $columnMeta,
  387. 'DefaultType',
  388. ''
  389. ),
  390. "field_collation_orig[${columnNumber}]" => Util::getValueByKey(
  391. $columnMeta,
  392. 'Collation',
  393. ''
  394. ),
  395. "field_attribute_orig[${columnNumber}]" => trim(
  396. Util::getValueByKey($extracted_columnspec, 'attribute', '')
  397. ),
  398. "field_null_orig[${columnNumber}]" => Util::getValueByKey(
  399. $columnMeta,
  400. 'Null',
  401. ''
  402. ),
  403. "field_extra_orig[${columnNumber}]" => Util::getValueByKey(
  404. $columnMeta,
  405. 'Extra',
  406. ''
  407. ),
  408. "field_comments_orig[${columnNumber}]" => Util::getValueByKey(
  409. $columnMeta,
  410. 'Comment',
  411. ''
  412. ),
  413. "field_virtuality_orig[${columnNumber}]" => Util::getValueByKey(
  414. $columnMeta,
  415. 'Virtuality',
  416. ''
  417. ),
  418. "field_expression_orig[${columnNumber}]" => Util::getValueByKey(
  419. $columnMeta,
  420. 'Expression',
  421. ''
  422. ),
  423. ]
  424. );
  425. }
  426. $default_value = '';
  427. $type_upper = mb_strtoupper($type);
  428. // For a TIMESTAMP, do not show the string "CURRENT_TIMESTAMP" as a default value
  429. if (isset($columnMeta['DefaultValue'])) {
  430. $default_value = $columnMeta['DefaultValue'];
  431. }
  432. if ($type_upper === 'BIT') {
  433. $default_value = Util::convertBitDefaultValue($columnMeta['DefaultValue']);
  434. } elseif ($type_upper === 'BINARY' || $type_upper === 'VARBINARY') {
  435. $default_value = bin2hex($columnMeta['DefaultValue']);
  436. }
  437. $content_cells[$columnNumber] = [
  438. 'column_number' => $columnNumber,
  439. 'column_meta' => $columnMeta,
  440. 'type_upper' => $type_upper,
  441. 'default_value' => $default_value,
  442. 'length_values_input_size' => $length_values_input_size,
  443. 'length' => $length,
  444. 'extracted_columnspec' => $extracted_columnspec,
  445. 'submit_attribute' => $submit_attribute,
  446. 'comments_map' => $comments_map,
  447. 'fields_meta' => $fields_meta ?? null,
  448. 'is_backup' => $is_backup,
  449. 'move_columns' => $move_columns,
  450. 'cfg_relation' => $cfgRelation,
  451. 'available_mime' => $available_mime,
  452. 'mime_map' => $mime_map ?? [],
  453. ];
  454. }
  455. $partitionDetails = TablePartitionDefinition::getDetails();
  456. $charsets = Charsets::getCharsets($dbi, $cfg['Server']['DisableIS']);
  457. $collations = Charsets::getCollations($dbi, $cfg['Server']['DisableIS']);
  458. $charsetsList = [];
  459. /** @var Charset $charset */
  460. foreach ($charsets as $charset) {
  461. $collationsList = [];
  462. /** @var Collation $collation */
  463. foreach ($collations[$charset->getName()] as $collation) {
  464. $collationsList[] = [
  465. 'name' => $collation->getName(),
  466. 'description' => $collation->getDescription(),
  467. ];
  468. }
  469. $charsetsList[] = [
  470. 'name' => $charset->getName(),
  471. 'description' => $charset->getDescription(),
  472. 'collations' => $collationsList,
  473. ];
  474. }
  475. $storageEngines = StorageEngine::getArray();
  476. return [
  477. 'is_backup' => $is_backup,
  478. 'fields_meta' => $fields_meta ?? null,
  479. 'mimework' => $cfgRelation['mimework'],
  480. 'action' => $action,
  481. 'form_params' => $form_params,
  482. 'content_cells' => $content_cells,
  483. 'partition_details' => $partitionDetails,
  484. 'primary_indexes' => $_POST['primary_indexes'] ?? null,
  485. 'unique_indexes' => $_POST['unique_indexes'] ?? null,
  486. 'indexes' => $_POST['indexes'] ?? null,
  487. 'fulltext_indexes' => $_POST['fulltext_indexes'] ?? null,
  488. 'spatial_indexes' => $_POST['spatial_indexes'] ?? null,
  489. 'table' => $_POST['table'] ?? null,
  490. 'comment' => $_POST['comment'] ?? null,
  491. 'tbl_collation' => $_POST['tbl_collation'] ?? null,
  492. 'charsets' => $charsetsList,
  493. 'tbl_storage_engine' => $_POST['tbl_storage_engine'] ?? null,
  494. 'storage_engines' => $storageEngines,
  495. 'connection' => $_POST['connection'] ?? null,
  496. 'change_column' => $_POST['change_column'] ?? $_GET['change_column'] ?? null,
  497. 'is_virtual_columns_supported' => Util::isVirtualColumnsSupported(),
  498. 'browse_mime' => $cfg['BrowseMIME'] ?? null,
  499. 'server_type' => Util::getServerType(),
  500. 'server_version' => $dbi->getVersion(),
  501. 'max_rows' => intval($cfg['MaxRows']),
  502. 'char_editing' => $cfg['CharEditing'] ?? null,
  503. 'attribute_types' => $dbi->types->getAttributes(),
  504. 'privs_available' => ($col_priv ?? false) && ($is_reload_priv ?? false),
  505. 'max_length' => $dbi->getVersion() >= 50503 ? 1024 : 255,
  506. 'have_partitioning' => Partition::havePartitioning(),
  507. 'dbi' => $dbi,
  508. 'disable_is' => $cfg['Server']['DisableIS'],
  509. ];
  510. }
  511. }