Passed
Push — develop ( d51928...4c214f )
by Felipe
31:34 queued 23:49
created

PrivilegesTrait   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 390
Duplicated Lines 6.15 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 24
loc 390
rs 5.488
c 0
b 0
f 0
wmc 76
lcom 1
cbo 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
C getPrivileges() 0 67 14
F parseACL() 24 124 26
F setPrivileges() 0 130 36
fieldClean() 0 1 ?
beginTransaction() 0 1 ?
rollbackTransaction() 0 1 ?
endTransaction() 0 1 ?
execute() 0 1 ?
setComment() 0 1 ?
selectSet() 0 1 ?
clean() 0 1 ?
hasGrantOption() 0 1 ?
getFunction() 0 1 ?
fieldArrayClean() 0 1 ?
selectField() 0 1 ?
hasRoles() 0 1 ?

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PrivilegesTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PrivilegesTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.48
5
 */
6
7
namespace PHPPgAdmin\Database\Traits;
8
9
/**
10
 * Common trait for privileges manipulation.
11
 */
12
trait PrivilegesTrait
13
{
14
    /**
15
     * Grabs an array of users and their privileges for an object,
16
     * given its type.
17
     *
18
     * @param string      $object The name of the object whose privileges are to be retrieved
19
     * @param string      $type   The type of the object (eg. database, schema, relation, function or language)
20
     * @param null|string $table  Optional, column's table if type = column
21
     *
22
     * @return array|int Privileges array or error code
23
     *                   - -1         invalid type
24
     *                   - -2         object not found
25
     *                   - -3         unknown privilege type
26
     */
27
    public function getPrivileges($object, $type, $table = null)
28
    {
29
        $c_schema = $this->_schema;
0 ignored issues
show
Bug introduced by
The property _schema does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
30
        $this->clean($c_schema);
31
        $this->clean($object);
32
33
        switch ($type) {
34
            case 'column':
35
                $this->clean($table);
36
                $sql = "
37
                    SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
38
                    FROM pg_catalog.pg_attribute a
39
                        LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
40
                        LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
41
                    WHERE n.nspname='{$c_schema}'
42
                        AND c.relname='{$table}'
43
                        AND a.attname='{$object}'";
44
45
                break;
46
            case 'table':
47
            case 'view':
48
            case 'sequence':
49
                $sql = "
50
                    SELECT relacl AS acl FROM pg_catalog.pg_class
51
                    WHERE relname='{$object}'
52
                        AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
53
                            WHERE nspname='{$c_schema}')";
54
55
                break;
56
            case 'database':
57
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
58
59
                break;
60
            case 'function':
61
                // Since we fetch functions by oid, they are already constrained to
62
                // the current schema.
63
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
64
65
                break;
66
            case 'language':
67
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
68
69
                break;
70
            case 'schema':
71
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
72
73
                break;
74
            case 'tablespace':
75
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
76
77
                break;
78
            default:
79
                return -1;
80
        }
81
82
        // Fetch the ACL for object
83
        $acl = $this->selectField($sql, 'acl');
84
        if ($acl == -1) {
85
            return -2;
86
        }
87
88
        if ($acl == '' || $acl === null || !(bool) $acl) {
89
            return [];
90
        }
91
92
        return $this->parseACL($acl);
93
    }
94
95
    /**
96
     * Internal function used for parsing ACLs.
97
     *
98
     * @param string $acl The ACL to parse (of type aclitem[])
99
     *
100
     * @return array|int Privileges array or integer with error code
101
     *
102
     * @internal bool $in_quotes toggles acl in_quotes attribute
103
     */
104
    protected function parseACL($acl)
105
    {
106
        // Take off the first and last characters (the braces)
107
        $acl = substr($acl, 1, strlen($acl) - 2);
108
109
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
110
        // to cope with usernames and stuff that contain commas
111
        $aces      = [];
112
        $i         = $j         = 0;
113
        $in_quotes = false;
114 View Code Duplication
        while ($i < strlen($acl)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
115
            // If current char is a double quote and it's not escaped, then
116
            // enter quoted bit
117
            $char = substr($acl, $i, 1);
118
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
119
                $in_quotes = !$in_quotes;
120
            } elseif ($char == ',' && !$in_quotes) {
121
                // Add text so far to the array
122
                $aces[] = substr($acl, $j, $i - $j);
123
                $j      = $i + 1;
124
            }
125
            ++$i;
126
        }
127
        // Add final text to the array
