Cancelled
Push — develop ( de9bfb...d662a5 )
by Felipe
06:08
created

Postgres::browseQuery()   D

Complexity

Conditions 24
Paths 182

Size

Total Lines 102
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 102
rs 4.345
c 0
b 0
f 0
cc 24
eloc 51
nc 182
nop 8

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.47
5
 */
6
7
namespace PHPPgAdmin\Database;
8
9
/**
10
 * A Class that implements the DB Interface for Postgres
11
 * Note: This Class uses ADODB and returns RecordSets.
12
 *
13
 * Id: Postgres.php,v 1.320 2008/02/20 20:43:09 ioguix Exp $
14
 *
15
 * @package PHPPgAdmin
16
 */
17
class Postgres extends ADOdbBase
18
{
19
    use \PHPPgAdmin\Traits\HelperTrait;
20
21
    use \PHPPgAdmin\Database\Traits\AggregateTrait;
22
    use \PHPPgAdmin\Database\Traits\DatabaseTrait;
23
    use \PHPPgAdmin\Database\Traits\DomainTrait;
24
    use \PHPPgAdmin\Database\Traits\FtsTrait;
25
    use \PHPPgAdmin\Database\Traits\FunctionTrait;
26
    use \PHPPgAdmin\Database\Traits\IndexTrait;
27
    use \PHPPgAdmin\Database\Traits\OperatorTrait;
28
    use \PHPPgAdmin\Database\Traits\RoleTrait;
29
    use \PHPPgAdmin\Database\Traits\SchemaTrait;
30
    use \PHPPgAdmin\Database\Traits\SequenceTrait;
31
    use \PHPPgAdmin\Database\Traits\TablespaceTrait;
32
    use \PHPPgAdmin\Database\Traits\TableTrait;
33
    use \PHPPgAdmin\Database\Traits\TypeTrait;
34
    use \PHPPgAdmin\Database\Traits\ViewTrait;
35
    use \PHPPgAdmin\Database\Traits\StatsTrait;
36
    use \PHPPgAdmin\Database\Traits\PrivilegesTrait;
37
38
    public $lang;
39
    public $conf;
40
    protected $container;
41
    protected $server_info;
42
43
    public function __construct(&$conn, $container, $server_info)
44
    {
45
        //$this->prtrace('major_version :' . $this->major_version);
46
        $this->conn      = $conn;
47
        $this->container = $container;
48
49
        $this->lang        = $container->get('lang');
50
        $this->conf        = $container->get('conf');
51
        $this->server_info = $server_info;
52
    }
53
54
    /**
55
     * Fetch a URL (or array of URLs) for a given help page.
56
     *
57
     * @param string $help
58
     *
59
     * @return null|array|string the help page or pages related to the $help topic, or null if none exists
60
     */
61
    public function getHelp($help)
62
    {
63
        $this->getHelpPages();
64
65
        if (isset($this->help_page[$help])) {
66
            if (is_array($this->help_page[$help])) {
67
                $urls = [];
68
                foreach ($this->help_page[$help] as $link) {
69
                    $urls[] = $this->help_base.$link;
70
                }
71
72
                return $urls;
73
            }
74
75
            return $this->help_base.$this->help_page[$help];
76
        }
77
78
        return null;
79
    }
80
81
    /**
82
     * Gets the help pages.
83
     * get help page by instancing the corresponding help class
84
     * if $this->help_page and $this->help_base are set, this function is a noop.
85
     */
86
    public function getHelpPages()
87
    {
88
        if ($this->help_page === null || $this->help_base === null) {
89
            $help_classname = '\PHPPgAdmin\Help\PostgresDoc'.str_replace('.', '', $this->major_version);
90
91
            $help_class = new $help_classname($this->conf, $this->major_version);
92
93
            $this->help_base = $help_class->getHelpBase();
94
        }
95
    }
96
97
    // Formatting functions
98
99
    /**
100
     * Outputs the HTML code for a particular field.
101
     *
102
     * @param string $name   The name to give the field
103
     * @param mixed  $value  The value of the field.  Note this could be 'numeric(7,2)' sort of thing...
104
     * @param string $type   The database type of the field
105
     * @param array  $extras An array of attributes name as key and attributes' values as value
106
     */
107
    public function printField($name, $value, $type, $extras = [])
108
    {
109
        $lang = $this->lang;
110
111
        // Determine actions string
112
        $extra_str = '';
113
        foreach ($extras as $k => $v) {
114
            $extra_str .= " {$k}=\"".htmlspecialchars($v).'"';
115
        }
116
117
        switch (substr($type, 0, 9)) {
118
            case 'bool':
119
            case 'boolean':
120
                if ($value !== null && $value == '') {
121
                    $value = null;
122
                } elseif ($value == 'true') {
123
                    $value = 't';
124
                } elseif ($value == 'false') {
125
                    $value = 'f';
126
                }
127
128
                // If value is null, 't' or 'f'...
129
                if ($value === null || $value == 't' || $value == 'f') {
130
                    echo '<select name="', htmlspecialchars($name), "\"{$extra_str}>\n";
131
                    echo '<option value=""', ($value === null) ? ' selected="selected"' : '', "></option>\n";
132
                    echo '<option value="t"', ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n";
133
                    echo '<option value="f"', ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n";
134
                    echo "</select>\n";
135
                } else {
136
                    echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
137
                }
138
139
                break;
140
            case 'bytea':
141
            case 'bytea[]':
142
                if (!is_null($value)) {
143
                    $value = $this->escapeBytea($value);
144
                }
145
            // no break
146
            case 'text':
147
            case 'text[]':
148
            case 'json':
149
            case 'jsonb':
150
            case 'xml':
151
            case 'xml[]':
152
                $n = substr_count($value, "\n");
153
                $n = $n < 5 ? max(2, $n) : $n;
154
                $n = $n > 20 ? 20 : $n;
155
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"85\"{$extra_str}>\n";
156
                echo htmlspecialchars($value);
157
                echo "</textarea>\n";
158
159
                break;
160
            case 'character':
161
            case 'character[]':
162
                $n = substr_count($value, "\n");
163
                $n = $n < 5 ? 5 : $n;
164
                $n = $n > 20 ? 20 : $n;
165
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n";
166
                echo htmlspecialchars($value);
167
                echo "</textarea>\n";
168
169
                break;
170
            default:
171
                echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
172
173
                break;
174
        }
175
    }
176
177
    /**
178
     * Searches all system catalogs to find objects that match a certain name.
179
     *
180
     * @param string $term   The search term
181
     * @param string $filter The object type to restrict to ('' means no restriction)
182
     *
183
     * @return \PHPPgAdmin\ADORecordSet A recordset
184
     */
185
    public function findObject($term, $filter)
186
    {
187
        $conf = $this->conf;
188
189
        /*about escaping:
190
         * SET standard_conforming_string is not available before 8.2
191
         * So we must use PostgreSQL specific notation :/
192
         * E'' notation is not available before 8.1
193
         * $$ is available since 8.0
194
         * Nothing specific from 7.4
195
         */
196
197
        // Escape search term for ILIKE match
198
        $this->clean($term);
199
        $this->clean($filter);
200
        $term = str_replace('_', '\_', $term);
201
        $term = str_replace('%', '\%', $term);
202
203
        // Exclude system relations if necessary
204
        if (!$conf['show_system']) {
205
            // XXX: The mention of information_schema here is in the wrong place, but
206
            // it's the quickest fix to exclude the info schema from 7.4
207
            $where     = " AND pn.nspname NOT LIKE \$_PATERN_\$pg\\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'";
208
            $lan_where = 'AND pl.lanispl';
209
        } else {
210
            $where     = '';
211
            $lan_where = '';
212
        }
213
214
        // Apply outer filter
215
        $sql = '';
216
        if ($filter != '') {
217
            $sql = 'SELECT * FROM (';
218
        }
219
220
        $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$";
221
222
        $sql .= "
223
			SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name
224
				FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE {$term} {$where}
225
			UNION ALL
226
			SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid,
227
				pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn
228
				WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where}
229
			UNION ALL
230
			SELECT CASE WHEN pc.relkind='r' THEN 'COLUMNTABLE' ELSE 'COLUMNVIEW' END, NULL, pn.nspname, pc.relname, pa.attname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
231
				pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid
232
				AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where}
