Passed
Push — develop ( ed1c52...ef375c )
by Felipe
09:28
created

DisplayController::getBrowseNavLinks()   C

Complexity

Conditions 11
Paths 48

Size

Total Lines 152
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 152
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 82
nc 48
nop 6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.48
5
 */
6
7
namespace PHPPgAdmin\Controller;
8
9
/**
10
 * Base controller class.
11
 *
12
 * @package PHPPgAdmin
13
 */
14
class DisplayController extends BaseController
15
{
16
    use \PHPPgAdmin\Traits\InsertEditRowTrait;
17
18
    /**
19
     * Default method to render the controller according to the action parameter.
20
     */
21
    public function render()
22
    {
23
        $this->misc     = $this->misc;
24
        $plugin_manager = $this->plugin_manager;
25
26
        if ('dobrowsefk' == $this->action) {
27
            return $this->doBrowseFK();
28
        }
29
30
        set_time_limit(0);
31
32
        $scripts = '<script src="'.\SUBFOLDER.'/assets/js/display.js" type="text/javascript"></script>';
33
34
        $scripts .= '<script type="text/javascript">'."\n";
35
        $scripts .= "var Display = {\n";
36
        $scripts .= "errmsg: '".str_replace("'", "\\'", $this->lang['strconnectionfail'])."'\n";
37
        $scripts .= "};\n";
38
        $scripts .= '</script>'."\n";
39
40
        $footer_template = 'footer.twig';
41
        $header_template = 'header.twig';
42
43
        ob_start();
44
        switch ($this->action) {
45
            case 'editrow':
46
                $header_template = 'header_sqledit.twig';
47
                $footer_template = 'footer_sqledit.twig';
48
                if (isset($_POST['save'])) {
49
                    $this->doEditRow();
50
                } else {
51
                    $this->doBrowse();
52
                }
53
54
                break;
55
            case 'confeditrow':
56
                $this->formEditRow();
57
58
                break;
59
            case 'delrow':
60
                $header_template = 'header_sqledit.twig';
61
                $footer_template = 'footer_sqledit.twig';
62
                if (isset($_POST['yes'])) {
63
                    $this->doDelRow(false);
64
                } else {
65
                    $this->doBrowse();
66
                }
67
68
                break;
69
            case 'confdelrow':
70
                $this->doDelRow(true);
71
72
                break;
73
            default:
74
                $header_template = 'header_sqledit.twig';
75
                $footer_template = 'footer_sqledit.twig';
76
                $this->doBrowse();
77
78
                break;
79
        }
80
        $output = ob_get_clean();
81
82
        $subject = $this->coalesceArr($_REQUEST, 'subject', 'table')['subject'];
83
84
        $object = null;
85
        $object = $this->setIfIsset($object, $_REQUEST[$subject]);
86
87
        // Set the title based on the subject of the request
88
        if ('table' == $subject) {
89
            $title = $this->headerTitle('strtables', '', $object);
90
        } elseif ('view' == $subject) {
91
            $title = $this->headerTitle('strviews', '', $object);
92
        } elseif ('matview' == $subject) {
93
            $title = $this->headerTitle('strviews', 'M', $object);
94
        } elseif ('column' == $subject) {
95
            $title = $this->headerTitle('strcolumn', '', $object);
96
        } else {
97
            $title = $this->headerTitle('strqueryresults');
98
        }
99
100
        $this->printHeader($title, $scripts, true, $header_template);
101
102
        $this->printBody();
103
104
        echo $output;
105
106
        $this->printFooter(true, $footer_template);
107
    }
108
109
    /**
110
     * Displays requested data.
111
     *
112
     * @param mixed $msg
113
     */
114
    public function doBrowse($msg = '')
115
    {
116
        $this->misc = $this->misc;
117
        $data       = $this->misc->getDatabaseAccessor();
118
119
        // If current page is not set, default to first page
120
        $page = $this->coalesceArr($_REQUEST, 'page', 1)['page'];
121
122
        $save_history = !isset($_REQUEST['nohistory']);
123
124
        $subject = $this->coalesceArr($_REQUEST, 'subject', 'table')['subject'];
125
126
        $object = $this->coalesceArr($_REQUEST, $subject)[$subject];
127
128
        if ($subject === 'column' && $object && isset($_REQUEST['f_schema'], $_REQUEST['f_table'])) {
129
            $f_schema = $_REQUEST['f_schema'];
130
            $f_table  = $_REQUEST['f_table'];
131
132
            $_REQUEST['query'] = "SELECT \"{$object}\",
133
            count(*) AS \"count\"
134
            FROM \"{$f_schema}\".\"{$f_table}\"
135
            GROUP BY \"{$object}\" ORDER BY \"{$object}\"";
136
        }
137
138
        //$object = $this->setIfIsset($object, $_REQUEST[$subject]);
139
140
        $this->printTrail($subject);
141
142
        $tabsPosition = 'browse';
143
        if ($subject === 'database') {
144
            $tabsPosition = 'sql';
145
        } elseif ($subject === 'column') {
146
            $tabsPosition = 'colproperties';
147
        }
148
149
        $this->printTabs($subject, $tabsPosition);
150
151
        $fkey = $this->coalesceArr($_REQUEST, 'fkey')['fkey'];
152
153
        $query = $this->coalesceArr($_REQUEST, 'query')['query'];
154
        // This code is used when browsing FK in pure-xHTML (without js)
155
        if ($fkey) {
156
            $ops = [];
157
            foreach (array_keys($fkey) as $x) {
158
                $ops[$x] = '=';
159
            }
160
            $query             = $data->getSelectSQL($_REQUEST['table'], [], $fkey, $ops);
161
            $_REQUEST['query'] = $query;
162
        }
163
164
        $title = 'strqueryresults';
165
        $type  = 'QUERY';
166
167
        if ($object && $query) {
168
            $_SESSION['sqlquery'] = $query;
169
            $title                = 'strselect';
170
            $type                 = 'SELECT';
171
        } elseif ($object) {
172
            $title = 'strselect';
173
            $type  = 'TABLE';
174
        } elseif (isset($_SESSION['sqlquery'])) {
175
            $query = $_SESSION['sqlquery'];
176
        }
177
178
        $this->printTitle($this->lang['strqueryresults']);
179
180
        //$this->prtrace($subject, $object, $query, $_SESSION['sqlquery']);
181
182
        $this->printMsg($msg);
183
184
        // If 'sortkey' is not set, default to ''
185
        $sortkey = $this->coalesceArr($_REQUEST, 'sortkey', '')['sortkey'];
186
187
        // If 'sortdir' is not set, default to ''
188
        $sortdir = $this->coalesceArr($_REQUEST, 'sortdir', '')['sortdir'];
189
190
        // If 'strings' is not set, default to collapsed
191
        $strings = $this->coalesceArr($_REQUEST, 'strings', 'collapsed')['strings'];
192
193
        $this->coalesceArr($_REQUEST, 'schema')['schema'];
194
        $search_path = $this->coalesceArr($_REQUEST, 'search_path')['search_path'];
195
196
        // Set the schema search path
197
        if (isset($search_path) && (0 != $data->setSearchPath(array_map('trim', explode(',', $search_path))))) {
198
            return;
199
        }
200
201
        try {
202
            // Retrieve page from query.  $max_pages is returned by reference.
203
            $resultset = $data->browseQuery(
204
                $type,
205
                $object,
206
                $query,
207
                $sortkey,
208
                $sortdir,
209
                $page,
210
                $this->conf['max_rows'],
211
                $max_pages
212
            );
213
        } catch (\PHPPgAdmin\ADOdbException $e) {
214
            return $this->halt($e->getMessage());
215
        }
216
217
        // Build strings for GETs in array
218
        $_gets = [
219
            'server'   => $_REQUEST['server'],
220
            'database' => $_REQUEST['database'],
221
        ];
222
223
        $this->coalesceArr($_REQUEST, 'query');
224
        $this->coalesceArr($_REQUEST, 'count');
225
        $this->coalesceArr($_REQUEST, 'return');
226
        $this->coalesceArr($_REQUEST, 'table');
227
        $this->coalesceArr($_REQUEST, 'nohistory');
228
229
        $this->setIfIsset($_gets['schema'], $_REQUEST['schema'], null, false);
230
        $this->setIfIsset($_gets[$subject], $object, null, false);
231
        $this->setIfIsset($_gets['subject'], $subject, null, false);
232
        $this->setIfIsset($_gets['query'], $_REQUEST['query'], null, false);
233
        $this->setIfIsset($_gets['count'], $_REQUEST['count'], null, false);
234
        $this->setIfIsset($_gets['return'], $_REQUEST['return'], null, false);
235
        $this->setIfIsset($_gets['search_path'], $_REQUEST['search_path'], null, false);
236
        $this->setIfIsset($_gets['table'], $_REQUEST['table'], null, false);
237
        $this->setIfIsset($_gets['nohistory'], $_REQUEST['nohistory'], null, false);
238
        $_gets['sortkey'] = $sortkey;
239
        $_gets['sortdir'] = $sortdir;
240
        $_gets['strings'] = $strings;
241
242
        if ($save_history && is_object($resultset) && ('QUERY' == $type)) {
243
            //{
244
            $this->misc->saveScriptHistory($_REQUEST['query']);
245
        }
246
247
        $query = $query ? $query : sprintf('SELECT * FROM %s.%s', $_REQUEST['schema'], $object);
248
249
        //$query = isset($_REQUEST['query'])? $_REQUEST['query'] : "select * from {$_REQUEST['schema']}.{$_REQUEST['table']};";
250
        //$this->prtrace($query);
251
252
        //die(htmlspecialchars($query));
253
254
        echo '<form method="post" id="sqlform" action="'.$_SERVER['REQUEST_URI'].'">';
255
        echo $this->misc->form;
256
        if ($object) {
257
            echo '<input type="hidden" name="'.$subject.'" value="', htmlspecialchars($object), '" />'."\n";
258
        }
259
        echo '<textarea width="90%" name="query"  id="query" rows="5" cols="100" resizable="true">';
260
        echo htmlspecialchars($query);
261
        echo '</textarea><br><input type="submit"/>';
262
263
        echo '</form>';
264
265
        $this->printResultsTable($resultset, $page, $max_pages, $_gets, $object);
266
        // Navigation links
267
268
        $navlinks = $this->getBrowseNavLinks($type, $_gets, $page, $subject, $object, $resultset);
269
        $this->printNavLinks($navlinks, 'display-browse', get_defined_vars());
270
    }
271
272
    public function getBrowseNavLinks($type, $_gets, $page, $subject, $object, $resultset)
273
    {
274
        $fields = [
275
            'server'   => $_REQUEST['server'],
276
            'database' => $_REQUEST['database'],
277
        ];
278
279
        $this->setIfIsset($fields['schema'], $_REQUEST['schema'], null, false);
280
281
        $navlinks = [];
282
        $strings  = $_gets['strings'];
283
        // Return
284
        if (isset($_REQUEST['return'])) {
285
            $urlvars = $this->misc->getSubjectParams($_REQUEST['return']);
286
287
            $navlinks['back'] = [
288
                'attr'    => [
289
                    'href' => [
290
                        'url'     => $urlvars['url'],
291
                        'urlvars' => $urlvars['params'],
292
                    ],
293
                ],
294
                'content' => $this->lang['strback'],
295
            ];
296
        }
297
298
        // Edit SQL link
299
        if ('QUERY' == $type) {
300
            $navlinks['edit'] = [
301
                'attr'    => [
302
                    'href' => [
303
                        'url'     => 'database',
304
                        'urlvars' => array_merge(
305
                            $fields,
306
                            [
307
                                'action'   => 'sql',
308
                                'paginate' => 'on',
309
                            ]
310
                        ),
311
                    ],
312
                ],
313
                'content' => $this->lang['streditsql'],
314
            ];
315
        }
316
317
        $navlinks['collapse'] = [
318
            'attr'    => [
319
                'href' => [
320
                    'url'     => 'display',
321
                    'urlvars' => array_merge(
322
                        $_gets,
323
                        [
324
                            'strings' => 'expanded',
325
                            'page'    => $page,
326
                        ]
327
                    ),
328
                ],
329
            ],
330
            'content' => $this->lang['strexpand'],
331
        ];
332
        // Expand/Collapse
333
        if ('expanded' == $strings) {
334
            $navlinks['collapse'] = [
335
                'attr'    => [
336
                    'href' => [
337
                        'url'     => 'display',
338
                        'urlvars' => array_merge(
339
                            $_gets,
340
                            [
341
                                'strings' => 'collapsed',
342
                                'page'    => $page,
343
                            ]
344
                        ),
345
                    ],
346
                ],
347
                'content' => $this->lang['strcollapse'],
348
            ];
349
        }
350
351
        // Create view and download
352
        if (isset($_REQUEST['query'], $resultset) && is_object($resultset) && $resultset->recordCount() > 0) {
353
            // Report views don't set a schema, so we need to disable create view in that case
354
            if (isset($_REQUEST['schema'])) {
355
                $navlinks['createview'] = [
356
                    'attr'    => [
357
                        'href' => [
358
                            'url'     => 'views',
359
                            'urlvars' => array_merge(
360
                                $fields,
361
                                [
362
                                    'action'         => 'create',
363
                                    'formDefinition' => $_REQUEST['query'],
364
                                ]
365
                            ),
366
                        ],
367
                    ],
368
                    'content' => $this->lang['strcreateview'],
369
                ];
370
            }
371
372
            $urlvars = [];
373
374
            $this->setIfIsset($urlvars['search_path'], $_REQUEST['search_path'], null, false);
375
376
            $navlinks['download'] = [
377
                'attr'    => [
378
                    'href' => [
379
                        'url'     => 'dataexport',
380
                        'urlvars' => array_merge($fields, $urlvars),
381
                    ],
382
                ],
383
                'content' => $this->lang['strdownload'],
384
            ];
385
        }
386
387
        // Insert
388
        if (isset($object) && (isset($subject) && 'table' == $subject)) {
389
            $navlinks['insert'] = [
390
                'attr'    => [
391
                    'href' => [
392
                        'url'     => 'tables',
393
                        'urlvars' => array_merge(
394
                            $fields,
395
                            [
396
                                'action' => 'confinsertrow',
397
                                'table'  => $object,
398
                            ]
399
                        ),
400
                    ],
401
                ],
402
                'content' => $this->lang['strinsert'],
403
            ];
404
        }
405
406
        // Refresh
407
        $navlinks['refresh'] = [
408
            'attr'    => [
409
                'href' => [
410
                    'url'     => 'display',
411
                    'urlvars' => array_merge(
412
                        $_gets,
413
                        [
414
                            'strings' => $strings,
415
                            'page'    => $page,
416
                        ]
417
                    ),
418
                ],
419
            ],
420
            'content' => $this->lang['strrefresh'],
421
        ];
422
423
        return $navlinks;
424
    }
425
426
    public function printResultsTable($resultset, $page, $max_pages, $_gets, $object)
427
    {
428
        if (!is_object($resultset) || $resultset->recordCount() <= 0) {
429
            echo "<p>{$this->lang['strnodata']}</p>"."\n";
430
431
            return;
432
        }
433
434
        $data           = $this->misc->getDatabaseAccessor();
435
        $plugin_manager = $this->plugin_manager;
436
        $strings        = $_gets['strings'];
437
        $key            = [];
438
439
        // Fetch unique row identifier, if this is a table browse request.
440
        if ($object) {
441
            $key = $data->getRowIdentifier($object);
442
        }
443
444
        $fkey_information = $this->getFKInfo();
445
        // Show page navigation
446
        $paginator = $this->_printPages($page, $max_pages, $_gets);
447
448
        echo $paginator;
449
        echo "<table id=\"data\">\n<tr>";
450
451
        // Check that the key is actually in the result set.  This can occur for select
452
        // operations where the key fields aren't part of the select.  XXX:  We should
453
        // be able to support this, somehow.
454
        foreach ($key as $v) {
455
            // If a key column is not found in the record set, then we
456
            // can't use the key.
457
            if (!array_key_exists($v, $resultset->fields)) {
458
                $key = [];
459
460
                break;
461
            }
462
        }
463
464
        $buttons = [
465
            'edit'   => [
466
                'content' => $this->lang['stredit'],
467
                'attr'    => [
468
                    'href' => [
469
                        'url'     => 'display',
470
                        'urlvars' => array_merge(
471
                            [
472
                                'action'  => 'confeditrow',
473
                                'strings' => $strings,
474
                                'page'    => $page,
475
                            ],
476
                            $_gets
477
                        ),
478
                    ],
479
                ],
480
            ],
481
            'delete' => [
482
                'content' => $this->lang['strdelete'],
483
                'attr'    => [
484
                    'href' => [
485
                        'url'     => 'display',
486
                        'urlvars' => array_merge(
487
                            [
488
                                'action'  => 'confdelrow',
489
                                'strings' => $strings,
490
                                'page'    => $page,
491
                            ],
492
                            $_gets
493
                        ),
494
                    ],
495
                ],
496
            ],
497
        ];
498
        $actions = [
499
            'actionbuttons' => &$buttons,
500
            'place'         => 'display-browse',
501
        ];
502
        $plugin_manager->doHook('actionbuttons', $actions);
503
504
        foreach (array_keys($actions['actionbuttons']) as $this->action) {
505
            $actions['actionbuttons'][$this->action]['attr']['href']['urlvars'] = array_merge(
506
                $actions['actionbuttons'][$this->action]['attr']['href']['urlvars'],
507
                $_gets
508
            );
509
        }
510
511
        $edit_params = isset($actions['actionbuttons']['edit']) ?
512
        $actions['actionbuttons']['edit'] : [];
513
        $delete_params = isset($actions['actionbuttons']['delete']) ?
514
        $actions['actionbuttons']['delete'] : [];
515
516
        // Display edit and delete actions if we have a key
517
        $colspan = count($buttons);
518
        if ($colspan > 0 and count($key) > 0) {
519
            echo "<th colspan=\"{$colspan}\" class=\"data\">{$this->lang['stractions']}</th>"."\n";
520
        }
521
522
        // we show OIDs only if we are in TABLE or SELECT type browsing
523
        $this->printTableHeaderCells($resultset, $_gets, isset($object));
524
525
        echo '</tr>'."\n";
526
527
        $i = 0;
528
        reset($resultset->fields);
529
        while (!$resultset->EOF) {
530
            $id = (0 == ($i % 2) ? '1' : '2');
531
            echo "<tr class=\"data{$id}\">"."\n";
532
            // Display edit and delete links if we have a key
533
            if ($colspan <= 0 || count($key) <= 0) {
534
                continue;
535
            }
536
            $keys_array = [];
537
            $has_nulls  = false;
538
            foreach ($key as $v) {
539
                if (null === $resultset->fields[$v]) {
540
                    $has_nulls = true;
541
542
                    break;
543
                }
544
                $keys_array["key[{$v}]"] = $resultset->fields[$v];
545
            }
546
            if ($has_nulls) {
547
                echo "<td colspan=\"{$colspan}\">&nbsp;</td>"."\n";
548
                $this->printTableRowCells($resultset, $fkey_information, isset($object));
549
550
                echo '</tr>'."\n";
551
                $resultset->moveNext();
552
                ++$i;
553
554
                continue;
555
            }
556
            if (isset($actions['actionbuttons']['edit'])) {
557
                $actions['actionbuttons']['edit']                            = $edit_params;
558
                $actions['actionbuttons']['edit']['attr']['href']['urlvars'] = array_merge(
559
                    $actions['actionbuttons']['edit']['attr']['href']['urlvars'],
560
                    $keys_array
561
                );
562
            }
563
564
            if (isset($actions['actionbuttons']['delete'])) {
565
                $actions['actionbuttons']['delete']                            = $delete_params;
566
                $actions['actionbuttons']['delete']['attr']['href']['urlvars'] = array_merge(
567
                    $actions['actionbuttons']['delete']['attr']['href']['urlvars'],
568
                    $keys_array
569
                );
570
            }
571
572
            foreach ($actions['actionbuttons'] as $this->action) {
573
                echo "<td class=\"opbutton{$id}\">";
574
                $this->printLink($this->action, true, __METHOD__);
575
                echo '</td>'."\n";
576
            }
577
578
            $this->printTableRowCells($resultset, $fkey_information, isset($object));
579
580
            echo '</tr>'."\n";
581
            $resultset->moveNext();
582
            ++$i;
583
        }
584
        echo '</table>'."\n";
585
586
        echo '<p>', $resultset->recordCount(), " {$this->lang['strrows']}</p>"."\n";
587
        // Show page navigation
588
        echo $paginator;
589
    }
590
591
    /**
592
     * Print table header cells.
593
     *
594
     * @param \PHPPgAdmin\ADORecordSet $resultset set of results from getRow operation
595
     * @param array|bool               $args      - associative array for sort link parameters, or false if there isn't any
596
     * @param bool                     $withOid   either to display OIDs or not
597
     */
598
    public function printTableHeaderCells(&$resultset, $args, $withOid)
599
    {
600
        $data = $this->misc->getDatabaseAccessor();
601
602
        foreach (array_keys($resultset->fields) as $index => $key) {
0 ignored issues
show
Bug introduced by
$resultset->fields of type boolean is incompatible with the type array expected by parameter $input of array_keys(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

602
        foreach (array_keys(/** @scrutinizer ignore-type */ $resultset->fields) as $index => $key) {
Loading history...
603
            if (($key === $data->id) && (!($withOid && $this->conf['show_oids']))) {
604
                continue;
605
            }
606
            $finfo = $resultset->fetchField($index);
607
608
            if (false === $args) {
609
                echo '<th class="data">', $this->misc->printVal($finfo->name), '</th>'."\n";
610
611
                continue;
612
            }
613
            $args['page']    = $_REQUEST['page'];
614
            $args['sortkey'] = $index + 1;
615
            // Sort direction opposite to current direction, unless it's currently ''
616
            $args['sortdir'] = ('asc' == $_REQUEST['sortdir'] && $_REQUEST['sortkey'] == ($index + 1)) ? 'desc' : 'asc';
617
618
            $sortLink = http_build_query($args);
619
620
            echo "<th class=\"data\"><a href=\"?{$sortLink}\">";
621
            echo $this->misc->printVal($finfo->name);
622
            if ($_REQUEST['sortkey'] == ($index + 1)) {
623
                $icon = ('asc' == $_REQUEST['sortdir']) ? $this->misc->icon('RaiseArgument') : $this->misc->icon('LowerArgument');
624
                echo sprintf('<img src="%s" alt="%s">', $icon, $_REQUEST['sortdir']);
625
            }
626
            echo '</a></th>'."\n";
627
        }
628
629
        reset($resultset->fields);
0 ignored issues
show
Bug introduced by
$resultset->fields of type boolean is incompatible with the type array expected by parameter $array of reset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

629
        reset(/** @scrutinizer ignore-type */ $resultset->fields);
Loading history...
630
    }
631
632
    /**
633
     * Print table rows.
634
     *
635
     * @param \PHPPgAdmin\ADORecordSet $resultset        The resultset
636
     * @param array                    $fkey_information The fkey information
637
     * @param bool                     $withOid          either to display OIDs or not
638
     */
639
    public function printTableRowCells(&$resultset, &$fkey_information, $withOid)
640
    {
641
        $data = $this->misc->getDatabaseAccessor();
642
        $j    = 0;
643
644
        $this->coalesceArr($_REQUEST, 'strings', 'collapsed');
645
646
        foreach ($resultset->fields as $k => $v) {
647
            $finfo = $resultset->fetchField($j++);
648
649
            if (($k === $data->id) && (!($withOid && $this->conf['show_oids']))) {
650
                continue;
651
            }
652
            $printvalOpts = ['null' => true, 'clip' => ('collapsed' == $_REQUEST['strings'])];
653
            if (null !== $v && '' == $v) {
654
                echo '<td>&nbsp;</td>';
655
            } else {
656
                echo '<td style="white-space:nowrap;">';
657
658
                if ((null !== $v) && isset($fkey_information['byfield'][$k])) {
659
                    foreach ($fkey_information['byfield'][$k] as $conid) {
660
                        $query_params = $fkey_information['byconstr'][$conid]['url_data'];
661
662
                        foreach ($fkey_information['byconstr'][$conid]['fkeys'] as $p_field => $f_field) {
663
                            $query_params .= '&amp;'.urlencode("fkey[{$f_field}]").'='.urlencode($resultset->fields[$p_field]);
664
                        }
665
666
                        // $fkey_information['common_url'] is already urlencoded
667
                        $query_params .= '&amp;'.$fkey_information['common_url'];
668
                        echo '<div style="display:inline-block;">';
669
                        echo '<a class="fk fk_'.htmlentities($conid, ENT_QUOTES, 'UTF-8')."\" href=\"display?{$query_params}\">";
670
                        echo '<img src="'.$this->misc->icon('ForeignKey').'" style="vertical-align:middle;" alt="[fk]" title="'
671
                        .htmlentities($fkey_information['byconstr'][$conid]['consrc'], ENT_QUOTES, 'UTF-8')
672
                            .'" />';
673
                        echo '</a>';
674
                        echo '</div>';
675
                    }
676
                    $printvalOpts['class'] = 'fk_value';
677
                }
678
                $val = $this->misc->printVal($v, $finfo->type, $printvalOpts);
679
680
                echo $val;
681
                echo '</td>';
682
            }
683
        }
684
    }
685
686
    /**
687
     * Show form to edit row.
688
     *
689
     * @param string $msg message to display on top of the form or after performing edition
690
     */
691
    public function formEditRow($msg = '')
692
    {
693
        $data = $this->misc->getDatabaseAccessor();
694
695
        if (is_array($_REQUEST['key'])) {
696
            $key = $_REQUEST['key'];
697
        } else {
698
            $key = unserialize(urldecode($_REQUEST['key']));
699
        }
700
701
        $this->printTrail($_REQUEST['subject']);
702
        $this->printTitle($this->lang['streditrow']);
703
        $this->printMsg($msg);
704
705
        $attrs     = $data->getTableAttributes($_REQUEST['table']);
706
        $resultset = $data->browseRow($_REQUEST['table'], $key);
707
708
        $fksprops = $this->_getFKProps();
709
710
        echo '<form action="'.\SUBFOLDER.'/src/views/display" method="post" id="ac_form">'."\n";
711
712
        $elements = 0;
713
        $error    = true;
714
        if (1 == $resultset->recordCount() && $attrs->recordCount() > 0) {
715
            echo '<table>'."\n";
716
717
            // Output table header
718
            echo "<tr><th class=\"data\">{$this->lang['strcolumn']}</th><th class=\"data\">{$this->lang['strtype']}</th>";
719
            echo "<th class=\"data\">{$this->lang['strformat']}</th>"."\n";
720
            echo "<th class=\"data\">{$this->lang['strnull']}</th><th class=\"data\">{$this->lang['strvalue']}</th></tr>";
721
722
            $i = 0;
723
            while (!$attrs->EOF) {
724
                $attrs->fields['attnotnull'] = $data->phpBool($attrs->fields['attnotnull']);
725
                $id                          = (0 == ($i % 2) ? '1' : '2');
726
727
                // Initialise variables
728
                if (!isset($_REQUEST['format'][$attrs->fields['attname']])) {
729
                    $_REQUEST['format'][$attrs->fields['attname']] = 'VALUE';
730
                }
731
732
                echo "<tr class=\"data{$id}\">"."\n";
733
                echo '<td style="white-space:nowrap;">', $this->misc->printVal($attrs->fields['attname']), '</td>';
734
                echo '<td style="white-space:nowrap;">'."\n";
735
                echo $this->misc->printVal($data->formatType($attrs->fields['type'], $attrs->fields['atttypmod']));
736
                echo '<input type="hidden" name="types[', htmlspecialchars($attrs->fields['attname']), ']" value="',
737
                htmlspecialchars($attrs->fields['type']), '" /></td>';
738
                ++$elements;
739
                echo '<td style="white-space:nowrap;">'."\n";
740
                echo '<select name="format['.htmlspecialchars($attrs->fields['attname']), ']">'."\n";
741
                echo '<option value="VALUE"', ($_REQUEST['format'][$attrs->fields['attname']] == 'VALUE') ? ' selected="selected"' : '', ">{$this->lang['strvalue']}</option>"."\n";
742
                $selected = ($_REQUEST['format'][$attrs->fields['attname']] == 'EXPRESSION') ? ' selected="selected"' : '';
743
                echo '<option value="EXPRESSION"'.$selected.">{$this->lang['strexpression']}</option>"."\n";
744
                echo "</select>\n</td>"."\n";
745
                ++$elements;
746
                echo '<td style="white-space:nowrap;">';
747
                // Output null box if the column allows nulls (doesn't look at CHECKs or ASSERTIONS)
748
                if (!$attrs->fields['attnotnull']) {
749
                    // Set initial null values
750
                    if ('confeditrow' == $_REQUEST['action'] && null === $resultset->fields[$attrs->fields['attname']]) {
751
                        $_REQUEST['nulls'][$attrs->fields['attname']] = 'on';
752
                    }
753
                    echo "<label><span><input type=\"checkbox\" class=\"nullcheckbox\" name=\"nulls[{$attrs->fields['attname']}]\"",
754
                    isset($_REQUEST['nulls'][$attrs->fields['attname']]) ? ' checked="checked"' : '', ' /></span></label></td>'."\n";
755
                    ++$elements;
756
                } else {
757
                    echo '&nbsp;</td>';
758
                }
759
760
                echo "<td id=\"row_att_{$attrs->fields['attnum']}\" style=\"white-space:nowrap;\">";
761
762
                $extras = [];
763
764
                // If the column allows nulls, then we put a JavaScript action on the data field to unset the
765
                // NULL checkbox as soon as anything is entered in the field.  We use the $elements variable to
766
                // keep track of which element offset we're up to.  We can't refer to the null checkbox by name
767
                // as it contains '[' and ']' characters.
768
                if (!$attrs->fields['attnotnull']) {
769
                    $extras['class'] = 'insert_row_input';
770
                }
771
772
                if ((false !== $fksprops) && isset($fksprops['byfield'][$attrs->fields['attnum']])) {
773
                    $extras['id']           = "attr_{$attrs->fields['attnum']}";
774
                    $extras['autocomplete'] = 'off';
775
                }
776
777
                echo $data->printField("values[{$attrs->fields['attname']}]", $resultset->fields[$attrs->fields['attname']], $attrs->fields['type'], $extras);
778
779
                echo '</td>';
780
                ++$elements;
781
                echo '</tr>'."\n";
782
                ++$i;
783
                $attrs->moveNext();
784
            }
785
            echo '</table>'."\n";
786
787
            $error = false;
788
        } elseif (1 != $resultset->recordCount()) {
789
            echo "<p>{$this->lang['strrownotunique']}</p>"."\n";
790
        } else {
791
            echo "<p>{$this->lang['strinvalidparam']}</p>"."\n";
792
        }
793
794
        echo '<input type="hidden" name="action" value="editrow" />'."\n";
795
        echo $this->misc->form;
796
        if (isset($_REQUEST['table'])) {
797
            echo '<input type="hidden" name="table" value="', htmlspecialchars($_REQUEST['table']), '" />'."\n";
798
        }
799
800
        if (isset($_REQUEST['subject'])) {
801
            echo '<input type="hidden" name="subject" value="', htmlspecialchars($_REQUEST['subject']), '" />'."\n";
802
        }
803
804
        if (isset($_REQUEST['query'])) {
805
            echo '<input type="hidden" name="query" value="', htmlspecialchars($_REQUEST['query']), '" />'."\n";
806
        }
807
808
        if (isset($_REQUEST['count'])) {
809
            echo '<input type="hidden" name="count" value="', htmlspecialchars($_REQUEST['count']), '" />'."\n";
810
        }
811
812
        if (isset($_REQUEST['return'])) {
813
            echo '<input type="hidden" name="return" value="', htmlspecialchars($_REQUEST['return']), '" />'."\n";
814
        }
815
816
        echo '<input type="hidden" name="page" value="', htmlspecialchars($_REQUEST['page']), '" />'."\n";
817
        echo '<input type="hidden" name="sortkey" value="', htmlspecialchars($_REQUEST['sortkey']), '" />'."\n";
818
        echo '<input type="hidden" name="sortdir" value="', htmlspecialchars($_REQUEST['sortdir']), '" />'."\n";
819
        echo '<input type="hidden" name="strings" value="', htmlspecialchars($_REQUEST['strings']), '" />'."\n";
820
        echo '<input type="hidden" name="key" value="', htmlspecialchars(urlencode(serialize($key))), '" />'."\n";
821
        echo '<p>';
822
        if (!$error) {
823
            echo "<input type=\"submit\" name=\"save\" accesskey=\"r\" value=\"{$this->lang['strsave']}\" />"."\n";
824
        }
825
826
        echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->lang['strcancel']}\" />"."\n";
827
828
        if (false !== $fksprops) {
829
            if ('default off' != $this->conf['autocomplete']) {
830
                echo "<input type=\"checkbox\" id=\"no_ac\" value=\"1\" checked=\"checked\" /><label for=\"no_ac\">{$this->lang['strac']}</label>"."\n";
831
            } else {
832
                echo "<input type=\"checkbox\" id=\"no_ac\" value=\"0\" /><label for=\"no_ac\">{$this->lang['strac']}</label>"."\n";
833
            }
834
        }
835
836
        echo '</p>'."\n";
837
        echo '</form>'."\n";
838
        echo '<script src="'.\SUBFOLDER.'/assets/js/insert_or_edit_row.js" type="text/javascript"></script>';
839
    }
840
841
    /**
842
     * Performs actual edition of row.
843
     */
844
    public function doEditRow()
845
    {
846
        $data = $this->misc->getDatabaseAccessor();
847
848
        if (is_array($_REQUEST['key'])) {
849
            $key = $_REQUEST['key'];
850
        } else {
851
            $key = unserialize(urldecode($_REQUEST['key']));
852
        }
853
854
        $this->coalesceArr($_POST, 'values', []);
855
856
        $this->coalesceArr($_POST, 'nulls', []);
857
858
        $status = $data->editRow(
859
            $_POST['table'],
860
            $_POST['values'],
861
            $_POST['nulls'],
862
            $_POST['format'],
863
            $_POST['types'],
864
            $key
865
        );
866
        if (0 == $status) {
867
            return $this->doBrowse($this->lang['strrowupdated']);
868
        }
869
        if ($status == -2) {
870
            return $this->formEditRow($this->lang['strrownotunique']);
871
        }
872
873
        return $this->formEditRow($this->lang['strrowupdatedbad']);
874
    }
875
876
    /**
877
     * Show confirmation of drop and perform actual drop.
878
     *
879
     * @param mixed $confirm
880
     */
881
    public function doDelRow($confirm)
882
    {
883
        $data = $this->misc->getDatabaseAccessor();
884
885
        if ($confirm) {
886
            $this->printTrail($_REQUEST['subject']);
887
            $this->printTitle($this->lang['strdeleterow']);
888
889
            $resultset = $data->browseRow($_REQUEST['table'], $_REQUEST['key']);
890
891
            echo '<form action="'.\SUBFOLDER.'/src/views/display" method="post">'."\n";
892
            echo $this->misc->form;
893
894
            if (1 == $resultset->recordCount()) {
895
                echo "<p>{$this->lang['strconfdeleterow']}</p>"."\n";
896
897
                $fkinfo = [];
898
                echo '<table><tr>';
899
                $this->printTableHeaderCells($resultset, false, true);
900
                echo '</tr>';
901
                echo '<tr class="data1">'."\n";
902
                $this->printTableRowCells($resultset, $fkinfo, true);
903
                echo '</tr>'."\n";
904
                echo '</table>'."\n";
905
                echo '<br />'."\n";
906
907
                echo '<input type="hidden" name="action" value="delrow" />'."\n";
908
                echo "<input type=\"submit\" name=\"yes\" value=\"{$this->lang['stryes']}\" />"."\n";
909
                echo "<input type=\"submit\" name=\"no\" value=\"{$this->lang['strno']}\" />"."\n";
910
            } elseif (1 != $resultset->recordCount()) {
911
                echo "<p>{$this->lang['strrownotunique']}</p>"."\n";
912
                echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->lang['strcancel']}\" />"."\n";
913
            } else {
914
                echo "<p>{$this->lang['strinvalidparam']}</p>"."\n";
915
                echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->lang['strcancel']}\" />"."\n";
916
            }
917
            if (isset($_REQUEST['table'])) {
918
                echo '<input type="hidden" name="table" value="', htmlspecialchars($_REQUEST['table']), '" />'."\n";
919
            }
920
921
            if (isset($_REQUEST['subject'])) {
922
                echo '<input type="hidden" name="subject" value="', htmlspecialchars($_REQUEST['subject']), '" />'."\n";
923
            }
924
925
            if (isset($_REQUEST['query'])) {
926
                echo '<input type="hidden" name="query" value="', htmlspecialchars($_REQUEST['query']), '" />'."\n";
927
            }
928
929
            if (isset($_REQUEST['count'])) {
930
                echo '<input type="hidden" name="count" value="', htmlspecialchars($_REQUEST['count']), '" />'."\n";
931
            }
932
933
            if (isset($_REQUEST['return'])) {
934
                echo '<input type="hidden" name="return" value="', htmlspecialchars($_REQUEST['return']), '" />'."\n";
935
            }
936
937
            echo '<input type="hidden" name="page" value="', htmlspecialchars($_REQUEST['page']), '" />'."\n";
938
            echo '<input type="hidden" name="sortkey" value="', htmlspecialchars($_REQUEST['sortkey']), '" />'."\n";
939
            echo '<input type="hidden" name="sortdir" value="', htmlspecialchars($_REQUEST['sortdir']), '" />'."\n";
940
            echo '<input type="hidden" name="strings" value="', htmlspecialchars($_REQUEST['strings']), '" />'."\n";
941
            echo '<input type="hidden" name="key" value="', htmlspecialchars(urlencode(serialize($_REQUEST['key']))), '" />'."\n";
942
            echo '</form>'."\n";
943
        } else {
944
            $status = $data->deleteRow($_POST['table'], unserialize(urldecode($_POST['key'])));
945
            if (0 == $status) {
946
                $this->doBrowse($this->lang['strrowdeleted']);
947
            } elseif ($status == -2) {
948
                $this->doBrowse($this->lang['strrownotunique']);
949
            } else {
950
                $this->doBrowse($this->lang['strrowdeletedbad']);
951
            }
952
        }
953
    }
