Completed
Push — authpdo ( 36726b...0476c5 )
by Andreas
06:52
created

auth_plugin_authpdo::deleteUsers()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 3
eloc 5
nc 3
nop 1
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
                $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
            )
79
        );
80
81
        // can login names be changed?
82
        $this->cando['modLogin'] = $this->_chkcnf(
83
            array(
84
                'select-user',
85
                'select-user-groups',
86
                'update-user-login'
87
            )
88
        );
89
90
        // can passwords be changed?
91
        $this->cando['modPass'] = $this->_chkcnf(
92
            array(
93
                'select-user',
94
                'select-user-groups',
95
                'update-user-pass'
96
            )
97
        );
98
99
        // can real names and emails be changed?
100
        $this->cando['modName'] = $this->cando['modMail'] = $this->_chkcnf(
101
            array(
102
                'select-user',
103
                'select-user-groups',
104
                'update-user-info'
105
            )
106
        );
107
108
        // can groups be changed?
109
        $this->cando['modGroups'] = $this->_chkcnf(
110
            array(
111
                'select-user',
112
                'select-user-groups',
113
                'select-groups',
114
                'leave-group',
115
                'join-group',
116
                'insert-group'
117
            )
118
        );
119
120
        // can a filtered list of users be retrieved?
121
        $this->cando['getUsers'] = $this->_chkcnf(
122
            array(
123
                'list-users'
124
            )
125
        );
126
127
        // can the number of users be retrieved?
128
        $this->cando['getUserCount'] = $this->_chkcnf(
129
            array(
130
                'count-users'
131
            )
132
        );
133
134
        // can a list of available groups be retrieved?
135
        $this->cando['getGroups'] = $this->_chkcnf(
136
            array(
137
                'select-groups'
138
            )
139
        );
140
141
        $this->success = true;
142
    }
143
144
    /**
145
     * Check user+password
146
     *
147
     * @param   string $user the user name
148
     * @param   string $pass the clear text password
149
     * @return  bool
150
     */
151
    public function checkPass($user, $pass) {
152
153
        $data = $this->_selectUser($user);
154
        if($data == false) return false;
155
156
        if(isset($data['hash'])) {
157
            // hashed password
158
            $passhash = new PassHash();
159
            return $passhash->verify_hash($pass, $data['hash']);
160
        } else {
161
            // clear text password in the database O_o
162
            return ($pass == $data['clear']);
163
        }
164
    }
165
166
    /**
167
     * Return user info
168
     *
169
     * Returns info about the given user needs to contain
170
     * at least these fields:
171
     *
172
     * name string  full name of the user
173
     * mail string  email addres of the user
174
     * grps array   list of groups the user is in
175
     *
176
     * @param   string $user the user name
177
     * @param   bool $requireGroups whether or not the returned data must include groups
178
     * @return array|bool containing user data or false
179
     */