233
			UNION ALL
234
			SELECT 'FUNCTION', pp.oid, pn.nspname, NULL, pp.proname || '(' || pg_catalog.oidvectortypes(pp.proargtypes) || ')' FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn
235
				WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where}
236
			UNION ALL
237
			SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
238
				pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid
239
				AND pi.indexrelid=pc2.oid
240
				AND NOT EXISTS (
241
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
242
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
243
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
244
				)
245
				AND pc2.relname ILIKE {$term} {$where}
246
			UNION ALL
247
			SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
248
				pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0
249
				AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS (
250
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
251
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
252
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
253
				) END
254
				AND pc2.conname ILIKE {$term} {$where}
255
			UNION ALL
256
			SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn,
257
				pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0
258
				AND pc.conname ILIKE {$term} {$where}
259
			UNION ALL
260
			SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
261
				pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid
262
					AND ( pt.tgconstraint = 0 OR NOT EXISTS
263
					(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
264
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
265
					WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f'))
266
				AND pt.tgname ILIKE {$term} {$where}
267
			UNION ALL
268
			SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
269
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
270
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
271
				WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
272
			UNION ALL
273
			SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
274
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
275
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
276
				WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
277
		";
278
279
        //\Kint::dump($sql);
280
281
        // Add advanced objects if show_advanced is set
282
        if ($conf['show_advanced']) {
283
            $sql .= "
284
				UNION ALL
285
				SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
286
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
287
					WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
288
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
289
					{$where}
290
			 	UNION ALL
291
				SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
292
					WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
293
				UNION ALL
294
				SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
295
					pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
296
				UNION ALL
297
				SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
298
					WHERE lanname ILIKE {$term} {$lan_where}
299
				UNION ALL
300
				SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
301
					LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
302
					WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
303
				UNION ALL
304
				SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
305
					pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
306
					AND po.opcname ILIKE {$term} {$where}
307
			";
308
        } else {
309
            // Otherwise just add domains
310
            $sql .= "
311
				UNION ALL
312
				SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
313
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
314
					WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
315
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
316
					{$where}
317
			";
318
        }
319
320
        if ($filter != '') {
321
            // We use like to make RULE, CONSTRAINT and COLUMN searches work
322
            $sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
323
        }
324
325
        $sql .= 'ORDER BY type, schemaname, relname, name';
326
327
        return $this->selectSet($sql);
328
    }
329
330
    /**
331
     * Given an array of attnums and a relation, returns an array mapping
332
     * attribute number to attribute name.
333
     *
334
     * @param string $table The table to get attributes for
335
     * @param array  $atts  An array of attribute numbers
336
     *
337
     * @return array|int An array mapping attnum to attname or error code
338
     *                   - -1 $atts must be an array
339
     *                   - -2 wrong number of attributes found
340
     */
341
    public function getAttributeNames($table, $atts)
342
    {
343
        $c_schema = $this->_schema;
344
        $this->clean($c_schema);
345
        $this->clean($table);
346
        $this->arrayClean($atts);
347
348
        if (!is_array($atts)) {
0 ignored issues
show
introduced by
The condition is_array($atts) is always true.
Loading history...
349
            return -1;
350
        }
351
352
        if (sizeof($atts) == 0) {
353
            return [];
354
        }
355
356
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
357
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
358
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
359
			AND attnum IN ('".join("','", $atts)."')";
360
361
        $rs = $this->selectSet($sql);
362
        if ($rs->RecordCount() != sizeof($atts)) {
363
            return -2;
364
        }
365
366
        $temp = [];
367
        while (!$rs->EOF) {
368
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
369
            $rs->moveNext();
370
        }
371
372
        return $temp;
373
    }
374
375
    /**
376
     * Gets all languages.
377
     *
378
     * @param bool $all True to get all languages, regardless of show_system
379
     *
380
     * @return \PHPPgAdmin\ADORecordSet A recordset
381
     */
382
    public function getLanguages($all = false)
383
    {
384
        $conf = $this->conf;
385
386
        if ($conf['show_system'] || $all) {
387
            $where = '';
388
        } else {
389
            $where = 'WHERE lanispl';
390
        }
391
392
        $sql = "
393
			SELECT
394
				lanname, lanpltrusted,
395
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
396
			FROM
397
				pg_catalog.pg_language
398
			{$where}
399
			ORDER BY lanname
400
		";
401
402
        return $this->selectSet($sql);
403
    }
404
405
    /**
406
     * Executes an SQL script as a series of SQL statements.  Returns
407
     * the result of the final step.  This is a very complicated lexer
408
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
409
     * the PostgreSQL source code.
410
     * XXX: It does not handle multibyte languages properly.
411
     *
412
     * @param string        $name     Entry in $_FILES to use
413
     * @param null|callable $callback (optional) Callback function to call with each query, its result and line number
414
     *
415
     * @return bool true for general success, false on any failure
416
     */
417
    public function executeScript($name, $callback = null)
418
    {
419
        // This whole function isn't very encapsulated, but hey...
420
        $conn = $this->conn->_connectionID;
421
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
422
            return false;
423
        }
424
425
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
426
        if ($fd === false) {
427
            return false;
428
        }
429
430
        // Build up each SQL statement, they can be multiline
431
        $query_buf    = null;
432
        $query_start  = 0;
433
        $in_quote     = 0;
434
        $in_xcomment  = 0;
435
        $bslash_count = 0;
436
        $dol_quote    = '';
437
        $paren_level  = 0;
438
        $len          = 0;
439
        $i            = 0;
440
        $prevlen      = 0;
441
        $thislen      = 0;
442
        $lineno       = 0;
443
444
        // Loop over each line in the file
445
        while (!feof($fd)) {
446
            $line = fgets($fd);
447
            ++$lineno;
448
449
            // Nothing left on line? Then ignore...
450
            if (trim($line) == '') {
451
                continue;
452
            }
453
454
            $len         = strlen($line);
455
            $query_start = 0;
456
457
            /**
458
             * Parse line, looking for command separators.
459
             *
460
             * The current character is at line[i], the prior character at line[i
461
             * - prevlen], the next character at line[i + thislen].
462
             */
463
            $prevlen = 0;
464
            $thislen = ($len > 0) ? 1 : 0;
465
466
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
467
                /* was the previous character a backslash? */
468
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
469
                    $this->prtrace('bslash_count', $bslash_count, $line);
470
                    ++$bslash_count;
471
                } else {
472
                    $bslash_count = 0;
473
                }
474
475
                /*
476
                 * It is important to place the in_* test routines before the
477
                 * in_* detection routines. i.e. we have to test if we are in
478
                 * a quote before testing for comments.
479
                 */
480
481
                /* in quote? */
482
                if ($in_quote !== 0) {
483
                    //$this->prtrace('in_quote', $in_quote, $line);
484
                    /*
485
                     * end of quote if matching non-backslashed character.
486
                     * backslashes don't count for double quotes, though.
487
                     */
488
                    if (substr($line, $i, 1) == $in_quote &&
489
                        ($bslash_count % 2 == 0 || $in_quote == '"')
490
                    ) {
491
                        $in_quote = 0;
492
                    }
493
                } elseif ($dol_quote) {
494
                    $this->prtrace('dol_quote', $dol_quote, $line);
495
                    if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
496
                        $this->advance_1($i, $prevlen, $thislen);
497
                        while (substr($line, $i, 1) != '$') {
498
                            $this->advance_1($i, $prevlen, $thislen);
499
                        }
500
501
                        $dol_quote = '';
502
                    }
503
                } elseif (substr($line, $i, 2) == '/*') {
504
                    $this->prtrace('open_xcomment', $in_xcomment, $line, $i, $prevlen, $thislen);
505
                    if ($in_xcomment == 0) {
506
                        ++$in_xcomment;
507
                        $finishpos = strpos(substr($line, $i, $len), '*/');
508
                        if ($finishpos === false) {
509
                            $line = substr($line, 0, $i); /* remove comment */
510
                            break;
511
                        }
512
                        $pre         = substr($line, 0, $i);
513
                        $post        = substr($line, $i + 2 + $finishpos, $len);
514
                        $line        = $pre.' '.$post;
515
                        $in_xcomment = 0;
516
                        $i           = 0;
517
                    }
518
                } elseif ($in_xcomment) {
519
                    $position = strpos(substr($line, $i, $len), '*/');
520
                    if ($position === false) {
521
                        $line = '';
522
523
                        break;
524
                    }
525
526
                    $substr = substr($line, $i, 2);
527
528
                    if ($substr == '*/' && !--$in_xcomment) {
529
                        $line = substr($line, $i + 2, $len);
530
                        $i += 2;
531
                        $this->advance_1($i, $prevlen, $thislen);
532
                    }
533
                    // old logic
534
                    //  } else if (substr($line, $i, 2) == '/*') {
535
                    //      if ($in_xcomment == 0) {
536
                    //          ++$in_xcomment;
537
                    //          $this->advance_1($i, $prevlen, $thislen);
538
                    //      }
539
                    //  } else if ($in_xcomment) {
540
                    //      $substr = substr($line, $i, 2);
541
                    //      if ($substr == '*/' && !--$in_xcomment) {
542
                    //          $this->advance_1($i, $prevlen, $thislen);
543
                    //      }
544
                } elseif (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
545
                    $in_quote = substr($line, $i, 1);
546
                } elseif (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
547
                    $dol_end   = strpos(substr($line, $i + 1), '$');
548
                    $dol_quote = substr($line, $i, $dol_end + 1);
549
                    $this->advance_1($i, $prevlen, $thislen);
550
                    while (substr($line, $i, 1) != '$') {
551
                        $this->advance_1($i, $prevlen, $thislen);
552
                    }
553
                } else {
554
                    if (substr($line, $i, 2) == '--') {
555
                        $line = substr($line, 0, $i); /* remove comment */
556
                        break;
557
                    } /* count nested parentheses */
558
559
                    if (substr($line, $i, 1) == '(') {
560
                        ++$paren_level;
561
                    } elseif (substr($line, $i, 1) == ')' && $paren_level > 0) {
562
                        --$paren_level;
563
                    } elseif (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
564
                        $subline = substr(substr($line, 0, $i), $query_start);
565
                        /*
566
                         * insert a cosmetic newline, if this is not the first
567
                         * line in the buffer
568
                         */
569
                        if (strlen($query_buf) > 0) {
570
                            $query_buf .= "\n";
571
                        }
572
573
                        /* append the line to the query buffer */
574
                        $query_buf .= $subline;
575
                        /* is there anything in the query_buf? */
576
                        if (trim($query_buf)) {
577
                            $query_buf .= ';';
578
579
                            // Execute the query. PHP cannot execute
580
                            // empty queries, unlike libpq
581
                            $res = @pg_query($conn, $query_buf);
582
583
                            // Call the callback function for display
584
                            if ($callback !== null) {
585
                                $callback($query_buf, $res, $lineno);
586
                            }
587
588
                            // Check for COPY request
589
                            if (pg_result_status($res) == 4) {
590
                                // 4 == PGSQL_COPY_FROM
591
                                while (!feof($fd)) {
592
                                    $copy = fgets($fd, 32768);
593
                                    ++$lineno;
594
                                    pg_put_line($conn, $copy);
595
                                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
596
                                        pg_end_copy($conn);
597
598
                                        break;
599
                                    }
600
                                }
601
                            }
602
                        }
603
                        $query_buf   = null;
604
                        $query_start = $i + $thislen;
605
                    } elseif (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
606
                        $sub = substr($line, $i, $thislen);
607
                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
608
                            /* keep going while we still have identifier chars */
609
                            $this->advance_1($i, $prevlen, $thislen);
610
                            $sub = substr($line, $i, $thislen);
611
                        }