954
955
    /**
956
     * Build & return the FK information data structure
957
     * used when deciding if a field should have a FK link or not.
958
     *
959
     * @return array associative array describing the FK
960
     */
961
    public function &getFKInfo()
962
    {
963
        $data = $this->misc->getDatabaseAccessor();
964
965
        // Get the foreign key(s) information from the current table
966
        $fkey_information = ['byconstr' => [], 'byfield' => []];
967
968
        if (isset($_REQUEST['table'])) {
969
            $constraints = $data->getConstraintsWithFields($_REQUEST['table']);
970
            if ($constraints->recordCount() > 0) {
971
                $fkey_information['common_url'] = $this->misc->getHREF('schema').'&amp;subject=table';
972
973
                // build the FK constraints data structure
974
                while (!$constraints->EOF) {
975
                    $constr = &$constraints->fields;
976
                    if ('f' == $constr['contype']) {
977
                        if (!isset($fkey_information['byconstr'][$constr['conid']])) {
978
                            $fkey_information['byconstr'][$constr['conid']] = [
979
                                'url_data' => 'table='.urlencode($constr['f_table']).'&amp;schema='.urlencode($constr['f_schema']),
980
                                'fkeys'    => [],
981
                                'consrc'   => $constr['consrc'],
982
                            ];
983
                        }
984
985
                        $fkey_information['byconstr'][$constr['conid']]['fkeys'][$constr['p_field']] = $constr['f_field'];
986
987
                        if (!isset($fkey_information['byfield'][$constr['p_field']])) {
988
                            $fkey_information['byfield'][$constr['p_field']] = [];
989
                        }
990
991
                        $fkey_information['byfield'][$constr['p_field']][] = $constr['conid'];
992
                    }
993
                    $constraints->moveNext();
994
                }
995
            }
996
        }
997
998
        return $fkey_information;
999
    }
