Completed
Push — fix-tests ( 5706b9...88ca24 )
by Henry
05:29 queued 02:49
created

auth_plugin_authpdo::_selectGroups()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 0
dl 0
loc 26
rs 8.8817
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
        if(is_array($result)){
420
            foreach($result as $row) {
421
                if(!isset($row['user'])) {
422
                    $this->_debug("list-users statement did not return 'user' attribute", -1, __LINE__);
423
                    return array();
424
                }
425
                $users[] = $this->getUserData($row['user']);
426
            }
427
        }else{
428
            $this->_debug("list-users statement did not return a list of result", -1, __LINE__);
429
        }
430
        return $users;
431
    }
432
433
    /**
434
     * Return a count of the number of user which meet $filter criteria
435
     *
436
     * @param  array $filter array of field/pattern pairs, empty array for no filter
437
     * @return int
438
     */
439
    public function getUserCount($filter = array()) {
440
        if(is_null($filter)) $filter = array();
441
442
        if(isset($filter['grps'])) $filter['group'] = $filter['grps'];
443
        foreach(array('user', 'name', 'mail', 'group') as $key) {
444
            if(!isset($filter[$key])) {
445
                $filter[$key] = '%';
446
            } else {
447
                $filter[$key] = '%' . $filter[$key] . '%';
448
            }
449
        }
450
451
        $result = $this->_query($this->getConf('count-users'), $filter);
452
        if(!$result || !isset($result[0]['count'])) {
453
            $this->_debug("Statement did not return 'count' attribute", -1, __LINE__);
454
        }
455
        return (int) $result[0]['count'];
456
    }
457
458
    /**
459
     * Create a new group with the given name
460
     *
461
     * @param string $group
462
     * @return bool
463
     */
464
    public function addGroup($group) {
465
        $sql = $this->getConf('insert-group');
466
467
        $result = $this->_query($sql, array(':group' => $group));
468
        $this->_clearGroupCache();
469
        if($result === false) return false;
470
        return true;
471
    }
472
473
    /**
474
     * Retrieve groups
475
     *
476
     * Set getGroups capability when implemented
477
     *
478
     * @param   int $start
479
     * @param   int $limit
480
     * @return  array
481
     */
482
    public function retrieveGroups($start = 0, $limit = 0) {
483
        $groups = array_keys($this->_selectGroups());
484
        if($groups === false) return array();
485
486
        if(!$limit) {
487
            return array_splice($groups, $start);
488
        } else {
489
            return array_splice($groups, $start, $limit);
490
        }
491
    }
492
493
    /**
494
     * Select data of a specified user
495
     *
496
     * @param string $user the user name
497
     * @return bool|array user data, false on error
498
     */
499
    protected function _selectUser($user) {
500
        $sql = $this->getConf('select-user');
501
502
        $result = $this->_query($sql, array(':user' => $user));
503
        if(!$result) return false;
504
505
        if(count($result) > 1) {
506
            $this->_debug('Found more than one matching user', -1, __LINE__);
507
            return false;
508
        }
509
510
        $data = array_shift($result);
511
        $dataok = true;
512
513
        if(!isset($data['user'])) {
514
            $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
515
            $dataok = false;
516
        }
517
        if(!isset($data['hash']) && !isset($data['clear']) && !$this->_chkcnf(array('check-pass'))) {
518
            $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
519
            $dataok = false;
520
        }
521
        if(!isset($data['name'])) {
522
            $this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
523
            $dataok = false;
524
        }
525
        if(!isset($data['mail'])) {
526
            $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
527
            $dataok = false;
528
        }
529
530
        if(!$dataok) return false;
531
        return $data;
532
    }
533
534
    /**
535
     * Delete a user after removing all their group memberships
536
     *
537
     * @param string $user
538
     * @return bool true when the user was deleted
539
     */
