DisplayController::render()   C
last analyzed

Complexity

Conditions 12
Paths 36

Size

Total Lines 90
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 59
nc 36
nop 0
dl 0
loc 90
rs 6.4678
c 1
b 0
f 0

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

539
        foreach (\array_keys(/** @scrutinizer ignore-type */ $resultset->fields) as $index => $key) {
Loading history...
540
            if (($key === $data->id) && (!($withOid && $this->conf['show_oids']))) {
541
                continue;
542
            }
543
            $finfo = $resultset->fetchField($index);
544
545
            if (false === $args) {
546
                echo '<th class="data">', $this->misc->printVal($finfo->name), '</th>' . \PHP_EOL;
547
548
                continue;
549
            }
550
            $args['page'] = $_REQUEST['page'];
551
            $args['sortkey'] = $index + 1;
552
            // Sort direction opposite to current direction, unless it's currently ''
553
            $args['sortdir'] = ('asc' === $_REQUEST['sortdir'] && ($index + 1) === $_REQUEST['sortkey']) ? 'desc' : 'asc';
554
555
            $sortLink = \http_build_query($args);
556
557
            echo "<th class=\"data\"><a href=\"?{$sortLink}\">";
558
            echo $this->misc->printVal($finfo->name);
559
560
            if (($index + 1) === $_REQUEST['sortkey']) {
561
                $icon = ('asc' === $_REQUEST['sortdir']) ? $this->view->icon('RaiseArgument') : $this->view->icon('LowerArgument');
562
                echo \sprintf('<img src="%s" alt="%s">', $icon, $_REQUEST['sortdir']);
563
            }
564
            echo '</a></th>' . \PHP_EOL;
565
        }
566
567
        \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

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