612
                        // Since we're now over the next character to be examined, it is necessary
613
                        // to move back one space.
614
                        $i -= $prevlen;
615
                    }
616
                }
617
            } // end for
618
619
            /* Put the rest of the line in the query buffer. */
620
            $subline = substr($line, $query_start);
621
622
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
623
                if (strlen($query_buf) > 0) {
624
                    $query_buf .= "\n";
625
                }
626
627
                $query_buf .= $subline;
628
            }
629
630
            $line = null;
631
        } // end while
632
633
        /*
634
         * Process query at the end of file without a semicolon, so long as
635
         * it's non-empty.
636
         */
637
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
638
            // Execute the query
639
            $res = @pg_query($conn, $query_buf);
640
641
            // Call the callback function for display
642
            if ($callback !== null) {
643
                $callback($query_buf, $res, $lineno);
644
            }
645
646
            // Check for COPY request
647
            if (pg_result_status($res) == 4) {
648
                // 4 == PGSQL_COPY_FROM
649
                while (!feof($fd)) {
650
                    $copy = fgets($fd, 32768);
651
                    ++$lineno;
652
                    pg_put_line($conn, $copy);
653
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
654
                        pg_end_copy($conn);
655
656
                        break;
657
                    }
658
                }
659
            }
660
        }
