CentralColumns.php 38 KB

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