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

Postgres::getHelp()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 1
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