540
    protected function _deleteUser($user) {
541
        $this->pdo->beginTransaction();
542
        {
543
            $userdata = $this->getUserData($user);
544
            if($userdata === false) goto FAIL;
545
            $allgroups = $this->_selectGroups();
546
547
            // remove group memberships (ignore errors)
548
            foreach($userdata['grps'] as $group) {
549
                if(isset($allgroups[$group])) {
550
                    $this->_leaveGroup($userdata, $allgroups[$group]);
0 ignored issues
show
Bug introduced by
It seems like $userdata defined by $this->getUserData($user) on line 543 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...
551
                }
552
            }
553
554
            $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 543 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...
555
            if($ok === false) goto FAIL;
556
        }
557
        $this->pdo->commit();
558
        return true;
559
560
        FAIL:
561
        $this->pdo->rollBack();
562
        return false;
563
    }
564
565
    /**
566
     * Select all groups of a user
567
     *
568
     * @param array $userdata The userdata as returned by _selectUser()
569
     * @return array|bool list of group names, false on error
570
     */
571
    protected function _selectUserGroups($userdata) {
572
        global $conf;
573
        $sql = $this->getConf('select-user-groups');
574
        $result = $this->_query($sql, $userdata);
575
        if($result === false) return false;
576
577
        $groups = array($conf['defaultgroup']); // always add default config
578
        if(is_array($result)){
579
            foreach($result as $row) {
580
                if(!isset($row['group'])) {
581
                    $this->_debug("No 'group' field returned in select-user-groups statement");
582
                    return false;
583
                }
584
                $groups[] = $row['group'];
585
            }
586
        }else{
587
            $this->_debug("select-user-groups statement did not return a list of result", -1, __LINE__);
588
        }
589
590
        $groups = array_unique($groups);
591
        sort($groups);
592
        return $groups;
593
    }
594
595
    /**
596
     * Select all available groups
597
     *
598
     * @return array|bool list of all available groups and their properties
599
     */
600
    protected function _selectGroups() {
601
        if($this->groupcache) return $this->groupcache;
602
603
        $sql = $this->getConf('select-groups');
604
        $result = $this->_query($sql);
605
        if($result === false) return false;
606
607
        $groups = array();
608
        if(is_array($result)){
609
            foreach($result as $row) {
610
                if(!isset($row['group'])) {
611
                    $this->_debug("No 'group' field returned from select-groups statement", -1, __LINE__);
612
                    return false;
613
                }
614
615
                // relayout result with group name as key
616
                $group = $row['group'];
617
                $groups[$group] = $row;
618
            }
619
        }else{
620
            $this->_debug("select-groups statement did not return a list of result", -1, __LINE__);
621
        }
622
623
        ksort($groups);
624
        return $groups;
625
    }
626
627
    /**
628
     * Remove all entries from the group cache
629
     */
630
    protected function _clearGroupCache() {
631
        $this->groupcache = null;
632
    }
633
634
    /**
635
     * Adds the user to the group
636
     *
637
     * @param array $userdata all the user data
638
     * @param array $groupdata all the group data
639
     * @return bool
640
     */
641
    protected function _joinGroup($userdata, $groupdata) {
642
        $data = array_merge($userdata, $groupdata);
643
        $sql = $this->getConf('join-group');
644
        $result = $this->_query($sql, $data);
645
        if($result === false) return false;
646
        return true;
647
    }
648
649
    /**
650
     * Removes the user from the group
651
     *
652
     * @param array $userdata all the user data
653
     * @param array $groupdata all the group data
654
     * @return bool
655
     */
656
    protected function _leaveGroup($userdata, $groupdata) {
657
        $data = array_merge($userdata, $groupdata);
658
        $sql = $this->getConf('leave-group');
659
        $result = $this->_query($sql, $data);
660
        if($result === false) return false;
661
        return true;
662
    }
663
664
    /**
665
     * Executes a query
666
     *
667
     * @param string $sql The SQL statement to execute
668
     * @param array $arguments Named parameters to be used in the statement
669
     * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
670
     */
