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 <neolao@neolao.com> 11 * @author Chris Smith <chris@jalakai.co.uk> 12 */ 13// must be run within Dokuwiki 14if(!defined('DOKU_INC')) die(); 15 16if(!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 */ 22class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { 23 24 var $_auth = null; // auth object 25 var $_user_total = 0; // number of registered users 26 var $_filter = array(); // user selection filter(s) 27 var $_start = 0; // index of first user to be displayed 28 var $_last = 0; // index of the last user to be displayed 29 var $_pagesize = 20; // number of users to list on one page 30 var $_edit_user = ''; // set to user selected for editing 31 var $_edit_userdata = array(); 32 var $_disabled = ''; // if disabled set to explanatory string 33 var $_import_failures = array(); 34 35 /** 36 * Constructor 37 */ 38 function admin_plugin_usermanager(){ 39 global $auth; 40 41 $this->setupLocale(); 42 43 if (!isset($auth)) { 44 $this->disabled = $this->lang['noauth']; 45 } else if (!$auth->canDo('getUsers')) { 46 $this->disabled = $this->lang['nosupport']; 47 } else { 48 49 // we're good to go 50 $this->_auth = & $auth; 51 52 } 53 54 // attempt to retrieve any import failures from the session 55 if ($_SESSION['import_failures']){ 56 $this->_import_failures = $_SESSION['import_failures']; 57 } 58 } 59 60 /** 61 * return prompt for admin menu 62 */ 63 function getMenuText($language) { 64 65 if (!is_null($this->_auth)) 66 return parent::getMenuText($language); 67 68 return $this->getLang('menu').' '.$this->disabled; 69 } 70 71 /** 72 * return sort order for position in admin menu 73 */ 74 function getMenuSort() { 75 return 2; 76 } 77 78 /** 79 * handle user request 80 */ 81 function handle() { 82 global $INPUT; 83 if (is_null($this->_auth)) return false; 84 85 // extract the command and any specific parameters 86 // submit button name is of the form - fn[cmd][param(s)] 87 $fn = $INPUT->param('fn'); 88 89 if (is_array($fn)) { 90 $cmd = key($fn); 91 $param = is_array($fn[$cmd]) ? key($fn[$cmd]) : null; 92 } else { 93 $cmd = $fn; 94 $param = null; 95 } 96 97 if ($cmd != "search") { 98 $this->_start = $INPUT->int('start', 0); 99 $this->_filter = $this->_retrieveFilter(); 100 } 101 102 switch($cmd){ 103 case "add" : $this->_addUser(); break; 104 case "delete" : $this->_deleteUser(); break; 105 case "modify" : $this->_modifyUser(); break; 106 case "edit" : $this->_editUser($param); break; 107 case "search" : $this->_setFilter($param); 108 $this->_start = 0; 109 break; 110 case "export" : $this->_export(); break; 111 case "import" : $this->_import(); break; 112 case "importfails" : $this->_downloadImportFailures(); break; 113 } 114 115 $this->_user_total = $this->_auth->canDo('getUserCount') ? $this->_auth->getUserCount($this->_filter) : -1; 116 117 // page handling 118 switch($cmd){ 119 case 'start' : $this->_start = 0; break; 120 case 'prev' : $this->_start -= $this->_pagesize; break; 121 case 'next' : $this->_start += $this->_pagesize; break; 122 case 'last' : $this->_start = $this->_user_total; break; 123 } 124 $this->_validatePagination(); 125 } 126 127 /** 128 * output appropriate html 129 */ 130 function html() { 131 global $ID; 132 133 if(is_null($this->_auth)) { 134 print $this->lang['badauth']; 135 return false; 136 } 137 138 $user_list = $this->_auth->retrieveUsers($this->_start, $this->_pagesize, $this->_filter); 139 $users = array_keys($user_list); 140 141 $page_buttons = $this->_pagination(); 142 $delete_disable = $this->_auth->canDo('delUser') ? '' : 'disabled="disabled"'; 143 144 $editable = $this->_auth->canDo('UserMod'); 145 $export_label = empty($this->_filter) ? $this->lang['export_all'] : $this->lang[export_filtered]; 146 147 print $this->locale_xhtml('intro'); 148 print $this->locale_xhtml('list'); 149 150 ptln("<div id=\"user__manager\">"); 151 ptln("<div class=\"level2\">"); 152 153 if ($this->_user_total > 0) { 154 ptln("<p>".sprintf($this->lang['summary'],$this->_start+1,$this->_last,$this->_user_total,$this->_auth->getUserCount())."</p>"); 155 } else { 156 if($this->_user_total < 0) { 157 $allUserTotal = 0; 158 } else { 159 $allUserTotal = $this->_auth->getUserCount(); 160 } 161 ptln("<p>".sprintf($this->lang['nonefound'], $allUserTotal)."</p>"); 162 } 163 ptln("<form action=\"".wl($ID)."\" method=\"post\">"); 164 formSecurityToken(); 165 ptln(" <div class=\"table\">"); 166 ptln(" <table class=\"inline\">"); 167 ptln(" <thead>"); 168 ptln(" <tr>"); 169 ptln(" <th> </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>"); 170 ptln(" </tr>"); 171 172 ptln(" <tr>"); 173 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>"); 174 ptln(" <td><input type=\"text\" name=\"userid\" class=\"edit\" value=\"".$this->_htmlFilter('user')."\" /></td>"); 175 ptln(" <td><input type=\"text\" name=\"username\" class=\"edit\" value=\"".$this->_htmlFilter('name')."\" /></td>"); 176 ptln(" <td><input type=\"text\" name=\"usermail\" class=\"edit\" value=\"".$this->_htmlFilter('mail')."\" /></td>"); 177 ptln(" <td><input type=\"text\" name=\"usergroups\" class=\"edit\" value=\"".$this->_htmlFilter('grps')."\" /></td>"); 178 ptln(" </tr>"); 179 ptln(" </thead>"); 180 181 if ($this->_user_total) { 182 ptln(" <tbody>"); 183 foreach ($user_list as $user => $userinfo) { 184 extract($userinfo); 185 $groups = join(', ',$grps); 186 ptln(" <tr class=\"user_info\">"); 187 ptln(" <td class=\"centeralign\"><input type=\"checkbox\" name=\"delete[".$user."]\" ".$delete_disable." /></td>"); 188 if ($editable) { 189 ptln(" <td><a href=\"".wl($ID,array('fn[edit]['.hsc($user).']' => 1, 190 'do' => 'admin', 191 'page' => 'usermanager', 192 'sectok' => getSecurityToken())). 193 "\" title=\"".$this->lang['edit_prompt']."\">".hsc($user)."</a></td>"); 194 } else { 195 ptln(" <td>".hsc($user)."</td>"); 196 } 197 ptln(" <td>".hsc($name)."</td><td>".hsc($mail)."</td><td>".hsc($groups)."</td>"); 198 ptln(" </tr>"); 199 } 200 ptln(" </tbody>"); 201 } 202 203 ptln(" <tbody>"); 204 ptln(" <tr><td colspan=\"5\" class=\"centeralign\">"); 205 ptln(" <span class=\"medialeft\">"); 206 ptln(" <input type=\"submit\" name=\"fn[delete]\" ".$delete_disable." class=\"button\" value=\"".$this->lang['delete_selected']."\" id=\"usrmgr__del\" />"); 207 ptln(" </span>"); 208 ptln(" <span class=\"mediaright\">"); 209 ptln(" <input type=\"submit\" name=\"fn[start]\" ".$page_buttons['start']." class=\"button\" value=\"".$this->lang['start']."\" />"); 210 ptln(" <input type=\"submit\" name=\"fn[prev]\" ".$page_buttons['prev']." class=\"button\" value=\"".$this->lang['prev']."\" />"); 211 ptln(" <input type=\"submit\" name=\"fn[next]\" ".$page_buttons['next']." class=\"button\" value=\"".$this->lang['next']."\" />"); 212 ptln(" <input type=\"submit\" name=\"fn[last]\" ".$page_buttons['last']." class=\"button\" value=\"".$this->lang['last']."\" />"); 213 ptln(" </span>"); 214 if (!empty($this->_filter)) { 215 ptln(" <input type=\"submit\" name=\"fn[search][clear]\" class=\"button\" value=\"".$this->lang['clear']."\" />"); 216 } 217 ptln(" <input type=\"submit\" name=\"fn[export]\" class=\"button\" value=\"".$export_label."\" />"); 218 ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />"); 219 ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />"); 220 221 $this->_htmlFilterSettings(2); 222 223 ptln(" </td></tr>"); 224 ptln(" </tbody>"); 225 ptln(" </table>"); 226 ptln(" </div>"); 227 228 ptln("</form>"); 229 ptln("</div>"); 230 231 $style = $this->_edit_user ? " class=\"edit_user\"" : ""; 232 233 if ($this->_auth->canDo('addUser')) { 234 ptln("<div".$style.">"); 235 print $this->locale_xhtml('add'); 236 ptln(" <div class=\"level2\">"); 237 238 $this->_htmlUserForm('add',null,array(),4); 239 240 ptln(" </div>"); 241 ptln("</div>"); 242 } 243 244 if($this->_edit_user && $this->_auth->canDo('UserMod')){ 245 ptln("<div".$style." id=\"scroll__here\">"); 246 print $this->locale_xhtml('edit'); 247 ptln(" <div class=\"level2\">"); 248 249 $this->_htmlUserForm('modify',$this->_edit_user,$this->_edit_userdata,4); 250 251 ptln(" </div>"); 252 ptln("</div>"); 253 } 254 255 if ($this->_auth->canDo('addUser')) { 256 $this->_htmlImportForm(); 257 } 258 ptln("</div>"); 259 } 260 261 262 /** 263 * @todo disable fields which the backend can't change 264 */ 265 function _htmlUserForm($cmd,$user='',$userdata=array(),$indent=0) { 266 global $conf; 267 global $ID; 268 269 $name = $mail = $groups = ''; 270 $notes = array(); 271 272 if ($user) { 273 extract($userdata); 274 if (!empty($grps)) $groups = join(',',$grps); 275 } else { 276 $notes[] = sprintf($this->lang['note_group'],$conf['defaultgroup']); 277 } 278 279 ptln("<form action=\"".wl($ID)."\" method=\"post\">",$indent); 280 formSecurityToken(); 281 ptln(" <div class=\"table\">",$indent); 282 ptln(" <table class=\"inline\">",$indent); 283 ptln(" <thead>",$indent); 284 ptln(" <tr><th>".$this->lang["field"]."</th><th>".$this->lang["value"]."</th></tr>",$indent); 285 ptln(" </thead>",$indent); 286 ptln(" <tbody>",$indent); 287 288 $this->_htmlInputField($cmd."_userid", "userid", $this->lang["user_id"], $user, $this->_auth->canDo("modLogin"), $indent+6); 289 $this->_htmlInputField($cmd."_userpass", "userpass", $this->lang["user_pass"], "", $this->_auth->canDo("modPass"), $indent+6); 290 $this->_htmlInputField($cmd."_username", "username", $this->lang["user_name"], $name, $this->_auth->canDo("modName"), $indent+6); 291 $this->_htmlInputField($cmd."_usermail", "usermail", $this->lang["user_mail"], $mail, $this->_auth->canDo("modMail"), $indent+6); 292 $this->_htmlInputField($cmd."_usergroups","usergroups",$this->lang["user_groups"],$groups,$this->_auth->canDo("modGroups"),$indent+6); 293 294 if ($this->_auth->canDo("modPass")) { 295 if ($cmd == 'add') { 296 $notes[] = $this->lang['note_pass']; 297 } 298 if ($user) { 299 $notes[] = $this->lang['note_notify']; 300 } 301 302 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); 303 } 304 305 ptln(" </tbody>",$indent); 306 ptln(" <tbody>",$indent); 307 ptln(" <tr>",$indent); 308 ptln(" <td colspan=\"2\">",$indent); 309 ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />",$indent); 310 ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />",$indent); 311 312 // save current $user, we need this to access details if the name is changed 313 if ($user) 314 ptln(" <input type=\"hidden\" name=\"userid_old\" value=\"".$user."\" />",$indent); 315 316 $this->_htmlFilterSettings($indent+10); 317 318 ptln(" <input type=\"submit\" name=\"fn[".$cmd."]\" class=\"button\" value=\"".$this->lang[$cmd]."\" />",$indent); 319 ptln(" </td>",$indent); 320 ptln(" </tr>",$indent); 321 ptln(" </tbody>",$indent); 322 ptln(" </table>",$indent); 323 324 if ($notes) { 325 ptln(" <ul class=\"notes\">"); 326 foreach ($notes as $note) { 327 ptln(" <li><span class=\"li\">".$note."</span></li>",$indent); 328 } 329 ptln(" </ul>"); 330 } 331 ptln(" </div>",$indent); 332 ptln("</form>",$indent); 333 } 334 335 function _htmlInputField($id, $name, $label, $value, $cando, $indent=0) { 336 $class = $cando ? '' : ' class="disabled"'; 337 echo str_pad('',$indent); 338 339 if($name == 'userpass'){ 340 $fieldtype = 'password'; 341 $autocomp = 'autocomplete="off"'; 342 }elseif($name == 'usermail'){ 343 $fieldtype = 'email'; 344 $autocomp = ''; 345 }else{ 346 $fieldtype = 'text'; 347 $autocomp = ''; 348 } 349 350 351 echo "<tr $class>"; 352 echo "<td><label for=\"$id\" >$label: </label></td>"; 353 echo "<td>"; 354 if($cando){ 355 echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit\" $autocomp />"; 356 }else{ 357 echo "<input type=\"hidden\" name=\"$name\" value=\"$value\" />"; 358 echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit disabled\" disabled=\"disabled\" />"; 359 } 360 echo "</td>"; 361 echo "</tr>"; 362 } 363 364 function _htmlFilter($key) { 365 if (empty($this->_filter)) return ''; 366 return (isset($this->_filter[$key]) ? hsc($this->_filter[$key]) : ''); 367 } 368 369 function _htmlFilterSettings($indent=0) { 370 371 ptln("<input type=\"hidden\" name=\"start\" value=\"".$this->_start."\" />",$indent); 372 373 foreach ($this->_filter as $key => $filter) { 374 ptln("<input type=\"hidden\" name=\"filter[".$key."]\" value=\"".hsc($filter)."\" />",$indent); 375 } 376 } 377 378 function _htmlImportForm($indent=0) { 379 global $ID; 380 381 $failure_download_link = wl($ID,array('do'=>'admin','page'=>'usermanager','fn[importfails]'=>1)); 382 383 ptln('<div class="level2 import_users">',$indent); 384 print $this->locale_xhtml('import'); 385 ptln(' <form action="'.wl($ID).'" method="post" enctype="multipart/form-data">',$indent); 386 formSecurityToken(); 387 ptln(' <label>User list file (csv): <input type="file" name="import" /></label>',$indent); 388 ptln(' <input type="submit" name="fn[import]" value="'.$this->lang['import'].'" />',$indent); 389 ptln(' <input type="hidden" name="do" value="admin" />',$indent); 390 ptln(' <input type="hidden" name="page" value="usermanager" />',$indent); 391 392 $this->_htmlFilterSettings($indent+4); 393 ptln(' </form>',$indent); 394 ptln('</div>'); 395 396 // list failures from the previous import 397 if ($this->_import_failures) { 398 $digits = strlen(count($this->_import_failures)); 399 ptln('<div class="level3 import_failures">',$indent); 400 ptln(' <h3>Most Recent Import - Failures</h3>'); 401 ptln(' <table class="import_failures">',$indent); 402 ptln(' <thead>',$indent); 403 ptln(' <tr>',$indent); 404 ptln(' <th class="line">'.$this->lang['line'].'</th>',$indent); 405 ptln(' <th class="error">'.$this->lang['error'].'</th>',$indent); 406 ptln(' <th class="userid">'.$this->lang['user_id'].'</th>',$indent); 407 ptln(' <th class="username">'.$this->lang['user_name'].'</th>',$indent); 408 ptln(' <th class="usermail">'.$this->lang['user_mail'].'</th>',$indent); 409 ptln(' <th class="usergroups">'.$this->lang['user_groups'].'</th>',$indent); 410 ptln(' </tr>',$indent); 411 ptln(' </thead>',$indent); 412 ptln(' <tbody>',$indent); 413 foreach ($this->_import_failures as $line => $failure) { 414 ptln(' <tr>',$indent); 415 ptln(' <td class="lineno"> '.sprintf('%0'.$digits.'d',$line).' </td>',$indent); 416 ptln(' <td class="error">' .$failure['error'].' </td>', $indent); 417 ptln(' <td class="field userid"> '.hsc($failure['user'][0]).' </td>',$indent); 418 ptln(' <td class="field username"> '.hsc($failure['user'][2]).' </td>',$indent); 419 ptln(' <td class="field usermail"> '.hsc($failure['user'][3]).' </td>',$indent); 420 ptln(' <td class="field usergroups"> '.hsc($failure['user'][4]).' </td>',$indent); 421 ptln(' </tr>',$indent); 422 } 423 ptln(' </tbody>',$indent); 424 ptln(' </table>',$indent); 425 ptln(' <p><a href="'.$failure_download_link.'">Download Failures as CSV for correction</a></p>'); 426 ptln('</div>'); 427 } 428 429 } 430 431 function _addUser(){ 432 global $INPUT; 433 if (!checkSecurityToken()) return false; 434 if (!$this->_auth->canDo('addUser')) return false; 435 436 list($user,$pass,$name,$mail,$grps) = $this->_retrieveUser(); 437 if (empty($user)) return false; 438 439 if ($this->_auth->canDo('modPass')){ 440 if (empty($pass)){ 441 if($INPUT->has('usernotify')){ 442 $pass = auth_pwgen($user); 443 } else { 444 msg($this->lang['add_fail'], -1); 445 return false; 446 } 447 } 448 } else { 449 if (!empty($pass)){ 450 msg($this->lang['add_fail'], -1); 451 return false; 452 } 453 } 454 455 if ($this->_auth->canDo('modName')){ 456 if (empty($name)){ 457 msg($this->lang['add_fail'], -1); 458 return false; 459 } 460 } else { 461 if (!empty($name)){ 462 return false; 463 } 464 } 465 466 if ($this->_auth->canDo('modMail')){ 467 if (empty($mail)){ 468 msg($this->lang['add_fail'], -1); 469 return false; 470 } 471 } else { 472 if (!empty($mail)){ 473 return false; 474 } 475 } 476 477 if ($ok = $this->_auth->triggerUserMod('create', array($user,$pass,$name,$mail,$grps))) { 478 479 msg($this->lang['add_ok'], 1); 480 481 if ($INPUT->has('usernotify') && $pass) { 482 $this->_notifyUser($user,$pass); 483 } 484 } else { 485 msg($this->lang['add_fail'], -1); 486 } 487 488 return $ok; 489 } 490 491 /** 492 * Delete user 493 */ 494 function _deleteUser(){ 495 global $conf, $INPUT; 496 497 if (!checkSecurityToken()) return false; 498 if (!$this->_auth->canDo('delUser')) return false; 499 500 $selected = $INPUT->arr('delete'); 501 if (empty($selected)) return false; 502 $selected = array_keys($selected); 503 504 if(in_array($_SERVER['REMOTE_USER'], $selected)) { 505 msg("You can't delete yourself!", -1); 506 return false; 507 } 508 509 $count = $this->_auth->triggerUserMod('delete', array($selected)); 510 if ($count == count($selected)) { 511 $text = str_replace('%d', $count, $this->lang['delete_ok']); 512 msg("$text.", 1); 513 } else { 514 $part1 = str_replace('%d', $count, $this->lang['delete_ok']); 515 $part2 = str_replace('%d', (count($selected)-$count), $this->lang['delete_fail']); 516 msg("$part1, $part2",-1); 517 } 518 519 // invalidate all sessions 520 io_saveFile($conf['cachedir'].'/sessionpurge',time()); 521 522 return true; 523 } 524 525 /** 526 * Edit user (a user has been selected for editing) 527 */ 528 function _editUser($param) { 529 if (!checkSecurityToken()) return false; 530 if (!$this->_auth->canDo('UserMod')) return false; 531 532 $user = cleanID(preg_replace('/.*:/','',$param)); 533 $userdata = $this->_auth->getUserData($user); 534 535 // no user found? 536 if (!$userdata) { 537 msg($this->lang['edit_usermissing'],-1); 538 return false; 539 } 540 541 $this->_edit_user = $user; 542 $this->_edit_userdata = $userdata; 543 544 return true; 545 } 546 547 /** 548 * Modify user (modified user data has been recieved) 549 */ 550 function _modifyUser(){ 551 global $conf, $INPUT; 552 553 if (!checkSecurityToken()) return false; 554 if (!$this->_auth->canDo('UserMod')) return false; 555 556 // get currently valid user data 557 $olduser = cleanID(preg_replace('/.*:/','',$INPUT->str('userid_old'))); 558 $oldinfo = $this->_auth->getUserData($olduser); 559 560 // get new user data subject to change 561 list($newuser,$newpass,$newname,$newmail,$newgrps) = $this->_retrieveUser(); 562 if (empty($newuser)) return false; 563 564 $changes = array(); 565 if ($newuser != $olduser) { 566 567 if (!$this->_auth->canDo('modLogin')) { // sanity check, shouldn't be possible 568 msg($this->lang['update_fail'],-1); 569 return false; 570 } 571 572 // check if $newuser already exists 573 if ($this->_auth->getUserData($newuser)) { 574 msg(sprintf($this->lang['update_exists'],$newuser),-1); 575 $re_edit = true; 576 } else { 577 $changes['user'] = $newuser; 578 } 579 } 580 581 // generate password if left empty and notification is on 582 if($INPUT->has('usernotify') && empty($newpass)){ 583 $newpass = auth_pwgen($olduser); 584 } 585 586 if (!empty($newpass) && $this->_auth->canDo('modPass')) 587 $changes['pass'] = $newpass; 588 if (!empty($newname) && $this->_auth->canDo('modName') && $newname != $oldinfo['name']) 589 $changes['name'] = $newname; 590 if (!empty($newmail) && $this->_auth->canDo('modMail') && $newmail != $oldinfo['mail']) 591 $changes['mail'] = $newmail; 592 if (!empty($newgrps) && $this->_auth->canDo('modGroups') && $newgrps != $oldinfo['grps']) 593 $changes['grps'] = $newgrps; 594 595 if ($ok = $this->_auth->triggerUserMod('modify', array($olduser, $changes))) { 596 msg($this->lang['update_ok'],1); 597 598 if ($INPUT->has('usernotify') && $newpass) { 599 $notify = empty($changes['user']) ? $olduser : $newuser; 600 $this->_notifyUser($notify,$newpass); 601 } 602 603 // invalidate all sessions 604 io_saveFile($conf['cachedir'].'/sessionpurge',time()); 605 606 } else { 607 msg($this->lang['update_fail'],-1); 608 } 609 610 if (!empty($re_edit)) { 611 $this->_editUser($olduser); 612 } 613 614 return $ok; 615 } 616 617 /** 618 * send password change notification email 619 */ 620 function _notifyUser($user, $password, $status_alert=true) { 621 622 if ($sent = auth_sendPassword($user,$password)) { 623 if ($status_alert) { 624 msg($this->lang['notify_ok'], 1); 625 } 626 } else { 627 if ($status_alert) { 628 msg($this->lang['notify_fail'], -1); 629 } 630 } 631 632 return $sent; 633 } 634 635 /** 636 * retrieve & clean user data from the form 637 * 638 * @return array (user, password, full name, email, array(groups)) 639 */ 640 function _retrieveUser($clean=true) { 641 global $auth; 642 global $INPUT; 643 644 $user[0] = ($clean) ? $auth->cleanUser($INPUT->str('userid')) : $INPUT->str('userid'); 645 $user[1] = $INPUT->str('userpass'); 646 $user[2] = $INPUT->str('username'); 647 $user[3] = $INPUT->str('usermail'); 648 $user[4] = explode(',',$INPUT->str('usergroups')); 649 650 $user[4] = array_map('trim',$user[4]); 651 if($clean) $user[4] = array_map(array($auth,'cleanGroup'),$user[4]); 652 $user[4] = array_filter($user[4]); 653 $user[4] = array_unique($user[4]); 654 if(!count($user[4])) $user[4] = null; 655 656 return $user; 657 } 658 659 function _setFilter($op) { 660 661 $this->_filter = array(); 662 663 if ($op == 'new') { 664 list($user,$pass,$name,$mail,$grps) = $this->_retrieveUser(false); 665 666 if (!empty($user)) $this->_filter['user'] = $user; 667 if (!empty($name)) $this->_filter['name'] = $name; 668 if (!empty($mail)) $this->_filter['mail'] = $mail; 669 if (!empty($grps)) $this->_filter['grps'] = join('|',$grps); 670 } 671 } 672 673 function _retrieveFilter() { 674 global $INPUT; 675 676 $t_filter = $INPUT->arr('filter'); 677 678 // messy, but this way we ensure we aren't getting any additional crap from malicious users 679 $filter = array(); 680 681 if (isset($t_filter['user'])) $filter['user'] = $t_filter['user']; 682 if (isset($t_filter['name'])) $filter['name'] = $t_filter['name']; 683 if (isset($t_filter['mail'])) $filter['mail'] = $t_filter['mail']; 684 if (isset($t_filter['grps'])) $filter['grps'] = $t_filter['grps']; 685 686 return $filter; 687 } 688 689 function _validatePagination() { 690 691 if ($this->_start >= $this->_user_total) { 692 $this->_start = $this->_user_total - $this->_pagesize; 693 } 694 if ($this->_start < 0) $this->_start = 0; 695 696 $this->_last = min($this->_user_total, $this->_start + $this->_pagesize); 697 } 698 699 /* 700 * return an array of strings to enable/disable pagination buttons 701 */ 702 function _pagination() { 703 704 $disabled = 'disabled="disabled"'; 705 706 $buttons['start'] = $buttons['prev'] = ($this->_start == 0) ? $disabled : ''; 707 708 if ($this->_user_total == -1) { 709 $buttons['last'] = $disabled; 710 $buttons['next'] = ''; 711 } else { 712 $buttons['last'] = $buttons['next'] = (($this->_start + $this->_pagesize) >= $this->_user_total) ? $disabled : ''; 713 } 714 715 return $buttons; 716 } 717 718 /* 719 * export a list of users in csv format using the current filter criteria 720 */ 721 function _export() { 722 // list of users for export - based on current filter criteria 723 $user_list = $this->_auth->retrieveUsers(0, 0, $this->_filter); 724 $column_headings = array( 725 $this->lang["user_id"], 726 $this->lang["user_name"], 727 $this->lang["user_mail"], 728 $this->lang["user_groups"] 729 ); 730 731 // ============================================================================================== 732 // GENERATE OUTPUT 733 // normal headers for downloading... 734 header('Content-type: text/csv;charset=utf-8'); 735 header('Content-Disposition: attachment; filename="wikiusers.csv"'); 736# // for debugging assistance, send as text plain to the browser 737# header('Content-type: text/plain;charset=utf-8'); 738 739 // output the csv 740 $fd = fopen('php://output','w'); 741 fputcsv($fd, $column_headings); 742 foreach ($user_list as $user => $info) { 743 $line = array($user, $info['name'], $info['mail'], join(',',$info['grps'])); 744 fputcsv($fd, $line); 745 } 746 fclose($fd); 747 die; 748 } 749 750 /* 751 * import a file of users in csv format 752 * 753 * csv file should have 4 columns, user_id, full name, email, groups (comma separated) 754 */ 755 function _import() { 756 // check we are allowed to add users 757 if (!checkSecurityToken()) return false; 758 if (!$this->_auth->canDo('addUser')) return false; 759 760 // check file uploaded ok. 761 if (empty($_FILES['import']['size']) || !empty($FILES['import']['error']) && is_uploaded_file($FILES['import']['tmp_name'])) { 762 msg($this->lang['import_error_upload'],-1); 763 return false; 764 } 765 // retrieve users from the file 766 $this->_import_failures = array(); 767 $import_success_count = 0; 768 $import_fail_count = 0; 769 $line = 0; 770 $fd = fopen($_FILES['import']['tmp_name'],'r'); 771 if ($fd) { 772 while($csv = fgets($fd)){ 773 if (!utf8_check($csv)) { 774 $csv = utf8_encode($csv); 775 } 776 $raw = str_getcsv($csv); 777 $error = ''; // clean out any errors from the previous line 778 // data checks... 779 if (1 == ++$line) { 780 if ($raw[0] == 'user_id' || $raw[0] == $this->lang['user_id']) continue; // skip headers 781 } 782 if (count($raw) < 4) { // need at least four fields 783 $import_fail_count++; 784 $error = sprintf($this->lang['import_error_fields'], count($raw)); 785 $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); 786 continue; 787 } 788 array_splice($raw,1,0,auth_pwgen()); // splice in a generated password 789 $clean = $this->_cleanImportUser($raw, $error); 790 if ($clean && $this->_addImportUser($clean, $error)) { 791 $sent = $this->_notifyUser($clean[0],$clean[1],false); 792 if (!$sent){ 793 msg(sprintf($this->lang['import_notify_fail'],$clean[0],$clean[3]),-1); 794 } 795 $import_success_count++; 796 } else { 797 $import_fail_count++; 798 $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); 799 } 800 } 801 msg(sprintf($this->lang['import_success_count'], ($import_success_count+$import_fail_count), $import_success_count),($import_success_count ? 1 : -1)); 802 if ($import_fail_count) { 803 msg(sprintf($this->lang['import_failure_count'], $import_fail_count),-1); 804 } 805 } else { 806 msg($this->lang['import_error_readfail'],-1); 807 } 808 809 // save import failures into the session 810 if (!headers_sent()) { 811 session_start(); 812 $_SESSION['import_failures'] = $this->_import_failures; 813 session_write_close(); 814 } 815 } 816 817 function _cleanImportUser($candidate, & $error){ 818 global $INPUT; 819 820 // kludgy .... 821 $INPUT->set('userid', $candidate[0]); 822 $INPUT->set('userpass', $candidate[1]); 823 $INPUT->set('username', $candidate[2]); 824 $INPUT->set('usermail', $candidate[3]); 825 $INPUT->set('usergroups', $candidate[4]); 826 827 $cleaned = $this->_retrieveUser(); 828 list($user,$pass,$name,$mail,$grps) = $cleaned; 829 if (empty($user)) { 830 $error = $this->lang['import_error_baduserid']; 831 return false; 832 } 833 834 // no need to check password, handled elsewhere 835 836 if (!($this->_auth->canDo('modName') xor empty($name))){ 837 $error = $this->lang['import_error_badname']; 838 return false; 839 } 840 841 if ($this->_auth->canDo('modMail')) { 842 if (empty($mail) || !mail_isvalid($mail)) { 843 $error = $this->lang['import_error_badmail']; 844 return false; 845 } 846 } else { 847 if (!empty($mail)) { 848 $error = $this->lang['import_error_badmail']; 849 return false; 850 } 851 } 852 853 return $cleaned; 854 } 855 856 function _addImportUser($user, & $error){ 857 if (!$this->_auth->triggerUserMod('create', $user)) { 858 $error = $this->lang['import_error_create']; 859 return false; 860 } 861 862 return true; 863 } 864 865 function _downloadImportFailures(){ 866 867 // ============================================================================================== 868 // GENERATE OUTPUT 869 // normal headers for downloading... 870 header('Content-type: text/csv;charset=utf-8'); 871 header('Content-Disposition: attachment; filename="importfails.csv"'); 872# // for debugging assistance, send as text plain to the browser 873# header('Content-type: text/plain;charset=utf-8'); 874 875 // output the csv 876 $fd = fopen('php://output','w'); 877 foreach ($this->_import_failures as $line => $fail) { 878 fputs($fd, $fail['orig']); 879 } 880 fclose($fd); 881 die; 882 } 883 884} 885