2 declare(strict_types
=1);
4 namespace PhpMyAdmin\Controllers\Table
;
7 use PhpMyAdmin\DatabaseInterface
;
8 use PhpMyAdmin\Html\Generator
;
10 use PhpMyAdmin\Relation
;
11 use PhpMyAdmin\Response
;
13 use PhpMyAdmin\Template
;
15 use function array_key_exists
;
16 use function array_keys
;
17 use function array_values
;
18 use function htmlspecialchars
;
19 use function mb_strtoupper
;
21 use function strtoupper
;
26 * Display table relations for viewing and editing.
28 * Includes phpMyAdmin relations and InnoDB relations.
30 final class RelationController
extends AbstractController
36 * @param Response $response Response object
37 * @param DatabaseInterface $dbi DatabaseInterface object
38 * @param Template $template Template object
39 * @param string $db Database name
40 * @param string $table Table name
41 * @param Relation $relation Relation instance
43 public function __construct(
51 parent
::__construct($response, $dbi, $template, $db, $table);
52 $this->relation
= $relation;
58 public function index(): void
63 'CASCADE' => 'CASCADE',
64 'SET_NULL' => 'SET NULL',
65 'NO_ACTION' => 'NO ACTION',
66 'RESTRICT' => 'RESTRICT',
69 $table = $this->dbi
->getTable($this->db
, $this->table
);
70 $storageEngine = mb_strtoupper((string) $table->getStatusInfo('Engine'));
72 $cfgRelation = $this->relation
->getRelationsParam();
75 if ($cfgRelation['relwork']) {
76 $relations = $this->relation
->getForeigners(
84 $relationsForeign = [];
85 if (Util
::isForeignKeySupported($storageEngine)) {
86 $relationsForeign = $this->relation
->getForeigners(
94 // Send table of column names to populate corresponding dropdowns depending
95 // on the current selection
96 if (isset($_POST['getDropdownValues'])
97 && $_POST['getDropdownValues'] === 'true'
99 // if both db and table are selected
100 if (isset($_POST['foreignTable'])) {
101 $this->getDropdownValueForTable();
102 } else { // if only the db is selected
103 $this->getDropdownValueForDatabase($storageEngine);
108 $this->response
->getHeader()->getScripts()->addFiles(
116 $this->dbi
->selectDb($this->db
);
118 // updates for Internal relations
119 if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
120 $this->updateForInternalRelation($table, $cfgRelation, $relations);
123 // updates for foreign keys
124 $this->updateForForeignKeys($table, $options, $relationsForeign);
126 // Updates for display field
127 if ($cfgRelation['displaywork'] && isset($_POST['display_field'])) {
128 $this->updateForDisplayField($table, $cfgRelation);
131 // If we did an update, refresh our data
132 if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
133 $relations = $this->relation
->getForeigners(
140 if (isset($_POST['destination_foreign_db'])
141 && Util
::isForeignKeySupported($storageEngine)
143 $relationsForeign = $this->relation
->getForeigners(
154 // Now find out the columns of our $table
155 // need to use DatabaseInterface::QUERY_STORE with $this->dbi->numRows()
157 $columns = $this->dbi
->getColumns($this->db
, $this->table
);
160 $column_hash_array = [];
161 $column_array[''] = '';
162 foreach ($columns as $column) {
163 if (strtoupper($storageEngine) == 'INNODB'
164 ||
! empty($column['Key'])
166 $column_array[$column['Field']] = $column['Field'];
167 $column_hash_array[$column['Field']] = md5($column['Field']);
170 if ($GLOBALS['cfg']['NaturalOrder']) {
171 uksort($column_array, 'strnatcasecmp');
175 $engine = $this->dbi
->getTable($this->db
, $this->table
)->getStorageEngine();
176 $foreignKeySupported = Util
::isForeignKeySupported($storageEngine);
177 $this->render('table/relation/common_form', [
178 'is_foreign_key_supported' => Util
::isForeignKeySupported($engine),
180 'table' => $this->table
,
181 'cfg_relation' => $cfgRelation,
182 'tbl_storage_engine' => $storageEngine,
183 'existrel' => $relations,
184 'existrel_foreign' => array_key_exists('foreign_keys_data', $relationsForeign)
185 ?
$relationsForeign['foreign_keys_data']
187 'options_array' => $options,
188 'column_array' => $column_array,
189 'column_hash_array' => $column_hash_array,
190 'save_row' => array_values($columns),
191 'url_params' => $GLOBALS['url_params'],
192 'databases' => $GLOBALS['dblist']->databases
,
194 'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
195 'foreignKeySupported' => $foreignKeySupported,
196 'indexes' => $foreignKeySupported ? Index
::getFromTable($this->table
, $this->db
) : null,
197 'indexes_duplicates' => $foreignKeySupported ? Index
::findDuplicates($this->table
, $this->db
) : null,
203 * Update for display field
205 * @param Table $table table
206 * @param array $cfgRelation relation parameters
208 private function updateForDisplayField(Table
$table, array $cfgRelation): void
210 if ($table->updateDisplayField(
211 $_POST['display_field'],
214 $this->response
->addHTML(
215 Generator
::getMessage(
216 __('Display column was successfully updated.'),
227 * @param Table $table Table
228 * @param array $options Options
229 * @param array $relationsForeign External relations
231 private function updateForForeignKeys(Table
$table, array $options, array $relationsForeign): void
233 $multi_edit_columns_name = $_POST['foreign_key_fields_name'] ??
null;
234 $preview_sql_data = '';
237 // (for now, one index name only; we keep the definitions if the
238 // foreign db is not the same)
239 if (isset($_POST['destination_foreign_db'], $_POST['destination_foreign_table'])
240 && isset($_POST['destination_foreign_column'])) {
246 ] = $table->updateForeignKeys(
247 $_POST['destination_foreign_db'],
248 $multi_edit_columns_name,
249 $_POST['destination_foreign_table'],
250 $_POST['destination_foreign_column'],
253 array_key_exists('foreign_keys_data', $relationsForeign)
254 ?
$relationsForeign['foreign_keys_data']
257 $this->response
->addHTML($html);
260 // If there is a request for SQL previewing.
261 if (isset($_POST['preview_sql'])) {
262 Core
::previewSQL($preview_sql_data);
265 if (! empty($display_query) && ! $seen_error) {
266 $GLOBALS['display_query'] = $display_query;
267 $this->response
->addHTML(
268 Generator
::getMessage(
269 __('Your SQL query has been executed successfully.'),
278 * Update for internal relation
280 * @param Table $table Table
281 * @param array $cfgRelation Relation parameters
282 * @param array $relations Relations
284 private function updateForInternalRelation(Table
$table, array $cfgRelation, array $relations): void
286 $multi_edit_columns_name = $_POST['fields_name'] ??
null;
288 if ($table->updateInternalRelations(
289 $multi_edit_columns_name,
290 $_POST['destination_db'],
291 $_POST['destination_table'],
292 $_POST['destination_column'],
296 $this->response
->addHTML(
297 Generator
::getMessage(
298 __('Internal relationships were successfully updated.'),
307 * Send table columns for foreign table dropdown
309 public function getDropdownValueForTable(): void
311 $foreignTable = $_POST['foreignTable'];
312 $table_obj = $this->dbi
->getTable($_POST['foreignDb'], $foreignTable);
313 // Since views do not have keys defined on them provide the full list of
315 if ($table_obj->isView()) {
316 $columnList = $table_obj->getColumns(false, false);
318 $columnList = $table_obj->getIndexedColumns(false, false);
321 foreach ($columnList as $column) {
322 $columns[] = htmlspecialchars($column);
324 if ($GLOBALS['cfg']['NaturalOrder']) {
325 usort($columns, 'strnatcasecmp');
327 $this->response
->addJSON('columns', $columns);
329 // @todo should be: $server->db($db)->table($table)->primary()
330 $primary = Index
::getPrimary($foreignTable, $_POST['foreignDb']);
331 if ($primary === false) {
335 $this->response
->addJSON('primary', array_keys($primary->getColumns()));
339 * Send database selection values for dropdown
341 * @param string $storageEngine Storage engine.
343 public function getDropdownValueForDatabase(string $storageEngine): void
346 $foreign = isset($_POST['foreign']) && $_POST['foreign'] === 'true';
349 $query = 'SHOW TABLE STATUS FROM '
350 . Util
::backquote($_POST['foreignDb']);
351 $tables_rs = $this->dbi
->query(
353 DatabaseInterface
::CONNECT_USER
,
354 DatabaseInterface
::QUERY_STORE
357 while ($row = $this->dbi
->fetchArray($tables_rs)) {
358 if (isset($row['Engine'])
359 && mb_strtoupper($row['Engine']) == $storageEngine
361 $tables[] = htmlspecialchars($row['Name']);
365 $query = 'SHOW TABLES FROM '
366 . Util
::backquote($_POST['foreignDb']);
367 $tables_rs = $this->dbi
->query(
369 DatabaseInterface
::CONNECT_USER
,
370 DatabaseInterface
::QUERY_STORE
372 while ($row = $this->dbi
->fetchArray($tables_rs)) {
373 $tables[] = htmlspecialchars($row[0]);
376 if ($GLOBALS['cfg']['NaturalOrder']) {
377 usort($tables, 'strnatcasecmp');
379 $this->response
->addJSON('tables', $tables);