Failed Conditions
Push — adminEvent ( 64cdf7 )
by Andreas
23:07 queued 16:19
created

isAccessibleByCurrentUser()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 0
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/*
3
 *  User Manager
4
 *
5
 *  Dokuwiki Admin Plugin
6
 *
7
 *  This version of the user manager has been modified to only work with
8
 *  objectified version of auth system
9
 *
10
 *  @author  neolao <[email protected]>
11
 *  @author  Chris Smith <[email protected]>
12
 */
13
// must be run within Dokuwiki
14
if(!defined('DOKU_INC')) die();
15
16
if(!defined('DOKU_PLUGIN_IMAGES')) define('DOKU_PLUGIN_IMAGES',DOKU_BASE.'lib/plugins/usermanager/images/');
17
18
/**
19
 * All DokuWiki plugins to extend the admin function
20
 * need to inherit from this class
21
 */
22
class admin_plugin_usermanager extends DokuWiki_Admin_Plugin {
23
24
    protected $_auth = null;        // auth object
25
    protected $_user_total = 0;     // number of registered users
26
    protected $_filter = array();   // user selection filter(s)
27
    protected $_start = 0;          // index of first user to be displayed
28
    protected $_last = 0;           // index of the last user to be displayed
29
    protected $_pagesize = 20;      // number of users to list on one page
30
    protected $_edit_user = '';     // set to user selected for editing
31
    protected $_edit_userdata = array();
32
    protected $_disabled = '';      // if disabled set to explanatory string
33
    protected $_import_failures = array();
34
    protected $_lastdisabled = false; // set to true if last user is unknown and last button is hence buggy
35
36
    /**
37
     * Constructor
38
     */
39
    public function __construct(){
40
        /** @var DokuWiki_Auth_Plugin $auth */
41
        global $auth;
42
43
        $this->setupLocale();
44
45
        if (!isset($auth)) {
46
            $this->_disabled = $this->lang['noauth'];
47
        } else if (!$auth->canDo('getUsers')) {
48
            $this->_disabled = $this->lang['nosupport'];
49
        } else {
50
51
            // we're good to go
52
            $this->_auth = & $auth;
53
54
        }
55
56
        // attempt to retrieve any import failures from the session
57
        if (!empty($_SESSION['import_failures'])){
58
            $this->_import_failures = $_SESSION['import_failures'];
59
        }
60
    }
61
62
    /**
63
     * Return prompt for admin menu
64
     *
65
     * @param string $language
66
     * @return string
67
     */
68
    public function getMenuText($language) {
69
70
        if (!is_null($this->_auth))
71
          return parent::getMenuText($language);
72
73
        return $this->getLang('menu').' '.$this->_disabled;
74
    }
75
76
    /**
77
     * return sort order for position in admin menu
78
     *
79
     * @return int
80
     */
81
    public function getMenuSort() {
82
        return 2;
83
    }
84
85
    /**
86
     * @return int current start value for pageination
87
     */
88
    public function getStart() {
89
        return $this->_start;
90
    }
91
92
    /**
93
     * @return int number of users per page
94
     */
95
    public function getPagesize() {
96
        return $this->_pagesize;
97
    }
98
99
    /**
100
     * @param boolean $lastdisabled
101
     */
102
    public function setLastdisabled($lastdisabled) {
103
        $this->_lastdisabled = $lastdisabled;
104
    }
105
106
    /**
107
     * Handle user request
108
     *
109
     * @return bool
110
     */
111
    public function handle() {
112
        global $INPUT;
113
        if (is_null($this->_auth)) return false;
114
115
        // extract the command and any specific parameters
116
        // submit button name is of the form - fn[cmd][param(s)]
117
        $fn   = $INPUT->param('fn');
118
119
        if (is_array($fn)) {
120
            $cmd = key($fn);
121
            $param = is_array($fn[$cmd]) ? key($fn[$cmd]) : null;
122
        } else {
123
            $cmd = $fn;
124
            $param = null;
125
        }
126
127
        if ($cmd != "search") {
128
            $this->_start = $INPUT->int('start', 0);
129
            $this->_filter = $this->_retrieveFilter();
130
        }
131
132
        switch($cmd){
133
            case "add"    : $this->_addUser(); break;
134
            case "delete" : $this->_deleteUser(); break;
135
            case "modify" : $this->_modifyUser(); break;
136
            case "edit"   : $this->_editUser($param); break;
137
            case "search" : $this->_setFilter($param);
138
                            $this->_start = 0;
139
                            break;
140
            case "export" : $this->_export(); break;
141
            case "import" : $this->_import(); break;
142
            case "importfails" : $this->_downloadImportFailures(); break;
143
        }
144
145
        $this->_user_total = $this->_auth->canDo('getUserCount') ? $this->_auth->getUserCount($this->_filter) : -1;
146
147
        // page handling
148
        switch($cmd){
149
            case 'start' : $this->_start = 0; break;
150
            case 'prev'  : $this->_start -= $this->_pagesize; break;
151
            case 'next'  : $this->_start += $this->_pagesize; break;
152
            case 'last'  : $this->_start = $this->_user_total; break;
153
        }
154
        $this->_validatePagination();
155
        return true;
156
    }
157
158
    /**
159
     * Output appropriate html
160
     *
161
     * @return bool
162
     */
163
    public function html() {
164
        global $ID;
165
166
        if(is_null($this->_auth)) {
167
            print $this->lang['badauth'];
168
            return false;
169
        }
170
171
        $user_list = $this->_auth->retrieveUsers($this->_start, $this->_pagesize, $this->_filter);
172
173
        $page_buttons = $this->_pagination();
174
        $delete_disable = $this->_auth->canDo('delUser') ? '' : 'disabled="disabled"';
175
176
        $editable = $this->_auth->canDo('UserMod');
177
        $export_label = empty($this->_filter) ? $this->lang['export_all'] : $this->lang['export_filtered'];
178
179
        print $this->locale_xhtml('intro');
180
        print $this->locale_xhtml('list');
181
182
        ptln("<div id=\"user__manager\">");
183
        ptln("<div class=\"level2\">");
184
185
        if ($this->_user_total > 0) {
186
            ptln("<p>".sprintf($this->lang['summary'],$this->_start+1,$this->_last,$this->_user_total,$this->_auth->getUserCount())."</p>");
187
        } else {
188
            if($this->_user_total < 0) {
189
                $allUserTotal = 0;
190
            } else {
191
                $allUserTotal = $this->_auth->getUserCount();
192
            }
193
            ptln("<p>".sprintf($this->lang['nonefound'], $allUserTotal)."</p>");
194
        }
195
        ptln("<form action=\"".wl($ID)."\" method=\"post\">");
196
        formSecurityToken();
197
        ptln("  <div class=\"table\">");
198
        ptln("  <table class=\"inline\">");
199
        ptln("    <thead>");
200
        ptln("      <tr>");
201
        ptln("        <th>&#160;</th><th>".$this->lang["user_id"]."</th><th>".$this->lang["user_name"]."</th><th>".$this->lang["user_mail"]."</th><th>".$this->lang["user_groups"]."</th>");
202
        ptln("      </tr>");
203
204
        ptln("      <tr>");
205
        ptln("        <td class=\"rightalign\"><input type=\"image\" src=\"".DOKU_PLUGIN_IMAGES."search.png\" name=\"fn[search][new]\" title=\"".$this->lang['search_prompt']."\" alt=\"".$this->lang['search']."\" class=\"button\" /></td>");
206
        ptln("        <td><input type=\"text\" name=\"userid\" class=\"edit\" value=\"".$this->_htmlFilter('user')."\" /></td>");
207
        ptln("        <td><input type=\"text\" name=\"username\" class=\"edit\" value=\"".$this->_htmlFilter('name')."\" /></td>");
208
        ptln("        <td><input type=\"text\" name=\"usermail\" class=\"edit\" value=\"".$this->_htmlFilter('mail')."\" /></td>");
209
        ptln("        <td><input type=\"text\" name=\"usergroups\" class=\"edit\" value=\"".$this->_htmlFilter('grps')."\" /></td>");
210
        ptln("      </tr>");
211
        ptln("    </thead>");
212
213
        if ($this->_user_total) {
214
            ptln("    <tbody>");
215
            foreach ($user_list as $user => $userinfo) {
216
                extract($userinfo);
217
                /**
218
                 * @var string $name
219
                 * @var string $pass
220
                 * @var string $mail
221
                 * @var array  $grps
222
                 */
223
                $groups = join(', ',$grps);
224
                ptln("    <tr class=\"user_info\">");
225
                ptln("      <td class=\"centeralign\"><input type=\"checkbox\" name=\"delete[".hsc($user)."]\" ".$delete_disable." /></td>");
226
                if ($editable) {
227
                    ptln("    <td><a href=\"".wl($ID,array('fn[edit]['.$user.']' => 1,
228
                                                           'do' => 'admin',
229
                                                           'page' => 'usermanager',
230
                                                           'sectok' => getSecurityToken())).
231
                         "\" title=\"".$this->lang['edit_prompt']."\">".hsc($user)."</a></td>");
232
                } else {
233
                    ptln("    <td>".hsc($user)."</td>");
234
                }
235
                ptln("      <td>".hsc($name)."</td><td>".hsc($mail)."</td><td>".hsc($groups)."</td>");
236
                ptln("    </tr>");
237
            }
238
            ptln("    </tbody>");
239
        }
240
241
        ptln("    <tbody>");
242
        ptln("      <tr><td colspan=\"5\" class=\"centeralign\">");
243
        ptln("        <span class=\"medialeft\">");
244
        ptln("          <button type=\"submit\" name=\"fn[delete]\" id=\"usrmgr__del\" ".$delete_disable.">".$this->lang['delete_selected']."</button>");
245
        ptln("        </span>");
246
        ptln("        <span class=\"mediaright\">");
247
        ptln("          <button type=\"submit\" name=\"fn[start]\" ".$page_buttons['start'].">".$this->lang['start']."</button>");
248
        ptln("          <button type=\"submit\" name=\"fn[prev]\" ".$page_buttons['prev'].">".$this->lang['prev']."</button>");
249
        ptln("          <button type=\"submit\" name=\"fn[next]\" ".$page_buttons['next'].">".$this->lang['next']."</button>");
250
        ptln("          <button type=\"submit\" name=\"fn[last]\" ".$page_buttons['last'].">".$this->lang['last']."</button>");
251
        ptln("        </span>");
252
        if (!empty($this->_filter)) {
253
            ptln("    <button type=\"submit\" name=\"fn[search][clear]\">".$this->lang['clear']."</button>");
254
        }
255
        ptln("        <button type=\"submit\" name=\"fn[export]\">".$export_label."</button>");
256
        ptln("        <input type=\"hidden\" name=\"do\"    value=\"admin\" />");
257
        ptln("        <input type=\"hidden\" name=\"page\"  value=\"usermanager\" />");
258
259
        $this->_htmlFilterSettings(2);
260
261
        ptln("      </td></tr>");
262
        ptln("    </tbody>");
263
        ptln("  </table>");
264
        ptln("  </div>");
265
266
        ptln("</form>");
267
        ptln("</div>");
268
269
        $style = $this->_edit_user ? " class=\"edit_user\"" : "";
270
271
        if ($this->_auth->canDo('addUser')) {
272
            ptln("<div".$style.">");
273
            print $this->locale_xhtml('add');
274
            ptln("  <div class=\"level2\">");
275
276
            $this->_htmlUserForm('add',null,array(),4);
277
278
            ptln("  </div>");
279
            ptln("</div>");
280
        }
281
282
        if($this->_edit_user  && $this->_auth->canDo('UserMod')){
283
            ptln("<div".$style." id=\"scroll__here\">");
284
            print $this->locale_xhtml('edit');
285
            ptln("  <div class=\"level2\">");
286
287
            $this->_htmlUserForm('modify',$this->_edit_user,$this->_edit_userdata,4);
288
289
            ptln("  </div>");
290
            ptln("</div>");
291
        }
292
293
        if ($this->_auth->canDo('addUser')) {
294
            $this->_htmlImportForm();
295
        }
296
        ptln("</div>");
297
        return true;
298
    }
299
300
    /**
301
     * User Manager is only available if the auth backend supports it
302
     *
303
     * @inheritdoc
304
     * @return bool
305
     */
306
    public function isAccessibleByCurrentUser()
307
    {
308
        /** @var DokuWiki_Auth_Plugin $auth */
309
        global $auth;
310
        if(!$auth || !$auth->canDo('getUsers') ) {
311
            return false;
312
        }
313
314
        return parent::isAccessibleByCurrentUser();
315
    }
316
317
318
    /**
319
     * Display form to add or modify a user
320
     *
321
     * @param string $cmd 'add' or 'modify'
322
     * @param string $user id of user
323
     * @param array  $userdata array with name, mail, pass and grps
324
     * @param int    $indent
325
     */
326
    protected function _htmlUserForm($cmd,$user='',$userdata=array(),$indent=0) {
327
        global $conf;
328
        global $ID;
329
        global $lang;
330
331
        $name = $mail = $groups = '';
332
        $notes = array();
333
334
        if ($user) {
335
            extract($userdata);
336
            if (!empty($grps)) $groups = join(',',$grps);
337
        } else {
338
            $notes[] = sprintf($this->lang['note_group'],$conf['defaultgroup']);
339
        }
340
341
        ptln("<form action=\"".wl($ID)."\" method=\"post\">",$indent);
342
        formSecurityToken();
343
        ptln("  <div class=\"table\">",$indent);
344
        ptln("  <table class=\"inline\">",$indent);
345
        ptln("    <thead>",$indent);
346
        ptln("      <tr><th>".$this->lang["field"]."</th><th>".$this->lang["value"]."</th></tr>",$indent);
347
        ptln("    </thead>",$indent);
348
        ptln("    <tbody>",$indent);
349
350
        $this->_htmlInputField($cmd."_userid",    "userid",    $this->lang["user_id"],    $user,  $this->_auth->canDo("modLogin"),   true, $indent+6);
351
        $this->_htmlInputField($cmd."_userpass",  "userpass",  $this->lang["user_pass"],  "",     $this->_auth->canDo("modPass"),   false, $indent+6);
352
        $this->_htmlInputField($cmd."_userpass2", "userpass2", $lang["passchk"],          "",     $this->_auth->canDo("modPass"),   false, $indent+6);
353
        $this->_htmlInputField($cmd."_username",  "username",  $this->lang["user_name"],  $name,  $this->_auth->canDo("modName"),    true, $indent+6);
354
        $this->_htmlInputField($cmd."_usermail",  "usermail",  $this->lang["user_mail"],  $mail,  $this->_auth->canDo("modMail"),    true, $indent+6);
355
        $this->_htmlInputField($cmd."_usergroups","usergroups",$this->lang["user_groups"],$groups,$this->_auth->canDo("modGroups"), false, $indent+6);
356
357
        if ($this->_auth->canDo("modPass")) {
358
            if ($cmd == 'add') {
359
                $notes[] = $this->lang['note_pass'];
360
            }
361
            if ($user) {
362
                $notes[] = $this->lang['note_notify'];
363
            }
364
365
            ptln("<tr><td><label for=\"".$cmd."_usernotify\" >".$this->lang["user_notify"].": </label></td><td><input type=\"checkbox\" id=\"".$cmd."_usernotify\" name=\"usernotify\" value=\"1\" /></td></tr>", $indent);
366
        }
367
368
        ptln("    </tbody>",$indent);
369
        ptln("    <tbody>",$indent);
370
        ptln("      <tr>",$indent);
371
        ptln("        <td colspan=\"2\">",$indent);
372
        ptln("          <input type=\"hidden\" name=\"do\"    value=\"admin\" />",$indent);
373
        ptln("          <input type=\"hidden\" name=\"page\"  value=\"usermanager\" />",$indent);
374
375
        // save current $user, we need this to access details if the name is changed
376
        if ($user)
377
          ptln("          <input type=\"hidden\" name=\"userid_old\"  value=\"".hsc($user)."\" />",$indent);
378
379
        $this->_htmlFilterSettings($indent+10);
380
381
        ptln("          <button type=\"submit\" name=\"fn[".$cmd."]\">".$this->lang[$cmd]."</button>",$indent);
382
        ptln("        </td>",$indent);
383
        ptln("      </tr>",$indent);
384
        ptln("    </tbody>",$indent);
385
        ptln("  </table>",$indent);
386
387
        if ($notes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $notes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
388
            ptln("    <ul class=\"notes\">");
389
            foreach ($notes as $note) {
390
                ptln("      <li><span class=\"li\">".$note."</li>",$indent);
391
            }
392
            ptln("    </ul>");
393
        }
394
        ptln("  </div>",$indent);
395
        ptln("</form>",$indent);
396
    }
397
398
    /**
399
     * Prints a inputfield
400
     *
401
     * @param string $id
402
     * @param string $name
403
     * @param string $label
404
     * @param string $value
405
     * @param bool   $cando whether auth backend is capable to do this action
406
     * @param bool   $required is this field required?
407
     * @param int $indent
408
     */
409
    protected function _htmlInputField($id, $name, $label, $value, $cando, $required, $indent=0) {
410
        $class = $cando ? '' : ' class="disabled"';
411
        echo str_pad('',$indent);
412
413
        if($name == 'userpass' || $name == 'userpass2'){
414
            $fieldtype = 'password';
415
            $autocomp  = 'autocomplete="off"';
416
        }elseif($name == 'usermail'){
417
            $fieldtype = 'email';
418
            $autocomp  = '';
419
        }else{
420
            $fieldtype = 'text';
421
            $autocomp  = '';
422
        }
423
        $value = hsc($value);
424
425
        echo "<tr $class>";
426
        echo "<td><label for=\"$id\" >$label: </label></td>";
427
        echo "<td>";
428
        if($cando){
429
            $req = '';
430
            if($required) $req = 'required="required"';
431
            echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit\" $autocomp $req />";
432
        }else{
433
            echo "<input type=\"hidden\" name=\"$name\" value=\"$value\" />";
434
            echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit disabled\" disabled=\"disabled\" />";
435
        }
436
        echo "</td>";
437
        echo "</tr>";
438
    }
439
440
    /**
441
     * Returns htmlescaped filter value
442
     *
443
     * @param string $key name of search field
444
     * @return string html escaped value
445
     */
446
    protected function _htmlFilter($key) {
447
        if (empty($this->_filter)) return '';
448
        return (isset($this->_filter[$key]) ? hsc($this->_filter[$key]) : '');
449
    }
450
451
    /**
452
     * Print hidden inputs with the current filter values
453
     *
454
     * @param int $indent
455
     */
456
    protected function _htmlFilterSettings($indent=0) {
457
458
        ptln("<input type=\"hidden\" name=\"start\" value=\"".$this->_start."\" />",$indent);
459
460
        foreach ($this->_filter as $key => $filter) {
461
            ptln("<input type=\"hidden\" name=\"filter[".$key."]\" value=\"".hsc($filter)."\" />",$indent);
462
        }
463
    }
464
465
    /**
466
     * Print import form and summary of previous import
467
     *
468
     * @param int $indent
469
     */
470
    protected function _htmlImportForm($indent=0) {
471
        global $ID;
472
473
        $failure_download_link = wl($ID,array('do'=>'admin','page'=>'usermanager','fn[importfails]'=>1));
474
475
        ptln('<div class="level2 import_users">',$indent);
476
        print $this->locale_xhtml('import');
477
        ptln('  <form action="'.wl($ID).'" method="post" enctype="multipart/form-data">',$indent);
478
        formSecurityToken();
479
        ptln('    <label>'.$this->lang['import_userlistcsv'].'<input type="file" name="import" /></label>',$indent);
480
        ptln('    <button type="submit" name="fn[import]">'.$this->lang['import'].'</button>',$indent);
481
        ptln('    <input type="hidden" name="do"    value="admin" />',$indent);
482
        ptln('    <input type="hidden" name="page"  value="usermanager" />',$indent);
483
484
        $this->_htmlFilterSettings($indent+4);
485
        ptln('  </form>',$indent);
486
        ptln('</div>');
487
488
        // list failures from the previous import
489
        if ($this->_import_failures) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_import_failures of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
490
            $digits = strlen(count($this->_import_failures));
491
            ptln('<div class="level3 import_failures">',$indent);
492
            ptln('  <h3>'.$this->lang['import_header'].'</h3>');
493
            ptln('  <table class="import_failures">',$indent);
494
            ptln('    <thead>',$indent);
495
            ptln('      <tr>',$indent);
496
            ptln('        <th class="line">'.$this->lang['line'].'</th>',$indent);
497
            ptln('        <th class="error">'.$this->lang['error'].'</th>',$indent);
498
            ptln('        <th class="userid">'.$this->lang['user_id'].'</th>',$indent);
499
            ptln('        <th class="username">'.$this->lang['user_name'].'</th>',$indent);
500
            ptln('        <th class="usermail">'.$this->lang['user_mail'].'</th>',$indent);
501
            ptln('        <th class="usergroups">'.$this->lang['user_groups'].'</th>',$indent);
502
            ptln('      </tr>',$indent);
503
            ptln('    </thead>',$indent);
504
            ptln('    <tbody>',$indent);
505
            foreach ($this->_import_failures as $line => $failure) {
506
                ptln('      <tr>',$indent);
507
                ptln('        <td class="lineno"> '.sprintf('%0'.$digits.'d',$line).' </td>',$indent);
508
                ptln('        <td class="error">' .$failure['error'].' </td>', $indent);
509
                ptln('        <td class="field userid"> '.hsc($failure['user'][0]).' </td>',$indent);
510
                ptln('        <td class="field username"> '.hsc($failure['user'][2]).' </td>',$indent);
511
                ptln('        <td class="field usermail"> '.hsc($failure['user'][3]).' </td>',$indent);
512
                ptln('        <td class="field usergroups"> '.hsc($failure['user'][4]).' </td>',$indent);
513
                ptln('      </tr>',$indent);
514
            }
515
            ptln('    </tbody>',$indent);
516
            ptln('  </table>',$indent);
517
            ptln('  <p><a href="'.$failure_download_link.'">'.$this->lang['import_downloadfailures'].'</a></p>');
518
            ptln('</div>');
519
        }
520
521
    }