1000
1001
    // Print the FK row, used in ajax requests
1002
    public function doBrowseFK()
1003
    {
1004
        $data = $this->misc->getDatabaseAccessor();
1005
1006
        $ops = [];
1007
        foreach ($_REQUEST['fkey'] as $x => $y) {
1008
            $ops[$x] = '=';
1009
        }
1010
        $query             = $data->getSelectSQL($_REQUEST['table'], [], $_REQUEST['fkey'], $ops);
1011
        $_REQUEST['query'] = $query;
1012
1013
        $fkinfo = $this->getFKInfo();
1014
1015
        $max_pages = 1;
1016
        // Retrieve page from query.  $max_pages is returned by reference.
1017
        $resultset = $data->browseQuery(
1018
            'SELECT',
1019
            $_REQUEST['table'],
1020
            $_REQUEST['query'],
1021
            null,
1022
            null,
1023
            1,
1024
            1,
1025
            $max_pages
1026
        );
1027
1028
        echo '<a href="javascript:void(0);" style="display:table-cell;" class="fk_delete"><img alt="[delete]" src="'.$this->misc->icon('Delete').'" /></a>'."\n";
1029
        echo '<div style="display:table-cell;">';
1030
1031
        if (is_object($resultset) && $resultset->recordCount() > 0) {
1032
            /* we are browsing a referenced table here
1033
             * we should show OID if show_oids is true
1034
             * so we give true to withOid in functions bellow
1035
             */
1036
            echo '<table><tr>';
1037
            $this->printTableHeaderCells($resultset, false, true);
1038
            echo '</tr>';
1039
            echo '<tr class="data1">'."\n";
1040
            $this->printTableRowCells($resultset, $fkinfo, true);
1041
            echo '</tr>'."\n";
1042
            echo '</table>'."\n";
1043
        } else {
1044
            echo $this->lang['strnodata'];
1045
        }
1046
        echo '</div>';
1047
    }