128
        $aces[] = substr($acl, $j);
129
130
        // Create the array to be returned
131
        $temp = [];
132
133
        // For each ACE, generate an entry in $temp
134
        foreach ($aces as $v) {
135
            // If the ACE begins with a double quote, strip them off both ends
136
            // and unescape backslashes and double quotes
137
            // $unquote = false;
138
            if (strpos($v, '"') === 0) {
139
                $v = substr($v, 1, strlen($v) - 2);
140
                $v = str_replace('\\"', '"', $v);
141
                $v = str_replace('\\\\', '\\', $v);
142
            }
143
144
            // Figure out type of ACE (public, user or group)
145
            if (strpos($v, '=') === 0) {
146
                $atype = 'public';
147
            } else {
148
                if ($this->hasRoles()) {
149
                    $atype = 'role';
150
                } else {
151
                    if (strpos($v, 'group ') === 0) {
152
                        $atype = 'group';
153
                        // Tear off 'group' prefix
154
                        $v = substr($v, 6);
155
                    } else {
156
                        $atype = 'user';
157
                    }
158
                }
159
            }
160
161
            // Break on unquoted equals sign...
162
            $i         = 0;
163
            $in_quotes = false;
164
            $entity    = null;
165
            $chars     = null;
166
            while ($i < strlen($v)) {
167
                // If current char is a double quote and it's not escaped, then
168
                // enter quoted bit
169
                $char      = substr($v, $i, 1);
170
                $next_char = substr($v, $i + 1, 1);
171
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
172
                    $in_quotes = !$in_quotes;
173
                } elseif ($char == '"' && $next_char == '"') {
174
                    // Skip over escaped double quotes
175
                    ++$i;
176
                } elseif ($char == '=' && !$in_quotes) {
177
                    // Split on current equals sign
178
                    $entity = substr($v, 0, $i);
179
                    $chars  = substr($v, $i + 1);
180
181
                    break;
182
                }
183
                ++$i;
184
            }
185
186
            // Check for quoting on entity name, and unescape if necessary
187
            if (strpos($entity, '"') === 0) {
188
                $entity = substr($entity, 1, strlen($entity) - 2);
189
                $entity = str_replace('""', '"', $entity);
190
            }
191
192
            // New row to be added to $temp
193
            // (type, grantee, privileges, grantor, grant option?
194
            $row = [$atype, $entity, [], '', []];
195
196
            // Loop over chars and add privs to $row
197
            for ($i = 0; $i < strlen($chars); ++$i) {
198
                // Append to row's privs list the string representing
199
                // the privilege
200
                $char = substr($chars, $i, 1);
201
                if ($char == '*') {
202
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
0 ignored issues
show
Bug introduced by
The property privmap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
203 View Code Duplication
                } elseif ($char == '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
204
                    $grantor = substr($chars, $i + 1);
205
                    // Check for quoting
206
                    if (strpos($grantor, '"') === 0) {
207
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
208
                        $grantor = str_replace('""', '"', $grantor);
209
                    }
210
                    $row[3] = $grantor;
211
212
                    break;
213
                } else {
214
                    if (!isset($this->privmap[$char])) {
215
                        return -3;
216
                    }
217
218
                    $row[2][] = $this->privmap[$char];
219
                }
220
            }
221
222
            // Append row to temp
223
            $temp[] = $row;
224
        }
225
226
        return $temp;
227
    }
228
229
    /**
230
     * Grants a privilege to a user, group or public.
231
     *
232
     * @param string $mode        'GRANT' or 'REVOKE';
233
     * @param mixed  $type        The type of object
234
     * @param string $object      The name of the object
235
     * @param bool   $public      True to grant to public, false otherwise
236
     * @param mixed  $usernames   the array of usernames to grant privs to
237
     * @param mixed  $groupnames  the array of group names to grant privs to
238
     * @param mixed  $privileges  The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) )
239
     * @param bool   $grantoption True if has grant option, false otherwise
240
     * @param bool   $cascade     True for cascade revoke, false otherwise
241
     * @param string $table       the column's table if type=column
242
     *
243
     * @return int 0 if operation was successful
244
     */