522
523
    /**
524
     * Add an user to auth backend
525
     *
526
     * @return bool whether succesful
527
     */
528
    protected function _addUser(){
529
        global $INPUT;
530
        if (!checkSecurityToken()) return false;
531
        if (!$this->_auth->canDo('addUser')) return false;
532
533
        list($user,$pass,$name,$mail,$grps,$passconfirm) = $this->_retrieveUser();
534
        if (empty($user)) return false;
535
536
        if ($this->_auth->canDo('modPass')){
537
            if (empty($pass)){
538
                if($INPUT->has('usernotify')){
539
                    $pass = auth_pwgen($user);
540
                } else {
541
                    msg($this->lang['add_fail'], -1);
542
                    msg($this->lang['addUser_error_missing_pass'], -1);
543
                    return false;
544
                }
545
            } else {
546
                if (!$this->_verifyPassword($pass,$passconfirm)) {
547
                    msg($this->lang['add_fail'], -1);
548
                    msg($this->lang['addUser_error_pass_not_identical'], -1);
549
                    return false;
550
                }
551
            }
552
        } else {
553
            if (!empty($pass)){
554
                msg($this->lang['add_fail'], -1);
555
                msg($this->lang['addUser_error_modPass_disabled'], -1);
556
                return false;
557
            }
558
        }
559
560
        if ($this->_auth->canDo('modName')){
561
            if (empty($name)){
562
                msg($this->lang['add_fail'], -1);
563
                msg($this->lang['addUser_error_name_missing'], -1);
564
                return false;
565
            }
566
        } else {
567
            if (!empty($name)){
568
                msg($this->lang['add_fail'], -1);
569
                msg($this->lang['addUser_error_modName_disabled'], -1);
570
                return false;
571
            }
572
        }
573
574
        if ($this->_auth->canDo('modMail')){
575
            if (empty($mail)){
576
                msg($this->lang['add_fail'], -1);
577
                msg($this->lang['addUser_error_mail_missing'], -1);
578
                return false;
579
            }
580
        } else {
581
            if (!empty($mail)){
582
                msg($this->lang['add_fail'], -1);
583
                msg($this->lang['addUser_error_modMail_disabled'], -1);
584
                return false;
585
            }
586
        }
587
588
        if ($ok = $this->_auth->triggerUserMod('create', array($user,$pass,$name,$mail,$grps))) {
589
590
            msg($this->lang['add_ok'], 1);
591
592
            if ($INPUT->has('usernotify') && $pass) {
593
                $this->_notifyUser($user,$pass);
594
            }
595
        } else {
596
            msg($this->lang['add_fail'], -1);
597
            msg($this->lang['addUser_error_create_event_failed'], -1);
598
        }
599
600
        return $ok;
601
    }
