PageRenderTime 32ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/classes/Database/Designer/Common.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 843 lines | 567 code | 110 blank | 166 comment | 61 complexity | 75490a5691fe76ffdeba960c8b786b59 MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Database\Designer;
  4. use PhpMyAdmin\DatabaseInterface;
  5. use PhpMyAdmin\Index;
  6. use PhpMyAdmin\Query\Generator as QueryGenerator;
  7. use PhpMyAdmin\Relation;
  8. use PhpMyAdmin\Table;
  9. use PhpMyAdmin\Util;
  10. use PhpMyAdmin\Utils\ForeignKey;
  11. use function __;
  12. use function _pgettext;
  13. use function count;
  14. use function explode;
  15. use function in_array;
  16. use function intval;
  17. use function is_array;
  18. use function is_string;
  19. use function json_decode;
  20. use function json_encode;
  21. use function mb_strtoupper;
  22. use function rawurlencode;
  23. /**
  24. * Common functions for Designer
  25. */
  26. class Common
  27. {
  28. /** @var Relation */
  29. private $relation;
  30. /** @var DatabaseInterface */
  31. private $dbi;
  32. /**
  33. * @param DatabaseInterface $dbi DatabaseInterface object
  34. * @param Relation $relation Relation instance
  35. */
  36. public function __construct(DatabaseInterface $dbi, Relation $relation)
  37. {
  38. $this->dbi = $dbi;
  39. $this->relation = $relation;
  40. }
  41. /**
  42. * Retrieves table info and returns it
  43. *
  44. * @param string $db (optional) Filter only a DB ($table is required if you use $db)
  45. * @param string $table (optional) Filter only a table ($db is now required)
  46. *
  47. * @return DesignerTable[] with table info
  48. */
  49. public function getTablesInfo(?string $db = null, ?string $table = null): array
  50. {
  51. $designerTables = [];
  52. $db = $db ?? $GLOBALS['db'];
  53. // seems to be needed later
  54. $this->dbi->selectDb($db);
  55. if ($table === null) {
  56. $tables = $this->dbi->getTablesFull($db);
  57. } else {
  58. $tables = $this->dbi->getTablesFull($db, $table);
  59. }
  60. foreach ($tables as $one_table) {
  61. $DF = $this->relation->getDisplayField($db, $one_table['TABLE_NAME']);
  62. $DF = is_string($DF) ? $DF : '';
  63. $DF = $DF !== '' ? $DF : null;
  64. $designerTables[] = new DesignerTable(
  65. $db,
  66. $one_table['TABLE_NAME'],
  67. is_string($one_table['ENGINE']) ? $one_table['ENGINE'] : '',
  68. $DF
  69. );
  70. }
  71. return $designerTables;
  72. }
  73. /**
  74. * Retrieves table column info
  75. *
  76. * @param DesignerTable[] $designerTables The designer tables
  77. *
  78. * @return array table column nfo
  79. */
  80. public function getColumnsInfo(array $designerTables): array
  81. {
  82. //$this->dbi->selectDb($GLOBALS['db']);
  83. $tabColumn = [];
  84. foreach ($designerTables as $designerTable) {
  85. $fieldsRs = $this->dbi->query(
  86. QueryGenerator::getColumnsSql(
  87. $designerTable->getDatabaseName(),
  88. $designerTable->getTableName()
  89. ),
  90. DatabaseInterface::CONNECT_USER,
  91. DatabaseInterface::QUERY_STORE
  92. );
  93. $j = 0;
  94. while ($row = $this->dbi->fetchAssoc($fieldsRs)) {
  95. if (! isset($tabColumn[$designerTable->getDbTableString()])) {
  96. $tabColumn[$designerTable->getDbTableString()] = [];
  97. }
  98. $tabColumn[$designerTable->getDbTableString()]['COLUMN_ID'][$j] = $j;
  99. $tabColumn[$designerTable->getDbTableString()]['COLUMN_NAME'][$j] = $row['Field'];
  100. $tabColumn[$designerTable->getDbTableString()]['TYPE'][$j] = $row['Type'];
  101. $tabColumn[$designerTable->getDbTableString()]['NULLABLE'][$j] = $row['Null'];
  102. $j++;
  103. }
  104. }
  105. return $tabColumn;
  106. }
  107. /**
  108. * Returns JavaScript code for initializing vars
  109. *
  110. * @param DesignerTable[] $designerTables The designer tables
  111. *
  112. * @return array JavaScript code
  113. */
  114. public function getScriptContr(array $designerTables): array
  115. {
  116. $this->dbi->selectDb($GLOBALS['db']);
  117. $con = [];
  118. $con['C_NAME'] = [];
  119. $i = 0;
  120. $alltab_rs = $this->dbi->query(
  121. 'SHOW TABLES FROM ' . Util::backquote($GLOBALS['db']),
  122. DatabaseInterface::CONNECT_USER,
  123. DatabaseInterface::QUERY_STORE
  124. );
  125. while ($val = @$this->dbi->fetchRow($alltab_rs)) {
  126. $row = $this->relation->getForeigners($GLOBALS['db'], $val[0], '', 'internal');
  127. foreach ($row as $field => $value) {
  128. $con['C_NAME'][$i] = '';
  129. $con['DTN'][$i] = rawurlencode($GLOBALS['db'] . '.' . $val[0]);
  130. $con['DCN'][$i] = rawurlencode((string) $field);
  131. $con['STN'][$i] = rawurlencode($value['foreign_db'] . '.' . $value['foreign_table']);
  132. $con['SCN'][$i] = rawurlencode($value['foreign_field']);
  133. $i++;
  134. }
  135. $row = $this->relation->getForeigners($GLOBALS['db'], $val[0], '', 'foreign');
  136. // We do not have access to the foreign keys if the user has partial access to the columns
  137. if (! isset($row['foreign_keys_data'])) {
  138. continue;
  139. }
  140. foreach ($row['foreign_keys_data'] as $one_key) {
  141. foreach ($one_key['index_list'] as $index => $one_field) {
  142. $con['C_NAME'][$i] = rawurlencode($one_key['constraint']);
  143. $con['DTN'][$i] = rawurlencode($GLOBALS['db'] . '.' . $val[0]);
  144. $con['DCN'][$i] = rawurlencode($one_field);
  145. $con['STN'][$i] = rawurlencode(
  146. ($one_key['ref_db_name'] ?? $GLOBALS['db'])
  147. . '.' . $one_key['ref_table_name']
  148. );
  149. $con['SCN'][$i] = rawurlencode($one_key['ref_index_list'][$index]);
  150. $i++;
  151. }
  152. }
  153. }
  154. $tableDbNames = [];
  155. foreach ($designerTables as $designerTable) {
  156. $tableDbNames[] = rawurlencode($designerTable->getDbTableString());
  157. }
  158. $ti = 0;
  159. $retval = [];
  160. for ($i = 0, $cnt = count($con['C_NAME']); $i < $cnt; $i++) {
  161. $c_name_i = $con['C_NAME'][$i];
  162. $dtn_i = $con['DTN'][$i];
  163. $retval[$ti] = [];
  164. $retval[$ti][$c_name_i] = [];
  165. if (in_array($dtn_i, $tableDbNames) && in_array($con['STN'][$i], $tableDbNames)) {
  166. $retval[$ti][$c_name_i][$dtn_i] = [];
  167. $retval[$ti][$c_name_i][$dtn_i][$con['DCN'][$i]] = [
  168. 0 => $con['STN'][$i],
  169. 1 => $con['SCN'][$i],
  170. ];
  171. }
  172. $ti++;
  173. }
  174. return $retval;
  175. }
  176. /**
  177. * Returns UNIQUE and PRIMARY indices
  178. *
  179. * @param DesignerTable[] $designerTables The designer tables
  180. *
  181. * @return array unique or primary indices
  182. */
  183. public function getPkOrUniqueKeys(array $designerTables): array
  184. {
  185. return $this->getAllKeys($designerTables, true);
  186. }
  187. /**
  188. * Returns all indices
  189. *
  190. * @param DesignerTable[] $designerTables The designer tables
  191. * @param bool $unique_only whether to include only unique ones
  192. *
  193. * @return array indices
  194. */
  195. public function getAllKeys(array $designerTables, bool $unique_only = false): array
  196. {
  197. $keys = [];
  198. foreach ($designerTables as $designerTable) {
  199. $schema = $designerTable->getDatabaseName();
  200. // for now, take into account only the first index segment
  201. foreach (Index::getFromTable($designerTable->getTableName(), $schema) as $index) {
  202. if ($unique_only && ! $index->isUnique()) {
  203. continue;
  204. }
  205. $columns = $index->getColumns();
  206. foreach ($columns as $column_name => $dummy) {
  207. $keys[$schema . '.' . $designerTable->getTableName() . '.' . $column_name] = 1;
  208. }
  209. }
  210. }
  211. return $keys;
  212. }
  213. /**
  214. * Return j_tab and h_tab arrays
  215. *
  216. * @param DesignerTable[] $designerTables The designer tables
  217. *
  218. * @return array
  219. */
  220. public function getScriptTabs(array $designerTables): array
  221. {
  222. $retval = [
  223. 'j_tabs' => [],
  224. 'h_tabs' => [],
  225. ];
  226. foreach ($designerTables as $designerTable) {
  227. $key = rawurlencode($designerTable->getDbTableString());
  228. $retval['j_tabs'][$key] = $designerTable->supportsForeignkeys() ? 1 : 0;
  229. $retval['h_tabs'][$key] = 1;
  230. }
  231. return $retval;
  232. }
  233. /**
  234. * Returns table positions of a given pdf page
  235. *
  236. * @param int $pg pdf page id
  237. *
  238. * @return array|null of table positions
  239. */
  240. public function getTablePositions($pg): ?array
  241. {
  242. $cfgRelation = $this->relation->getRelationsParam();
  243. if (! $cfgRelation['pdfwork']) {
  244. return [];
  245. }
  246. $query = "
  247. SELECT CONCAT_WS('.', `db_name`, `table_name`) AS `name`,
  248. `db_name` as `dbName`, `table_name` as `tableName`,
  249. `x` AS `X`,
  250. `y` AS `Y`,
  251. 1 AS `V`,
  252. 1 AS `H`
  253. FROM " . Util::backquote($cfgRelation['db'])
  254. . '.' . Util::backquote($cfgRelation['table_coords']) . '
  255. WHERE pdf_page_number = ' . intval($pg);
  256. return $this->dbi->fetchResult(
  257. $query,
  258. 'name',
  259. null,
  260. DatabaseInterface::CONNECT_CONTROL,
  261. DatabaseInterface::QUERY_STORE
  262. );
  263. }
  264. /**
  265. * Returns page name of a given pdf page
  266. *
  267. * @param int $pg pdf page id
  268. *
  269. * @return string|null table name
  270. */
  271. public function getPageName($pg)
  272. {
  273. $cfgRelation = $this->relation->getRelationsParam();
  274. if (! $cfgRelation['pdfwork']) {
  275. return null;
  276. }
  277. $query = 'SELECT `page_descr`'
  278. . ' FROM ' . Util::backquote($cfgRelation['db'])
  279. . '.' . Util::backquote($cfgRelation['pdf_pages'])
  280. . ' WHERE ' . Util::backquote('page_nr') . ' = ' . intval($pg);
  281. $page_name = $this->dbi->fetchResult(
  282. $query,
  283. null,
  284. null,
  285. DatabaseInterface::CONNECT_CONTROL,
  286. DatabaseInterface::QUERY_STORE
  287. );
  288. return is_array($page_name) && isset($page_name[0]) ? $page_name[0] : null;
  289. }
  290. /**
  291. * Deletes a given pdf page and its corresponding coordinates
  292. *
  293. * @param int $pg page id
  294. */
  295. public function deletePage($pg): bool
  296. {
  297. $cfgRelation = $this->relation->getRelationsParam();
  298. if (! $cfgRelation['pdfwork']) {
  299. return false;
  300. }
  301. $query = 'DELETE FROM ' . Util::backquote($cfgRelation['db'])
  302. . '.' . Util::backquote($cfgRelation['table_coords'])
  303. . ' WHERE ' . Util::backquote('pdf_page_number') . ' = ' . intval($pg);
  304. $success = $this->relation->queryAsControlUser($query, true, DatabaseInterface::QUERY_STORE);
  305. if ($success) {
  306. $query = 'DELETE FROM ' . Util::backquote($cfgRelation['db'])
  307. . '.' . Util::backquote($cfgRelation['pdf_pages'])
  308. . ' WHERE ' . Util::backquote('page_nr') . ' = ' . intval($pg);
  309. $success = $this->relation->queryAsControlUser($query, true, DatabaseInterface::QUERY_STORE);
  310. }
  311. return (bool) $success;
  312. }
  313. /**
  314. * Returns the id of the default pdf page of the database.
  315. * Default page is the one which has the same name as the database.
  316. *
  317. * @param string $db database
  318. *
  319. * @return int|null id of the default pdf page for the database
  320. */
  321. public function getDefaultPage($db): ?int
  322. {
  323. $cfgRelation = $this->relation->getRelationsParam();
  324. if (! $cfgRelation['pdfwork']) {
  325. return -1;
  326. }
  327. $query = 'SELECT `page_nr`'
  328. . ' FROM ' . Util::backquote($cfgRelation['db'])
  329. . '.' . Util::backquote($cfgRelation['pdf_pages'])
  330. . " WHERE `db_name` = '" . $this->dbi->escapeString($db) . "'"
  331. . " AND `page_descr` = '" . $this->dbi->escapeString($db) . "'";
  332. $default_page_no = $this->dbi->fetchResult(
  333. $query,
  334. null,
  335. null,
  336. DatabaseInterface::CONNECT_CONTROL,
  337. DatabaseInterface::QUERY_STORE
  338. );
  339. if (is_array($default_page_no) && isset($default_page_no[0])) {
  340. return intval($default_page_no[0]);
  341. }
  342. return -1;
  343. }
  344. /**
  345. * Get the status if the page already exists
  346. * If no such exists, returns negative index.
  347. *
  348. * @param string $pg name
  349. */
  350. public function getPageExists(string $pg): bool
  351. {
  352. $cfgRelation = $this->relation->getRelationsParam();
  353. if (! $cfgRelation['pdfwork']) {
  354. return false;
  355. }
  356. $query = 'SELECT `page_nr`'
  357. . ' FROM ' . Util::backquote($cfgRelation['db'])
  358. . '.' . Util::backquote($cfgRelation['pdf_pages'])
  359. . " WHERE `page_descr` = '" . $this->dbi->escapeString($pg) . "'";
  360. $pageNos = $this->dbi->fetchResult(
  361. $query,
  362. null,
  363. null,
  364. DatabaseInterface::CONNECT_CONTROL,
  365. DatabaseInterface::QUERY_STORE
  366. );
  367. return is_array($pageNos) && count($pageNos) > 0;
  368. }
  369. /**
  370. * Get the id of the page to load. If a default page exists it will be returned.
  371. * If no such exists, returns the id of the first page of the database.
  372. *
  373. * @param string $db database
  374. *
  375. * @return int id of the page to load
  376. */
  377. public function getLoadingPage($db)
  378. {
  379. $cfgRelation = $this->relation->getRelationsParam();
  380. if (! $cfgRelation['pdfwork']) {
  381. return -1;
  382. }
  383. $page_no = -1;
  384. $default_page_no = $this->getDefaultPage($db);
  385. if ($default_page_no != -1) {
  386. $page_no = $default_page_no;
  387. } else {
  388. $query = 'SELECT MIN(`page_nr`)'
  389. . ' FROM ' . Util::backquote($cfgRelation['db'])
  390. . '.' . Util::backquote($cfgRelation['pdf_pages'])
  391. . " WHERE `db_name` = '" . $this->dbi->escapeString($db) . "'";
  392. $min_page_no = $this->dbi->fetchResult(
  393. $query,
  394. null,
  395. null,
  396. DatabaseInterface::CONNECT_CONTROL,
  397. DatabaseInterface::QUERY_STORE
  398. );
  399. if (is_array($min_page_no) && isset($min_page_no[0])) {
  400. $page_no = $min_page_no[0];
  401. }
  402. }
  403. return intval($page_no);
  404. }
  405. /**
  406. * Creates a new page and returns its auto-incrementing id
  407. *
  408. * @param string $pageName name of the page
  409. * @param string $db name of the database
  410. *
  411. * @return int|null
  412. */
  413. public function createNewPage($pageName, $db)
  414. {
  415. $cfgRelation = $this->relation->getRelationsParam();
  416. if ($cfgRelation['pdfwork']) {
  417. $page = $this->relation->createPage($pageName, $cfgRelation, $db);
  418. return $page !== false ? $page : null;
  419. }
  420. return null;
  421. }
  422. /**
  423. * Saves positions of table(s) of a given pdf page
  424. *
  425. * @param int $pg pdf page id
  426. */
  427. public function saveTablePositions($pg): bool
  428. {
  429. $pageId = $this->dbi->escapeString((string) $pg);
  430. $cfgRelation = $this->relation->getRelationsParam();
  431. if (! $cfgRelation['pdfwork']) {
  432. return false;
  433. }
  434. $query = 'DELETE FROM '
  435. . Util::backquote($cfgRelation['db'])
  436. . '.' . Util::backquote($cfgRelation['table_coords'])
  437. . " WHERE `pdf_page_number` = '" . $pageId . "'";
  438. $res = $this->relation->queryAsControlUser($query, true, DatabaseInterface::QUERY_STORE);
  439. if (! $res) {
  440. return (bool) $res;
  441. }
  442. foreach ($_POST['t_h'] as $key => $value) {
  443. $DB = $_POST['t_db'][$key];
  444. $TAB = $_POST['t_tbl'][$key];
  445. if (! $value) {
  446. continue;
  447. }
  448. $query = 'INSERT INTO '
  449. . Util::backquote($cfgRelation['db']) . '.'
  450. . Util::backquote($cfgRelation['table_coords'])
  451. . ' (`db_name`, `table_name`, `pdf_page_number`, `x`, `y`)'
  452. . ' VALUES ('
  453. . "'" . $this->dbi->escapeString($DB) . "', "
  454. . "'" . $this->dbi->escapeString($TAB) . "', "
  455. . "'" . $pageId . "', "
  456. . "'" . $this->dbi->escapeString($_POST['t_x'][$key]) . "', "
  457. . "'" . $this->dbi->escapeString($_POST['t_y'][$key]) . "')";
  458. $res = $this->relation->queryAsControlUser($query, true, DatabaseInterface::QUERY_STORE);
  459. }
  460. return (bool) $res;
  461. }
  462. /**
  463. * Saves the display field for a table.
  464. *
  465. * @param string $db database name
  466. * @param string $table table name
  467. * @param string $field display field name
  468. *
  469. * @return array<int,string|bool|null>
  470. * @psalm-return array{0: bool, 1: string|null}
  471. */
  472. public function saveDisplayField($db, $table, $field): array
  473. {
  474. $cfgRelation = $this->relation->getRelationsParam();
  475. if (! $cfgRelation['displaywork']) {
  476. return [
  477. false,
  478. _pgettext(
  479. 'phpMyAdmin configuration storage is not configured for'
  480. . ' "Display Features" on designer when user tries to set a display field.',
  481. 'phpMyAdmin configuration storage is not configured for "Display Features".'
  482. ),
  483. ];
  484. }
  485. $upd_query = new Table($table, $db, $this->dbi);
  486. $upd_query->updateDisplayField($field, $cfgRelation);
  487. return [
  488. true,
  489. null,
  490. ];
  491. }
  492. /**
  493. * Adds a new foreign relation
  494. *
  495. * @param string $db database name
  496. * @param string $T1 foreign table
  497. * @param string $F1 foreign field
  498. * @param string $T2 master table
  499. * @param string $F2 master field
  500. * @param string $on_delete on delete action
  501. * @param string $on_update on update action
  502. * @param string $DB1 database
  503. * @param string $DB2 database
  504. *
  505. * @return array<int,string|bool> array of success/failure and message
  506. * @psalm-return array{0: bool, 1: string}
  507. */
  508. public function addNewRelation($db, $T1, $F1, $T2, $F2, $on_delete, $on_update, $DB1, $DB2): array
  509. {
  510. $tables = $this->dbi->getTablesFull($DB1, $T1);
  511. $type_T1 = mb_strtoupper($tables[$T1]['ENGINE'] ?? '');
  512. $tables = $this->dbi->getTablesFull($DB2, $T2);
  513. $type_T2 = mb_strtoupper($tables[$T2]['ENGINE'] ?? '');
  514. // native foreign key
  515. if (ForeignKey::isSupported($type_T1) && ForeignKey::isSupported($type_T2) && $type_T1 == $type_T2) {
  516. // relation exists?
  517. $existrel_foreign = $this->relation->getForeigners($DB2, $T2, '', 'foreign');
  518. $foreigner = $this->relation->searchColumnInForeigners($existrel_foreign, $F2);
  519. if ($foreigner && isset($foreigner['constraint'])) {
  520. return [
  521. false,
  522. __('Error: relationship already exists.'),
  523. ];
  524. }
  525. // note: in InnoDB, the index does not requires to be on a PRIMARY
  526. // or UNIQUE key
  527. // improve: check all other requirements for InnoDB relations
  528. $result = $this->dbi->query(
  529. 'SHOW INDEX FROM ' . Util::backquote($DB1)
  530. . '.' . Util::backquote($T1) . ';'
  531. );
  532. // will be use to emphasis prim. keys in the table view
  533. $index_array1 = [];
  534. while ($row = $this->dbi->fetchAssoc($result)) {
  535. $index_array1[$row['Column_name']] = 1;
  536. }
  537. $this->dbi->freeResult($result);
  538. $result = $this->dbi->query(
  539. 'SHOW INDEX FROM ' . Util::backquote($DB2)
  540. . '.' . Util::backquote($T2) . ';'
  541. );
  542. // will be used to emphasis prim. keys in the table view
  543. $index_array2 = [];
  544. while ($row = $this->dbi->fetchAssoc($result)) {
  545. $index_array2[$row['Column_name']] = 1;
  546. }
  547. $this->dbi->freeResult($result);
  548. if (! empty($index_array1[$F1]) && ! empty($index_array2[$F2])) {
  549. $upd_query = 'ALTER TABLE ' . Util::backquote($DB2)
  550. . '.' . Util::backquote($T2)
  551. . ' ADD FOREIGN KEY ('
  552. . Util::backquote($F2) . ')'
  553. . ' REFERENCES '
  554. . Util::backquote($DB1) . '.'
  555. . Util::backquote($T1) . '('
  556. . Util::backquote($F1) . ')';
  557. if ($on_delete !== 'nix') {
  558. $upd_query .= ' ON DELETE ' . $on_delete;
  559. }
  560. if ($on_update !== 'nix') {
  561. $upd_query .= ' ON UPDATE ' . $on_update;
  562. }
  563. $upd_query .= ';';
  564. if ($this->dbi->tryQuery($upd_query)) {
  565. return [
  566. true,
  567. __('FOREIGN KEY relationship has been added.'),
  568. ];
  569. }
  570. $error = $this->dbi->getError();
  571. return [
  572. false,
  573. __('Error: FOREIGN KEY relationship could not be added!')
  574. . '<br>' . $error,
  575. ];
  576. }
  577. return [
  578. false,
  579. __('Error: Missing index on column(s).'),
  580. ];
  581. }
  582. // internal (pmadb) relation
  583. if ($GLOBALS['cfgRelation']['relwork'] == false) {
  584. return [
  585. false,
  586. __('Error: Relational features are disabled!'),
  587. ];
  588. }
  589. // no need to recheck if the keys are primary or unique at this point,
  590. // this was checked on the interface part
  591. $q = 'INSERT INTO '
  592. . Util::backquote($GLOBALS['cfgRelation']['db'])
  593. . '.'
  594. . Util::backquote($GLOBALS['cfgRelation']['relation'])
  595. . '(master_db, master_table, master_field, '
  596. . 'foreign_db, foreign_table, foreign_field)'
  597. . ' values('
  598. . "'" . $this->dbi->escapeString($DB2) . "', "
  599. . "'" . $this->dbi->escapeString($T2) . "', "
  600. . "'" . $this->dbi->escapeString($F2) . "', "
  601. . "'" . $this->dbi->escapeString($DB1) . "', "
  602. . "'" . $this->dbi->escapeString($T1) . "', "
  603. . "'" . $this->dbi->escapeString($F1) . "')";
  604. if ($this->relation->queryAsControlUser($q, false, DatabaseInterface::QUERY_STORE)) {
  605. return [
  606. true,
  607. __('Internal relationship has been added.'),
  608. ];
  609. }
  610. $error = $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL);
  611. return [
  612. false,
  613. __('Error: Internal relationship could not be added!')
  614. . '<br>' . $error,
  615. ];
  616. }
  617. /**
  618. * Removes a foreign relation
  619. *
  620. * @param string $T1 foreign db.table
  621. * @param string $F1 foreign field
  622. * @param string $T2 master db.table
  623. * @param string $F2 master field
  624. *
  625. * @return array array of success/failure and message
  626. */
  627. public function removeRelation($T1, $F1, $T2, $F2)
  628. {
  629. [$DB1, $T1] = explode('.', $T1);
  630. [$DB2, $T2] = explode('.', $T2);
  631. $tables = $this->dbi->getTablesFull($DB1, $T1);
  632. $type_T1 = mb_strtoupper($tables[$T1]['ENGINE']);
  633. $tables = $this->dbi->getTablesFull($DB2, $T2);
  634. $type_T2 = mb_strtoupper($tables[$T2]['ENGINE']);
  635. if (ForeignKey::isSupported($type_T1) && ForeignKey::isSupported($type_T2) && $type_T1 == $type_T2) {
  636. // InnoDB
  637. $existrel_foreign = $this->relation->getForeigners($DB2, $T2, '', 'foreign');
  638. $foreigner = $this->relation->searchColumnInForeigners($existrel_foreign, $F2);
  639. if (is_array($foreigner) && isset($foreigner['constraint'])) {
  640. $upd_query = 'ALTER TABLE ' . Util::backquote($DB2)
  641. . '.' . Util::backquote($T2) . ' DROP FOREIGN KEY '
  642. . Util::backquote($foreigner['constraint']) . ';';
  643. if ($this->dbi->query($upd_query)) {
  644. return [
  645. true,
  646. __('FOREIGN KEY relationship has been removed.'),
  647. ];
  648. }
  649. $error = $this->dbi->getError();
  650. return [
  651. false,
  652. __('Error: FOREIGN KEY relationship could not be removed!')
  653. . '<br>' . $error,
  654. ];
  655. }
  656. }
  657. // internal (pmadb) relation is not working, skip delete
  658. if ($GLOBALS['cfgRelation']['relwork'] == false) {
  659. return [
  660. false,
  661. __('Error: Relational features are disabled!'),
  662. ];
  663. }
  664. // internal relations
  665. $delete_query = 'DELETE FROM '
  666. . Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
  667. . Util::backquote($GLOBALS['cfgRelation']['relation']) . ' WHERE '
  668. . "master_db = '" . $this->dbi->escapeString($DB2) . "'"
  669. . " AND master_table = '" . $this->dbi->escapeString($T2) . "'"
  670. . " AND master_field = '" . $this->dbi->escapeString($F2) . "'"
  671. . " AND foreign_db = '" . $this->dbi->escapeString($DB1) . "'"
  672. . " AND foreign_table = '" . $this->dbi->escapeString($T1) . "'"
  673. . " AND foreign_field = '" . $this->dbi->escapeString($F1) . "'";
  674. $result = $this->relation->queryAsControlUser($delete_query, false, DatabaseInterface::QUERY_STORE);
  675. if (! $result) {
  676. $error = $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL);
  677. return [
  678. false,
  679. __('Error: Internal relationship could not be removed!') . '<br>' . $error,
  680. ];
  681. }
  682. return [
  683. true,
  684. __('Internal relationship has been removed.'),
  685. ];
  686. }
  687. /**
  688. * Save value for a designer setting
  689. *
  690. * @param string $index setting
  691. * @param string $value value
  692. */
  693. public function saveSetting($index, $value): bool
  694. {
  695. $cfgRelation = $this->relation->getRelationsParam();
  696. $success = true;
  697. if ($cfgRelation['designersettingswork']) {
  698. $cfgDesigner = [
  699. 'user' => $GLOBALS['cfg']['Server']['user'],
  700. 'db' => $cfgRelation['db'],
  701. 'table' => $cfgRelation['designer_settings'],
  702. ];
  703. $orig_data_query = 'SELECT settings_data'
  704. . ' FROM ' . Util::backquote($cfgDesigner['db'])
  705. . '.' . Util::backquote($cfgDesigner['table'])
  706. . " WHERE username = '"
  707. . $this->dbi->escapeString($cfgDesigner['user']) . "';";
  708. $orig_data = $this->dbi->fetchSingleRow($orig_data_query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL);
  709. if (! empty($orig_data)) {
  710. $orig_data = json_decode($orig_data['settings_data'], true);
  711. $orig_data[$index] = $value;
  712. $orig_data = json_encode($orig_data);
  713. $save_query = 'UPDATE '
  714. . Util::backquote($cfgDesigner['db'])
  715. . '.' . Util::backquote($cfgDesigner['table'])
  716. . " SET settings_data = '" . $orig_data . "'"
  717. . " WHERE username = '"
  718. . $this->dbi->escapeString($cfgDesigner['user']) . "';";
  719. $success = $this->relation->queryAsControlUser($save_query);
  720. } else {
  721. $save_data = [$index => $value];
  722. $query = 'INSERT INTO '
  723. . Util::backquote($cfgDesigner['db'])
  724. . '.' . Util::backquote($cfgDesigner['table'])
  725. . ' (username, settings_data)'
  726. . " VALUES('" . $this->dbi->escapeString($cfgDesigner['user'])
  727. . "', '" . json_encode($save_data) . "');";
  728. $success = $this->relation->queryAsControlUser($query);
  729. }
  730. }
  731. return (bool) $success;
  732. }
  733. }