1048
1049
    private function _getMinMaxPages($page, $pages)
1050
    {
1051
        $window = 10;
1052
        if ($page <= $window) {
1053
            $min_page = 1;
1054
            $max_page = min(2 * $window, $pages);
1055
        } elseif ($page > $window && $pages >= $page + $window) {
1056
            $min_page = ($page - $window) + 1;
1057
            $max_page = $page + $window;
1058
        } else {
1059
            $min_page = ($page - (2 * $window - ($pages - $page))) + 1;
1060
            $max_page = $pages;
1061
        }
1062
1063
        // Make sure min_page is always at least 1
1064
        // and max_page is never greater than $pages
1065
        $min_page = max($min_page, 1);
1066
        $max_page = min($max_page, $pages);
1067
1068
        return [$min_page, $max_page];
1069
    }
1070
1071
    /**
1072
     * Do multi-page navigation.  Displays the prev, next and page options.
1073
     *
1074
     * @param int   $page      - the page currently viewed
1075
     * @param int   $pages     - the maximum number of pages
1076
     * @param array $gets      -  the parameters to include in the link to the wanted page
1077
     * @param int   $max_width - the number of pages to make available at any one time (default = 20)
1078
     *
1079
     * @return string the pagination links
1080
     */
1081
    private function _printPages($page, $pages, $gets, $max_width = 20)