602
603
    /**
604
     * Delete user from auth backend
605
     *
606
     * @return bool whether succesful
607
     */
608
    protected function _deleteUser(){
609
        global $conf, $INPUT;
610
611
        if (!checkSecurityToken()) return false;
612
        if (!$this->_auth->canDo('delUser')) return false;
613
614
        $selected = $INPUT->arr('delete');
615
        if (empty($selected)) return false;
616
        $selected = array_keys($selected);
617
618
        if(in_array($_SERVER['REMOTE_USER'], $selected)) {
619
            msg("You can't delete yourself!", -1);
620
            return false;
621
        }
622
623
        $count = $this->_auth->triggerUserMod('delete', array($selected));
624
        if ($count == count($selected)) {
625
            $text = str_replace('%d', $count, $this->lang['delete_ok']);
626
            msg("$text.", 1);
627
        } else {
628
            $part1 = str_replace('%d', $count, $this->lang['delete_ok']);
629
            $part2 = str_replace('%d', (count($selected)-$count), $this->lang['delete_fail']);
630
            msg("$part1, $part2",-1);
631
        }
632
633
        // invalidate all sessions
634
        io_saveFile($conf['cachedir'].'/sessionpurge',time());
635
636
        return true;
637
    }
638
639
    /**
640
     * Edit user (a user has been selected for editing)
641
     *
642
     * @param string $param id of the user
643
     * @return bool whether succesful
644
     */