661
662
        fclose($fd);
663
664
        return true;
665
    }
666
667
    /**
668
     * A private helper method for executeScript that advances the
669
     * character by 1.  In psql this is careful to take into account
670
     * multibyte languages, but we don't at the moment, so this function
671
     * is someone redundant, since it will always advance by 1.
672
     *
673
     * @param int $i       The current character position in the line
674
     * @param int $prevlen Length of previous character (ie. 1)
675
     * @param int $thislen Length of current character (ie. 1)
676
     */
677
    protected function advance_1(&$i, &$prevlen, &$thislen)
678
    {
679
        $prevlen = $thislen;
680
        $i += $thislen;
681
        $thislen = 1;
682
    }
683
684
    /**
685
     * Private helper method to detect a valid $foo$ quote delimiter at
686
     * the start of the parameter dquote.
687
     *
688
     * @param string $dquote
689
     *
690
     * @return bool true if valid, false otherwise
691
     */
692
    protected function valid_dolquote($dquote)
693
    {
694
        // XXX: support multibyte
695
        return preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote);
696
    }
697
698
    // Capabilities
699
700
    /**
701
     * Returns a recordset of all columns in a query.  Supports paging.
702
     *
703
     * @param string   $type      Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
704
     *                            or 'SELECT" if it's a select query
705
     * @param string   $table     The base table of the query.  NULL for no table.
706
     * @param string   $query     The query that is being executed.  NULL for no query.
707
     * @param string   $sortkey   The column number to sort by, or '' or null for no sorting
708
     * @param string   $sortdir   The direction in which to sort the specified column ('asc' or 'desc')
709
     * @param null|int $page      The page of the relation to retrieve
710
     * @param null|int $page_size The number of rows per page
711
     * @param int      $max_pages (return-by-ref) The max number of pages in the relation
712
     *
713
     * @return int|\PHPPgAdmin\ADORecordSet A  recordset on success or an int with error code
714
     *                                      - -1 transaction error
715
     *                                      - -2 counting error
716
     *                                      - -3 page or page_size invalid
717
     *                                      - -4 unknown type
718
     *                                      - -5 failed setting transaction read only
719
     */
