Issues (217)

src/controllers/IndexesController.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * PHPPgAdmin 6.1.3
5
 */
6
7
namespace PHPPgAdmin\Controller;
8
9
use PHPPgAdmin\Decorators\Decorator;
10
11
/**
12
 * Base controller class.
13
 */
14
class IndexesController extends BaseController
15
{
16
    public $controller_title = 'strindexes';
17
18
    public $scripts = '<script src="/assets/js/indexes.js" type="text/javascript"></script>';
19
20
    /**
21
     * Default method to render the controller according to the action parameter.
22
     */
23
    public function render()
24
    {
25
        if ('tree' === $this->action) {
26
            return $this->doTree();
27
        }
28
        $this->scripts = '<script src="' . \containerInstance()->subFolder . '/assets/js/indexes.js" type="text/javascript"></script>';
29
        $this->printHeader($this->headerTitle(), $this->scripts);
30
31
        $onloadInit = false;
32
33
        if ('create_index' === $this->action || 'save_create_index' === $this->action) {
34
            $onloadInit = true;
35
        }
36
        $this->printBody(true, 'detailbody', $onloadInit);
37
38
        switch ($this->action) {
39
            case 'cluster_index':
40
                if (isset($_POST['cluster'])) {
41
                    $this->doClusterIndex(false);
42
                } else {
43
                    $this->doDefault();
44
                }
45
46
                break;
47
            case 'confirm_cluster_index':
48
                $this->doClusterIndex(true);
49
50
                break;
51
            case 'reindex':
52
                $this->doReindex();
53
54
                break;
55
            case 'save_create_index':
56
                if (null !== $this->getPostParam('cancel')) {
57
                    $this->doDefault();
58
                } else {
59
                    $this->doSaveCreateIndex();
60
                }
61
62
                break;
63
            case 'create_index':
64
                $this->doCreateIndex();
65
66
                break;
67
            case 'drop_index':
68
                if (null !== $this->getPostParam('drop')) {
69
                    $this->doDropIndex(false);
70
                } else {
71
                    $this->doDefault();
72
                }
73
74
                break;
75
            case 'confirm_drop_index':
76
                $this->doDropIndex(true);
77
78
                break;
79
80
            default:
81
                $this->doDefault();
82
83
                break;
84
        }
85
86
        return $this->printFooter();
87
    }
88
89
    public function doDefault(string $msg = ''): void
90
    {
91
        $data = $this->misc->getDatabaseAccessor();
92
93
        $lang = $this->lang;
94
        $indPre = static function (&$rowdata, $actions) use ($data, $lang) {
95
            if ($data->phpBool($rowdata->fields['indisprimary'])) {
96
                $rowdata->fields['+constraints'] = $lang['strprimarykey'];
97
                $actions['drop']['disable'] = true;
98
            } elseif ($data->phpBool($rowdata->fields['indisunique'])) {
99
                $rowdata->fields['+constraints'] = $lang['struniquekey'];
100
                $actions['drop']['disable'] = true;
101
            } else {
102
                $rowdata->fields['+constraints'] = '';
103
            }
104
105
            return $actions;
106
        };
107
        $this->coalesceArr($_REQUEST, 'subject', 'table');
108
109
        $subject = \urlencode($this->getRequestParam('subject', 'table'));
110
        $object = \urlencode($this->getRequestParam($subject));
111
112
        $this->printTrail($subject);
113
        $this->printTabs($subject, 'indexes');
114
        $this->printMsg($msg);
115
116
        $indexes = $data->getIndexes($object);
117
118
        $columns = [
119
            'index' => [
120
                'title' => $this->lang['strname'],
121
                'field' => Decorator::field('indname'),
122
            ],
123
            'definition' => [
124
                'title' => $this->lang['strdefinition'],
125
                'field' => Decorator::field('inddef'),
126
            ],
127
            'constraints' => [
128
                'title' => $this->lang['strconstraints'],
129
                'field' => Decorator::field('+constraints'),
130
                'type' => 'verbatim',
131
                'params' => ['align' => 'center'],
132
            ],
133
            'clustered' => [
134
                'title' => $this->lang['strclustered'],
135
                'field' => Decorator::field('indisclustered'),
136
                'type' => 'yesno',
137
            ],
138
            'actions' => [
139
                'title' => $this->lang['stractions'],
140
            ],
141
            'comment' => [
142
                'title' => $this->lang['strcomment'],
143
                'field' => Decorator::field('idxcomment'),
144
            ],
145
        ];
146
147
        $url = \containerInstance()->subFolder . '/src/views/indexes';
148
149
        $actions = [
150
            'cluster' => [
151
                'content' => $this->lang['strclusterindex'],
152
                'attr' => [
153
                    'href' => [
154
                        'url' => $url,
155
                        'urlvars' => [
156
                            'action' => 'confirm_cluster_index',
157
                            'subject' => $subject,
158
                            $subject => $object,
159
                            'index' => Decorator::field('indname'),
160
                        ],
161
                    ],
162
                ],
163
            ],
164
            'reindex' => [
165
                'content' => $this->lang['strreindex'],
166
                'attr' => [
167
                    'href' => [
168
                        'url' => $url,
169
                        'urlvars' => [
170
                            'action' => 'reindex',
171
                            'subject' => $subject,
172
                            $subject => $object,
173
                            'index' => Decorator::field('indname'),
174
                        ],
175
                    ],
176
                ],
177
            ],
178
            'drop' => [
179
                'content' => $this->lang['strdrop'],
180
                'attr' => [
181
                    'href' => [
182
                        'url' => $url,
183
                        'urlvars' => [
184
                            'action' => 'confirm_drop_index',
185
                            'subject' => $subject,
186
                            $subject => $object,
187
                            'index' => Decorator::field('indname'),
188
                        ],
189
                    ],
190
                ],
191
            ],
192
        ];
193
194
        echo $this->printTable($indexes, $columns, $actions, 'indexes-indexes', $this->lang['strnoindexes'], $indPre);
195
196
        $this->printNavLinks([
197
            'create' => [
198
                'attr' => [
199
                    'href' => [
200
                        'url' => 'indexes',
201
                        'urlvars' => [
202
                            'action' => 'create_index',
203
                            'server' => $_REQUEST['server'],
204
                            'database' => $_REQUEST['database'],
205
                            'schema' => $_REQUEST['schema'],
206
                            $subject => $object,
207
                            'subject' => $subject,
208
                        ],
209
                    ],
210
                ],
211
                'content' => $this->lang['strcreateindex'],
212
            ],
213
        ], 'indexes-indexes', \get_defined_vars());
214
    }
215
216
    public function doTree()
217
    {
218
        $data = $this->misc->getDatabaseAccessor();
219
        $this->coalesceArr($_REQUEST, 'subject', 'table');
220
221
        $subject = \urlencode($_REQUEST['subject']);
222
        $object = \urlencode($_REQUEST[$subject]);
223
224
        $indexes = $data->getIndexes($object);
225
226
        $getIcon = static function ($f) {
227
            if ('t' === $f['indisprimary']) {
228
                return 'PrimaryKey';
229
            }
230
231
            if ('t' === $f['indisunique']) {
232
                return 'UniqueConstraint';
233
            }
234
235
            return 'Index';
236
        };
237
238
        $attrs = [
239
            'text' => Decorator::field('indname'),
240
            'icon' => Decorator::callback($getIcon),
241
        ];
242
243
        return $this->printTree($indexes, $attrs, 'indexes');
0 ignored issues
show
It seems like $indexes can also be of type integer; however, parameter $_treedata of PHPPgAdmin\Controller\BaseController::printTree() does only seem to accept PHPPgAdmin\ADORecordSet|PHPPgAdmin\ArrayRecordSet, maybe add an additional type check? ( Ignorable by Annotation )

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

243
        return $this->printTree(/** @scrutinizer ignore-type */ $indexes, $attrs, 'indexes');
Loading history...
244
    }
245
246
    /**
247
     * Show confirmation of cluster index and perform actual cluster.
248
     *
249
     * @param mixed $confirm
250
     */
251
    public function doClusterIndex($confirm): void
252
    {
253
        $data = $this->misc->getDatabaseAccessor();
254
255
        $this->coalesceArr($_REQUEST, 'subject', 'table');
256
        $subject = \urlencode($_REQUEST['subject']);
257
        $object = \urlencode($_REQUEST[$subject]);
258
259
        //$this->printTrail($subject);
260
261
        if ($confirm) {
262
            // Default analyze to on
263
            $_REQUEST['analyze'] = true;
264
265
            $this->printTrail('index');
266
            $this->printTabs($subject, 'indexes');
267
            $this->printTitle($this->lang['strclusterindex'], 'pg.index.cluster');
268
269
            echo '<p>', \sprintf($this->lang['strconfcluster'], $this->misc->printVal($_REQUEST['index'])), '</p>' . \PHP_EOL;
270
271
            echo '<form action="' . \containerInstance()->subFolder . '/src/views/indexes" method="post">' . \PHP_EOL;
272
            echo '<p><input type="checkbox" id="analyze" name="analyze"', (isset($_REQUEST['analyze']) ? ' checked="checked"' : ''), ' />';
273
            echo "<label for=\"analyze\">{$this->lang['stranalyze']}</label></p>" . \PHP_EOL;
274
            echo '<input type="hidden" name="action" value="cluster_index" />' . \PHP_EOL;
275
            echo '<input type="hidden" name="table" value="', \htmlspecialchars($object), '" />' . \PHP_EOL;
276
            echo '<input type="hidden" name="index" value="', \htmlspecialchars($_REQUEST['index']), '" />' . \PHP_EOL;
277
            echo $this->view->form;
278
            echo "<input type=\"submit\" name=\"cluster\" value=\"{$this->lang['strclusterindex']}\" />" . \PHP_EOL;
279
            echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->lang['strcancel']}\" />" . \PHP_EOL;
280
            echo '</form>' . \PHP_EOL;
281
        } else {
282
            \set_time_limit(0);
283
            [$status, $sql] = $data->clusterIndex($object, $_POST['index']);
284
285
            if (0 === $status) {
286
                if (isset($_POST['analyze'])) {
287
                    $status = $data->analyzeDB($object);
288
289
                    if (0 === $status) {
290
                        $this->doDefault($sql . '<br>' . $this->lang['strclusteredgood'] . ' ' . $this->lang['stranalyzegood']);
291
                    } else {
292
                        $this->doDefault($sql . '<br>' . $this->lang['stranalyzebad']);
293
                    }
294
                } else {
295
                    $this->doDefault($sql . '<br>' . $this->lang['strclusteredgood']);
296
                }
297
            } else {
298
                $this->doDefault($sql . '<br>' . $this->lang['strclusteredbad']);
299
            }
300
        }
301
    }