645
    protected function _editUser($param) {
646
        if (!checkSecurityToken()) return false;
647
        if (!$this->_auth->canDo('UserMod')) return false;
648
        $user = $this->_auth->cleanUser(preg_replace('/.*[:\/]/','',$param));
649
        $userdata = $this->_auth->getUserData($user);
650
651
        // no user found?
652
        if (!$userdata) {
653
            msg($this->lang['edit_usermissing'],-1);
654
            return false;
655
        }
656
657
        $this->_edit_user = $user;
658
        $this->_edit_userdata = $userdata;
659
660
        return true;
661
    }
662
663
    /**
664
     * Modify user in the auth backend (modified user data has been recieved)
665
     *
666
     * @return bool whether succesful
667
     */
668
    protected function _modifyUser(){
669
        global $conf, $INPUT;
670
671
        if (!checkSecurityToken()) return false;
672
        if (!$this->_auth->canDo('UserMod')) return false;
673
674
        // get currently valid  user data
675
        $olduser = $this->_auth->cleanUser(preg_replace('/.*[:\/]/','',$INPUT->str('userid_old')));
676
        $oldinfo = $this->_auth->getUserData($olduser);
677
678
        // get new user data subject to change
679
        list($newuser,$newpass,$newname,$newmail,$newgrps,$passconfirm) = $this->_retrieveUser();
680
        if (empty($newuser)) return false;
681
682
        $changes = array();
683
        if ($newuser != $olduser) {
684
685
            if (!$this->_auth->canDo('modLogin')) {        // sanity check, shouldn't be possible
686
                msg($this->lang['update_fail'],-1);
687
                return false;
688
            }
689
690
            // check if $newuser already exists
691
            if ($this->_auth->getUserData($newuser)) {
692
                msg(sprintf($this->lang['update_exists'],$newuser),-1);
693
                $re_edit = true;
694
            } else {
695
                $changes['user'] = $newuser;
696
            }
697
        }
698
        if ($this->_auth->canDo('modPass')) {
699
            if ($newpass || $passconfirm) {
700
                if ($this->_verifyPassword($newpass,$passconfirm)) {
701
                    $changes['pass'] = $newpass;
702
                } else {
703
                    return false;
704
                }
705
            } else {
706
                // no new password supplied, check if we need to generate one (or it stays unchanged)
707
                if ($INPUT->has('usernotify')) {
708
                    $changes['pass'] = auth_pwgen($olduser);
709
                }
710
            }
711
        }
712
713
        if (!empty($newname) && $this->_auth->canDo('modName') && $newname != $oldinfo['name']) {
714
            $changes['name'] = $newname;
715
        }
716
        if (!empty($newmail) && $this->_auth->canDo('modMail') && $newmail != $oldinfo['mail']) {
717
            $changes['mail'] = $newmail;
718
        }
719
        if (!empty($newgrps) && $this->_auth->canDo('modGroups') && $newgrps != $oldinfo['grps']) {
720
            $changes['grps'] = $newgrps;
721
        }
722
723
        if ($ok = $this->_auth->triggerUserMod('modify', array($olduser, $changes))) {
724
            msg($this->lang['update_ok'],1);
725
726
            if ($INPUT->has('usernotify') && !empty($changes['pass'])) {
727
                $notify = empty($changes['user']) ? $olduser : $newuser;
728
                $this->_notifyUser($notify,$changes['pass']);
729
            }
730
731
            // invalidate all sessions
732
            io_saveFile($conf['cachedir'].'/sessionpurge',time());
733
734
        } else {
735
            msg($this->lang['update_fail'],-1);
736
        }
737
738
        if (!empty($re_edit)) {
739
            $this->_editUser($olduser);
740
        }
741
742
        return $ok;
743
    }