720
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
721
    {
722
        // Check that we're not going to divide by zero
723
        if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) {
724
            return -3;
725
        }
726
727
        // If $type is TABLE, then generate the query
728
        switch ($type) {
729
            case 'TABLE':
730
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
731
                    $orderby = [$sortkey => $sortdir];
732
                } else {
733
                    $orderby = [];
734
                }
735
736
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
737
738
                break;
739
            case 'QUERY':
740
            case 'SELECT':
741
                // Trim query
742
                $query = trim($query);
743
                // Trim off trailing semi-colon if there is one
744
                if (substr($query, strlen($query) - 1, 1) == ';') {
745
                    $query = substr($query, 0, strlen($query) - 1);
746
                }
747
748
                break;
749
            default:
750
                return -4;
751
        }
752
753
        // Generate count query
754
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
755
756
        // Open a transaction
757
        $status = $this->beginTransaction();
758
        if ($status != 0) {
759
            return -1;
760
        }
761
762
        // If backend supports read only queries, then specify read only mode
763
        // to avoid side effects from repeating queries that do writes.
764
        if ($this->hasReadOnlyQueries()) {
765
            $status = $this->execute('SET TRANSACTION READ ONLY');
766
            if ($status != 0) {
767
                $this->rollbackTransaction();
768
769
                return -5;
770
            }
771
        }