1082
    {
1083
        $lang = $this->lang;
1084
        $page = (int) $page;
1085
1086
        if ($page < 0 || $page > $pages || $pages <= 1 || $max_width <= 0) {
1087
            return;
1088
        }
1089
1090
        unset($gets['page']);
1091
        $url = http_build_query($gets);
1092
1093
        $result = '<p style="text-align: center">'."\n";
1094
        if ($page != 1) {
1095
            $result .= sprintf('<a class="pagenav" href="?%s&page=1">%s</a>%s&nbsp;', $url, $lang['strfirst'], "\n");
1096
            $result .= sprintf('<a class="pagenav" href="?%s&page=%s">%s</a>%s', $url, $page - 1, $lang['strprev'], "\n");
1097
        }
1098
1099
        list($min_page, $max_page) = $this->_getMinMaxPages($page, $pages);
1100
1101
        for ($i = $min_page; $i <= $max_page; ++$i) {
1102
            $result .= (($i === $page) ? $i : sprintf('<a class="pagenav" href="display?%s&page=%s">%s</a>', $url, $i, $i))."\n";
1103
        }
1104
1105
        if ($page != $pages) {
1106
            $result .= sprintf('<a class="pagenav" href="?%s&page=%s">%s</a>%s', $url, $page + 1, $lang['strnext'], "\n");
1107
            $result .= sprintf('&nbsp;<a class="pagenav" href="?%s&page=%s">%s</a>%s', $url, $pages, $lang['strlast'], "\n");
1108
        }
1109
        $result .= "</p>\n";
1110
1111
        return $result;
1112
    }
1113
}
1114