744
745
    /**
746
     * Send password change notification email
747
     *
748
     * @param string $user         id of user
749
     * @param string $password     plain text
750
     * @param bool   $status_alert whether status alert should be shown
751
     * @return bool whether succesful
752
     */
753
    protected function _notifyUser($user, $password, $status_alert=true) {
754
755
        if ($sent = auth_sendPassword($user,$password)) {
756
            if ($status_alert) {
757
                msg($this->lang['notify_ok'], 1);
758
            }
759
        } else {
760
            if ($status_alert) {
761
                msg($this->lang['notify_fail'], -1);
762
            }
763
        }
764
765
        return $sent;
766
    }
767
768
    /**
769
     * Verify password meets minimum requirements
770
     * :TODO: extend to support password strength
771
     *
772
     * @param string  $password   candidate string for new password
773
     * @param string  $confirm    repeated password for confirmation
774
     * @return bool   true if meets requirements, false otherwise
775
     */
776
    protected function _verifyPassword($password, $confirm) {
777
        global $lang;
778
779
        if (empty($password) && empty($confirm)) {
780
            return false;
781
        }
782
783
        if ($password !== $confirm) {
784
            msg($lang['regbadpass'], -1);
785
            return false;
786
        }
787
788
        // :TODO: test password for required strength
789
790
        // if we make it this far the password is good
791
        return true;
792
    }