772
773
        // Count the number of rows
774
        $total = $this->browseQueryCount($count);
775
        if ($total < 0) {
776
            $this->rollbackTransaction();
777
778
            return -2;
779
        }
780
781
        // Calculate max pages
782
        $max_pages = ceil($total / $page_size);
783
784
        // Check that page is less than or equal to max pages
785
        if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) {
786
            $this->rollbackTransaction();
787
788
            return -3;
789
        }
790
791
        // Set fetch mode to NUM so that duplicate field names are properly returned
792
        // for non-table queries.  Since the SELECT feature only allows selecting one
793
        // table, duplicate fields shouldn't appear.
794
        if ($type == 'QUERY') {
795
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
796
        }
797
798
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
799
        // of the column to order by.  Only need to do this for non-TABLE queries
800
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
801
            $orderby = " ORDER BY {$sortkey}";
802
            // Add sort order
803
            if ($sortdir == 'desc') {
804
                $orderby .= ' DESC';
805
            } else {
806
                $orderby .= ' ASC';
807
            }
808
        } else {
809
            $orderby = '';
810
        }
811
812
        // Actually retrieve the rows, with offset and limit
813
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET ".($page - 1) * $page_size);
814
        $status = $this->endTransaction();
815
        if ($status != 0) {
816
            $this->rollbackTransaction();
817
818
            return -1;
819
        }