671
    protected function _query($sql, $arguments = array()) {
672
        $sql = trim($sql);
673
        if(empty($sql)) {
674
            $this->_debug('No SQL query given', -1, __LINE__);
675
            return false;
676
        }
677
678
        // execute
679
        $params = array();
680
        $sth = $this->pdo->prepare($sql);
681
        $result = false;
682
        try {
683
            // prepare parameters - we only use those that exist in the SQL
684
            foreach($arguments as $key => $value) {
685
                if(is_array($value)) continue;
686
                if(is_object($value)) continue;
687
                if($key[0] != ':') $key = ":$key"; // prefix with colon if needed
688
                if(strpos($sql, $key) === false) continue; // skip if parameter is missing
689
690
                if(is_int($value)) {
691
                    $sth->bindValue($key, $value, PDO::PARAM_INT);
692
                } else {
693
                    $sth->bindValue($key, $value);
694
                }
695
                $params[$key] = $value; //remember for debugging
696
            }
697
698
            $sth->execute();
699
            // only report last line's result
700
            $hasnextrowset = true;
701
            $currentsql = $sql;
702
            while($hasnextrowset){
703
                if(strtolower(substr($currentsql, 0, 6)) == 'select') {
704
                    $result = $sth->fetchAll();
705
                } else {
706
                    $result = $sth->rowCount();
707
                }
708
                $semi_pos = strpos($currentsql, ';');
709
                if($semi_pos){
710
                    $currentsql = trim(substr($currentsql, $semi_pos+1));
711
                }
712
                try{
713
                    $hasnextrowset = $sth->nextRowset(); // run next rowset
714
                }catch(PDOException $rowset_e){
715
                    $hasnextrowset = false; // driver does not support multi-rowset, should be executed in one time
716
                }
717
            }
718
        } catch(Exception $e) {
719
            // report the caller's line
720
            $trace = debug_backtrace();
721
            $line = $trace[0]['line'];
722
            $dsql = $this->_debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
723
            $this->_debug($e, -1, $line);
724
            $this->_debug("SQL: <pre>$dsql</pre>", -1, $line);
725
        }
726
        $sth->closeCursor();
727
        $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...
728
729
        return $result;
730
    }
731
732
    /**
733
     * Wrapper around msg() but outputs only when debug is enabled
734
     *
735
     * @param string|Exception $message
736
     * @param int $err
737
     * @param int $line
738
     */
739
    protected function _debug($message, $err = 0, $line = 0) {
740
        if(!$this->getConf('debug')) return;
741
        if(is_a($message, 'Exception')) {
742
            $err = -1;
743
            $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...
744
            if(!$line) $line = $message->getLine();
745
        } else {
746
            $msg = $message;
747
        }
748
749
        if(defined('DOKU_UNITTEST')) {
750
            printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
751
        } else {
752
            msg('authpdo: ' . $msg, $err, $line, __FILE__);
753
        }
754
    }
755
756
    /**
757
     * Check if the given config strings are set
758
     *
759
     * @author  Matthias Grimm <[email protected]>
760
     *
761
     * @param   string[] $keys
762
     * @return  bool
763
     */
764
    protected function _chkcnf($keys) {
765
        foreach($keys as $key) {
766
            $params = explode(':', $key);
767
            $key = array_shift($params);
768
            $sql = trim($this->getConf($key));
769
770
            // check if sql is set
771
            if(!$sql) return false;
772
            // check if needed params are there
773
            foreach($params as $param) {
774
                if(strpos($sql, ":$param") === false) return false;
775
            }
776
        }
777
778
        return true;
779
    }
780
781
    /**
782
     * create an approximation of the SQL string with parameters replaced
783
     *
784
     * @param string $sql
785
     * @param array $params
786
     * @param bool $htmlescape Should the result be escaped for output in HTML?
787
     * @return string
788
     */
789
    protected function _debugSQL($sql, $params, $htmlescape = true) {
790
        foreach($params as $key => $val) {
791
            if(is_int($val)) {
792
                $val = $this->pdo->quote($val, PDO::PARAM_INT);
793
            } elseif(is_bool($val)) {
794
                $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
795
            } elseif(is_null($val)) {
796
                $val = 'NULL';
797
            } else {
798
                $val = $this->pdo->quote($val);
799
            }
800
            $sql = str_replace($key, $val, $sql);
801
        }
802
        if($htmlescape) $sql = hsc($sql);
803
        return $sql;
804
    }
805
}
806
807
// vim:ts=4:sw=4:et:
808