Completed
Push — authpdo ( 7f89f0...388201 )
by Andreas
18:44 queued 12:59
created

auth_plugin_authpdo::modifyUser()   F

Complexity

Conditions 23
Paths 1080

Size

Total Lines 82
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 47
nc 1080
nop 2
dl 0
loc 82
rs 2.1786
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * DokuWiki Plugin authpdo (Auth Component)
4
 *
5
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6
 * @author  Andreas Gohr <[email protected]>
7
 */
8
9
// must be run within Dokuwiki
10
if(!defined('DOKU_INC')) die();
11
12
/**
13
 * Class auth_plugin_authpdo
14
 */
15
class auth_plugin_authpdo extends DokuWiki_Auth_Plugin {
16
17
    /** @var PDO */
18
    protected $pdo;
19
20
    /** @var null|array The list of all groups */
21
    protected $groupcache = null;
22
23
    /**
24
     * Constructor.
25
     */
26
    public function __construct() {
27
        parent::__construct(); // for compatibility
28
29
        if(!class_exists('PDO')) {
30
            $this->_debug('PDO extension for PHP not found.', -1, __LINE__);
31
            $this->success = false;
32
            return;
33
        }
34
35
        if(!$this->getConf('dsn')) {
36
            $this->_debug('No DSN specified', -1, __LINE__);
37
            $this->success = false;
38
            return;
39
        }
40
41
        try {
42
            $this->pdo = new PDO(
43
                $this->getConf('dsn'),
44
                $this->getConf('user'),
45
                conf_decodeString($this->getConf('pass')),
46
                array(
47
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array
48
                    PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names
49
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes
50
                )
51
            );
52
        } catch(PDOException $e) {
53
            $this->_debug($e);
54
            msg($this->getLang('connectfail'), -1);
55
            $this->success = false;
56
            return;
57
        }
58
59
        // can Users be created?
60
        $this->cando['addUser'] = $this->_chkcnf(
61
            array(
62
                'select-user',
63
                'select-user-groups',
64
                'select-groups',
65
                'insert-user',
66
                'insert-group',
67
                'join-group'
68
            )
69
        );
70
71
        // can Users be deleted?
72
        $this->cando['delUser'] = $this->_chkcnf(
73
            array(
74
                'select-user',
75
                'select-user-groups',
76
                'select-groups',
77
                'leave-group',
78
                'delete-user'
79
            )
80
        );
81
82
        // can login names be changed?
83
        $this->cando['modLogin'] = $this->_chkcnf(
84
            array(
85
                'select-user',
86
                'select-user-groups',
87
                'update-user-login'
88
            )
89
        );
90
91
        // can passwords be changed?
92
        $this->cando['modPass'] = $this->_chkcnf(
93
            array(
94
                'select-user',
95
                'select-user-groups',
96
                'update-user-pass'
97
            )
98
        );
99
100
        // can real names and emails be changed?
101
        $this->cando['modName'] = $this->cando['modMail'] = $this->_chkcnf(
102
            array(
103
                'select-user',
104
                'select-user-groups',
105
                'update-user-info'
106
            )
107
        );
108
109
        // can groups be changed?
110
        $this->cando['modGroups'] = $this->_chkcnf(
111
            array(
112
                'select-user',
113
                'select-user-groups',
114
                'select-groups',
115
                'leave-group',
116
                'join-group',
117
                'insert-group'
118
            )
119
        );
120
121
        // can a filtered list of users be retrieved?
122
        $this->cando['getUsers'] = $this->_chkcnf(
123
            array(
124
                'list-users'
125
            )
126
        );
127
128
        // can the number of users be retrieved?
129
        $this->cando['getUserCount'] = $this->_chkcnf(
130
            array(
131
                'count-users'
132
            )
133
        );
134
135
        // can a list of available groups be retrieved?
136
        $this->cando['getGroups'] = $this->_chkcnf(
137
            array(
138
                'select-groups'
139
            )
140
        );
141
142
        $this->success = true;
143
    }
144
145
    /**
146
     * Check user+password
147
     *
148
     * @param   string $user the user name
149
     * @param   string $pass the clear text password
150
     * @return  bool
151
     */
152
    public function checkPass($user, $pass) {
153
154
        $data = $this->_selectUser($user);
155
        if($data == false) return false;
156
157
        if(isset($data['hash'])) {
158
            // hashed password
159
            $passhash = new PassHash();
160
            return $passhash->verify_hash($pass, $data['hash']);
161
        } else {
162
            // clear text password in the database O_o
163
            return ($pass == $data['clear']);
164
        }
165
    }
166
167
    /**
168
     * Return user info
169
     *
170
     * Returns info about the given user needs to contain
171
     * at least these fields:
172
     *
173
     * name string  full name of the user
174
     * mail string  email addres of the user
175
     * grps array   list of groups the user is in
176
     *
177
     * @param   string $user the user name
178
     * @param   bool $requireGroups whether or not the returned data must include groups
179
     * @return array|bool containing user data or false
180
     */
181
    public function getUserData($user, $requireGroups = true) {
182
        $data = $this->_selectUser($user);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_selectUser($user); of type boolean|array adds the type array to the return on line 193 which is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserData of type boolean.
Loading history...
183
        if($data == false) return false;
184
185
        if(isset($data['hash'])) unset($data['hash']);
186
        if(isset($data['clean'])) unset($data['clean']);
187
188
        if($requireGroups) {
189
            $data['grps'] = $this->_selectUserGroups($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->_selectUser($user) on line 182 can also be of type boolean; however, auth_plugin_authpdo::_selectUserGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
190
            if($data['grps'] === false) return false;
191
        }
192
193
        return $data;
194
    }
195
196
    /**
197
     * Create a new User [implement only where required/possible]
198
     *
199
     * Returns false if the user already exists, null when an error
200
     * occurred and true if everything went well.
201
     *
202
     * The new user HAS TO be added to the default group by this
203
     * function!
204
     *
205
     * Set addUser capability when implemented
206
     *
207
     * @param  string $user
208
     * @param  string $clear
209
     * @param  string $name
210
     * @param  string $mail
211
     * @param  null|array $grps
212
     * @return bool|null
213
     */
214
    public function createUser($user, $clear, $name, $mail, $grps = null) {
215
        global $conf;
216
217
        if(($info = $this->getUserData($user, false)) !== false) {
218
            msg($this->getLang('userexists'), -1);
219
            return false; // user already exists
220
        }
221
222
        // prepare data
223
        if($grps == null) $grps = array();
224
        array_unshift($grps, $conf['defaultgroup']);
225
        $grps = array_unique($grps);
226
        $hash = auth_cryptPassword($clear);
227
        $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
228
229
        // action protected by transaction
230
        $this->pdo->beginTransaction();
231
        {
232
            // insert the user
233
            $ok = $this->_query($this->getConf('insert-user'), $userdata);
234
            if($ok === false) goto FAIL;
235
            $userdata = $this->getUserData($user, false);
236
            if($userdata === false) goto FAIL;
237
238
            // create all groups that do not exist, the refetch the groups
239
            $allgroups = $this->_selectGroups();
240
            foreach($grps as $group) {
241
                if(!isset($allgroups[$group])) {
242
                    $ok = $this->addGroup($group);
243
                    if($ok === false) goto FAIL;
244
                }
245
            }
246
            $allgroups = $this->_selectGroups();
247
248
            // add user to the groups
249
            foreach($grps as $group) {
250
                $ok = $this->_joinGroup($userdata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->getUserData($user, false) on line 235 can also be of type boolean; however, auth_plugin_authpdo::_joinGroup() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
251
                if($ok === false) goto FAIL;
252
            }
253
        }
254
        $this->pdo->commit();
255
        return true;
256
257
        // something went wrong, rollback
258
        FAIL:
259
        $this->pdo->rollBack();
260
        $this->_debug('Transaction rolled back', 0, __LINE__);
261
        msg($this->getLang('writefail'), -1);
262
        return null; // return error
263
    }
264
265
    /**
266
     * Modify user data
267
     *
268
     * @param   string $user nick of the user to be changed
269
     * @param   array $changes array of field/value pairs to be changed (password will be clear text)
270
     * @return  bool
271
     */
272
    public function modifyUser($user, $changes) {
273
        // secure everything in transaction
274
        $this->pdo->beginTransaction();
275
        {
276
            $olddata = $this->getUserData($user);
277
            $oldgroups = $olddata['grps'];
278
            unset($olddata['grps']);
279
280
            // changing the user name?
281
            if(isset($changes['user'])) {
282
                if($this->getUserData($changes['user'], false)) goto FAIL;
283
                $params = $olddata;
284
                $params['newlogin'] = $changes['user'];
285
286
                $ok = $this->_query($this->getConf('update-user-login'), $params);
0 ignored issues
show
Bug introduced by
It seems like $params defined by $olddata on line 283 can also be of type boolean; however, auth_plugin_authpdo::_query() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
287
                if($ok === false) goto FAIL;
288
            }
289
290
            // changing the password?
291
            if(isset($changes['pass'])) {
292
                $params = $olddata;
293
                $params['clear'] = $changes['pass'];
294
                $params['hash'] = auth_cryptPassword($changes['pass']);
295
296
                $ok = $this->_query($this->getConf('update-user-pass'), $params);
0 ignored issues
show
Bug introduced by
It seems like $params defined by $olddata on line 292 can also be of type boolean; however, auth_plugin_authpdo::_query() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
297
                if($ok === false) goto FAIL;
298
            }
299
300
            // changing info?
301
            if(isset($changes['mail']) || isset($changes['name'])) {
302
                $params = $olddata;
303
                if(isset($changes['mail'])) $params['mail'] = $changes['mail'];
304
                if(isset($changes['name'])) $params['name'] = $changes['name'];
305
306
                $ok = $this->_query($this->getConf('update-user-info'), $params);
0 ignored issues
show
Bug introduced by
It seems like $params defined by $olddata on line 302 can also be of type boolean; however, auth_plugin_authpdo::_query() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
307
                if($ok === false) goto FAIL;
308
            }
309
310
            // changing groups?
311
            if(isset($changes['grps'])) {
312
                $allgroups = $this->_selectGroups();
313
314
                // remove membership for previous groups
315
                foreach($oldgroups as $group) {
316
                    if(!in_array($group, $changes['grps']) && isset($allgroups[$group])) {
317
                        $ok = $this->_leaveGroup($olddata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $olddata defined by $this->getUserData($user) on line 276 can also be of type boolean; however, auth_plugin_authpdo::_leaveGroup() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
318
                        if($ok === false) goto FAIL;
319
                    }
320
                }
321
322
                // create all new groups that are missing
323
                $added = 0;
324
                foreach($changes['grps'] as $group) {
325
                    if(!isset($allgroups[$group])) {
326
                        $ok = $this->addGroup($group);
327
                        if($ok === false) goto FAIL;
328
                        $added++;
329
                    }
330
                }
331
                // reload group info
332
                if($added > 0) $allgroups = $this->_selectGroups();
333
334
                // add membership for new groups
335
                foreach($changes['grps'] as $group) {
336
                    if(!in_array($group, $oldgroups)) {
337
                        $ok = $this->_joinGroup($olddata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $olddata defined by $this->getUserData($user) on line 276 can also be of type boolean; however, auth_plugin_authpdo::_joinGroup() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
338
                        if($ok === false) goto FAIL;
339
                    }
340
                }
341
            }
342
343
        }
344
        $this->pdo->commit();
345
        return true;
346
347
        // something went wrong, rollback
348
        FAIL:
349
        $this->pdo->rollBack();
350
        $this->_debug('Transaction rolled back', 0, __LINE__);
351
        msg($this->getLang('writefail'), -1);
352
        return false; // return error
353
    }
354
355
    /**
356
     * Delete one or more users
357
     *
358
     * Set delUser capability when implemented
359
     *
360
     * @param   array $users
361
     * @return  int    number of users deleted
362
     */
363
    public function deleteUsers($users) {
364
        $count = 0;
365
        foreach($users as $user) {
366
            if($this->_deleteUser($user)) $count++;
367
        }
368
        return $count;
369
    }
370
371
    /**
372
     * Bulk retrieval of user data [implement only where required/possible]
373
     *
374
     * Set getUsers capability when implemented
375
     *
376
     * @param   int $start index of first user to be returned
377
     * @param   int $limit max number of users to be returned
378
     * @param   array $filter array of field/pattern pairs, null for no filter
379
     * @return  array list of userinfo (refer getUserData for internal userinfo details)
380
     */
381
    public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
382
        if($limit < 0) $limit = 10000; // we don't support no limit
383
        if(is_null($filter)) $filter = array();
384
385
        foreach(array('user', 'name', 'mail', 'group') as $key) {
386
            if(!isset($filter[$key])) {
387
                $filter[$key] = '%';
388
            } else {
389
                $filter[$key] = '%' . $filter[$key] . '%';
390
            }
391
        }
392
        $filter['start'] = (int) $start;
393
        $filter['end'] = (int) $start + $limit;
394
        $filter['limit'] = (int) $limit;
395
396
        $result = $this->_query($this->getConf('list-users'), $filter);
397
        if(!$result) return array();
398
        $users = array();
399
        foreach($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type integer|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
400
            if(!isset($row['user'])) {
401
                $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
402
                return array();
403
            }
404
            $users[] = $row['user'];
405
        }
406
        return $users;
407
    }
408
409
    /**
410
     * Return a count of the number of user which meet $filter criteria
411
     *
412
     * @param  array $filter array of field/pattern pairs, empty array for no filter
413
     * @return int
414
     */
415
    public function getUserCount($filter = array()) {
416
        if(is_null($filter)) $filter = array();
417
418
        foreach(array('user', 'name', 'mail', 'group') as $key) {
419
            if(!isset($filter[$key])) {
420
                $filter[$key] = '%';
421
            } else {
422
                $filter[$key] = '%' . $filter[$key] . '%';
423
            }
424
        }
425
426
        $result = $this->_query($this->getConf('count-users'), $filter);
427
        if(!$result || !isset($result[0]['count'])) {
428
            $this->_debug("Statement did not return 'count' attribute", -1, __LINE__);
429
        }
430
        return isset($result[0]['count']);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return isset($result[0]['count']); (boolean) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserCount of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
431
    }
432
433
    /**
434
     * Create a new group with the given name
435
     *
436
     * @param string $group
437
     * @return bool
438
     */
439
    public function addGroup($group) {
440
        $sql = $this->getConf('insert-group');
441
442
        $result = $this->_query($sql, array(':group' => $group));
443
        $this->_clearGroupCache();
444
        if($result === false) return false;
445
        return true;
446
    }
447
448
    /**
449
     * Retrieve groups
450
     *
451
     * Set getGroups capability when implemented
452
     *
453
     * @param   int $start
454
     * @param   int $limit
455
     * @return  array
456
     */
457
    public function retrieveGroups($start = 0, $limit = 0) {
458
        $groups = array_keys($this->_selectGroups());
459
        if($groups === false) return array();
460
461
        if(!$limit) {
462
            return array_splice($groups, $start);
463
        } else {
464
            return array_splice($groups, $start, $limit);
465
        }
466
    }
467
468
    /**
469
     * Select data of a specified user
470
     *
471
     * @param string $user the user name
472
     * @return bool|array user data, false on error
473
     */
474
    protected function _selectUser($user) {
475
        $sql = $this->getConf('select-user');
476
477
        $result = $this->_query($sql, array(':user' => $user));
478
        if(!$result) return false;
479
480
        if(count($result) > 1) {
481
            $this->_debug('Found more than one matching user', -1, __LINE__);
482
            return false;
483
        }
484
485
        $data = array_shift($result);
486
        $dataok = true;
487
488
        if(!isset($data['user'])) {
489
            $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
490
            $dataok = false;
491
        }
492
        if(!isset($data['hash']) && !isset($data['clear'])) {
493
            $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
494
            $dataok = false;
495
        }
496
        if(!isset($data['name'])) {
497
            $this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
498
            $dataok = false;
499
        }
500
        if(!isset($data['mail'])) {
501
            $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
502
            $dataok = false;
503
        }
504
505
        if(!$dataok) return false;
506
        return $data;
507
    }
508
509
    /**
510
     * Delete a user after removing all their group memberships
511
     *
512
     * @param string $user
513
     * @return bool true when the user was deleted
514
     */
515
    protected function _deleteUser($user) {
516
        $this->pdo->beginTransaction();
517
        {
518
            $userdata = $this->getUserData($user);
519
            if($userdata === false) goto FAIL;
520
            $allgroups = $this->_selectGroups();
521
522
            // remove group memberships (ignore errors)
523
            foreach($userdata['grps'] as $group) {
524
                if(isset($allgroups[$group])) {
525
                    $this->_leaveGroup($userdata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->getUserData($user) on line 518 can also be of type boolean; however, auth_plugin_authpdo::_leaveGroup() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
526
                }
527
            }
528
529
            $ok = $this->_query($this->getConf('delete-user'), $userdata);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->getUserData($user) on line 518 can also be of type boolean; however, auth_plugin_authpdo::_query() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
530
            if($ok === false) goto FAIL;
531
        }
532
        $this->pdo->commit();
533
        return true;
534
535
        FAIL:
536
        $this->pdo->rollBack();
537
        return false;
538
    }
539
540
    /**
541
     * Select all groups of a user
542
     *
543
     * @param array $userdata The userdata as returned by _selectUser()
544
     * @return array|bool list of group names, false on error
545
     */
546
    protected function _selectUserGroups($userdata) {
547
        global $conf;
548
        $sql = $this->getConf('select-user-groups');
549
        $result = $this->_query($sql, $userdata);
550
        if($result === false) return false;
551
552
        $groups = array($conf['defaultgroup']); // always add default config
553
        foreach($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type integer|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
554
            if(!isset($row['group'])) {
555
                $this->_debug("No 'group' field returned in select-user-groups statement");
556
                return false;
557
            }
558
            $groups[] = $row['group'];
559
        }
560
561
        $groups = array_unique($groups);
562
        sort($groups);
563
        return $groups;
564
    }
565
566
    /**
567
     * Select all available groups
568
     *
569
     * @return array|bool list of all available groups and their properties
570
     */
571
    protected function _selectGroups() {
572
        if($this->groupcache) return $this->groupcache;
573
574
        $sql = $this->getConf('select-groups');
575
        $result = $this->_query($sql);
576
        if($result === false) return false;
577
578
        $groups = array();
579
        foreach($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type integer|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
580
            if(!isset($row['group'])) {
581
                $this->_debug("No 'group' field returned from select-groups statement", -1, __LINE__);
582
                return false;
583
            }
584
585
            // relayout result with group name as key
586
            $group = $row['group'];
587
            $groups[$group] = $row;
588
        }
589
590
        ksort($groups);
591
        return $groups;
592
    }
593
594
    /**
595
     * Remove all entries from the group cache
596
     */
597
    protected function _clearGroupCache() {
598
        $this->groupcache = null;
599
    }
600
601
    /**
602
     * Adds the user to the group
603
     *
604
     * @param array $userdata all the user data
605
     * @param array $groupdata all the group data
606
     * @return bool
607
     */
608
    protected function _joinGroup($userdata, $groupdata) {
609
        $data = array_merge($userdata, $groupdata);
610
        $sql = $this->getConf('join-group');
611
        $result = $this->_query($sql, $data);
612
        if($result === false) return false;
613
        return true;
614
    }
615
616
    /**
617
     * Removes the user from the group
618
     *
619
     * @param array $userdata all the user data
620
     * @param array $groupdata all the group data
621
     * @return bool
622
     */
623
    protected function _leaveGroup($userdata, $groupdata) {
624
        $data = array_merge($userdata, $groupdata);
625
        $sql = $this->getConf('leave-group');
626
        $result = $this->_query($sql, $data);
627
        if($result === false) return false;
628
        return true;
629
    }
630
631
    /**
632
     * Executes a query
633
     *
634
     * @param string $sql The SQL statement to execute
635
     * @param array $arguments Named parameters to be used in the statement
636
     * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
637
     */
638
    protected function _query($sql, $arguments = array()) {
639
        $sql = trim($sql);
640
        if(empty($sql)) {
641
            $this->_debug('No SQL query given', -1, __LINE__);
642
            return false;
643
        }
644
645
        // execute
646
        $params = array();
647
        $sth = $this->pdo->prepare($sql);
648
        try {
649
            // prepare parameters - we only use those that exist in the SQL
650
            foreach($arguments as $key => $value) {
651
                if(is_array($value)) continue;
652
                if(is_object($value)) continue;
653
                if($key[0] != ':') $key = ":$key"; // prefix with colon if needed
654
                if(strpos($sql, $key) === false) continue; // skip if parameter is missing
655
656
                if(is_int($value)) {
657
                    $sth->bindValue($key, $value, PDO::PARAM_INT);
658
                } else {
659
                    $sth->bindValue($key, $value);
660
                }
661
                $params[$key] = $value; //remember for debugging
662
            }
663
664
            $sth->execute();
665
            if(strtolower(substr($sql, 0, 6)) == 'select') {
666
                $result = $sth->fetchAll();
667
            } else {
668
                $result = $sth->rowCount();
669
            }
670
        } catch(Exception $e) {
671
            // report the caller's line
672
            $trace = debug_backtrace();
673
            $line = $trace[0]['line'];
674
            $dsql = $this->_debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
675
            $this->_debug($e, -1, $line);
676
            $this->_debug("SQL: <pre>$dsql</pre>", -1, $line);
677
            $result = false;
678
        }
679
        $sth->closeCursor();
680
        $sth = null;
0 ignored issues
show
Unused Code introduced by
$sth is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
681
682
        return $result;
683
    }
684
685
    /**
686
     * Wrapper around msg() but outputs only when debug is enabled
687
     *
688
     * @param string|Exception $message
689
     * @param int $err
690
     * @param int $line
691
     */
692
    protected function _debug($message, $err = 0, $line = 0) {
693
        if(!$this->getConf('debug')) return;
694
        if(is_a($message, 'Exception')) {
695
            $err = -1;
696
            $msg = $message->getMessage();
0 ignored issues
show
Bug introduced by
It seems like $message is not always an object, but can also be of type string. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
697
            if(!$line) $line = $message->getLine();
698
        } else {
699
            $msg = $message;
700
        }
701
702
        if(defined('DOKU_UNITTEST')) {
703
            printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
704
        } else {
705
            msg('authpdo: ' . $msg, $err, $line, __FILE__);
706
        }
707
    }
708
709
    /**
710
     * Check if the given config strings are set
711
     *
712
     * @author  Matthias Grimm <[email protected]>
713
     *
714
     * @param   string[] $keys
715
     * @return  bool
716
     */
717
    protected function _chkcnf($keys) {
718
        foreach($keys as $key) {
719
            if(!trim($this->getConf($key))) return false;
720
        }
721
722
        return true;
723
    }
724
725
    /**
726
     * create an approximation of the SQL string with parameters replaced
727
     *
728
     * @param string $sql
729
     * @param array $params
730
     * @param bool $htmlescape Should the result be escaped for output in HTML?
731
     * @return string
732
     */
733
    protected function _debugSQL($sql, $params, $htmlescape = true) {
734
        foreach($params as $key => $val) {
735
            if(is_int($val)) {
736
                $val = $this->pdo->quote($val, PDO::PARAM_INT);
737
            } elseif(is_bool($val)) {
738
                $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
739
            } elseif(is_null($val)) {
740
                $val = 'NULL';
741
            } else {
742
                $val = $this->pdo->quote($val);
743
            }
744
            $sql = str_replace($key, $val, $sql);
745
        }
746
        if($htmlescape) $sql = hsc($sql);
747
        return $sql;
748
    }
749
}
750
751
// vim:ts=4:sw=4:et:
752