[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 // must be run within Dokuwiki 3 if(!defined('DOKU_INC')) die(); 4 5 /** 6 * MySQL authentication backend 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Andreas Gohr <andi@splitbrain.org> 10 * @author Chris Smith <chris@jalakai.co.uk> 11 * @author Matthias Grimm <matthias.grimmm@sourceforge.net> 12 * @author Jan Schumann <js@schumann-it.com> 13 */ 14 class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { 15 /** @var resource holds the database connection */ 16 protected $dbcon = 0; 17 /** @var int database version*/ 18 protected $dbver = 0; 19 /** @var int database revision */ 20 protected $dbrev = 0; 21 /** @var int database subrevision */ 22 protected $dbsub = 0; 23 24 /** @var array cache to avoid re-reading user info data */ 25 protected $cacheUserInfo = array(); 26 27 /** 28 * Constructor 29 * 30 * checks if the mysql interface is available, otherwise it will 31 * set the variable $success of the basis class to false 32 * 33 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 34 */ 35 public function __construct() { 36 parent::__construct(); 37 38 if(!function_exists('mysql_connect')) { 39 $this->_debug("MySQL err: PHP MySQL extension not found.", -1, __LINE__, __FILE__); 40 $this->success = false; 41 return; 42 } 43 44 // set capabilities based upon config strings set 45 if(!$this->getConf('server') || !$this->getConf('user') || !$this->getConf('database')) { 46 $this->_debug("MySQL err: insufficient configuration.", -1, __LINE__, __FILE__); 47 48 $this->success = false; 49 return; 50 } 51 52 $this->cando['addUser'] = $this->_chkcnf( 53 array( 54 'getUserInfo', 55 'getGroups', 56 'addUser', 57 'getUserID', 58 'getGroupID', 59 'addGroup', 60 'addUserGroup' 61 ), true 62 ); 63 $this->cando['delUser'] = $this->_chkcnf( 64 array( 65 'getUserID', 66 'delUser', 67 'delUserRefs' 68 ), true 69 ); 70 $this->cando['modLogin'] = $this->_chkcnf( 71 array( 72 'getUserID', 73 'updateUser', 74 'UpdateTarget' 75 ), true 76 ); 77 $this->cando['modPass'] = $this->cando['modLogin']; 78 $this->cando['modName'] = $this->cando['modLogin']; 79 $this->cando['modMail'] = $this->cando['modLogin']; 80 $this->cando['modGroups'] = $this->_chkcnf( 81 array( 82 'getUserID', 83 'getGroups', 84 'getGroupID', 85 'addGroup', 86 'addUserGroup', 87 'delGroup', 88 'getGroupID', 89 'delUserGroup' 90 ), true 91 ); 92 /* getGroups is not yet supported 93 $this->cando['getGroups'] = $this->_chkcnf(array('getGroups', 94 'getGroupID'),false); */ 95 $this->cando['getUsers'] = $this->_chkcnf( 96 array( 97 'getUsers', 98 'getUserInfo', 99 'getGroups' 100 ), false 101 ); 102 $this->cando['getUserCount'] = $this->_chkcnf(array('getUsers'), false); 103 104 if($this->getConf('debug') >= 2) { 105 $candoDebug = ''; 106 foreach($this->cando as $cd => $value) { 107 if($value) { $value = 'yes'; } else { $value = 'no'; } 108 $candoDebug .= $cd . ": " . $value . " | "; 109 } 110 $this->_debug("authmysql cando: " . $candoDebug, 0, __LINE__, __FILE__); 111 } 112 } 113 114 /** 115 * Check if the given config strings are set 116 * 117 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 118 * 119 * @param string[] $keys 120 * @param bool $wop is this a check for a write operation? 121 * @return bool 122 */ 123 protected function _chkcnf($keys, $wop = false) { 124 foreach($keys as $key) { 125 if(!$this->getConf($key)) return false; 126 } 127 128 /* write operation and lock array filled with tables names? */ 129 if($wop && (!is_array($this->getConf('TablesToLock')) || 130 !count($this->getConf('TablesToLock'))) 131 ) { 132 return false; 133 } 134 135 return true; 136 } 137 138 /** 139 * Checks if the given user exists and the given plaintext password 140 * is correct. Furtheron it might be checked wether the user is 141 * member of the right group 142 * 143 * Depending on which SQL string is defined in the config, password 144 * checking is done here (getpass) or by the database (passcheck) 145 * 146 * @param string $user user who would like access 147 * @param string $pass user's clear text password to check 148 * @return bool 149 * 150 * @author Andreas Gohr <andi@splitbrain.org> 151 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 152 */ 153 public function checkPass($user, $pass) { 154 global $conf; 155 $rc = false; 156 157 if($this->_openDB()) { 158 $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('checkPass')); 159 $sql = str_replace('%{pass}', $this->_escape($pass), $sql); 160 $sql = str_replace('%{dgroup}', $this->_escape($conf['defaultgroup']), $sql); 161 $result = $this->_queryDB($sql); 162 163 if($result !== false && count($result) == 1) { 164 if($this->getConf('forwardClearPass') == 1) { 165 $rc = true; 166 } else { 167 $rc = auth_verifyPassword($pass, $result[0]['pass']); 168 } 169 } 170 $this->_closeDB(); 171 } 172 return $rc; 173 } 174 175 /** 176 * Return user info 177 * 178 * @author Andreas Gohr <andi@splitbrain.org> 179 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 180 * 181 * @param string $user user login to get data for 182 * @param bool $requireGroups when true, group membership information should be included in the returned array; 183 * when false, it maybe included, but is not required by the caller 184 * @return array|bool 185 */ 186 public function getUserData($user, $requireGroups=true) { 187 if($this->_cacheExists($user, $requireGroups)) { 188 return $this->cacheUserInfo[$user]; 189 } 190 191 if($this->_openDB()) { 192 $this->_lockTables("READ"); 193 $info = $this->_getUserInfo($user, $requireGroups); 194 $this->_unlockTables(); 195 $this->_closeDB(); 196 } else { 197 $info = false; 198 } 199 return $info; 200 } 201 202 /** 203 * Create a new User. Returns false if the user already exists, 204 * null when an error occurred and true if everything went well. 205 * 206 * The new user will be added to the default group by this 207 * function if grps are not specified (default behaviour). 208 * 209 * @author Andreas Gohr <andi@splitbrain.org> 210 * @author Chris Smith <chris@jalakai.co.uk> 211 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 212 * 213 * @param string $user nick of the user 214 * @param string $pwd clear text password 215 * @param string $name full name of the user 216 * @param string $mail email address 217 * @param array $grps array of groups the user should become member of 218 * @return bool|null 219 */ 220 public function createUser($user, $pwd, $name, $mail, $grps = null) { 221 global $conf; 222 223 if($this->_openDB()) { 224 if(($info = $this->_getUserInfo($user)) !== false) { 225 msg($this->getLang('userexists'), -1); 226 return false; // user already exists 227 } 228 229 // set defaultgroup if no groups were given 230 if($grps == null) { 231 $grps = array($conf['defaultgroup']); 232 } 233 234 $this->_lockTables("WRITE"); 235 $pwd = $this->getConf('forwardClearPass') ? $pwd : auth_cryptPassword($pwd); 236 $rc = $this->_addUser($user, $pwd, $name, $mail, $grps); 237 $this->_unlockTables(); 238 $this->_closeDB(); 239 if(!$rc) { 240 msg($this->getLang('writefail')); 241 return null; 242 } 243 return true; 244 } else { 245 msg($this->getLang('connectfail'), -1); 246 } 247 return null; // return error 248 } 249 250 /** 251 * Modify user data 252 * 253 * An existing user dataset will be modified. Changes are given in an array. 254 * 255 * The dataset update will be rejected if the user name should be changed 256 * to an already existing one. 257 * 258 * The password must be provided unencrypted. Pasword encryption is done 259 * automatically if configured. 260 * 261 * If one or more groups can't be updated, an error will be set. In 262 * this case the dataset might already be changed and we can't rollback 263 * the changes. Transactions would be really useful here. 264 * 265 * modifyUser() may be called without SQL statements defined that are 266 * needed to change group membership (for example if only the user profile 267 * should be modified). In this case we assure that we don't touch groups 268 * even when $changes['grps'] is set by mistake. 269 * 270 * @author Chris Smith <chris@jalakai.co.uk> 271 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 272 * 273 * @param string $user nick of the user to be changed 274 * @param array $changes array of field/value pairs to be changed (password will be clear text) 275 * @return bool true on success, false on error 276 */ 277 public function modifyUser($user, $changes) { 278 $rc = false; 279 280 if(!is_array($changes) || !count($changes)) { 281 return true; // nothing to change 282 } 283 284 if($this->_openDB()) { 285 $this->_lockTables("WRITE"); 286 287 $rc = $this->_updateUserInfo($user, $changes); 288 289 if(!$rc) { 290 msg($this->getLang('usernotexists'), -1); 291 } elseif(isset($changes['grps']) && $this->cando['modGroups']) { 292 $groups = $this->_getGroups($user); 293 $grpadd = array_diff($changes['grps'], $groups); 294 $grpdel = array_diff($groups, $changes['grps']); 295 296 foreach($grpadd as $group) { 297 if(($this->_addUserToGroup($user, $group, true)) == false) { 298 $rc = false; 299 } 300 } 301 302 foreach($grpdel as $group) { 303 if(($this->_delUserFromGroup($user, $group)) == false) { 304 $rc = false; 305 } 306 } 307 308 if(!$rc) msg($this->getLang('writefail')); 309 } 310 311 $this->_unlockTables(); 312 $this->_closeDB(); 313 } else { 314 msg($this->getLang('connectfail'), -1); 315 } 316 return $rc; 317 } 318 319 /** 320 * [public function] 321 * 322 * Remove one or more users from the list of registered users 323 * 324 * @param array $users array of users to be deleted 325 * @return int the number of users deleted 326 * 327 * @author Christopher Smith <chris@jalakai.co.uk> 328 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 329 */ 330 function deleteUsers($users) { 331 $count = 0; 332 333 if($this->_openDB()) { 334 if(is_array($users) && count($users)) { 335 $this->_lockTables("WRITE"); 336 foreach($users as $user) { 337 if($this->_delUser($user)) { 338 $count++; 339 } 340 } 341 $this->_unlockTables(); 342 } 343 $this->_closeDB(); 344 } else { 345 msg($this->getLang('connectfail'), -1); 346 } 347 return $count; 348 } 349 350 /** 351 * Counts users which meet certain $filter criteria. 352 * 353 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 354 * 355 * @param array $filter filter criteria in item/pattern pairs 356 * @return int count of found users 357 */ 358 public function getUserCount($filter = array()) { 359 $rc = 0; 360 361 if($this->_openDB()) { 362 $sql = $this->_createSQLFilter($this->getConf('getUsers'), $filter); 363 364 if($this->dbver >= 4) { 365 $sql = substr($sql, 6); /* remove 'SELECT' or 'select' */ 366 $sql = "SELECT SQL_CALC_FOUND_ROWS".$sql." LIMIT 1"; 367 $this->_queryDB($sql); 368 $result = $this->_queryDB("SELECT FOUND_ROWS()"); 369 $rc = $result[0]['FOUND_ROWS()']; 370 } else if(($result = $this->_queryDB($sql))) 371 $rc = count($result); 372 373 $this->_closeDB(); 374 } 375 return $rc; 376 } 377 378 /** 379 * Bulk retrieval of user data 380 * 381 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 382 * 383 * @param int $first index of first user to be returned 384 * @param int $limit max number of users to be returned 385 * @param array $filter array of field/pattern pairs 386 * @return array userinfo (refer getUserData for internal userinfo details) 387 */ 388 public function retrieveUsers($first = 0, $limit = 0, $filter = array()) { 389 $out = array(); 390 391 if($this->_openDB()) { 392 $this->_lockTables("READ"); 393 $sql = $this->_createSQLFilter($this->getConf('getUsers'), $filter); 394 $sql .= " ".$this->getConf('SortOrder'); 395 if($limit) { 396 $sql .= " LIMIT $first, $limit"; 397 } elseif($first) { 398 $sql .= " LIMIT $first"; 399 } 400 $result = $this->_queryDB($sql); 401 402 if(!empty($result)) { 403 foreach($result as $user) { 404 if(($info = $this->_getUserInfo($user['user']))) { 405 $out[$user['user']] = $info; 406 } 407 } 408 } 409 410 $this->_unlockTables(); 411 $this->_closeDB(); 412 } 413 return $out; 414 } 415 416 /** 417 * Give user membership of a group 418 * 419 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 420 * 421 * @param string $user 422 * @param string $group 423 * @return bool true on success, false on error 424 */ 425 protected function joinGroup($user, $group) { 426 $rc = false; 427 428 if($this->_openDB()) { 429 $this->_lockTables("WRITE"); 430 $rc = $this->_addUserToGroup($user, $group); 431 $this->_unlockTables(); 432 $this->_closeDB(); 433 } 434 return $rc; 435 } 436 437 /** 438 * Remove user from a group 439 * 440 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 441 * 442 * @param string $user user that leaves a group 443 * @param string $group group to leave 444 * @return bool 445 */ 446 protected function leaveGroup($user, $group) { 447 $rc = false; 448 449 if($this->_openDB()) { 450 $this->_lockTables("WRITE"); 451 $rc = $this->_delUserFromGroup($user, $group); 452 $this->_unlockTables(); 453 $this->_closeDB(); 454 } 455 return $rc; 456 } 457 458 /** 459 * MySQL is case-insensitive 460 */ 461 public function isCaseSensitive() { 462 return false; 463 } 464 465 /** 466 * Adds a user to a group. 467 * 468 * If $force is set to true non existing groups would be created. 469 * 470 * The database connection must already be established. Otherwise 471 * this function does nothing and returns 'false'. It is strongly 472 * recommended to call this function only after all participating 473 * tables (group and usergroup) have been locked. 474 * 475 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 476 * 477 * @param string $user user to add to a group 478 * @param string $group name of the group 479 * @param bool $force create missing groups 480 * @return bool true on success, false on error 481 */ 482 protected function _addUserToGroup($user, $group, $force = false) { 483 $newgroup = 0; 484 485 if(($this->dbcon) && ($user)) { 486 $gid = $this->_getGroupID($group); 487 if(!$gid) { 488 if($force) { // create missing groups 489 $sql = str_replace('%{group}', $this->_escape($group), $this->getConf('addGroup')); 490 $gid = $this->_modifyDB($sql); 491 $newgroup = 1; // group newly created 492 } 493 if(!$gid) return false; // group didn't exist and can't be created 494 } 495 496 $sql = $this->getConf('addUserGroup'); 497 if(strpos($sql, '%{uid}') !== false) { 498 $uid = $this->_getUserID($user); 499 $sql = str_replace('%{uid}', $this->_escape($uid), $sql); 500 } 501 $sql = str_replace('%{user}', $this->_escape($user), $sql); 502 $sql = str_replace('%{gid}', $this->_escape($gid), $sql); 503 $sql = str_replace('%{group}', $this->_escape($group), $sql); 504 if($this->_modifyDB($sql) !== false) { 505 $this->_flushUserInfoCache($user); 506 return true; 507 } 508 509 if($newgroup) { // remove previously created group on error 510 $sql = str_replace('%{gid}', $this->_escape($gid), $this->getConf('delGroup')); 511 $sql = str_replace('%{group}', $this->_escape($group), $sql); 512 $this->_modifyDB($sql); 513 } 514 } 515 return false; 516 } 517 518 /** 519 * Remove user from a group 520 * 521 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 522 * 523 * @param string $user user that leaves a group 524 * @param string $group group to leave 525 * @return bool true on success, false on error 526 */ 527 protected function _delUserFromGroup($user, $group) { 528 $rc = false; 529 530 if(($this->dbcon) && ($user)) { 531 $sql = $this->getConf('delUserGroup'); 532 if(strpos($sql, '%{uid}') !== false) { 533 $uid = $this->_getUserID($user); 534 $sql = str_replace('%{uid}', $this->_escape($uid), $sql); 535 } 536 $gid = $this->_getGroupID($group); 537 if($gid) { 538 $sql = str_replace('%{user}', $this->_escape($user), $sql); 539 $sql = str_replace('%{gid}', $this->_escape($gid), $sql); 540 $sql = str_replace('%{group}', $this->_escape($group), $sql); 541 $rc = $this->_modifyDB($sql) == 0 ? true : false; 542 543 if ($rc) { 544 $this->_flushUserInfoCache($user); 545 } 546 } 547 } 548 return $rc; 549 } 550 551 /** 552 * Retrieves a list of groups the user is a member off. 553 * 554 * The database connection must already be established 555 * for this function to work. Otherwise it will return 556 * false. 557 * 558 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 559 * 560 * @param string $user user whose groups should be listed 561 * @return bool|array false on error, all groups on success 562 */ 563 protected function _getGroups($user) { 564 $groups = array(); 565 566 if($this->dbcon) { 567 $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('getGroups')); 568 $result = $this->_queryDB($sql); 569 570 if($result !== false && count($result)) { 571 foreach($result as $row) { 572 $groups[] = $row['group']; 573 } 574 } 575 return $groups; 576 } 577 return false; 578 } 579 580 /** 581 * Retrieves the user id of a given user name 582 * 583 * The database connection must already be established 584 * for this function to work. Otherwise it will return 585 * false. 586 * 587 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 588 * 589 * @param string $user user whose id is desired 590 * @return mixed user id 591 */ 592 protected function _getUserID($user) { 593 if($this->dbcon) { 594 $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('getUserID')); 595 $result = $this->_queryDB($sql); 596 return $result === false ? false : $result[0]['id']; 597 } 598 return false; 599 } 600 601 /** 602 * Adds a new User to the database. 603 * 604 * The database connection must already be established 605 * for this function to work. Otherwise it will return 606 * false. 607 * 608 * @author Andreas Gohr <andi@splitbrain.org> 609 * @author Chris Smith <chris@jalakai.co.uk> 610 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 611 * 612 * @param string $user login of the user 613 * @param string $pwd encrypted password 614 * @param string $name full name of the user 615 * @param string $mail email address 616 * @param array $grps array of groups the user should become member of 617 * @return bool 618 */ 619 protected function _addUser($user, $pwd, $name, $mail, $grps) { 620 if($this->dbcon && is_array($grps)) { 621 $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('addUser')); 622 $sql = str_replace('%{pass}', $this->_escape($pwd), $sql); 623 $sql = str_replace('%{name}', $this->_escape($name), $sql); 624 $sql = str_replace('%{email}', $this->_escape($mail), $sql); 625 $uid = $this->_modifyDB($sql); 626 $gid = false; 627 $group = ''; 628 629 if($uid) { 630 foreach($grps as $group) { 631 $gid = $this->_addUserToGroup($user, $group, true); 632 if($gid === false) break; 633 } 634 635 if($gid !== false){ 636 $this->_flushUserInfoCache($user); 637 return true; 638 } else { 639 /* remove the new user and all group relations if a group can't 640 * be assigned. Newly created groups will remain in the database 641 * and won't be removed. This might create orphaned groups but 642 * is not a big issue so we ignore this problem here. 643 */ 644 $this->_delUser($user); 645 $this->_debug("MySQL err: Adding user '$user' to group '$group' failed.", -1, __LINE__, __FILE__); 646 } 647 } 648 } 649 return false; 650 } 651 652 /** 653 * Deletes a given user and all his group references. 654 * 655 * The database connection must already be established 656 * for this function to work. Otherwise it will return 657 * false. 658 * 659 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 660 * 661 * @param string $user username of the user to be deleted 662 * @return bool 663 */ 664 protected function _delUser($user) { 665 if($this->dbcon) { 666 $uid = $this->_getUserID($user); 667 if($uid) { 668 $sql = str_replace('%{uid}', $this->_escape($uid), $this->getConf('delUserRefs')); 669 $this->_modifyDB($sql); 670 $sql = str_replace('%{uid}', $this->_escape($uid), $this->getConf('delUser')); 671 $sql = str_replace('%{user}', $this->_escape($user), $sql); 672 $this->_modifyDB($sql); 673 $this->_flushUserInfoCache($user); 674 return true; 675 } 676 } 677 return false; 678 } 679 680 /** 681 * Flush cached user information 682 * 683 * @author Christopher Smith <chris@jalakai.co.uk> 684 * 685 * @param string $user username of the user whose data is to be removed from the cache 686 * if null, empty the whole cache 687 */ 688 protected function _flushUserInfoCache($user=null) { 689 if (is_null($user)) { 690 $this->cacheUserInfo = array(); 691 } else { 692 unset($this->cacheUserInfo[$user]); 693 } 694 } 695 696 /** 697 * Quick lookup to see if a user's information has been cached 698 * 699 * This test does not need a database connection or read lock 700 * 701 * @author Christopher Smith <chris@jalakai.co.uk> 702 * 703 * @param string $user username to be looked up in the cache 704 * @param bool $requireGroups true, if cached info should include group memberships 705 * 706 * @return bool existence of required user information in the cache 707 */ 708 protected function _cacheExists($user, $requireGroups=true) { 709 if (isset($this->cacheUserInfo[$user])) { 710 if (!is_array($this->cacheUserInfo[$user])) { 711 return true; // user doesn't exist 712 } 713 714 if (!$requireGroups || isset($this->cacheUserInfo[$user]['grps'])) { 715 return true; 716 } 717 } 718 719 return false; 720 } 721 722 /** 723 * Get a user's information 724 * 725 * The database connection must already be established for this function to work. 726 * 727 * @author Christopher Smith <chris@jalakai.co.uk> 728 * 729 * @param string $user username of the user whose information is being reterieved 730 * @param bool $requireGroups true if group memberships should be included 731 * @param bool $useCache true if ok to return cached data & to cache returned data 732 * 733 * @return mixed false|array false if the user doesn't exist 734 * array containing user information if user does exist 735 */ 736 protected function _getUserInfo($user, $requireGroups=true, $useCache=true) { 737 $info = null; 738 739 if ($useCache && isset($this->cacheUserInfo[$user])) { 740 $info = $this->cacheUserInfo[$user]; 741 } 742 743 if (is_null($info)) { 744 $info = $this->_retrieveUserInfo($user); 745 } 746 747 if (($requireGroups == true) && $info && !isset($info['grps'])) { 748 $info['grps'] = $this->_getGroups($user); 749 } 750 751 if ($useCache) { 752 $this->cacheUserInfo[$user] = $info; 753 } 754 755 return $info; 756 } 757 758 /** 759 * retrieveUserInfo 760 * 761 * Gets the data for a specific user. The database connection 762 * must already be established for this function to work. 763 * Otherwise it will return 'false'. 764 * 765 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 766 * 767 * @param string $user user's nick to get data for 768 * @return false|array false on error, user info on success 769 */ 770 protected function _retrieveUserInfo($user) { 771 $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('getUserInfo')); 772 $result = $this->_queryDB($sql); 773 if($result !== false && count($result)) { 774 $info = $result[0]; 775 return $info; 776 } 777 return false; 778 } 779 780 /** 781 * Updates the user info in the database 782 * 783 * Update a user data structure in the database according changes 784 * given in an array. The user name can only be changes if it didn't 785 * exists already. If the new user name exists the update procedure 786 * will be aborted. The database keeps unchanged. 787 * 788 * The database connection has already to be established for this 789 * function to work. Otherwise it will return 'false'. 790 * 791 * The password will be encrypted if necessary. 792 * 793 * @param string $user user's nick being updated 794 * @param array $changes array of items to change as pairs of item and value 795 * @return bool true on success or false on error 796 * 797 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 798 */ 799 protected function _updateUserInfo($user, $changes) { 800 $sql = $this->getConf('updateUser')." "; 801 $cnt = 0; 802 $err = 0; 803 804 if($this->dbcon) { 805 $uid = $this->_getUserID($user); 806 if ($uid === false) { 807 return false; 808 } 809 810 foreach($changes as $item => $value) { 811 if($item == 'user') { 812 if(($this->_getUserID($changes['user']))) { 813 $err = 1; /* new username already exists */ 814 break; /* abort update */ 815 } 816 if($cnt++ > 0) $sql .= ", "; 817 $sql .= str_replace('%{user}', $value, $this->getConf('UpdateLogin')); 818 } else if($item == 'name') { 819 if($cnt++ > 0) $sql .= ", "; 820 $sql .= str_replace('%{name}', $value, $this->getConf('UpdateName')); 821 } else if($item == 'pass') { 822 if(!$this->getConf('forwardClearPass')) 823 $value = auth_cryptPassword($value); 824 if($cnt++ > 0) $sql .= ", "; 825 $sql .= str_replace('%{pass}', $value, $this->getConf('UpdatePass')); 826 } else if($item == 'mail') { 827 if($cnt++ > 0) $sql .= ", "; 828 $sql .= str_replace('%{email}', $value, $this->getConf('UpdateEmail')); 829 } 830 } 831 832 if($err == 0) { 833 if($cnt > 0) { 834 $sql .= " ".str_replace('%{uid}', $uid, $this->getConf('UpdateTarget')); 835 if(get_class($this) == 'auth_mysql') $sql .= " LIMIT 1"; //some PgSQL inheritance comp. 836 $this->_modifyDB($sql); 837 $this->_flushUserInfoCache($user); 838 } 839 return true; 840 } 841 } 842 return false; 843 } 844 845 /** 846 * Retrieves the group id of a given group name 847 * 848 * The database connection must already be established 849 * for this function to work. Otherwise it will return 850 * false. 851 * 852 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 853 * 854 * @param string $group group name which id is desired 855 * @return false|string group id 856 */ 857 protected function _getGroupID($group) { 858 if($this->dbcon) { 859 $sql = str_replace('%{group}', $this->_escape($group), $this->getConf('getGroupID')); 860 $result = $this->_queryDB($sql); 861 return $result === false ? false : $result[0]['id']; 862 } 863 return false; 864 } 865 866 /** 867 * Opens a connection to a database and saves the handle for further 868 * usage in the object. The successful call to this functions is 869 * essential for most functions in this object. 870 * 871 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 872 * 873 * @return bool 874 */ 875 protected function _openDB() { 876 if(!$this->dbcon) { 877 $con = @mysql_connect($this->getConf('server'), $this->getConf('user'), conf_decodeString($this->getConf('password'))); 878 if($con) { 879 if((mysql_select_db($this->getConf('database'), $con))) { 880 if((preg_match('/^(\d+)\.(\d+)\.(\d+).*/', mysql_get_server_info($con), $result)) == 1) { 881 $this->dbver = $result[1]; 882 $this->dbrev = $result[2]; 883 $this->dbsub = $result[3]; 884 } 885 $this->dbcon = $con; 886 if($this->getConf('charset')) { 887 mysql_query('SET CHARACTER SET "'.$this->getConf('charset').'"', $con); 888 } 889 return true; // connection and database successfully opened 890 } else { 891 mysql_close($con); 892 $this->_debug("MySQL err: No access to database {$this->getConf('database')}.", -1, __LINE__, __FILE__); 893 } 894 } else { 895 $this->_debug( 896 "MySQL err: Connection to {$this->getConf('user')}@{$this->getConf('server')} not possible.", 897 -1, __LINE__, __FILE__ 898 ); 899 } 900 901 return false; // connection failed 902 } 903 return true; // connection already open 904 } 905 906 /** 907 * Closes a database connection. 908 * 909 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 910 */ 911 protected function _closeDB() { 912 if($this->dbcon) { 913 mysql_close($this->dbcon); 914 $this->dbcon = 0; 915 } 916 } 917 918 /** 919 * Sends a SQL query to the database and transforms the result into 920 * an associative array. 921 * 922 * This function is only able to handle queries that returns a 923 * table such as SELECT. 924 * 925 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 926 * 927 * @param string $query SQL string that contains the query 928 * @return array|false with the result table 929 */ 930 protected function _queryDB($query) { 931 if($this->getConf('debug') >= 2) { 932 msg('MySQL query: '.hsc($query), 0, __LINE__, __FILE__); 933 } 934 935 $resultarray = array(); 936 if($this->dbcon) { 937 $result = @mysql_query($query, $this->dbcon); 938 if($result) { 939 while(($t = mysql_fetch_assoc($result)) !== false) 940 $resultarray[] = $t; 941 mysql_free_result($result); 942 return $resultarray; 943 } 944 $this->_debug('MySQL err: '.mysql_error($this->dbcon), -1, __LINE__, __FILE__); 945 } 946 return false; 947 } 948 949 /** 950 * Sends a SQL query to the database 951 * 952 * This function is only able to handle queries that returns 953 * either nothing or an id value such as INPUT, DELETE, UPDATE, etc. 954 * 955 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 956 * 957 * @param string $query SQL string that contains the query 958 * @return int|bool insert id or 0, false on error 959 */ 960 protected function _modifyDB($query) { 961 if($this->getConf('debug') >= 2) { 962 msg('MySQL query: '.hsc($query), 0, __LINE__, __FILE__); 963 } 964 965 if($this->dbcon) { 966 $result = @mysql_query($query, $this->dbcon); 967 if($result) { 968 $rc = mysql_insert_id($this->dbcon); //give back ID on insert 969 if($rc !== false) return $rc; 970 } 971 $this->_debug('MySQL err: '.mysql_error($this->dbcon), -1, __LINE__, __FILE__); 972 } 973 return false; 974 } 975 976 /** 977 * Locked a list of tables for exclusive access so that modifications 978 * to the database can't be disturbed by other threads. The list 979 * could be set with $conf['plugin']['authmysql']['TablesToLock'] = array() 980 * 981 * If aliases for tables are used in SQL statements, also this aliases 982 * must be locked. For eg. you use a table 'user' and the alias 'u' in 983 * some sql queries, the array must looks like this (order is important): 984 * array("user", "user AS u"); 985 * 986 * MySQL V3 is not able to handle transactions with COMMIT/ROLLBACK 987 * so that this functionality is simulated by this function. Nevertheless 988 * it is not as powerful as transactions, it is a good compromise in safty. 989 * 990 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 991 * 992 * @param string $mode could be 'READ' or 'WRITE' 993 * @return bool 994 */ 995 protected function _lockTables($mode) { 996 if($this->dbcon) { 997 $ttl = $this->getConf('TablesToLock'); 998 if(is_array($ttl) && !empty($ttl)) { 999 if($mode == "READ" || $mode == "WRITE") { 1000 $sql = "LOCK TABLES "; 1001 $cnt = 0; 1002 foreach($ttl as $table) { 1003 if($cnt++ != 0) $sql .= ", "; 1004 $sql .= "$table $mode"; 1005 } 1006 $this->_modifyDB($sql); 1007 return true; 1008 } 1009 } 1010 } 1011 return false; 1012 } 1013 1014 /** 1015 * Unlock locked tables. All existing locks of this thread will be 1016 * abrogated. 1017 * 1018 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 1019 * 1020 * @return bool 1021 */ 1022 protected function _unlockTables() { 1023 if($this->dbcon) { 1024 $this->_modifyDB("UNLOCK TABLES"); 1025 return true; 1026 } 1027 return false; 1028 } 1029 1030 /** 1031 * Transforms the filter settings in an filter string for a SQL database 1032 * The database connection must already be established, otherwise the 1033 * original SQL string without filter criteria will be returned. 1034 * 1035 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 1036 * 1037 * @param string $sql SQL string to which the $filter criteria should be added 1038 * @param array $filter array of filter criteria as pairs of item and pattern 1039 * @return string SQL string with attached $filter criteria on success, original SQL string on error 1040 */ 1041 protected function _createSQLFilter($sql, $filter) { 1042 $SQLfilter = ""; 1043 $cnt = 0; 1044 1045 if($this->dbcon) { 1046 foreach($filter as $item => $pattern) { 1047 $tmp = '%'.$this->_escape($pattern).'%'; 1048 if($item == 'user') { 1049 if($cnt++ > 0) $SQLfilter .= " AND "; 1050 $SQLfilter .= str_replace('%{user}', $tmp, $this->getConf('FilterLogin')); 1051 } else if($item == 'name') { 1052 if($cnt++ > 0) $SQLfilter .= " AND "; 1053 $SQLfilter .= str_replace('%{name}', $tmp, $this->getConf('FilterName')); 1054 } else if($item == 'mail') { 1055 if($cnt++ > 0) $SQLfilter .= " AND "; 1056 $SQLfilter .= str_replace('%{email}', $tmp, $this->getConf('FilterEmail')); 1057 } else if($item == 'grps') { 1058 if($cnt++ > 0) $SQLfilter .= " AND "; 1059 $SQLfilter .= str_replace('%{group}', $tmp, $this->getConf('FilterGroup')); 1060 } 1061 } 1062 1063 // we have to check SQLfilter here and must not use $cnt because if 1064 // any of cnf['Filter????'] is not defined, a malformed SQL string 1065 // would be generated. 1066 1067 if(strlen($SQLfilter)) { 1068 $glue = strpos(strtolower($sql), "where") ? " AND " : " WHERE "; 1069 $sql = $sql.$glue.$SQLfilter; 1070 } 1071 } 1072 1073 return $sql; 1074 } 1075 1076 /** 1077 * Escape a string for insertion into the database 1078 * 1079 * @author Andreas Gohr <andi@splitbrain.org> 1080 * 1081 * @param string $string The string to escape 1082 * @param boolean $like Escape wildcard chars as well? 1083 * @return string 1084 */ 1085 protected function _escape($string, $like = false) { 1086 if($this->dbcon) { 1087 $string = mysql_real_escape_string($string, $this->dbcon); 1088 } else { 1089 $string = addslashes($string); 1090 } 1091 if($like) { 1092 $string = addcslashes($string, '%_'); 1093 } 1094 return $string; 1095 } 1096 1097 /** 1098 * Wrapper around msg() but outputs only when debug is enabled 1099 * 1100 * @param string $message 1101 * @param int $err 1102 * @param int $line 1103 * @param string $file 1104 * @return void 1105 */ 1106 protected function _debug($message, $err, $line, $file) { 1107 if(!$this->getConf('debug')) return; 1108 msg($message, $err, $line, $file); 1109 } 1110 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body