793
794
    /**
795
     * Retrieve & clean user data from the form
796
     *
797
     * @param bool $clean whether the cleanUser method of the authentication backend is applied
798
     * @return array (user, password, full name, email, array(groups))
799
     */
800
    protected function _retrieveUser($clean=true) {
801
        /** @var DokuWiki_Auth_Plugin $auth */
802
        global $auth;
803
        global $INPUT;
804
805
        $user = array();
806
        $user[0] = ($clean) ? $auth->cleanUser($INPUT->str('userid')) : $INPUT->str('userid');
807
        $user[1] = $INPUT->str('userpass');
808
        $user[2] = $INPUT->str('username');
809
        $user[3] = $INPUT->str('usermail');
810
        $user[4] = explode(',',$INPUT->str('usergroups'));
811
        $user[5] = $INPUT->str('userpass2');                // repeated password for confirmation
812
813
        $user[4] = array_map('trim',$user[4]);
814
        if($clean) $user[4] = array_map(array($auth,'cleanGroup'),$user[4]);
815
        $user[4] = array_filter($user[4]);
816
        $user[4] = array_unique($user[4]);
817
        if(!count($user[4])) $user[4] = null;
818
819
        return $user;
820
    }
821
822
    /**
823
     * Set the filter with the current search terms or clear the filter
824
     *
825
     * @param string $op 'new' or 'clear'
826
     */
