Completed
Push — authpdobug ( 12c7f5 )
by Andreas
03:50
created

auth_plugin_authpdo::retrieveUsers()   D

Complexity

Conditions 9
Paths 96

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 21
nc 96
nop 3
dl 0
loc 28
rs 4.909
c 0
b 0
f 0
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 be changed?
101
        $this->cando['modName'] = $this->_chkcnf(
102
            array(
103
                'select-user',
104
                'select-user-groups',
105
                'update-user-info:name'
106
            )
107
        );
108
109
        // can real email be changed?
110
        $this->cando['modMail'] = $this->_chkcnf(
111
            array(
112
                'select-user',
113
                'select-user-groups',
114
                'update-user-info:mail'
115
            )
116
        );
117
118
        // can groups be changed?
119
        $this->cando['modGroups'] = $this->_chkcnf(
120
            array(
121
                'select-user',
122
                'select-user-groups',
123
                'select-groups',
124
                'leave-group',
125
                'join-group',
126
                'insert-group'
127
            )
128
        );
129
130
        // can a filtered list of users be retrieved?
131
        $this->cando['getUsers'] = $this->_chkcnf(
132
            array(
133
                'list-users'
134
            )
135
        );
136
137
        // can the number of users be retrieved?
138
        $this->cando['getUserCount'] = $this->_chkcnf(
139
            array(
140
                'count-users'
141
            )
142
        );
143
144
        // can a list of available groups be retrieved?
145
        $this->cando['getGroups'] = $this->_chkcnf(
146
            array(
147
                'select-groups'
148
            )
149
        );
150
151
        $this->success = true;
152
    }
153
154
    /**
155
     * Check user+password
156
     *
157
     * @param   string $user the user name
158
     * @param   string $pass the clear text password
159
     * @return  bool
160
     */
161
    public function checkPass($user, $pass) {
162
163
        $userdata = $this->_selectUser($user);
164
        if($userdata == false) return false;
165
166
        // password checking done in SQL?
167
        if($this->_chkcnf(array('check-pass'))) {
168
            $userdata['clear'] = $pass;
169
            $userdata['hash'] = auth_cryptPassword($pass);
170
            $result = $this->_query($this->getConf('check-pass'), $userdata);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->_selectUser($user) on line 163 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...
171
            if($result === false) return false;
172
            return (count($result) == 1);
173
        }
174
175
        // we do password checking on our own
176
        if(isset($userdata['hash'])) {
177
            // hashed password
178
            $passhash = new PassHash();
179
            return $passhash->verify_hash($pass, $userdata['hash']);
180
        } else {
181
            // clear text password in the database O_o
182
            return ($pass === $userdata['clear']);
183
        }
184
    }
185
186
    /**
187
     * Return user info
188
     *
189
     * Returns info about the given user needs to contain
190
     * at least these fields:
191
     *
192
     * name string  full name of the user
193
     * mail string  email addres of the user
194
     * grps array   list of groups the user is in
195
     *
196
     * @param   string $user the user name
197
     * @param   bool $requireGroups whether or not the returned data must include groups
198
     * @return array|bool containing user data or false
199
     */