302
303
    public function doReindex(): void
304
    {
305
        $data = $this->misc->getDatabaseAccessor();
306
        \set_time_limit(0);
307
        $status = $data->reindex('INDEX', $_REQUEST['index']);
308
309
        if (0 === $status) {
310
            $this->doDefault($this->lang['strreindexgood']);
311
        } else {
312
            $this->doDefault($this->lang['strreindexbad']);
313
        }
314
    }
315
316
    /**
317
     * Displays a screen where they can enter a new index.
318
     *
319
     * @param mixed $msg
320
     */
321
    public function doCreateIndex($msg = ''): void
322
    {
323
        $data = $this->misc->getDatabaseAccessor();
324
325
        $subject = \urlencode($this->getRequestParam('subject', 'table'));
326
        $object = \urlencode($this->getRequestParam($subject));
327
328
        $formIndexName = $this->getPostParam('formIndexName', '');
329
        $formIndexType = $this->getPostParam('formIndexType');
330
        $formUnique = $this->getPostParam('formUnique');
331
        $formConcur = $this->getPostParam('formConcur');
332
        $formWhere = $this->getPostParam('formWhere', '');
333
        $formSpc = $this->getPostParam('formSpc', '');
334
        $tablespaces = null;
335
336
        $attrs = $data->getTableAttributes($object);
337
        // Fetch all tablespaces from the database
338
        if ($data->hasTablespaces()) {
339
            $tablespaces = $data->getTablespaces();
340
        }
341
342
        $this->printTrail($subject);
343
        $this->printTabs($subject, 'indexes');
344
        $this->printTitle($this->lang['strcreateindex'], 'pg.index.create');
345
        $this->printMsg($msg);
346
347
        $selColumns = new \PHPPgAdmin\XHtml\XHtmlSelect('TableColumnList', true, 10);
348
        $selColumns->set_style('width: 14em;');
349
350
        if (0 < $attrs->recordCount()) {
351
            while (!$attrs->EOF) {
352
                $attname = new \PHPPgAdmin\XHtml\XHtmlOption($attrs->fields['attname']);
353
                $selColumns->add($attname);
354
                $attrs->moveNext();
355
            }
356
        }
357
358
        $selIndex = new \PHPPgAdmin\XHtml\XHtmlSelect('IndexColumnList[]', true, 10);
359
        $selIndex->set_style('width: 14em;');
360
        $selIndex->set_attribute('id', 'IndexColumnList');
361
        $buttonAdd = new \PHPPgAdmin\XHtml\XHtmlButton('add', '>>');
362
        $buttonAdd->set_attribute('onclick', 'buttonPressed(this);');
363
        $buttonAdd->set_attribute('type', 'button');
364
365
        $buttonRemove = new \PHPPgAdmin\XHtml\XHtmlButton('remove', '<<');
366
        $buttonRemove->set_attribute('onclick', 'buttonPressed(this);');
367
        $buttonRemove->set_attribute('type', 'button');
368
369
        echo '<form onsubmit="doSelectAll();" name="formIndex" action="indexes" method="post">' . \PHP_EOL;
370
371
        echo '<table>' . \PHP_EOL;
372
        echo '<tr><th class="data required" colspan="3">' . $this->lang['strindexname'] . '</th></tr>';
373
        echo '<tr>';
374
        echo '<td class="data1" colspan="3">';
375
        echo 'Index name cannot exceed ' . $data->_maxNameLen . ' characters<br>';
376
        echo '<input type="text" name="formIndexName" size="32" placeholder="Index Name" maxlength="' .
377
        $data->_maxNameLen . '" value="' .
378
        \htmlspecialchars($formIndexName) . '" />';
379
        echo '</td>';
380
        echo '</tr>';
381
382
        echo '<tr>';
383
        echo '<th class="data">' . $this->lang['strtablecolumnlist'] . '</th><th class="data">&nbsp;</th>';
384
        echo '<th class="data required">' . $this->lang['strindexcolumnlist'] . '</th>';
385
        echo '</tr>' . \PHP_EOL;
386
387
        echo '<tr><td class="data1">' . $selColumns->fetch() . '</td>' . \PHP_EOL;
388
        echo '<td class="data1">' . $buttonRemove->fetch() . $buttonAdd->fetch() . '</td>';
389
        echo '<td class="data1">' . $selIndex->fetch() . '</td></tr>' . \PHP_EOL;
390
        echo '<tr>';
391
        echo '<th class="data left required" scope="row">' . $this->lang['strindextype'] . '</th>';
392
        echo '<td colspan="2" class="data1"><select name="formIndexType">';
393
394
        foreach ($data->typIndexes as $v) {
395
            echo '<option value="', \htmlspecialchars($v), '"',
396
            ($v === $formIndexType) ? ' selected="selected"' : '', '>', \htmlspecialchars($v), '</option>' . \PHP_EOL;
397
        }
398
        echo '</select></td></tr>' . \PHP_EOL;
399
        echo '<tr>';
400
        echo "<th class=\"data left\" scope=\"row\"><label for=\"formUnique\">{$this->lang['strunique']}</label></th>";
401
        echo '<td  colspan="2" class="data1"><input type="checkbox" id="formUnique" name="formUnique"', ($formUnique ? 'checked="checked"' : ''), ' /></td>';
402
        echo '</tr>';
403
        echo '<tr>';
404
        echo "<th class=\"data left\" scope=\"row\">{$this->lang['strwhere']}</th>";
405
        echo '<td  colspan="2"  class="data1">(<input name="formWhere" size="32" maxlength="' . $data->_maxNameLen . '" value="' . \htmlspecialchars($formWhere) . '" />)</td>';
406
        echo '</tr>';
407
408
        // Tablespace (if there are any)
409
        if ($data->hasTablespaces() && 0 < $tablespaces->recordCount()) {
410
            echo '<tr>' . \PHP_EOL;
411
            echo "<th class=\"data left\">{$this->lang['strtablespace']}</th>" . \PHP_EOL;
412
            echo '<td  colspan="2" class="data1">';
413
            echo "\n\t\t\t<select name=\"formSpc\">" . \PHP_EOL;
414
            // Always offer the default (empty) option
415
            echo "\t\t\t\t<option value=\"\"",
416
            ('' === $formSpc) ? ' selected="selected"' : '', '></option>' . \PHP_EOL;
417
            // Display all other tablespaces
418
            while (!$tablespaces->EOF) {
419
                $spcname = \htmlspecialchars($tablespaces->fields['spcname']);
420
                echo "\t\t\t\t<option value=\"{$spcname}\"",
421
                ($spcname === $formSpc) ? ' selected="selected"' : '', ">{$spcname}</option>" . \PHP_EOL;
422
                $tablespaces->moveNext();
423
            }
424
            echo "\t\t\t</select>\n\t\t</td>\n\t</tr>" . \PHP_EOL;
425
        }
426
427
        if ($data->hasConcurrentIndexBuild()) {
428
            echo '<tr>';
429
            echo "<th class=\"data left\" scope=\"row\"><label for=\"formConcur\">{$this->lang['strconcurrently']}</label></th>";
430
            echo '<td  colspan="2"  class="data1"><input type="checkbox" id="formConcur" name="formConcur"', ($formConcur ? 'checked="checked"' : ''), ' /></td>';
431
            echo '</tr>';
432
        }
433
434
        echo '</table>';
435
436
        echo '<p><input type="hidden" name="action" value="save_create_index" />' . \PHP_EOL;
437
        echo $this->view->form;
438
        echo '<input type="hidden" name="subject" value="', \htmlspecialchars($subject), '" />' . \PHP_EOL;
439
        echo '<input type="hidden" name="' . $subject . '" value="', \htmlspecialchars($object), '" />' . \PHP_EOL;
440
        echo "<input type=\"submit\" value=\"{$this->lang['strcreate']}\" />" . \PHP_EOL;
441
        echo \sprintf('<input type="submit" name="cancel" value="%s"  /></p>%s', $this->lang['strcancel'], \PHP_EOL);
442
        echo '</form>' . \PHP_EOL;
443
    }
