PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/classes/CentralColumns.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 1203 lines | 897 code | 58 blank | 248 comment | 89 complexity | 610d1aaebbb2cd4075889abb9ce91eec MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. /**
  3. * Functions for displaying user preferences pages
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin;
  7. use PhpMyAdmin\Charsets\Charset;
  8. use PhpMyAdmin\Charsets\Collation;
  9. use PhpMyAdmin\Html\Generator;
  10. use function array_unique;
  11. use function ceil;
  12. use function count;
  13. use function explode;
  14. use function htmlspecialchars;
  15. use function implode;
  16. use function in_array;
  17. use function is_bool;
  18. use function mb_strtoupper;
  19. use function sprintf;
  20. use function trim;
  21. /**
  22. * PhpMyAdmin\CentralColumns class
  23. */
  24. class CentralColumns
  25. {
  26. /**
  27. * DatabaseInterface instance
  28. *
  29. * @var DatabaseInterface
  30. */
  31. private $dbi;
  32. /**
  33. * Current user
  34. *
  35. * @var string
  36. */
  37. private $user;
  38. /**
  39. * Number of rows displayed when browsing a result set
  40. *
  41. * @var int
  42. */
  43. private $maxRows;
  44. /**
  45. * Which editor should be used for CHAR/VARCHAR fields
  46. *
  47. * @var string
  48. */
  49. private $charEditing;
  50. /**
  51. * Disable use of INFORMATION_SCHEMA
  52. *
  53. * @var bool
  54. */
  55. private $disableIs;
  56. /** @var Relation */
  57. private $relation;
  58. /** @var Template */
  59. public $template;
  60. /**
  61. * @param DatabaseInterface $dbi DatabaseInterface instance
  62. */
  63. public function __construct(DatabaseInterface $dbi)
  64. {
  65. $this->dbi = $dbi;
  66. $this->user = $GLOBALS['cfg']['Server']['user'];
  67. $this->maxRows = (int) $GLOBALS['cfg']['MaxRows'];
  68. $this->charEditing = $GLOBALS['cfg']['CharEditing'];
  69. $this->disableIs = (bool) $GLOBALS['cfg']['Server']['DisableIS'];
  70. $this->relation = new Relation($this->dbi);
  71. $this->template = new Template();
  72. }
  73. /**
  74. * Defines the central_columns parameters for the current user
  75. *
  76. * @return array|bool the central_columns parameters for the current user
  77. *
  78. * @access public
  79. */
  80. public function getParams()
  81. {
  82. static $cfgCentralColumns = null;
  83. if ($cfgCentralColumns !== null) {
  84. return $cfgCentralColumns;
  85. }
  86. $cfgRelation = $this->relation->getRelationsParam();
  87. if ($cfgRelation['centralcolumnswork']) {
  88. $cfgCentralColumns = [
  89. 'user' => $this->user,
  90. 'db' => $cfgRelation['db'],
  91. 'table' => $cfgRelation['central_columns'],
  92. ];
  93. } else {
  94. $cfgCentralColumns = false;
  95. }
  96. return $cfgCentralColumns;
  97. }
  98. /**
  99. * get $num columns of given database from central columns list
  100. * starting at offset $from
  101. *
  102. * @param string $db selected database
  103. * @param int $from starting offset of first result
  104. * @param int $num maximum number of results to return
  105. *
  106. * @return array list of $num columns present in central columns list
  107. * starting at offset $from for the given database
  108. */
  109. public function getColumnsList(string $db, int $from = 0, int $num = 25): array
  110. {
  111. $cfgCentralColumns = $this->getParams();
  112. if (empty($cfgCentralColumns)) {
  113. return [];
  114. }
  115. $pmadb = $cfgCentralColumns['db'];
  116. $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL);
  117. $central_list_table = $cfgCentralColumns['table'];
  118. //get current values of $db from central column list
  119. if ($num == 0) {
  120. $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' '
  121. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';';
  122. } else {
  123. $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' '
  124. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' '
  125. . 'LIMIT ' . $from . ', ' . $num . ';';
  126. }
  127. $has_list = (array) $this->dbi->fetchResult(
  128. $query,
  129. null,
  130. null,
  131. DatabaseInterface::CONNECT_CONTROL
  132. );
  133. $this->handleColumnExtra($has_list);
  134. return $has_list;
  135. }
  136. /**
  137. * Get the number of columns present in central list for given db
  138. *
  139. * @param string $db current database
  140. *
  141. * @return int number of columns in central list of columns for $db
  142. */
  143. public function getCount(string $db): int
  144. {
  145. $cfgCentralColumns = $this->getParams();
  146. if (empty($cfgCentralColumns)) {
  147. return 0;
  148. }
  149. $pmadb = $cfgCentralColumns['db'];
  150. $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL);
  151. $central_list_table = $cfgCentralColumns['table'];
  152. $query = 'SELECT count(db_name) FROM ' .
  153. Util::backquote($central_list_table) . ' '
  154. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';';
  155. $res = $this->dbi->fetchResult(
  156. $query,
  157. null,
  158. null,
  159. DatabaseInterface::CONNECT_CONTROL
  160. );
  161. if (isset($res[0])) {
  162. return (int) $res[0];
  163. }
  164. return 0;
  165. }
  166. /**
  167. * return the existing columns in central list among the given list of columns
  168. *
  169. * @param string $db the selected database
  170. * @param string $cols comma separated list of given columns
  171. * @param bool $allFields set if need all the fields of existing columns,
  172. * otherwise only column_name is returned
  173. *
  174. * @return array list of columns in central columns among given set of columns
  175. */
  176. private function findExistingColNames(
  177. string $db,
  178. string $cols,
  179. bool $allFields = false
  180. ): array {
  181. $cfgCentralColumns = $this->getParams();
  182. if (empty($cfgCentralColumns)) {
  183. return [];
  184. }
  185. $pmadb = $cfgCentralColumns['db'];
  186. $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL);
  187. $central_list_table = $cfgCentralColumns['table'];
  188. if ($allFields) {
  189. $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' '
  190. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');';
  191. $has_list = (array) $this->dbi->fetchResult(
  192. $query,
  193. null,
  194. null,
  195. DatabaseInterface::CONNECT_CONTROL
  196. );
  197. $this->handleColumnExtra($has_list);
  198. } else {
  199. $query = 'SELECT col_name FROM '
  200. . Util::backquote($central_list_table) . ' '
  201. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');';
  202. $has_list = (array) $this->dbi->fetchResult(
  203. $query,
  204. null,
  205. null,
  206. DatabaseInterface::CONNECT_CONTROL
  207. );
  208. }
  209. return $has_list;
  210. }
  211. /**
  212. * return error message to be displayed if central columns
  213. * configuration storage is not completely configured
  214. */
  215. private function configErrorMessage(): Message
  216. {
  217. return Message::error(
  218. __(
  219. 'The configuration storage is not ready for the central list'
  220. . ' of columns feature.'
  221. )
  222. );
  223. }
  224. /**
  225. * build the insert query for central columns list given PMA storage
  226. * db, central_columns table, column name and corresponding definition to be added
  227. *
  228. * @param string $column column to add into central list
  229. * @param array $def list of attributes of the column being added
  230. * @param string $db PMA configuration storage database name
  231. * @param string $central_list_table central columns configuration storage table name
  232. *
  233. * @return string query string to insert the given column
  234. * with definition into central list
  235. */
  236. private function getInsertQuery(
  237. string $column,
  238. array $def,
  239. string $db,
  240. string $central_list_table
  241. ): string {
  242. $type = '';
  243. $length = 0;
  244. $attribute = '';
  245. if (isset($def['Type'])) {
  246. $extracted_columnspec = Util::extractColumnSpec($def['Type']);
  247. $attribute = trim($extracted_columnspec['attribute']);
  248. $type = $extracted_columnspec['type'];
  249. $length = $extracted_columnspec['spec_in_brackets'];
  250. }
  251. if (isset($def['Attribute'])) {
  252. $attribute = $def['Attribute'];
  253. }
  254. $collation = $def['Collation'] ?? '';
  255. $isNull = $def['Null'] == 'NO' ? '0' : '1';
  256. $extra = $def['Extra'] ?? '';
  257. $default = $def['Default'] ?? '';
  258. return 'INSERT INTO '
  259. . Util::backquote($central_list_table) . ' '
  260. . 'VALUES ( \'' . $this->dbi->escapeString($db) . '\' ,'
  261. . '\'' . $this->dbi->escapeString($column) . '\',\''
  262. . $this->dbi->escapeString($type) . '\','
  263. . '\'' . $this->dbi->escapeString((string) $length) . '\',\''
  264. . $this->dbi->escapeString($collation) . '\','
  265. . '\'' . $this->dbi->escapeString($isNull) . '\','
  266. . '\'' . implode(',', [$extra, $attribute])
  267. . '\',\'' . $this->dbi->escapeString($default) . '\');';
  268. }
  269. /**
  270. * If $isTable is true then unique columns from given tables as $field_select
  271. * are added to central list otherwise the $field_select is considered as
  272. * list of columns and these columns are added to central list if not already added
  273. *
  274. * @param array $field_select if $isTable is true selected tables list
  275. * otherwise selected columns list
  276. * @param bool $isTable if passed array is of tables or columns
  277. * @param string $table if $isTable is false, then table name to
  278. * which columns belong
  279. *
  280. * @return true|Message
  281. */
  282. public function syncUniqueColumns(
  283. array $field_select,
  284. bool $isTable = true,
  285. ?string $table = null
  286. ) {
  287. $cfgCentralColumns = $this->getParams();
  288. if (empty($cfgCentralColumns)) {
  289. return $this->configErrorMessage();
  290. }
  291. $db = $_POST['db'];
  292. $pmadb = $cfgCentralColumns['db'];
  293. $central_list_table = $cfgCentralColumns['table'];
  294. $this->dbi->selectDb($db);
  295. $existingCols = [];
  296. $cols = '';
  297. $insQuery = [];
  298. $fields = [];
  299. $message = true;
  300. if ($isTable) {
  301. foreach ($field_select as $table) {
  302. $fields[$table] = (array) $this->dbi->getColumns(
  303. $db,
  304. $table,
  305. null,
  306. true
  307. );
  308. foreach ($fields[$table] as $field => $def) {
  309. $cols .= "'" . $this->dbi->escapeString($field) . "',";
  310. }
  311. }
  312. $has_list = $this->findExistingColNames($db, trim($cols, ','));
  313. foreach ($field_select as $table) {
  314. foreach ($fields[$table] as $field => $def) {
  315. if (! in_array($field, $has_list)) {
  316. $has_list[] = $field;
  317. $insQuery[] = $this->getInsertQuery(
  318. $field,
  319. $def,
  320. $db,
  321. $central_list_table
  322. );
  323. } else {
  324. $existingCols[] = "'" . $field . "'";
  325. }
  326. }
  327. }
  328. } else {
  329. if ($table === null) {
  330. $table = $_POST['table'];
  331. }
  332. foreach ($field_select as $column) {
  333. $cols .= "'" . $this->dbi->escapeString($column) . "',";
  334. }
  335. $has_list = $this->findExistingColNames($db, trim($cols, ','));
  336. foreach ($field_select as $column) {
  337. if (! in_array($column, $has_list)) {
  338. $has_list[] = $column;
  339. $field = (array) $this->dbi->getColumns(
  340. $db,
  341. $table,
  342. $column,
  343. true
  344. );
  345. $insQuery[] = $this->getInsertQuery(
  346. $column,
  347. $field,
  348. $db,
  349. $central_list_table
  350. );
  351. } else {
  352. $existingCols[] = "'" . $column . "'";
  353. }
  354. }
  355. }
  356. if (! empty($existingCols)) {
  357. $existingCols = implode(',', array_unique($existingCols));
  358. $message = Message::notice(
  359. sprintf(
  360. __(
  361. 'Could not add %1$s as they already exist in central list!'
  362. ),
  363. htmlspecialchars($existingCols)
  364. )
  365. );
  366. $message->addMessage(
  367. Message::notice(
  368. 'Please remove them first '
  369. . 'from central list if you want to update above columns'
  370. )
  371. );
  372. }
  373. $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL);
  374. if (! empty($insQuery)) {
  375. foreach ($insQuery as $query) {
  376. if (! $this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) {
  377. $message = Message::error(__('Could not add columns!'));
  378. $message->addMessage(
  379. Message::rawError(
  380. $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL)
  381. )
  382. );
  383. break;
  384. }
  385. }
  386. }
  387. return $message;
  388. }
  389. /**
  390. * if $isTable is true it removes all columns of given tables as $field_select from
  391. * central columns list otherwise $field_select is columns list and it removes
  392. * given columns if present in central list
  393. *
  394. * @param string $database Database name
  395. * @param array $field_select if $isTable selected list of tables otherwise
  396. * selected list of columns to remove from central list
  397. * @param bool $isTable if passed array is of tables or columns
  398. *
  399. * @return true|Message
  400. */
  401. public function deleteColumnsFromList(
  402. string $database,
  403. array $field_select,
  404. bool $isTable = true
  405. ) {
  406. $cfgCentralColumns = $this->getParams();
  407. if (empty($cfgCentralColumns)) {
  408. return $this->configErrorMessage();
  409. }
  410. $pmadb = $cfgCentralColumns['db'];
  411. $central_list_table = $cfgCentralColumns['table'];
  412. $this->dbi->selectDb($database);
  413. $message = true;
  414. $colNotExist = [];
  415. $fields = [];
  416. if ($isTable) {
  417. $cols = '';
  418. foreach ($field_select as $table) {
  419. $fields[$table] = (array) $this->dbi->getColumnNames(
  420. $database,
  421. $table
  422. );
  423. foreach ($fields[$table] as $col_select) {
  424. $cols .= '\'' . $this->dbi->escapeString($col_select) . '\',';
  425. }
  426. }
  427. $cols = trim($cols, ',');
  428. $has_list = $this->findExistingColNames($database, $cols);
  429. foreach ($field_select as $table) {
  430. foreach ($fields[$table] as $column) {
  431. if (! in_array($column, $has_list)) {
  432. $colNotExist[] = "'" . $column . "'";
  433. }
  434. }
  435. }
  436. } else {
  437. $cols = '';
  438. foreach ($field_select as $col_select) {
  439. $cols .= '\'' . $this->dbi->escapeString($col_select) . '\',';
  440. }
  441. $cols = trim($cols, ',');
  442. $has_list = $this->findExistingColNames($database, $cols);
  443. foreach ($field_select as $column) {
  444. if (! in_array($column, $has_list)) {
  445. $colNotExist[] = "'" . $column . "'";
  446. }
  447. }
  448. }
  449. if (! empty($colNotExist)) {
  450. $colNotExist = implode(',', array_unique($colNotExist));
  451. $message = Message::notice(
  452. sprintf(
  453. __(
  454. 'Couldn\'t remove Column(s) %1$s '
  455. . 'as they don\'t exist in central columns list!'
  456. ),
  457. htmlspecialchars($colNotExist)
  458. )
  459. );
  460. }
  461. $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL);
  462. $query = 'DELETE FROM ' . Util::backquote($central_list_table) . ' '
  463. . 'WHERE db_name = \'' . $this->dbi->escapeString($database) . '\' AND col_name IN (' . $cols . ');';
  464. if (! $this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) {
  465. $message = Message::error(__('Could not remove columns!'));
  466. $message->addHtml('<br>' . htmlspecialchars($cols) . '<br>');
  467. $message->addMessage(
  468. Message::rawError(
  469. $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL)
  470. )
  471. );
  472. }
  473. return $message;
  474. }
  475. /**
  476. * Make the columns of given tables consistent with central list of columns.
  477. * Updates only those columns which are not being referenced.
  478. *
  479. * @param string $db current database
  480. * @param array $selected_tables list of selected tables.
  481. *
  482. * @return true|Message
  483. */
  484. public function makeConsistentWithList(
  485. string $db,
  486. array $selected_tables
  487. ) {
  488. $message = true;
  489. foreach ($selected_tables as $table) {
  490. $query = 'ALTER TABLE ' . Util::backquote($table);
  491. $has_list = $this->getFromTable($db, $table, true);
  492. $this->dbi->selectDb($db);
  493. foreach ($has_list as $column) {
  494. $column_status = $this->relation->checkChildForeignReferences(
  495. $db,
  496. $table,
  497. $column['col_name']
  498. );
  499. //column definition can only be changed if
  500. //it is not referenced by another column
  501. if ($column_status['isEditable']) {
  502. $query .= ' MODIFY ' . Util::backquote($column['col_name']) . ' '
  503. . $this->dbi->escapeString($column['col_type']);
  504. if ($column['col_length']) {
  505. $query .= '(' . $column['col_length'] . ')';
  506. }
  507. $query .= ' ' . $column['col_attribute'];
  508. if ($column['col_isNull']) {
  509. $query .= ' NULL';
  510. } else {
  511. $query .= ' NOT NULL';
  512. }
  513. $query .= ' ' . $column['col_extra'];
  514. if ($column['col_default']) {
  515. if ($column['col_default'] != 'CURRENT_TIMESTAMP'
  516. && $column['col_default'] != 'current_timestamp()') {
  517. $query .= ' DEFAULT \'' . $this->dbi->escapeString(
  518. (string) $column['col_default']
  519. ) . '\'';
  520. } else {
  521. $query .= ' DEFAULT ' . $this->dbi->escapeString(
  522. $column['col_default']
  523. );
  524. }
  525. }
  526. $query .= ',';
  527. }
  528. }
  529. $query = trim($query, ' ,') . ';';
  530. if (! $this->dbi->tryQuery($query)) {
  531. if ($message === true) {
  532. $message = Message::error(
  533. $this->dbi->getError()
  534. );
  535. } else {
  536. $message->addText(
  537. $this->dbi->getError(),
  538. '<br>'
  539. );
  540. }
  541. }
  542. }
  543. return $message;
  544. }
  545. /**
  546. * return the columns present in central list of columns for a given
  547. * table of a given database
  548. *
  549. * @param string $db given database
  550. * @param string $table given table
  551. * @param bool $allFields set if need all the fields of existing columns,
  552. * otherwise only column_name is returned
  553. *
  554. * @return array columns present in central list from given table of given db.
  555. */
  556. public function getFromTable(
  557. string $db,
  558. string $table,
  559. bool $allFields = false
  560. ): array {
  561. $cfgCentralColumns = $this->getParams();
  562. if (empty($cfgCentralColumns)) {
  563. return [];
  564. }
  565. $this->dbi->selectDb($db);
  566. $fields = (array) $this->dbi->getColumnNames(
  567. $db,
  568. $table
  569. );
  570. $cols = '';
  571. foreach ($fields as $col_select) {
  572. $cols .= '\'' . $this->dbi->escapeString((string) $col_select) . '\',';
  573. }
  574. $cols = trim($cols, ',');
  575. $has_list = $this->findExistingColNames($db, $cols, $allFields);
  576. if (! empty($has_list)) {
  577. return (array) $has_list;
  578. }
  579. return [];
  580. }
  581. /**
  582. * update a column in central columns list if a edit is requested
  583. *
  584. * @param string $db current database
  585. * @param string $orig_col_name original column name before edit
  586. * @param string $col_name new column name
  587. * @param string $col_type new column type
  588. * @param string $col_attribute new column attribute
  589. * @param string $col_length new column length
  590. * @param int $col_isNull value 1 if new column isNull is true, 0 otherwise
  591. * @param string $collation new column collation
  592. * @param string $col_extra new column extra property
  593. * @param string $col_default new column default value
  594. *
  595. * @return true|Message
  596. */
  597. public function updateOneColumn(
  598. string $db,
  599. string $orig_col_name,
  600. string $col_name,
  601. string $col_type,
  602. string $col_attribute,
  603. string $col_length,
  604. int $col_isNull,
  605. string $collation,
  606. string $col_extra,
  607. string $col_default
  608. ) {
  609. $cfgCentralColumns = $this->getParams();
  610. if (empty($cfgCentralColumns)) {
  611. return $this->configErrorMessage();
  612. }
  613. $centralTable = $cfgCentralColumns['table'];
  614. $this->dbi->selectDb($cfgCentralColumns['db'], DatabaseInterface::CONNECT_CONTROL);
  615. if ($orig_col_name == '') {
  616. $def = [];
  617. $def['Type'] = $col_type;
  618. if ($col_length) {
  619. $def['Type'] .= '(' . $col_length . ')';
  620. }
  621. $def['Collation'] = $collation;
  622. $def['Null'] = $col_isNull ? __('YES') : __('NO');
  623. $def['Extra'] = $col_extra;
  624. $def['Attribute'] = $col_attribute;
  625. $def['Default'] = $col_default;
  626. $query = $this->getInsertQuery($col_name, $def, $db, $centralTable);
  627. } else {
  628. $query = 'UPDATE ' . Util::backquote($centralTable)
  629. . ' SET col_type = \'' . $this->dbi->escapeString($col_type) . '\''
  630. . ', col_name = \'' . $this->dbi->escapeString($col_name) . '\''
  631. . ', col_length = \'' . $this->dbi->escapeString($col_length) . '\''
  632. . ', col_isNull = ' . $col_isNull
  633. . ', col_collation = \'' . $this->dbi->escapeString($collation) . '\''
  634. . ', col_extra = \''
  635. . implode(',', [$col_extra, $col_attribute]) . '\''
  636. . ', col_default = \'' . $this->dbi->escapeString($col_default) . '\''
  637. . ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' '
  638. . 'AND col_name = \'' . $this->dbi->escapeString($orig_col_name)
  639. . '\'';
  640. }
  641. if (! $this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) {
  642. return Message::error(
  643. $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL)
  644. );
  645. }
  646. return true;
  647. }
  648. /**
  649. * Update Multiple column in central columns list if a change is requested
  650. *
  651. * @param array $params Request parameters
  652. *
  653. * @return true|Message
  654. */
  655. public function updateMultipleColumn(array $params)
  656. {
  657. $columnDefault = $params['field_default_type'];
  658. $columnIsNull = [];
  659. $columnExtra = [];
  660. $numberCentralFields = count($params['orig_col_name']);
  661. for ($i = 0; $i < $numberCentralFields; $i++) {
  662. $columnIsNull[$i] = isset($params['field_null'][$i]) ? 1 : 0;
  663. $columnExtra[$i] = $params['col_extra'][$i] ?? '';
  664. if ($columnDefault[$i] === 'NONE') {
  665. $columnDefault[$i] = '';
  666. } elseif ($columnDefault[$i] === 'USER_DEFINED') {
  667. $columnDefault[$i] = $params['field_default_value'][$i];
  668. }
  669. $message = $this->updateOneColumn(
  670. $params['db'],
  671. $params['orig_col_name'][$i],
  672. $params['field_name'][$i],
  673. $params['field_type'][$i],
  674. $params['field_attribute'][$i],
  675. $params['field_length'][$i],
  676. $columnIsNull[$i],
  677. $params['field_collation'][$i],
  678. $columnExtra[$i],
  679. $columnDefault[$i]
  680. );
  681. if (! is_bool($message)) {
  682. return $message;
  683. }
  684. }
  685. return true;
  686. }
  687. /**
  688. * Function generate and return the table header for
  689. * multiple edit central columns page
  690. *
  691. * @param array $headers headers list
  692. *
  693. * @return string html for table header in central columns multi edit page
  694. */
  695. private function getEditTableHeader(array $headers): string
  696. {
  697. return $this->template->render('database/central_columns/edit_table_header', [
  698. 'headers' => $headers,
  699. ]);
  700. }
  701. /**
  702. * build html for editing a row in central columns table
  703. *
  704. * @param array $row array contains complete information of a
  705. * particular row of central list table
  706. * @param int $row_num position the row in the table
  707. *
  708. * @return string html of a particular row in the central columns table.
  709. */
  710. private function getHtmlForEditTableRow(array $row, int $row_num): string
  711. {
  712. $tableHtml = '<tr>'
  713. . '<input name="orig_col_name[' . $row_num . ']" type="hidden" '
  714. . 'value="' . htmlspecialchars($row['col_name']) . '">'
  715. . '<td name="col_name" class="nowrap">'
  716. . $this->template->render('columns_definitions/column_name', [
  717. 'column_number' => $row_num,
  718. 'ci' => 0,
  719. 'ci_offset' => 0,
  720. 'column_meta' => [
  721. 'Field' => $row['col_name'],
  722. ],
  723. 'cfg_relation' => [
  724. 'centralcolumnswork' => false,
  725. ],
  726. 'max_rows' => $this->maxRows,
  727. ])
  728. . '</td>';
  729. $tableHtml .=
  730. '<td name = "col_type" class="nowrap">'
  731. . $this->template->render('columns_definitions/column_type', [
  732. 'column_number' => $row_num,
  733. 'ci' => 1,
  734. 'ci_offset' => 0,
  735. 'type_upper' => mb_strtoupper($row['col_type']),
  736. 'column_meta' => [],
  737. ])
  738. . '</td>';
  739. $tableHtml .=
  740. '<td class="nowrap" name="col_length">'
  741. . $this->template->render('columns_definitions/column_length', [
  742. 'column_number' => $row_num,
  743. 'ci' => 2,
  744. 'ci_offset' => 0,
  745. 'length_values_input_size' => 8,
  746. 'length_to_display' => $row['col_length'],
  747. ])
  748. . '</td>';
  749. $meta = [];
  750. if (! isset($row['col_default']) || $row['col_default'] == '') {
  751. $meta['DefaultType'] = 'NONE';
  752. } elseif ($row['col_default'] == 'CURRENT_TIMESTAMP'
  753. || $row['col_default'] == 'current_timestamp()'
  754. ) {
  755. $meta['DefaultType'] = 'CURRENT_TIMESTAMP';
  756. } elseif ($row['col_default'] == 'NULL') {
  757. $meta['DefaultType'] = $row['col_default'];
  758. } else {
  759. $meta['DefaultType'] = 'USER_DEFINED';
  760. $meta['DefaultValue'] = $row['col_default'];
  761. }
  762. $tableHtml .=
  763. '<td class="nowrap" name="col_default">'
  764. . $this->template->render('columns_definitions/column_default', [
  765. 'column_number' => $row_num,
  766. 'ci' => 3,
  767. 'ci_offset' => 0,
  768. 'type_upper' => mb_strtoupper((string) $row['col_default']),
  769. 'column_meta' => $meta,
  770. 'char_editing' => $this->charEditing,
  771. ])
  772. . '</td>';
  773. $tableHtml .= '<td name="collation" class="nowrap">';
  774. $tableHtml .= '<select lang="en" dir="ltr" name="field_collation[' . $row_num . ']"';
  775. $tableHtml .= ' id="field_' . $row_num . '_4">' . "\n";
  776. $tableHtml .= '<option value=""></option>' . "\n";
  777. $charsets = Charsets::getCharsets($this->dbi, $this->disableIs);
  778. $collations = Charsets::getCollations($this->dbi, $this->disableIs);
  779. /** @var Charset $charset */
  780. foreach ($charsets as $charset) {
  781. $tableHtml .= '<optgroup label="' . $charset->getName()
  782. . '" title="' . $charset->getDescription() . '">' . "\n";
  783. /** @var Collation $collation */
  784. foreach ($collations[$charset->getName()] as $collation) {
  785. $tableHtml .= '<option value="' . $collation->getName()
  786. . '" title="' . $collation->getDescription() . '"'
  787. . ($row['col_collation'] == $collation->getName() ? ' selected' : '') . '>'
  788. . $collation->getName() . '</option>' . "\n";
  789. }
  790. $tableHtml .= '</optgroup>' . "\n";
  791. }
  792. $tableHtml .= '</select>' . "\n";
  793. $tableHtml .= '</td>';
  794. $tableHtml .=
  795. '<td class="nowrap" name="col_attribute">'
  796. . $this->template->render('columns_definitions/column_attribute', [
  797. 'column_number' => $row_num,
  798. 'ci' => 5,
  799. 'ci_offset' => 0,
  800. 'extracted_columnspec' => [
  801. 'attribute' => $row['col_attribute'],
  802. ],
  803. 'column_meta' => [],
  804. 'submit_attribute' => false,
  805. 'attribute_types' => $this->dbi->types->getAttributes(),
  806. ])
  807. . '</td>';
  808. $tableHtml .=
  809. '<td class="nowrap" name="col_isNull">'
  810. . $this->template->render('columns_definitions/column_null', [
  811. 'column_number' => $row_num,
  812. 'ci' => 6,
  813. 'ci_offset' => 0,
  814. 'column_meta' => [
  815. 'Null' => $row['col_isNull'],
  816. ],
  817. ])
  818. . '</td>';
  819. $tableHtml .=
  820. '<td class="nowrap" name="col_extra">'
  821. . $this->template->render('columns_definitions/column_extra', [
  822. 'column_number' => $row_num,
  823. 'ci' => 7,
  824. 'ci_offset' => 0,
  825. 'column_meta' => ['Extra' => $row['col_extra']],
  826. ])
  827. . '</td>';
  828. $tableHtml .= '</tr>';
  829. return $tableHtml;
  830. }
  831. /**
  832. * get the list of columns in given database excluding
  833. * the columns present in current table
  834. *
  835. * @param string $db selected database
  836. * @param string $table current table name
  837. *
  838. * @return array encoded list of columns present in central list for the given
  839. * database
  840. */
  841. public function getListRaw(string $db, string $table): array
  842. {
  843. $cfgCentralColumns = $this->getParams();
  844. if (empty($cfgCentralColumns)) {
  845. return [];
  846. }
  847. $centralTable = $cfgCentralColumns['table'];
  848. if (empty($table) || $table == '') {
  849. $query = 'SELECT * FROM ' . Util::backquote($centralTable) . ' '
  850. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';';
  851. } else {
  852. $this->dbi->selectDb($db);
  853. $columns = (array) $this->dbi->getColumnNames(
  854. $db,
  855. $table
  856. );
  857. $cols = '';
  858. foreach ($columns as $col_select) {
  859. $cols .= '\'' . $this->dbi->escapeString($col_select) . '\',';
  860. }
  861. $cols = trim($cols, ',');
  862. $query = 'SELECT * FROM ' . Util::backquote($centralTable) . ' '
  863. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
  864. if ($cols) {
  865. $query .= ' AND col_name NOT IN (' . $cols . ')';
  866. }
  867. $query .= ';';
  868. }
  869. $this->dbi->selectDb($cfgCentralColumns['db'], DatabaseInterface::CONNECT_CONTROL);
  870. $columns_list = (array) $this->dbi->fetchResult(
  871. $query,
  872. null,
  873. null,
  874. DatabaseInterface::CONNECT_CONTROL
  875. );
  876. $this->handleColumnExtra($columns_list);
  877. return $columns_list;
  878. }
  879. /**
  880. * Get HTML for "check all" check box with "with selected" dropdown
  881. *
  882. * @param string $pmaThemeImage pma theme image url
  883. * @param string $text_dir url for text directory
  884. */
  885. public function getTableFooter(string $pmaThemeImage, string $text_dir): string
  886. {
  887. $html_output = $this->template->render('select_all', [
  888. 'pma_theme_image' => $pmaThemeImage,
  889. 'text_dir' => $text_dir,
  890. 'form_name' => 'tableslistcontainer',
  891. ]);
  892. $html_output .= Generator::getButtonOrImage(
  893. 'edit_central_columns',
  894. 'mult_submit change_central_columns',
  895. __('Edit'),
  896. 'b_edit',
  897. 'edit central columns'
  898. );
  899. $html_output .= Generator::getButtonOrImage(
  900. 'delete_central_columns',
  901. 'mult_submit',
  902. __('Delete'),
  903. 'b_drop',
  904. 'remove_from_central_columns'
  905. );
  906. return $html_output;
  907. }
  908. /**
  909. * function generate and return the table footer for
  910. * multiple edit central columns page
  911. *
  912. * @return string html for table footer in central columns multi edit page
  913. */
  914. private function getEditTableFooter(): string
  915. {
  916. return '<fieldset class="tblFooters">'
  917. . '<input class="btn btn-primary" type="submit" '
  918. . 'name="save_multi_central_column_edit" value="' . __('Save') . '">'
  919. . '</fieldset>';
  920. }
  921. /**
  922. * Column `col_extra` is used to store both extra and attributes for a column.
  923. * This method separates them.
  924. *
  925. * @param array $columns_list columns list
  926. */
  927. private function handleColumnExtra(array &$columns_list): void
  928. {
  929. foreach ($columns_list as &$row) {
  930. $vals = explode(',', $row['col_extra']);
  931. if (in_array('BINARY', $vals)) {
  932. $row['col_attribute'] = 'BINARY';
  933. } elseif (in_array('UNSIGNED', $vals)) {
  934. $row['col_attribute'] = 'UNSIGNED';
  935. } elseif (in_array('UNSIGNED ZEROFILL', $vals)) {
  936. $row['col_attribute'] = 'UNSIGNED ZEROFILL';
  937. } elseif (in_array('on update CURRENT_TIMESTAMP', $vals)) {
  938. $row['col_attribute'] = 'on update CURRENT_TIMESTAMP';
  939. } else {
  940. $row['col_attribute'] = '';
  941. }
  942. if (in_array('auto_increment', $vals)) {
  943. $row['col_extra'] = 'auto_increment';
  944. } else {
  945. $row['col_extra'] = '';
  946. }
  947. }
  948. }
  949. /**
  950. * Get HTML for editing page central columns
  951. *
  952. * @param array $selected_fld Array containing the selected fields
  953. * @param string $selected_db String containing the name of database
  954. *
  955. * @return string HTML for complete editing page for central columns
  956. */
  957. public function getHtmlForEditingPage(array $selected_fld, string $selected_db): string
  958. {
  959. $html = '<form id="multi_edit_central_columns">';
  960. $header_cells = [
  961. __('Name'),
  962. __('Type'),
  963. __('Length/Values'),
  964. __('Default'),
  965. __('Collation'),
  966. __('Attributes'),
  967. __('Null'),
  968. __('A_I'),
  969. ];
  970. $html .= $this->getEditTableHeader($header_cells);
  971. $selected_fld_safe = [];
  972. foreach ($selected_fld as $key) {
  973. $selected_fld_safe[] = $this->dbi->escapeString($key);
  974. }
  975. $columns_list = implode("','", $selected_fld_safe);
  976. $columns_list = "'" . $columns_list . "'";
  977. $list_detail_cols = $this->findExistingColNames($selected_db, $columns_list, true);
  978. $row_num = 0;
  979. foreach ($list_detail_cols as $row) {
  980. $tableHtmlRow = $this->getHtmlForEditTableRow(
  981. $row,
  982. $row_num
  983. );
  984. $html .= $tableHtmlRow;
  985. $row_num++;
  986. }
  987. $html .= '</table>';
  988. $html .= $this->getEditTableFooter();
  989. $html .= '</form>';
  990. return $html;
  991. }
  992. /**
  993. * get number of columns of given database from central columns list
  994. * starting at offset $from
  995. *
  996. * @param string $db selected database
  997. * @param int $from starting offset of first result
  998. * @param int $num maximum number of results to return
  999. *
  1000. * @return int count of $num columns present in central columns list
  1001. * starting at offset $from for the given database
  1002. */
  1003. public function getColumnsCount(string $db, int $from = 0, int $num = 25): int
  1004. {
  1005. $cfgCentralColumns = $this->getParams();
  1006. if (empty($cfgCentralColumns)) {
  1007. return 0;
  1008. }
  1009. $pmadb = $cfgCentralColumns['db'];
  1010. $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL);
  1011. $central_list_table = $cfgCentralColumns['table'];
  1012. //get current values of $db from central column list
  1013. $query = 'SELECT COUNT(db_name) FROM ' . Util::backquote($central_list_table) . ' '
  1014. . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'' .
  1015. ($num === 0 ? '' : 'LIMIT ' . $from . ', ' . $num) . ';';
  1016. $result = (array) $this->dbi->fetchResult(
  1017. $query,
  1018. null,
  1019. null,
  1020. DatabaseInterface::CONNECT_CONTROL
  1021. );
  1022. if (isset($result[0])) {
  1023. return (int) $result[0];
  1024. }
  1025. return -1;
  1026. }
  1027. /**
  1028. * build dropdown select html to select column in selected table,
  1029. * include only columns which are not already in central list
  1030. *
  1031. * @param string $db current database to which selected table belongs
  1032. * @param string $selected_tbl selected table
  1033. *
  1034. * @return string html to select column
  1035. */
  1036. public function getHtmlForColumnDropdown($db, $selected_tbl)
  1037. {
  1038. $existing_cols = $this->getFromTable($db, $selected_tbl);
  1039. $this->dbi->selectDb($db);
  1040. $columns = (array) $this->dbi->getColumnNames(
  1041. $db,
  1042. $selected_tbl
  1043. );
  1044. $selectColHtml = '';
  1045. foreach ($columns as $column) {
  1046. if (! in_array($column, $existing_cols)) {
  1047. $selectColHtml .= '<option value="' . htmlspecialchars($column) . '">'
  1048. . htmlspecialchars($column)
  1049. . '</option>';
  1050. }
  1051. }
  1052. return $selectColHtml;
  1053. }
  1054. /**
  1055. * build html for adding a new user defined column to central list
  1056. *
  1057. * @param string $db current database
  1058. * @param int $total_rows number of rows in central columns
  1059. * @param int $pos offset of first result with complete result set
  1060. * @param string $pmaThemeImage table footer theme image directorie
  1061. * @param string $text_dir table footer arrow direction
  1062. *
  1063. * @return string html of the form to let user add a new user defined column to the
  1064. * list
  1065. */
  1066. public function getHtmlForMain(
  1067. string $db,
  1068. int $total_rows,
  1069. int $pos,
  1070. string $pmaThemeImage,
  1071. string $text_dir
  1072. ): string {
  1073. $max_rows = $this->maxRows;
  1074. $attribute_types = $this->dbi->types->getAttributes();
  1075. $tn_pageNow = ($pos / $this->maxRows) + 1;
  1076. $tn_nbTotalPage = ceil($total_rows / $this->maxRows);
  1077. $tn_page_selector = $tn_nbTotalPage > 1 ? Util::pageselector(
  1078. 'pos',
  1079. $this->maxRows,
  1080. $tn_pageNow,
  1081. $tn_nbTotalPage
  1082. ) : '';
  1083. $this->dbi->selectDb($db);
  1084. $tables = $this->dbi->getTables($db);
  1085. $rows_list = $this->getColumnsList($db, $pos, $max_rows);
  1086. $rows_meta = [];
  1087. $types_upper = [];
  1088. $row_num = 0;
  1089. foreach ($rows_list as $row) {
  1090. $rows_meta[$row_num] = [];
  1091. if (! isset($row['col_default']) || $row['col_default'] == '') {
  1092. $rows_meta[$row_num]['DefaultType'] = 'NONE';
  1093. } else {
  1094. if ($row['col_default'] == 'CURRENT_TIMESTAMP'
  1095. || $row['col_default'] == 'current_timestamp()'
  1096. ) {
  1097. $rows_meta[$row_num]['DefaultType'] = 'CURRENT_TIMESTAMP';
  1098. } elseif ($row['col_default'] == 'NULL') {
  1099. $rows_meta[$row_num]['DefaultType'] = $row['col_default'];
  1100. } else {
  1101. $rows_meta[$row_num]['DefaultType'] = 'USER_DEFINED';
  1102. $rows_meta[$row_num]['DefaultValue'] = $row['col_default'];
  1103. }
  1104. }
  1105. $types_upper[$row_num] = mb_strtoupper((string) $row['col_type']);
  1106. $row_num++;
  1107. }
  1108. $charsets = Charsets::getCharsets($this->dbi, $this->disableIs);
  1109. $collations = Charsets::getCollations($this->dbi, $this->disableIs);
  1110. $charsetsList = [];
  1111. /** @var Charset $charset */
  1112. foreach ($charsets as $charset) {
  1113. $collationsList = [];
  1114. /** @var Collation $collation */
  1115. foreach ($collations[$charset->getName()] as $collation) {
  1116. $collationsList[] = [
  1117. 'name' => $collation->getName(),
  1118. 'description' => $collation->getDescription(),
  1119. ];
  1120. }
  1121. $charsetsList[] = [
  1122. 'name' => $charset->getName(),
  1123. 'description' => $charset->getDescription(),
  1124. 'collations' => $collationsList,
  1125. ];
  1126. }
  1127. return $this->template->render('database/central_columns/main', [
  1128. 'db' => $db,
  1129. 'total_rows' => $total_rows,
  1130. 'max_rows' => $max_rows,
  1131. 'pos' => $pos,
  1132. 'char_editing' => $this->charEditing,
  1133. 'attribute_types' => $attribute_types,
  1134. 'tn_nbTotalPage' => $tn_nbTotalPage,
  1135. 'tn_page_selector' => $tn_page_selector,
  1136. 'tables' => $tables,
  1137. 'rows_list' => $rows_list,
  1138. 'rows_meta' => $rows_meta,
  1139. 'types_upper' => $types_upper,
  1140. 'pmaThemeImage' => $pmaThemeImage,
  1141. 'text_dir' => $text_dir,
  1142. 'charsets' => $charsetsList,
  1143. ]);
  1144. }
  1145. }