200
    public function getUserData($user, $requireGroups = true) {
201
        $data = $this->_selectUser($user);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_selectUser($user); of type boolean|array adds the type boolean to the return on line 212 which is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserData of type false|array.
Loading history...
202
        if($data == false) return false;
203
204
        if(isset($data['hash'])) unset($data['hash']);
205
        if(isset($data['clean'])) unset($data['clean']);
206
207
        if($requireGroups) {
208
            $data['grps'] = $this->_selectUserGroups($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->_selectUser($user) on line 201 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...
209
            if($data['grps'] === false) return false;
210
        }
211
212
        return $data;
213
    }
214
215
    /**
216
     * Create a new User [implement only where required/possible]
217
     *
218
     * Returns false if the user already exists, null when an error
219
     * occurred and true if everything went well.
220
     *
221
     * The new user HAS TO be added to the default group by this
222
     * function!
223
     *
224
     * Set addUser capability when implemented
225
     *
226
     * @param  string $user
227
     * @param  string $clear
228
     * @param  string $name
229
     * @param  string $mail
230
     * @param  null|array $grps
231
     * @return bool|null
232
     */
233
    public function createUser($user, $clear, $name, $mail, $grps = null) {
234
        global $conf;
235
236
        if(($info = $this->getUserData($user, false)) !== false) {
237
            msg($this->getLang('userexists'), -1);
238
            return false; // user already exists
239
        }
240
241
        // prepare data
242
        if($grps == null) $grps = array();
243
        array_unshift($grps, $conf['defaultgroup']);
244
        $grps = array_unique($grps);
245
        $hash = auth_cryptPassword($clear);
246
        $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
247
248
        // action protected by transaction
249
        $this->pdo->beginTransaction();
250
        {
251
            // insert the user
252
            $ok = $this->_query($this->getConf('insert-user'), $userdata);
253
            if($ok === false) goto FAIL;
254
            $userdata = $this->getUserData($user, false);
255
            if($userdata === false) goto FAIL;
256
257
            // create all groups that do not exist, the refetch the groups
258
            $allgroups = $this->_selectGroups();
259
            foreach($grps as $group) {
260
                if(!isset($allgroups[$group])) {
261
                    $ok = $this->addGroup($group);
262
                    if($ok === false) goto FAIL;
263
                }
264
            }
265
            $allgroups = $this->_selectGroups();
266
267
            // add user to the groups
268
            foreach($grps as $group) {
269
                $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 254 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...
270
                if($ok === false) goto FAIL;
271
            }
272
        }
273
        $this->pdo->commit();
274
        return true;
275
276
        // something went wrong, rollback
277
        FAIL:
278
        $this->pdo->rollBack();
279
        $this->_debug('Transaction rolled back', 0, __LINE__);
280
        msg($this->getLang('writefail'), -1);
281
        return null; // return error
282
    }
283
284
    /**
285
     * Modify user data
286
     *
287
     * @param   string $user nick of the user to be changed
288
     * @param   array $changes array of field/value pairs to be changed (password will be clear text)
289
     * @return  bool
290
     */
291
    public function modifyUser($user, $changes) {
292
        // secure everything in transaction
293
        $this->pdo->beginTransaction();
294
        {
295
            $olddata = $this->getUserData($user);
296
            $oldgroups = $olddata['grps'];
297
            unset($olddata['grps']);
298
299
            // changing the user name?
300
            if(isset($changes['user'])) {
301
                if($this->getUserData($changes['user'], false)) goto FAIL;
302
                $params = $olddata;
303
                $params['newlogin'] = $changes['user'];
304
305
                $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 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...
306
                if($ok === false) goto FAIL;
307
            }
308
309
            // changing the password?
310
            if(isset($changes['pass'])) {
311
                $params = $olddata;
312
                $params['clear'] = $changes['pass'];
313
                $params['hash'] = auth_cryptPassword($changes['pass']);
314
315
                $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 311 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...
316
                if($ok === false) goto FAIL;
317
            }
318
319
            // changing info?
320
            if(isset($changes['mail']) || isset($changes['name'])) {
321
                $params = $olddata;
322
                if(isset($changes['mail'])) $params['mail'] = $changes['mail'];
323
                if(isset($changes['name'])) $params['name'] = $changes['name'];
324
325
                $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 321 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...
326
                if($ok === false) goto FAIL;
327
            }
328
329
            // changing groups?
330
            if(isset($changes['grps'])) {
331
                $allgroups = $this->_selectGroups();
332
333
                // remove membership for previous groups
334
                foreach($oldgroups as $group) {
335
                    if(!in_array($group, $changes['grps']) && isset($allgroups[$group])) {
336
                        $ok = $this->_leaveGroup($olddata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $olddata defined by $this->getUserData($user) on line 295 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...
337
                        if($ok === false) goto FAIL;
338
                    }
339
                }
340
341
                // create all new groups that are missing
342
                $added = 0;
343
                foreach($changes['grps'] as $group) {
344
                    if(!isset($allgroups[$group])) {
345
                        $ok = $this->addGroup($group);
346
                        if($ok === false) goto FAIL;
347
                        $added++;
348
                    }
349
                }
350
                // reload group info
351
                if($added > 0) $allgroups = $this->_selectGroups();
352
353
                // add membership for new groups
354
                foreach($changes['grps'] as $group) {
355
                    if(!in_array($group, $oldgroups)) {
356
                        $ok = $this->_joinGroup($olddata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $olddata defined by $this->getUserData($user) on line 295 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...
357
                        if($ok === false) goto FAIL;
358
                    }
359
                }
360
            }
361
362
        }
363
        $this->pdo->commit();
364
        return true;
365
366
        // something went wrong, rollback
367
        FAIL:
368
        $this->pdo->rollBack();
369
        $this->_debug('Transaction rolled back', 0, __LINE__);
370
        msg($this->getLang('writefail'), -1);
371
        return false; // return error
372
    }
373
374
    /**
375
     * Delete one or more users
376
     *
377
     * Set delUser capability when implemented
378
     *
379
     * @param   array $users
380
     * @return  int    number of users deleted
381
     */
382
    public function deleteUsers($users) {
383
        $count = 0;
384
        foreach($users as $user) {
385
            if($this->_deleteUser($user)) $count++;
386
        }
387
        return $count;
388
    }
389
390
    /**
391
     * Bulk retrieval of user data [implement only where required/possible]
392
     *
393
     * Set getUsers capability when implemented
394
     *
395
     * @param   int $start index of first user to be returned
396
     * @param   int $limit max number of users to be returned
397
     * @param   array $filter array of field/pattern pairs, null for no filter
398
     * @return  array list of userinfo (refer getUserData for internal userinfo details)
399
     */
400
    public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
401
        if($limit < 0) $limit = 10000; // we don't support no limit
402
        if(is_null($filter)) $filter = array();
403
404
        if(isset($filter['grps'])) $filter['group'] = $filter['grps'];
405
        foreach(array('user', 'name', 'mail', 'group') as $key) {
406
            if(!isset($filter[$key])) {
407
                $filter[$key] = '%';
408
            } else {
409
                $filter[$key] = '%' . $filter[$key] . '%';
410
            }
411
        }
412
        $filter['start'] = (int) $start;
413
        $filter['end'] = (int) $start + $limit;
414
        $filter['limit'] = (int) $limit;
415
416
        $result = $this->_query($this->getConf('list-users'), $filter);
417
        if(!$result) return array();
418
        $users = array();
419
        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...
420
            if(!isset($row['user'])) {
421
                $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
422
                return array();
423
            }
424
            $users[] = $this->getUserData($row['user']);
425
        }
426
        return $users;
427
    }
428
429
    /**
430
     * Return a count of the number of user which meet $filter criteria
431
     *
432
     * @param  array $filter array of field/pattern pairs, empty array for no filter
433
     * @return int
434
     */
435
    public function getUserCount($filter = array()) {
436
        if(is_null($filter)) $filter = array();
437
438
        if(isset($filter['grps'])) $filter['group'] = $filter['grps'];
439
        foreach(array('user', 'name', 'mail', 'group') as $key) {
440
            if(!isset($filter[$key])) {
441
                $filter[$key] = '%';
442
            } else {
443
                $filter[$key] = '%' . $filter[$key] . '%';
444
            }
445
        }
446
447
        $result = $this->_query($this->getConf('count-users'), $filter);
448
        if(!$result || !isset($result[0]['count'])) {
449
            $this->_debug("Statement did not return 'count' attribute", -1, __LINE__);
450
        }
451
        return (int) $result[0]['count'];
452
    }
453
454
    /**
455
     * Create a new group with the given name
456
     *
457
     * @param string $group
458
     * @return bool
459
     */
460
    public function addGroup($group) {
461
        $sql = $this->getConf('insert-group');
462
463
        $result = $this->_query($sql, array(':group' => $group));
464
        $this->_clearGroupCache();
465
        if($result === false) return false;
466
        return true;
467
    }
468
469
    /**
470
     * Retrieve groups
471
     *
472
     * Set getGroups capability when implemented
473
     *
474
     * @param   int $start
475
     * @param   int $limit
476
     * @return  array
477
     */
478
    public function retrieveGroups($start = 0, $limit = 0) {
479
        $groups = array_keys($this->_selectGroups());
480
        if($groups === false) return array();
481
482
        if(!$limit) {
483
            return array_splice($groups, $start);
484
        } else {
485
            return array_splice($groups, $start, $limit);
486
        }
487
    }
488
489
    /**
490
     * Select data of a specified user
491
     *
492
     * @param string $user the user name
493
     * @return bool|array user data, false on error
494
     */
495
    protected function _selectUser($user) {
496
        $sql = $this->getConf('select-user');
497
498
        $result = $this->_query($sql, array(':user' => $user));
499
        if(!$result) return false;
500
501
        if(count($result) > 1) {
502
            $this->_debug('Found more than one matching user', -1, __LINE__);
503
            return false;
504
        }
505
506
        $data = array_shift($result);
507
        $dataok = true;
508
509
        if(!isset($data['user'])) {
510
            $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
511
            $dataok = false;
512
        }
513
        if(!isset($data['hash']) && !isset($data['clear']) && !$this->_chkcnf(array('check-pass'))) {
514
            $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
515
            $dataok = false;
516
        }
517
        if(!isset($data['name'])) {
518
            $this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
519
            $dataok = false;
520
        }
521
        if(!isset($data['mail'])) {
522
            $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
523
            $dataok = false;
524
        }
525
526
        if(!$dataok) return false;
527
        return $data;
528
    }
529
530
    /**
531
     * Delete a user after removing all their group memberships
532
     *
533
     * @param string $user
534
     * @return bool true when the user was deleted
535
     */
536
    protected function _deleteUser($user) {
537
        $this->pdo->beginTransaction();
538
        {
539
            $userdata = $this->getUserData($user);
540
            if($userdata === false) goto FAIL;
541
            $allgroups = $this->_selectGroups();
542
543
            // remove group memberships (ignore errors)
544
            foreach($userdata['grps'] as $group) {
545
                if(isset($allgroups[$group])) {
546
                    $this->_leaveGroup($userdata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->getUserData($user) on line 539 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...
547
                }
548
            }
549
550
            $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 539 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...
551
            if($ok === false) goto FAIL;
552
        }
553
        $this->pdo->commit();
554
        return true;
555
556
        FAIL:
557
        $this->pdo->rollBack();
558
        return false;
559
    }
560
561
    /**
562
     * Select all groups of a user
563
     *
564
     * @param array $userdata The userdata as returned by _selectUser()
565
     * @return array|bool list of group names, false on error
566
     */
567
    protected function _selectUserGroups($userdata) {
568
        global $conf;
569
        $sql = $this->getConf('select-user-groups');
570
        $result = $this->_query($sql, $userdata);
571
        if($result === false) return false;
572
573
        $groups = array($conf['defaultgroup']); // always add default config
574
        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...
575
            if(!isset($row['group'])) {
576
                $this->_debug("No 'group' field returned in select-user-groups statement");
577
                return false;
578
            }
579
            $groups[] = $row['group'];
580
        }
581
582
        $groups = array_unique($groups);
583
        sort($groups);
584
        return $groups;
585
    }
586
587
    /**
588
     * Select all available groups
589
     *
590
     * @return array|bool list of all available groups and their properties
591
     */
592
    protected function _selectGroups() {
593
        if($this->groupcache) return $this->groupcache;
594
595
        $sql = $this->getConf('select-groups');
596
        $result = $this->_query($sql);
597
        if($result === false) return false;
598
599
        $groups = array();
600
        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...
601
            if(!isset($row['group'])) {
602
                $this->_debug("No 'group' field returned from select-groups statement", -1, __LINE__);
603
                return false;
604
            }
605
606
            // relayout result with group name as key
607
            $group = $row['group'];
608
            $groups[$group] = $row;
609
        }
610
611
        ksort($groups);
612
        return $groups;
613
    }