444
445
    /**
446
     * Actually creates the new index in the database.
447
     *
448
     * @@ Note: this function can't handle columns with commas in them
449
     */
450
    public function doSaveCreateIndex(): void
451
    {
452
        $data = $this->misc->getDatabaseAccessor();
453
454
        $this->coalesceArr($_POST, 'subject', 'table');
455
        $subject = \urlencode($_POST['subject']);
456
        $object = \urlencode($_POST[$subject]);
457
458
        // Handle databases that don't have partial indexes
459
        $formWhere = $this->getPostParam('formWhere', '');
460
461
        // Default tablespace to null if it isn't set
462
        $formSpc = $this->getPostParam('formSpc');
463
464
        $IndexColumnList = $this->getPostParam('IndexColumnList', '');
465
466
        // Check that they've given a name and at least one column
467
        if ('' === $IndexColumnList) {
468
            $this->doCreateIndex($this->lang['strindexneedscols']);
469
        } else {
470
            [$status, $sql] = $data->createIndex(
471
                $this->getPostParam('formIndexName', ''),
472
                $object,
473
                $IndexColumnList,
474
                $this->getPostParam('formIndexType'),
475
                $this->getPostParam('formUnique'),
476
                $formWhere,
477
                $formSpc,
478
                $this->getPostParam('formConcur')
479
            );
480
481
            if (0 === $status) {
482
                $this->doDefault($sql . '<br>' . $this->lang['strindexcreated']);
483
            } else {
484
                $this->doCreateIndex($this->lang['strindexcreatedbad']);
485
            }
486
        }
487
    }