820
821
        return $rs;
822
    }
823
824
    /**
825
     * Generates the SQL for the 'select' function.
826
     *
827
     * @param string $table   The table from which to select
828
     * @param array  $show    An array of columns to show.  Empty array means all columns.
829
     * @param array  $values  An array mapping columns to values
830
     * @param array  $ops     An array of the operators to use
831
     * @param array  $orderby (optional) An array of column numbers or names (one based)
832
     *                        mapped to sort direction (asc or desc or '' or null) to order by
833
     *
834
     * @return string The SQL query
835
     */
836
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
837
    {
838
        $this->fieldArrayClean($show);
839
840
        // If an empty array is passed in, then show all columns
841
        if (sizeof($show) == 0) {
842
            if ($this->hasObjectID($table)) {
843
                $sql = "SELECT \"{$this->id}\", * FROM ";
844
            } else {
845
                $sql = 'SELECT * FROM ';
846
            }
847
        } else {
848
            // Add oid column automatically to results for editing purposes
849
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
850
                $sql = "SELECT \"{$this->id}\", \"";
851
            } else {
852
                $sql = 'SELECT "';
853
            }
854
855
            $sql .= join('","', $show).'" FROM ';
856
        }
857
858
        $this->fieldClean($table);
859
860
        if (isset($_REQUEST['schema'])) {
861
            $f_schema = $_REQUEST['schema'];
862
            $this->fieldClean($f_schema);
863
            $sql .= "\"{$f_schema}\".";
864
        }
865
        $sql .= "\"{$table}\"";
866
867
        // If we have values specified, add them to the WHERE clause
868
        $first = true;
869
        if (is_array($values) && sizeof($values) > 0) {
870
            foreach ($values as $k => $v) {
871
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
872
                    $this->fieldClean($k);
873
                    if ($first) {
874
                        $sql .= ' WHERE ';
875
                        $first = false;
876
                    } else {
877
                        $sql .= ' AND ';
878
                    }
879
                    // Different query format depending on operator type
880
                    switch ($this->selectOps[$ops[$k]]) {
881
                        case 'i':
882
                            // Only clean the field for the inline case
883
                            // this is because (x), subqueries need to
884
                            // to allow 'a','b' as input.
885
                            $this->clean($v);
886
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
887
888
                            break;
889
                        case 'p':
890
                            $sql .= "\"{$k}\" {$ops[$k]}";
891
892
                            break;
893
                        case 'x':
894
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
895
896
                            break;
897
                        case 't':
898
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
899
900
                            break;
901
                        default:
902
                            // Shouldn't happen
903
                    }
904
                }
905
            }
906
        }
907
908
        // ORDER BY
909
        if (is_array($orderby) && sizeof($orderby) > 0) {
910
            $sql .= ' ORDER BY ';
911
            $first = true;
912
            foreach ($orderby as $k => $v) {
913
                if ($first) {
914
                    $first = false;
915
                } else {
916
                    $sql .= ', ';
917
                }
918
919
                if (preg_match('/^[0-9]+$/', $k)) {
920
                    $sql .= $k;
921
                } else {
922
                    $this->fieldClean($k);
923
                    $sql .= '"'.$k.'"';
924
                }
925
                if (strtoupper($v) == 'DESC') {
926
                    $sql .= ' DESC';
927
                }
928
            }
929
        }
930
931
        return $sql;
932
    }
933
934
    /**
935
     * Finds the number of rows that would be returned by a
936
     * query.
937
     *
938
     * @param string $count The count query
939
     *
940
     * @return int|string The count of rows or -1 of no rows are found
941
     */
942
    public function browseQueryCount($count)
943
    {
944
        return $this->selectField($count, 'total');
945
    }
946
}
947