Common.php 28 KB

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