827
    protected function _setFilter($op) {
828
829
        $this->_filter = array();
830
831
        if ($op == 'new') {
832
            list($user,/* $pass */,$name,$mail,$grps) = $this->_retrieveUser(false);
833
834
            if (!empty($user)) $this->_filter['user'] = $user;
835
            if (!empty($name)) $this->_filter['name'] = $name;
836
            if (!empty($mail)) $this->_filter['mail'] = $mail;
837
            if (!empty($grps)) $this->_filter['grps'] = join('|',$grps);
838
        }
839
    }
840
841
    /**
842
     * Get the current search terms
843
     *
844
     * @return array
845
     */
846
    protected function _retrieveFilter() {
847
        global $INPUT;
848
849
        $t_filter = $INPUT->arr('filter');
850
851
        // messy, but this way we ensure we aren't getting any additional crap from malicious users
852
        $filter = array();
853
854
        if (isset($t_filter['user'])) $filter['user'] = $t_filter['user'];
855
        if (isset($t_filter['name'])) $filter['name'] = $t_filter['name'];
856
        if (isset($t_filter['mail'])) $filter['mail'] = $t_filter['mail'];
857
        if (isset($t_filter['grps'])) $filter['grps'] = $t_filter['grps'];
858
859
        return $filter;
860
    }
861
862
    /**
863
     * Validate and improve the pagination values
864
     */
865
    protected function _validatePagination() {
866
867
        if ($this->_start >= $this->_user_total) {
868
            $this->_start = $this->_user_total - $this->_pagesize;
869
        }
870
        if ($this->_start < 0) $this->_start = 0;
871
872
        $this->_last = min($this->_user_total, $this->_start + $this->_pagesize);
873
    }
874
875
    /**
876
     * Return an array of strings to enable/disable pagination buttons
877
     *
878
     * @return array with enable/disable attributes
879
     */
880
    protected function _pagination() {
881
882
        $disabled = 'disabled="disabled"';
883
884
        $buttons = array();
885
        $buttons['start'] = $buttons['prev'] = ($this->_start == 0) ? $disabled : '';
886
887
        if ($this->_user_total == -1) {
888
            $buttons['last'] = $disabled;
889
            $buttons['next'] = '';
890
        } else {
891
            $buttons['last'] = $buttons['next'] = (($this->_start + $this->_pagesize) >= $this->_user_total) ? $disabled : '';
892
        }
893
894
        if ($this->_lastdisabled) {
895
            $buttons['last'] = $disabled;
896
        }
897
898
        return $buttons;
899
    }
900
901
    /**
902
     * Export a list of users in csv format using the current filter criteria
903
     */
904
    protected function _export() {
905
        // list of users for export - based on current filter criteria
906
        $user_list = $this->_auth->retrieveUsers(0, 0, $this->_filter);
907
        $column_headings = array(
908
            $this->lang["user_id"],
909
            $this->lang["user_name"],
910
            $this->lang["user_mail"],
911
            $this->lang["user_groups"]
912
        );
913
914
        // ==============================================================================================
915
        // GENERATE OUTPUT
916
        // normal headers for downloading...
917
        header('Content-type: text/csv;charset=utf-8');
918
        header('Content-Disposition: attachment; filename="wikiusers.csv"');
919
#       // for debugging assistance, send as text plain to the browser
920
#       header('Content-type: text/plain;charset=utf-8');
921
922
        // output the csv
923
        $fd = fopen('php://output','w');
924
        fputcsv($fd, $column_headings);
925
        foreach ($user_list as $user => $info) {
926
            $line = array($user, $info['name'], $info['mail'], join(',',$info['grps']));
927
            fputcsv($fd, $line);
928
        }
929
        fclose($fd);
930
        if (defined('DOKU_UNITTEST')){ return; }
931
932
        die;
933
    }
934
935
    /**
936
     * Import a file of users in csv format
937
     *
938
     * csv file should have 4 columns, user_id, full name, email, groups (comma separated)
939
     *
940
     * @return bool whether successful
941
     */
