Passed
Pull Request — master (#267)
by Felipe
07:20
created

DisplayController::doBrowse()   D

Complexity

Conditions 16
Paths 90

Size

Total Lines 144
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 88
c 2
b 0
f 0
nc 90
nop 1
dl 0
loc 144
rs 4.715

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

524
        foreach (\array_keys(/** @scrutinizer ignore-type */ $resultset->fields) as $index => $key) {
Loading history...
525
            if (($key === $data->id) && (!($withOid && $this->conf['show_oids']))) {
526
                continue;
527
            }
528
            $finfo = $resultset->fetchField($index);
529
530
            if (false === $args) {
531
                echo '<th class="data">', $this->misc->printVal($finfo->name), '</th>' . \PHP_EOL;
532
533
                continue;
534
            }
535
            $args['page']    = $_REQUEST['page'];
536
            $args['sortkey'] = $index + 1;
537
            // Sort direction opposite to current direction, unless it's currently ''
538
            $args['sortdir'] = ('asc' === $_REQUEST['sortdir'] && ($index + 1) === $_REQUEST['sortkey']) ? 'desc' : 'asc';
539
540
            $sortLink = \http_build_query($args);
541
542
            echo "<th class=\"data\"><a href=\"?{$sortLink}\">";
543
            echo $this->misc->printVal($finfo->name);
544
545
            if (($index + 1) === $_REQUEST['sortkey']) {
546
                $icon = ('asc' === $_REQUEST['sortdir']) ? $this->misc->icon('RaiseArgument') : $this->misc->icon('LowerArgument');
547
                echo \sprintf('<img src="%s" alt="%s">', $icon, $_REQUEST['sortdir']);
548
            }
549
            echo '</a></th>' . \PHP_EOL;
550
        }
551
552
        \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

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