180
    public function getUserData($user, $requireGroups = true) {
181
        $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 192 which is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserData of type boolean.
Loading history...
182
        if($data == false) return false;
183
184
        if(isset($data['hash'])) unset($data['hash']);
185
        if(isset($data['clean'])) unset($data['clean']);
186
187
        if($requireGroups) {
188
            $data['grps'] = $this->_selectUserGroups($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->_selectUser($user) on line 181 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...
189
            if($data['grps'] === false) return false;
190
        }
191
192
        return $data;
193
    }
194
195
    /**
196
     * Create a new User [implement only where required/possible]
197
     *
198
     * Returns false if the user already exists, null when an error
199
     * occurred and true if everything went well.
200
     *
201
     * The new user HAS TO be added to the default group by this
202
     * function!
203
     *
204
     * Set addUser capability when implemented
205
     *
206
     * @param  string $user
207
     * @param  string $clear
208
     * @param  string $name
209
     * @param  string $mail
210
     * @param  null|array $grps
211
     * @return bool|null
212
     */
213
    public function createUser($user, $clear, $name, $mail, $grps = null) {
214
        global $conf;
215
216
        if(($info = $this->getUserData($user, false)) !== false) {
217
            msg($this->getLang('userexists'), -1);
218
            return false; // user already exists
219
        }
220
221
        // prepare data
222
        if($grps == null) $grps = array();
223
        array_unshift($grps, $conf['defaultgroup']);
224
        $grps = array_unique($grps);
225
        $hash = auth_cryptPassword($clear);
226
        $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
227
228
        // action protected by transaction
229
        $this->pdo->beginTransaction();
230
        {
231
            // insert the user
232
            $ok = $this->_query($this->getConf('insert-user'), $userdata);
233
            if($ok === false) goto FAIL;
234
            $userdata = $this->getUserData($user, false);
235
            if($userdata === false) goto FAIL;
236
237
            // create all groups that do not exist, the refetch the groups
238
            $allgroups = $this->_selectGroups();
239
            foreach($grps as $group) {
240
                if(!isset($allgroups[$group])) {
241
                    $ok = $this->addGroup($group);
242
                    if($ok === false) goto FAIL;
243
                }
244
            }
245
            $allgroups = $this->_selectGroups();
246
247
            // add user to the groups
248
            foreach($grps as $group) {
249
                $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 234 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...
250
                if($ok === false) goto FAIL;
251
            }
252
        }
253
        $this->pdo->commit();
254
        return true;
255
256
        // something went wrong, rollback
257
        FAIL:
258
        $this->pdo->rollBack();
259
        $this->_debug('Transaction rolled back', 0, __LINE__);
260
        msg($this->getLang('writefail'), -1);
261
        return null; // return error
262
    }
263
264
    /**
265
     * Modify user data
266
     *
267
     * @param   string $user nick of the user to be changed
268
     * @param   array $changes array of field/value pairs to be changed (password will be clear text)
269
     * @return  bool
270
     */
271
    public function modifyUser($user, $changes) {
272
        // secure everything in transaction
273
        $this->pdo->beginTransaction();
274
        {
275
            $olddata = $this->getUserData($user);
276
            $oldgroups = $olddata['grps'];
277
            unset($olddata['grps']);
278
279
            // changing the user name?
280
            if(isset($changes['user'])) {
281
                if($this->getUserData($changes['user'], false)) goto FAIL;
282
                $params = $olddata;
283
                $params['newlogin'] = $changes['user'];
284
285
                $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 282 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...
286
                if($ok === false) goto FAIL;
287
            }
288
289
            // changing the password?
290
            if(isset($changes['pass'])) {
291
                $params = $olddata;
292
                $params['clear'] = $changes['pass'];
293
                $params['hash'] = auth_cryptPassword($changes['pass']);
294
295
                $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 291 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...
296
                if($ok === false) goto FAIL;
297
            }
298
299
            // changing info?
300
            if(isset($changes['mail']) || isset($changes['name'])) {
301
                $params = $olddata;
302
                if(isset($changes['mail'])) $params['mail'] = $changes['mail'];
303
                if(isset($changes['name'])) $params['name'] = $changes['name'];
304
305
                $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 301 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 groups?
310
            if(isset($changes['grps'])) {
311
                $allgroups = $this->_selectGroups();
312
313
                // remove membership for previous groups
314
                foreach($oldgroups as $group) {
315
                    if(!in_array($group, $changes['grps']) && isset($allgroups[$group])) {
316
                        $ok = $this->_leaveGroup($olddata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $olddata defined by $this->getUserData($user) on line 275 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...
317
                        if($ok === false) goto FAIL;
318
                    }
319
                }
320
321
                // create all new groups that are missing
322
                $added = 0;
323
                foreach($changes['grps'] as $group) {
324
                    if(!isset($allgroups[$group])) {
325
                        $ok = $this->addGroup($group);
326
                        if($ok === false) goto FAIL;
327
                        $added++;
328
                    }
329
                }
330
                // reload group info
331
                if($added > 0) $allgroups = $this->_selectGroups();
332
333
                // add membership for new groups
334
                foreach($changes['grps'] as $group) {
335
                    if(!in_array($group, $oldgroups)) {
336
                        $ok = $this->_joinGroup($olddata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $olddata defined by $this->getUserData($user) on line 275 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...
337
                        if($ok === false) goto FAIL;
338
                    }
339
                }
340
            }
341
342
        }
343
        $this->pdo->commit();
344
        return true;
345
346
        // something went wrong, rollback
347
        FAIL:
348
        $this->pdo->rollBack();
349
        $this->_debug('Transaction rolled back', 0, __LINE__);
350
        msg($this->getLang('writefail'), -1);
351
        return false; // return error
352
    }
353
354
    /**
355
     * Delete one or more users
356
     *
357
     * Set delUser capability when implemented
358
     *
359
     * @param   array $users
360
     * @return  int    number of users deleted
361
     */
362
    public function deleteUsers($users) {
363
        $count = 0;
364
        foreach($users as $user) {
365
            if($this->_deleteUser($user)) $count++;
366
        }
367
        return $count;
368
    }
369
370
    /**
371
     * Bulk retrieval of user data [implement only where required/possible]
372
     *
373
     * Set getUsers capability when implemented
374
     *
375
     * @param   int $start index of first user to be returned
376
     * @param   int $limit max number of users to be returned
377
     * @param   array $filter array of field/pattern pairs, null for no filter
378
     * @return  array list of userinfo (refer getUserData for internal userinfo details)
379
     */
380
    public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
381
        if($limit < 0) $limit = 10000; // we don't support no limit
382
        if(is_null($filter)) $filter = array();
383
384
        foreach(array('user', 'name', 'mail', 'group') as $key) {
385
            if(!isset($filter[$key])) {
386
                $filter[$key] = '%';
387
            } else {
388
                $filter[$key] = '%' . $filter[$key] . '%';
389
            }
390
        }
391
        $filter['start'] = (int) $start;
392
        $filter['end'] = (int) $start + $limit;
393
        $filter['limit'] = (int) $limit;
394
395
        $result = $this->_query($this->getConf('list-users'), $filter);
396
        if(!$result) return array();
397
        $users = array();
398
        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...
399
            if(!isset($row['user'])) {
400
                $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
401
                return array();
402
            }
403
            $users[] = $row['user'];
404
        }
405
        return $users;
406
    }
407
408
    /**
409
     * Return a count of the number of user which meet $filter criteria
410
     *
411
     * @param  array $filter array of field/pattern pairs, empty array for no filter
412
     * @return int
413
     */
414
    public function getUserCount($filter = array()) {
415
        if(is_null($filter)) $filter = array();
416
417
        foreach(array('user', 'name', 'mail', 'group') as $key) {
418
            if(!isset($filter[$key])) {
419
                $filter[$key] = '%';
420
            } else {
421
                $filter[$key] = '%' . $filter[$key] . '%';
422
            }
423
        }
424
425
        $result = $this->_query($this->getConf('count-users'), $filter);
426
        if(!$result || !isset($result[0]['count'])) {
427
            $this->_debug("Statement did not return 'count' attribute", -1, __LINE__);
428
        }
429
        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...
430
    }
431
432
    /**
433
     * Create a new group with the given name
434
     *
435
     * @param string $group
436
     * @return bool
437
     */
438
    public function addGroup($group) {
439
        $sql = $this->getConf('insert-group');
440
441
        $result = $this->_query($sql, array(':group' => $group));
442
        $this->_clearGroupCache();
443
        if($result === false) return false;
444
        return true;
445
    }
446
447
    /**
448
     * Retrieve groups
449
     *
450
     * Set getGroups capability when implemented
451
     *
452
     * @param   int $start
453
     * @param   int $limit
454
     * @return  array
455
     */
456
    public function retrieveGroups($start = 0, $limit = 0) {
457
        $groups = array_keys($this->_selectGroups());
458
        if($groups === false) return array();
459
460
        if(!$limit) {
461
            return array_splice($groups, $start);
462
        } else {
463
            return array_splice($groups, $start, $limit);
464
        }
465
    }
466
467
    /**
468
     * Select data of a specified user
469
     *
470
     * @param string $user the user name
471
     * @return bool|array user data, false on error
472
     */
473
    protected function _selectUser($user) {
474
        $sql = $this->getConf('select-user');
475
476
        $result = $this->_query($sql, array(':user' => $user));
477
        if(!$result) return false;
478
479
        if(count($result) > 1) {
480
            $this->_debug('Found more than one matching user', -1, __LINE__);
481
            return false;
482
        }
483
484
        $data = array_shift($result);
485
        $dataok = true;
486
487
        if(!isset($data['user'])) {
488
            $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
489
            $dataok = false;
490
        }
491
        if(!isset($data['hash']) && !isset($data['clear'])) {
492
            $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
493
            $dataok = false;
494
        }
495
        if(!isset($data['name'])) {
496
            $this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
497
            $dataok = false;
498
        }
499
        if(!isset($data['mail'])) {
500
            $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
501
            $dataok = false;
502
        }
503
504
        if(!$dataok) return false;
505
        return $data;
506
    }
507
508
    /**
509
     * Delete a user after removing all their group memberships
510
     *
511
     * @param string $user
512
     * @return bool true when the user was deleted
513
     */
514
    protected function _deleteUser($user) {
515
        $this->pdo->beginTransaction();
516
        {
517
            $userdata = $this->getUserData($user);
518
            if($userdata === false) goto FAIL;
519
            $allgroups = $this->_selectGroups();
520
521
            // remove group memberships (ignore errors)
522
            foreach($userdata['grps'] as $group) {
523
                if(isset($allgroups[$group])) {
524
                    $this->_leaveGroup($userdata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->getUserData($user) on line 517 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...
525
                }
526
            }
527
528
            $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 517 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...
529
            if($ok === false) goto FAIL;
530
        }
531
        $this->pdo->commit();
532
        return true;
533
534
        FAIL:
535
        $this->pdo->rollBack();
536
        return false;
537
    }
538
539
    /**
540
     * Select all groups of a user
541
     *
542
     * @param array $userdata The userdata as returned by _selectUser()
543
     * @return array|bool list of group names, false on error
544
     */
545
    protected function _selectUserGroups($userdata) {
546
        global $conf;
547
        $sql = $this->getConf('select-user-groups');
548
        $result = $this->_query($sql, $userdata);
549
        if($result === false) return false;
550
551
        $groups = array($conf['defaultgroup']); // always add default config
552
        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...
553
            if(!isset($row['group'])) {
554
                $this->_debug("No 'group' field returned in select-user-groups statement");
555
                return false;
556
            }
557
            $groups[] = $row['group'];
558
        }
559
560
        $groups = array_unique($groups);
561
        sort($groups);
562
        return $groups;
563
    }
564
565
    /**
566
     * Select all available groups
567
     *
568
     * @return array|bool list of all available groups and their properties
569
     */
570
    protected function _selectGroups() {
571
        if($this->groupcache) return $this->groupcache;
572
573
        $sql = $this->getConf('select-groups');
574
        $result = $this->_query($sql);
575
        if($result === false) return false;
576
577
        $groups = array();
578
        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...
579
            if(!isset($row['group'])) {
580
                $this->_debug("No 'group' field returned from select-groups statement", -1, __LINE__);
581
                return false;
582
            }
583
584
            // relayout result with group name as key
585
            $group = $row['group'];
586
            $groups[$group] = $row;
587
        }
588
589
        ksort($groups);
590
        return $groups;
591
    }
592
593
    /**
594
     * Remove all entries from the group cache
595
     */
596
    protected function _clearGroupCache() {
597
        $this->groupcache = null;
598
    }
599
600
    /**
601
     * Adds the user to the group
602
     *
603
     * @param array $userdata all the user data
604
     * @param array $groupdata all the group data
605
     * @return bool
606
     */
607
    protected function _joinGroup($userdata, $groupdata) {
608
        $data = array_merge($userdata, $groupdata);
609
        $sql = $this->getConf('join-group');
610
        $result = $this->_query($sql, $data);
611
        if($result === false) return false;
612
        return true;
613
    }
614
615
    /**
616
     * Removes the user from the group
617
     *
618
     * @param array $userdata all the user data
619
     * @param array $groupdata all the group data
620
     * @return bool
621
     */
622
    protected function _leaveGroup($userdata, $groupdata) {
623
        $data = array_merge($userdata, $groupdata);
624
        $sql = $this->getConf('leave-group');
625
        $result = $this->_query($sql, $data);
626
        if($result === false) return false;
627
        return true;
628
    }
629
630
    /**
631
     * Executes a query
632
     *
633
     * @param string $sql The SQL statement to execute
634
     * @param array $arguments Named parameters to be used in the statement
635
     * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
636
     */
637
    protected function _query($sql, $arguments = array()) {
638
        $sql = trim($sql);
639
        if(empty($sql)) {
640
            $this->_debug('No SQL query given', -1, __LINE__);
641
            return false;
642
        }
643
644
        // execute
645
        $params = array();
646
        $sth = $this->pdo->prepare($sql);
647
        try {
648
            // prepare parameters - we only use those that exist in the SQL
649
            foreach($arguments as $key => $value) {
650
                if(is_array($value)) continue;
651
                if(is_object($value)) continue;
652
                if($key[0] != ':') $key = ":$key"; // prefix with colon if needed
653
                if(strpos($sql, $key) === false) continue; // skip if parameter is missing
654
655
                if(is_int($value)) {
656
                    $sth->bindValue($key, $value, PDO::PARAM_INT);
657
                } else {
658
                    $sth->bindValue($key, $value);
659
                }
660
                $param[$key] = $value; //remember for debugging
0 ignored issues
show
Coding Style Comprehensibility introduced by
$param was never initialized. Although not strictly required by PHP, it is generally a good practice to add $param = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
661
            }
662
663
            $sth->execute();
664
            if(strtolower(substr($sql, 0, 6)) == 'select') {
665
                $result = $sth->fetchAll();
666
            } else {
667
                $result = $sth->rowCount();
668
            }
669
        } catch(Exception $e) {
670
            // report the caller's line
671
            $trace = debug_backtrace();
672
            $line = $trace[0]['line'];
673
            $dsql = $this->_debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
674
            $this->_debug($e, -1, $line);
675
            $this->_debug("SQL: <pre>$dsql</pre>", -1, $line);
676
            $result = false;
677
        }
678
        $sth->closeCursor();
679
        $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...
680
681
        return $result;
682
    }
683
684
    /**
685
     * Wrapper around msg() but outputs only when debug is enabled
686
     *
687
     * @param string|Exception $message
688
     * @param int $err
689
     * @param int $line
690
     */
691
    protected function _debug($message, $err = 0, $line = 0) {
692
        if(!$this->getConf('debug')) return;
693
        if(is_a($message, 'Exception')) {
694
            $err = -1;
695
            $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...
696
            if(!$line) $line = $message->getLine();
697
        } else {
698
            $msg = $message;
699
        }
700
701
        if(defined('DOKU_UNITTEST')) {
702
            printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
703
        } else {
704
            msg('authpdo: ' . $msg, $err, $line, __FILE__);
705
        }
706
    }
707
708
    /**
709
     * Check if the given config strings are set
710
     *
711
     * @author  Matthias Grimm <[email protected]>
712
     *
713
     * @param   string[] $keys
714
     * @return  bool
715
     */
716
    protected function _chkcnf($keys) {
717
        foreach($keys as $key) {
718
            if(!trim($this->getConf($key))) return false;
719
        }
720
721
        return true;
722
    }
723
724
    /**
725
     * create an approximation of the SQL string with parameters replaced
726
     *
727
     * @param string $sql
728
     * @param array $params
729
     * @param bool $htmlescape Should the result be escaped for output in HTML?
730
     * @return string
731
     */
732
    protected function _debugSQL($sql, $params, $htmlescape = true) {
733
        foreach($params as $key => $val) {
734
            if(is_int($val)) {
735
                $val = $this->pdo->quote($val, PDO::PARAM_INT);
736
            } elseif(is_bool($val)) {
737
                $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
738
            } elseif(is_null($val)) {
739
                $val = 'NULL';
740
            } else {
741
                $val = $this->pdo->quote($val);
742
            }
743
            $sql = str_replace($key, $val, $sql);
744
        }
745
        if($htmlescape) $sql = hsc($sql);
746
        return $sql;
747
    }
748
}
749
750
// vim:ts=4:sw=4:et:
751