488
489
    /**
490
     * Show confirmation of drop index and perform actual drop.
491
     *
492
     * @param mixed $confirm
493
     */
494
    public function doDropIndex($confirm): void
495
    {
496
        $data = $this->misc->getDatabaseAccessor();
497
498
        $subject = \urlencode($this->getRequestParam('subject', 'table'));
499
        $object = \urlencode($this->getRequestParam($subject));
500
501
        if ($confirm) {
502
            $this->printTrail('index');
503
            $this->printTitle($this->lang['strdrop'], 'pg.index.drop');
504
505
            echo '<p>', \sprintf($this->lang['strconfdropindex'], $this->misc->printVal($this->getRequestParam('index'))), '</p>' . \PHP_EOL;
506
            echo '<form action="' . \containerInstance()->subFolder . '/src/views/indexes" method="post">' . \PHP_EOL;
507
            echo '<input type="hidden" name="action" value="drop_index" />' . \PHP_EOL;
508
            echo '<input type="hidden" name="table" value="', \htmlspecialchars($object), '" />' . \PHP_EOL;
509
            echo '<input type="hidden" name="index" value="', \htmlspecialchars($this->getRequestParam('index')), '" />' . \PHP_EOL;
510
            echo $this->view->form;
511
            echo '<p><input type="checkbox" id="cascade" name="cascade" value="1" />';
512
            echo '<label for="cascade">' . $this->lang['strcascade'] . '</label></p>' . \PHP_EOL;
513
            echo "<input type=\"submit\" name=\"drop\" value=\"{$this->lang['strdrop']}\" />" . \PHP_EOL;
514
            echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->lang['strcancel']}\" />" . \PHP_EOL;
515
            echo '</form>' . \PHP_EOL;
516
        } else {
517
            try {
518
                [$status, $sql] = $data->dropIndex($this->getPostParam('index'), $this->getPostParam('cascade'));
519
520
                if (0 === $status) {
521
                    $this->doDefault($sql . \PHP_EOL . $this->lang['strindexdropped']);
522
                } else {
523
                    $this->doDefault($sql . \PHP_EOL . $this->lang['strindexdroppedbad']);
524
                }
525
            } catch (\PHPPgAdmin\ADOdbException $e) {
526
                $this->doDefault($this->lang['strindexdroppedbad']);
527
            }
528
        }
529
    }
530
}
531