614
615
    /**
616
     * Remove all entries from the group cache
617
     */
618
    protected function _clearGroupCache() {
619
        $this->groupcache = null;
620
    }
621
622
    /**
623
     * Adds the user to the group
624
     *
625
     * @param array $userdata all the user data
626
     * @param array $groupdata all the group data
627
     * @return bool
628
     */
629
    protected function _joinGroup($userdata, $groupdata) {
630
        $data = array_merge($userdata, $groupdata);
631
        $sql = $this->getConf('join-group');
632
        $result = $this->_query($sql, $data);
633
        if($result === false) return false;
634
        return true;
635
    }
636
637
    /**
638
     * Removes the user from the group
639
     *
640
     * @param array $userdata all the user data
641
     * @param array $groupdata all the group data
642
     * @return bool
643
     */
644
    protected function _leaveGroup($userdata, $groupdata) {
645
        $data = array_merge($userdata, $groupdata);
646
        $sql = $this->getConf('leave-group');
647
        $result = $this->_query($sql, $data);
648
        if($result === false) return false;
649
        return true;
650
    }
651
652
    /**
653
     * Executes a query
654
     *
655
     * @param string $sql The SQL statement to execute
656
     * @param array $arguments Named parameters to be used in the statement
657
     * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
658
     */
659
    protected function _query($sql, $arguments = array()) {
660
        $sql = trim($sql);
661
        if(empty($sql)) {
662
            $this->_debug('No SQL query given', -1, __LINE__);
663
            return false;
664
        }
665
666
        // execute
667
        $params = array();
668
        $sth = $this->pdo->prepare($sql);
669
        try {
670
            // prepare parameters - we only use those that exist in the SQL
671
            foreach($arguments as $key => $value) {
672
                if(is_array($value)) continue;
673
                if(is_object($value)) continue;
674
                if($key[0] != ':') $key = ":$key"; // prefix with colon if needed
675
                if(strpos($sql, $key) === false) continue; // skip if parameter is missing
676
677
                if(is_int($value)) {
678
                    $sth->bindValue($key, $value, PDO::PARAM_INT);
679
                } else {
680
                    $sth->bindValue($key, $value);
681
                }
682
                $params[$key] = $value; //remember for debugging
683
            }
684
685
            $sth->execute();
686
            if(strtolower(substr($sql, 0, 6)) == 'select') {
687
                $result = $sth->fetchAll();
688
            } else {
689
                $result = $sth->rowCount();
690
            }
691
        } catch(Exception $e) {
692
            // report the caller's line
693
            $trace = debug_backtrace();
694
            $line = $trace[0]['line'];
695
            $dsql = $this->_debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
696
            $this->_debug($e, -1, $line);
697
            $this->_debug("SQL: <pre>$dsql</pre>", -1, $line);
698
            $result = false;
699
        }
700
        $sth->closeCursor();
701
        $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...
702
703
        return $result;
704
    }