942
    protected function _import() {
943
        // check we are allowed to add users
944
        if (!checkSecurityToken()) return false;
945
        if (!$this->_auth->canDo('addUser')) return false;
946
947
        // check file uploaded ok.
948
        if (empty($_FILES['import']['size']) || !empty($_FILES['import']['error']) && $this->_isUploadedFile($_FILES['import']['tmp_name'])) {
949
            msg($this->lang['import_error_upload'],-1);
950
            return false;
951
        }
952
        // retrieve users from the file
953
        $this->_import_failures = array();
954
        $import_success_count = 0;
955
        $import_fail_count = 0;
956
        $line = 0;
957
        $fd = fopen($_FILES['import']['tmp_name'],'r');
958
        if ($fd) {
959
            while($csv = fgets($fd)){
960
                if (!utf8_check($csv)) {
961
                    $csv = utf8_encode($csv);
962
                }
963
                $raw = str_getcsv($csv);
964
                $error = '';                        // clean out any errors from the previous line
965
                // data checks...
966
                if (1 == ++$line) {
967
                    if ($raw[0] == 'user_id' || $raw[0] == $this->lang['user_id']) continue;    // skip headers
968
                }
969
                if (count($raw) < 4) {                                        // need at least four fields
970
                    $import_fail_count++;
971
                    $error = sprintf($this->lang['import_error_fields'], count($raw));
972
                    $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv);
973
                    continue;
974
                }
975
                array_splice($raw,1,0,auth_pwgen());                          // splice in a generated password
976
                $clean = $this->_cleanImportUser($raw, $error);
977
                if ($clean && $this->_addImportUser($clean, $error)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $clean of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
978
                    $sent = $this->_notifyUser($clean[0],$clean[1],false);
979
                    if (!$sent){
980
                        msg(sprintf($this->lang['import_notify_fail'],$clean[0],$clean[3]),-1);
981
                    }
982
                    $import_success_count++;
983
                } else {
984
                    $import_fail_count++;
985
                    array_splice($raw, 1, 1);                                  // remove the spliced in password
986
                    $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv);
987
                }
988
            }
989
            msg(sprintf($this->lang['import_success_count'], ($import_success_count+$import_fail_count), $import_success_count),($import_success_count ? 1 : -1));
990
            if ($import_fail_count) {
991
                msg(sprintf($this->lang['import_failure_count'], $import_fail_count),-1);
992
            }
993
        } else {
994
            msg($this->lang['import_error_readfail'],-1);
995
        }
996
997
        // save import failures into the session
998
        if (!headers_sent()) {
999
            session_start();
1000
            $_SESSION['import_failures'] = $this->_import_failures;
1001
            session_write_close();
1002
        }
1003
        return true;
1004
    }
1005
1006
    /**
1007
     * Returns cleaned user data
1008
     *
1009
     * @param array $candidate raw values of line from input file
1010
     * @param string $error
1011
     * @return array|false cleaned data or false
1012
     */
1013
    protected function _cleanImportUser($candidate, & $error){
1014
        global $INPUT;
1015
1016
        // kludgy ....
1017
        $INPUT->set('userid', $candidate[0]);
1018
        $INPUT->set('userpass', $candidate[1]);
1019
        $INPUT->set('username', $candidate[2]);
1020
        $INPUT->set('usermail', $candidate[3]);
1021
        $INPUT->set('usergroups', $candidate[4]);
1022
1023
        $cleaned = $this->_retrieveUser();
1024
        list($user,/* $pass */,$name,$mail,/* $grps */) = $cleaned;
1025
        if (empty($user)) {
1026
            $error = $this->lang['import_error_baduserid'];
1027
            return false;
1028
        }
1029
1030
        // no need to check password, handled elsewhere
1031
1032
        if (!($this->_auth->canDo('modName') xor empty($name))){
1033
            $error = $this->lang['import_error_badname'];
1034
            return false;
1035
        }
1036
1037
        if ($this->_auth->canDo('modMail')) {
1038
            if (empty($mail) || !mail_isvalid($mail)) {
1039
                $error = $this->lang['import_error_badmail'];
1040
                return false;
1041
            }
1042
        } else {
1043
            if (!empty($mail)) {
1044
                $error = $this->lang['import_error_badmail'];
1045
                return false;
1046
            }
1047
        }
1048
1049
        return $cleaned;
1050
    }
1051
1052
    /**
1053
     * Adds imported user to auth backend
1054
     *
1055
     * Required a check of canDo('addUser') before
1056
     *
1057
     * @param array  $user   data of user
1058
     * @param string &$error reference catched error message
1059
     * @return bool whether successful
1060
     */
1061
    protected function _addImportUser($user, & $error){
1062
        if (!$this->_auth->triggerUserMod('create', $user)) {
1063
            $error = $this->lang['import_error_create'];
1064
            return false;
1065
        }
1066
1067
        return true;
1068
    }
1069
1070
    /**
1071
     * Downloads failures as csv file
1072
     */
1073
    protected function _downloadImportFailures(){
1074
1075
        // ==============================================================================================
1076
        // GENERATE OUTPUT
1077
        // normal headers for downloading...
1078
        header('Content-type: text/csv;charset=utf-8');
1079
        header('Content-Disposition: attachment; filename="importfails.csv"');
1080
#       // for debugging assistance, send as text plain to the browser
1081
#       header('Content-type: text/plain;charset=utf-8');
1082
1083
        // output the csv
1084
        $fd = fopen('php://output','w');
1085
        foreach ($this->_import_failures as $fail) {
1086
            fputs($fd, $fail['orig']);
1087
        }
1088
        fclose($fd);
1089
        die;
1090
    }
1091
1092
    /**
1093
     * wrapper for is_uploaded_file to facilitate overriding by test suite
1094
     *
1095
     * @param string $file filename
1096
     * @return bool
1097
     */
1098
    protected function _isUploadedFile($file) {
1099
        return is_uploaded_file($file);
1100
    }
1101
}
1102