PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/classes/Table/ColumnsDefinition.php

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