705
706
    /**
707
     * Wrapper around msg() but outputs only when debug is enabled
708
     *
709
     * @param string|Exception $message
710
     * @param int $err
711
     * @param int $line
712
     */
713
    protected function _debug($message, $err = 0, $line = 0) {
714
        if(!$this->getConf('debug')) return;
715
        if(is_a($message, 'Exception')) {
716
            $err = -1;
717
            $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...
718
            if(!$line) $line = $message->getLine();
719
        } else {
720
            $msg = $message;
721
        }
722
723
        if(defined('DOKU_UNITTEST')) {
724
            printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
725
        } else {
726
            msg('authpdo: ' . $msg, $err, $line, __FILE__);
727
        }
728
    }
729
730
    /**
731
     * Check if the given config strings are set
732
     *
733
     * @author  Matthias Grimm <[email protected]>
734
     *
735
     * @param   string[] $keys
736
     * @return  bool
737
     */
738
    protected function _chkcnf($keys) {
739
        foreach($keys as $key) {
740
            $params = explode(':', $key);
741
            $key = array_shift($params);
742
            $sql = trim($this->getConf($key));
743
744
            // check if sql is set
745
            if(!$sql) return false;
746
            // check if needed params are there
747
            foreach($params as $param) {
748
                if(strpos($sql, ":$param") === false) return false;
749
            }
750
        }
751
752
        return true;
753
    }
754
755
    /**
756
     * create an approximation of the SQL string with parameters replaced
757
     *
758
     * @param string $sql
759
     * @param array $params
760
     * @param bool $htmlescape Should the result be escaped for output in HTML?
761
     * @return string
762
     */
763
    protected function _debugSQL($sql, $params, $htmlescape = true) {
764
        foreach($params as $key => $val) {
765
            if(is_int($val)) {
766
                $val = $this->pdo->quote($val, PDO::PARAM_INT);
767
            } elseif(is_bool($val)) {
768
                $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
769
            } elseif(is_null($val)) {
770
                $val = 'NULL';
771
            } else {
772
                $val = $this->pdo->quote($val);
773
            }
774
            $sql = str_replace($key, $val, $sql);
775
        }
776
        if($htmlescape) $sql = hsc($sql);
777
        return $sql;
778
    }
779
}
780
781
// vim:ts=4:sw=4:et:
782