Passed
Push — master ( b6b992...292d2d )
by William
08:14
created

libraries/classes/Database/Designer/Common.php (1 issue)

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