Common.php 29 KB

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