245
    public function setPrivileges(
246
        $mode,
247
        $type,
248
        $object,
249
        $public,
250
        $usernames,
251
        $groupnames,
252
        $privileges,
253
        $grantoption,
254
        $cascade,
255
        $table
256
    ) {
257
        $f_schema = $this->_schema;
258
        $this->fieldClean($f_schema);
259
        $this->fieldArrayClean($usernames);
260
        $this->fieldArrayClean($groupnames);
261
262
        // Input checking
263
        if (!is_array($privileges) || sizeof($privileges) == 0) {
264
            return -3;
265
        }
266
267
        if (!is_array($usernames) || !is_array($groupnames) ||
268
            (!$public && sizeof($usernames) == 0 && sizeof($groupnames) == 0)) {
269
            return -4;
270
        }
271
272
        if ($mode != 'GRANT' && $mode != 'REVOKE') {
273
            return -5;
274
        }
275
276
        $sql = $mode;
277
278
        // Grant option
279
        if ($this->hasGrantOption() && $mode == 'REVOKE' && $grantoption) {
280
            $sql .= ' GRANT OPTION FOR';
281
        }
282
283
        if (in_array('ALL PRIVILEGES', $privileges, true)) {
284
            $sql .= ' ALL PRIVILEGES';
285
        } else {
286
            if ($type == 'column') {
287
                $this->fieldClean($object);
288
                $sql .= ' '.join(" (\"{$object}\"), ", $privileges);
289
            } else {
290
                $sql .= ' '.join(', ', $privileges);
291
            }
292
        }
293
294
        switch ($type) {
295
            case 'column':
296
                $sql .= " (\"{$object}\")";
297
                $object = $table;
298
            // no break
299
            case 'table':
300
            case 'view':
301
            case 'sequence':
302
                $this->fieldClean($object);
303
                $sql .= " ON \"{$f_schema}\".\"{$object}\"";
304
305
                break;
306
            case 'database':
307
                $this->fieldClean($object);
308
                $sql .= " ON DATABASE \"{$object}\"";
309
310
                break;
311
            case 'function':
312
                // Function comes in with $object as function OID
313
                $fn = $this->getFunction($object);
314
                $this->fieldClean($fn->fields['proname']);
315
                $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
316
317
                break;
318
            case 'language':
319
                $this->fieldClean($object);
320
                $sql .= " ON LANGUAGE \"{$object}\"";
321
322
                break;
323
            case 'schema':
324
                $this->fieldClean($object);
325
                $sql .= " ON SCHEMA \"{$object}\"";
326
327
                break;
328
            case 'tablespace':
329
                $this->fieldClean($object);
330
                $sql .= " ON TABLESPACE \"{$object}\"";
331
332
                break;
333
            default:
334
                return -1;
335
        }
336
337
        // Dump PUBLIC
338
        $first = true;
339
        $sql .= ($mode == 'GRANT') ? ' TO ' : ' FROM ';
340
        if ($public) {
341
            $sql .= 'PUBLIC';
342
            $first = false;
343
        }
344
        // Dump users
345
        foreach ($usernames as $v) {
346
            if ($first) {
347
                $sql .= "\"{$v}\"";
348
                $first = false;
349
            } else {
350
                $sql .= ", \"{$v}\"";
351
            }
352
        }
353
        // Dump groups
354
        foreach ($groupnames as $v) {
355
            if ($first) {
356
                $sql .= "GROUP \"{$v}\"";
357
                $first = false;
358
            } else {
359
                $sql .= ", GROUP \"{$v}\"";
360
            }
361
        }
362
363
        // Grant option
364
        if ($this->hasGrantOption() && $mode == 'GRANT' && $grantoption) {
365
            $sql .= ' WITH GRANT OPTION';
366
        }
367
368
        // Cascade revoke
369
        if ($this->hasGrantOption() && $mode == 'REVOKE' && $cascade) {
370
            $sql .= ' CASCADE';
371
        }
372
373
        return $this->execute($sql);
374
    }
375
376
    abstract public function fieldClean(&$str);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
377
378
    abstract public function beginTransaction();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
379
380
    abstract public function rollbackTransaction();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
381
382
    abstract public function endTransaction();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
383
384
    abstract public function execute($sql);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
385
386
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
Coding Style Naming introduced by
The parameter $obj_type is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style Naming introduced by
The parameter $obj_name is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
387
388
    abstract public function selectSet($sql);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
389
390
    abstract public function clean(&$str);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
391
392
    abstract public function hasGrantOption();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
393
394
    abstract public function getFunction($function_oid);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
Coding Style Naming introduced by
The parameter $function_oid is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
395
396
    abstract public function fieldArrayClean(&$arr);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
397
398
    abstract public function selectField($sql, $field);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
399
400
    abstract public function hasRoles();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
401
}
402