1f64dbc90SAndreas Gohr<?php 2d4f83172SAndreas Gohr 38553d24dSAndreas Gohruse dokuwiki\Extension\AuthPlugin; 4ab9790caSAndreas Gohruse dokuwiki\PassHash; 50489c64bSMoisés Braga Ribeirouse dokuwiki\Utf8\Sort; 60489c64bSMoisés Braga Ribeiro 7f64dbc90SAndreas Gohr/** 8f64dbc90SAndreas Gohr * DokuWiki Plugin authpdo (Auth Component) 9f64dbc90SAndreas Gohr * 10f64dbc90SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 11f64dbc90SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 12f64dbc90SAndreas Gohr */ 13f64dbc90SAndreas Gohr 140d586afdSAndreas Gohr/** 150d586afdSAndreas Gohr * Class auth_plugin_authpdo 160d586afdSAndreas Gohr */ 178553d24dSAndreas Gohrclass auth_plugin_authpdo extends AuthPlugin 183213bf4eSAndreas Gohr{ 19f64dbc90SAndreas Gohr /** @var PDO */ 20f64dbc90SAndreas Gohr protected $pdo; 21f64dbc90SAndreas Gohr 220cec3e2aSAndreas Gohr /** @var null|array The list of all groups */ 23ab9790caSAndreas Gohr protected $groupcache; 240cec3e2aSAndreas Gohr 25f64dbc90SAndreas Gohr /** 26f64dbc90SAndreas Gohr * Constructor. 27f64dbc90SAndreas Gohr */ 283213bf4eSAndreas Gohr public function __construct() 293213bf4eSAndreas Gohr { 30f64dbc90SAndreas Gohr parent::__construct(); // for compatibility 31f64dbc90SAndreas Gohr 32f64dbc90SAndreas Gohr if (!class_exists('PDO')) { 333213bf4eSAndreas Gohr $this->debugMsg('PDO extension for PHP not found.', -1, __LINE__); 34f64dbc90SAndreas Gohr $this->success = false; 35f64dbc90SAndreas Gohr return; 36f64dbc90SAndreas Gohr } 37f64dbc90SAndreas Gohr 38f64dbc90SAndreas Gohr if (!$this->getConf('dsn')) { 393213bf4eSAndreas Gohr $this->debugMsg('No DSN specified', -1, __LINE__); 40f64dbc90SAndreas Gohr $this->success = false; 41f64dbc90SAndreas Gohr return; 42f64dbc90SAndreas Gohr } 43f64dbc90SAndreas Gohr 44f64dbc90SAndreas Gohr try { 45f64dbc90SAndreas Gohr $this->pdo = new PDO( 46f64dbc90SAndreas Gohr $this->getConf('dsn'), 47f64dbc90SAndreas Gohr $this->getConf('user'), 480cc11d97SAndreas Gohr conf_decodeString($this->getConf('pass')), 49ab9790caSAndreas Gohr [ 5070a89417SAndreas Gohr PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array 5170a89417SAndreas Gohr PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names 525de3a6a5SAndreas Gohr PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes 53ab9790caSAndreas Gohr ] 54f64dbc90SAndreas Gohr ); 55f64dbc90SAndreas Gohr } catch (PDOException $e) { 563213bf4eSAndreas Gohr $this->debugMsg($e); 57c27579a6SAndreas Gohr msg($this->getLang('connectfail'), -1); 58f64dbc90SAndreas Gohr $this->success = false; 59f64dbc90SAndreas Gohr return; 60f64dbc90SAndreas Gohr } 61f64dbc90SAndreas Gohr 620d586afdSAndreas Gohr // can Users be created? 633213bf4eSAndreas Gohr $this->cando['addUser'] = $this->checkConfig( 64ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'select-groups', 'insert-user', 'insert-group', 'join-group'] 650d586afdSAndreas Gohr ); 66f64dbc90SAndreas Gohr 670d586afdSAndreas Gohr // can Users be deleted? 683213bf4eSAndreas Gohr $this->cando['delUser'] = $this->checkConfig( 69ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'select-groups', 'leave-group', 'delete-user'] 700d586afdSAndreas Gohr ); 710d586afdSAndreas Gohr 720d586afdSAndreas Gohr // can login names be changed? 733213bf4eSAndreas Gohr $this->cando['modLogin'] = $this->checkConfig( 74ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'update-user-login'] 750d586afdSAndreas Gohr ); 760d586afdSAndreas Gohr 770d586afdSAndreas Gohr // can passwords be changed? 783213bf4eSAndreas Gohr $this->cando['modPass'] = $this->checkConfig( 79ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'update-user-pass'] 800d586afdSAndreas Gohr ); 810d586afdSAndreas Gohr 82ad4d5631SAndreas Gohr // can real names be changed? 833213bf4eSAndreas Gohr $this->cando['modName'] = $this->checkConfig( 84ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'update-user-info:name'] 85ad4d5631SAndreas Gohr ); 86ad4d5631SAndreas Gohr 87ad4d5631SAndreas Gohr // can real email be changed? 883213bf4eSAndreas Gohr $this->cando['modMail'] = $this->checkConfig( 89ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'update-user-info:mail'] 900d586afdSAndreas Gohr ); 910d586afdSAndreas Gohr 920d586afdSAndreas Gohr // can groups be changed? 933213bf4eSAndreas Gohr $this->cando['modGroups'] = $this->checkConfig( 94ab9790caSAndreas Gohr ['select-user', 'select-user-groups', 'select-groups', 'leave-group', 'join-group', 'insert-group'] 950d586afdSAndreas Gohr ); 960d586afdSAndreas Gohr 970d586afdSAndreas Gohr // can a filtered list of users be retrieved? 983213bf4eSAndreas Gohr $this->cando['getUsers'] = $this->checkConfig( 99ab9790caSAndreas Gohr ['list-users'] 1000d586afdSAndreas Gohr ); 1010d586afdSAndreas Gohr 1020d586afdSAndreas Gohr // can the number of users be retrieved? 1033213bf4eSAndreas Gohr $this->cando['getUserCount'] = $this->checkConfig( 104ab9790caSAndreas Gohr ['count-users'] 1050d586afdSAndreas Gohr ); 1060d586afdSAndreas Gohr 1070d586afdSAndreas Gohr // can a list of available groups be retrieved? 1083213bf4eSAndreas Gohr $this->cando['getGroups'] = $this->checkConfig( 109ab9790caSAndreas Gohr ['select-groups'] 1100d586afdSAndreas Gohr ); 1110d586afdSAndreas Gohr 112f64dbc90SAndreas Gohr $this->success = true; 113f64dbc90SAndreas Gohr } 114f64dbc90SAndreas Gohr 115f64dbc90SAndreas Gohr /** 116f64dbc90SAndreas Gohr * Check user+password 117f64dbc90SAndreas Gohr * 118f64dbc90SAndreas Gohr * @param string $user the user name 119f64dbc90SAndreas Gohr * @param string $pass the clear text password 120f64dbc90SAndreas Gohr * @return bool 121f64dbc90SAndreas Gohr */ 1223213bf4eSAndreas Gohr public function checkPass($user, $pass) 1233213bf4eSAndreas Gohr { 124f64dbc90SAndreas Gohr 1253213bf4eSAndreas Gohr $userdata = $this->selectUser($user); 126*9c952d3bSAndreas Gohr if ($userdata === false) { 127*9c952d3bSAndreas Gohr auth_cryptPassword('dummy'); // run a crypt op to prevent timing attacks 128*9c952d3bSAndreas Gohr return false; 129*9c952d3bSAndreas Gohr } 130f64dbc90SAndreas Gohr 131397d62a2SAndreas Gohr // password checking done in SQL? 132ab9790caSAndreas Gohr if ($this->checkConfig(['check-pass'])) { 133397d62a2SAndreas Gohr $userdata['clear'] = $pass; 134397d62a2SAndreas Gohr $userdata['hash'] = auth_cryptPassword($pass); 1353213bf4eSAndreas Gohr $result = $this->query($this->getConf('check-pass'), $userdata); 136397d62a2SAndreas Gohr if ($result === false) return false; 137397d62a2SAndreas Gohr return (count($result) == 1); 138397d62a2SAndreas Gohr } 139397d62a2SAndreas Gohr 140397d62a2SAndreas Gohr // we do password checking on our own 141397d62a2SAndreas Gohr if (isset($userdata['hash'])) { 142f64dbc90SAndreas Gohr // hashed password 143ab9790caSAndreas Gohr $passhash = new PassHash(); 144397d62a2SAndreas Gohr return $passhash->verify_hash($pass, $userdata['hash']); 145f64dbc90SAndreas Gohr } else { 146f64dbc90SAndreas Gohr // clear text password in the database O_o 147d5c0422fSAndreas Gohr return ($pass === $userdata['clear']); 148f64dbc90SAndreas Gohr } 149f64dbc90SAndreas Gohr } 150f64dbc90SAndreas Gohr 151f64dbc90SAndreas Gohr /** 152f64dbc90SAndreas Gohr * Return user info 153f64dbc90SAndreas Gohr * 154f64dbc90SAndreas Gohr * Returns info about the given user needs to contain 155f64dbc90SAndreas Gohr * at least these fields: 156f64dbc90SAndreas Gohr * 157f64dbc90SAndreas Gohr * name string full name of the user 158f64dbc90SAndreas Gohr * mail string email addres of the user 159f64dbc90SAndreas Gohr * grps array list of groups the user is in 160f64dbc90SAndreas Gohr * 161f64dbc90SAndreas Gohr * @param string $user the user name 162f64dbc90SAndreas Gohr * @param bool $requireGroups whether or not the returned data must include groups 163358942b5SAndreas Gohr * @return array|bool containing user data or false 164f64dbc90SAndreas Gohr */ 1653213bf4eSAndreas Gohr public function getUserData($user, $requireGroups = true) 1663213bf4eSAndreas Gohr { 1673213bf4eSAndreas Gohr $data = $this->selectUser($user); 168f64dbc90SAndreas Gohr if ($data == false) return false; 169f64dbc90SAndreas Gohr 17070a89417SAndreas Gohr if (isset($data['hash'])) unset($data['hash']); 17170a89417SAndreas Gohr if (isset($data['clean'])) unset($data['clean']); 172f64dbc90SAndreas Gohr 17370a89417SAndreas Gohr if ($requireGroups) { 1743213bf4eSAndreas Gohr $data['grps'] = $this->selectUserGroups($data); 1755de3a6a5SAndreas Gohr if ($data['grps'] === false) return false; 176f64dbc90SAndreas Gohr } 177f64dbc90SAndreas Gohr 178f64dbc90SAndreas Gohr return $data; 179f64dbc90SAndreas Gohr } 180f64dbc90SAndreas Gohr 181f64dbc90SAndreas Gohr /** 182f64dbc90SAndreas Gohr * Create a new User [implement only where required/possible] 183f64dbc90SAndreas Gohr * 184f64dbc90SAndreas Gohr * Returns false if the user already exists, null when an error 185f64dbc90SAndreas Gohr * occurred and true if everything went well. 186f64dbc90SAndreas Gohr * 187f64dbc90SAndreas Gohr * The new user HAS TO be added to the default group by this 188f64dbc90SAndreas Gohr * function! 189f64dbc90SAndreas Gohr * 190f64dbc90SAndreas Gohr * Set addUser capability when implemented 191f64dbc90SAndreas Gohr * 192f64dbc90SAndreas Gohr * @param string $user 1935de3a6a5SAndreas Gohr * @param string $clear 194f64dbc90SAndreas Gohr * @param string $name 195f64dbc90SAndreas Gohr * @param string $mail 196f64dbc90SAndreas Gohr * @param null|array $grps 197f64dbc90SAndreas Gohr * @return bool|null 198f64dbc90SAndreas Gohr */ 1993213bf4eSAndreas Gohr public function createUser($user, $clear, $name, $mail, $grps = null) 2003213bf4eSAndreas Gohr { 2015de3a6a5SAndreas Gohr global $conf; 2025de3a6a5SAndreas Gohr 2035de3a6a5SAndreas Gohr if (($info = $this->getUserData($user, false)) !== false) { 2045de3a6a5SAndreas Gohr msg($this->getLang('userexists'), -1); 2055de3a6a5SAndreas Gohr return false; // user already exists 2065de3a6a5SAndreas Gohr } 2075de3a6a5SAndreas Gohr 2085de3a6a5SAndreas Gohr // prepare data 209ab9790caSAndreas Gohr if ($grps == null) $grps = []; 21007a11e2aSAndreas Gohr array_unshift($grps, $conf['defaultgroup']); 2115de3a6a5SAndreas Gohr $grps = array_unique($grps); 2125de3a6a5SAndreas Gohr $hash = auth_cryptPassword($clear); 213ab9790caSAndreas Gohr $userdata = ['user' => $user, 'clear' => $clear, 'hash' => $hash, 'name' => $name, 'mail' => $mail]; 2145de3a6a5SAndreas Gohr 2155de3a6a5SAndreas Gohr // action protected by transaction 2165de3a6a5SAndreas Gohr $this->pdo->beginTransaction(); 2175de3a6a5SAndreas Gohr { 2185de3a6a5SAndreas Gohr // insert the user 2193213bf4eSAndreas Gohr $ok = $this->query($this->getConf('insert-user'), $userdata); 2205de3a6a5SAndreas Gohr if ($ok === false) goto FAIL; 2215de3a6a5SAndreas Gohr $userdata = $this->getUserData($user, false); 2225de3a6a5SAndreas Gohr if ($userdata === false) goto FAIL; 2235de3a6a5SAndreas Gohr 2245de3a6a5SAndreas Gohr // create all groups that do not exist, the refetch the groups 2253213bf4eSAndreas Gohr $allgroups = $this->selectGroups(); 2265de3a6a5SAndreas Gohr foreach ($grps as $group) { 2275de3a6a5SAndreas Gohr if (!isset($allgroups[$group])) { 2286459f496SAndreas Gohr $ok = $this->addGroup($group); 2295de3a6a5SAndreas Gohr if ($ok === false) goto FAIL; 2305de3a6a5SAndreas Gohr } 2315de3a6a5SAndreas Gohr } 2323213bf4eSAndreas Gohr $allgroups = $this->selectGroups(); 2335de3a6a5SAndreas Gohr 2345de3a6a5SAndreas Gohr // add user to the groups 2355de3a6a5SAndreas Gohr foreach ($grps as $group) { 2363213bf4eSAndreas Gohr $ok = $this->joinGroup($userdata, $allgroups[$group]); 2375de3a6a5SAndreas Gohr if ($ok === false) goto FAIL; 2385de3a6a5SAndreas Gohr } 2395de3a6a5SAndreas Gohr } 2405de3a6a5SAndreas Gohr $this->pdo->commit(); 2415de3a6a5SAndreas Gohr return true; 2425de3a6a5SAndreas Gohr 2435de3a6a5SAndreas Gohr // something went wrong, rollback 2445de3a6a5SAndreas Gohr FAIL: 2455de3a6a5SAndreas Gohr $this->pdo->rollBack(); 2463213bf4eSAndreas Gohr $this->debugMsg('Transaction rolled back', 0, __LINE__); 247c27579a6SAndreas Gohr msg($this->getLang('writefail'), -1); 2485de3a6a5SAndreas Gohr return null; // return error 2495de3a6a5SAndreas Gohr } 250f64dbc90SAndreas Gohr 251f64dbc90SAndreas Gohr /** 2524fb8dfabSAndreas Gohr * Modify user data 253f64dbc90SAndreas Gohr * 254f64dbc90SAndreas Gohr * @param string $user nick of the user to be changed 255f64dbc90SAndreas Gohr * @param array $changes array of field/value pairs to be changed (password will be clear text) 256f64dbc90SAndreas Gohr * @return bool 257f64dbc90SAndreas Gohr */ 2583213bf4eSAndreas Gohr public function modifyUser($user, $changes) 2593213bf4eSAndreas Gohr { 2604fb8dfabSAndreas Gohr // secure everything in transaction 2614fb8dfabSAndreas Gohr $this->pdo->beginTransaction(); 2624fb8dfabSAndreas Gohr { 2634fb8dfabSAndreas Gohr $olddata = $this->getUserData($user); 2644fb8dfabSAndreas Gohr $oldgroups = $olddata['grps']; 2654fb8dfabSAndreas Gohr unset($olddata['grps']); 2664fb8dfabSAndreas Gohr 2674fb8dfabSAndreas Gohr // changing the user name? 2684fb8dfabSAndreas Gohr if (isset($changes['user'])) { 2694fb8dfabSAndreas Gohr if ($this->getUserData($changes['user'], false)) goto FAIL; 2704fb8dfabSAndreas Gohr $params = $olddata; 2714fb8dfabSAndreas Gohr $params['newlogin'] = $changes['user']; 2724fb8dfabSAndreas Gohr 2733213bf4eSAndreas Gohr $ok = $this->query($this->getConf('update-user-login'), $params); 2744fb8dfabSAndreas Gohr if ($ok === false) goto FAIL; 2754fb8dfabSAndreas Gohr } 2764fb8dfabSAndreas Gohr 2774fb8dfabSAndreas Gohr // changing the password? 2784fb8dfabSAndreas Gohr if (isset($changes['pass'])) { 2794fb8dfabSAndreas Gohr $params = $olddata; 2804fb8dfabSAndreas Gohr $params['clear'] = $changes['pass']; 2814fb8dfabSAndreas Gohr $params['hash'] = auth_cryptPassword($changes['pass']); 2824fb8dfabSAndreas Gohr 2833213bf4eSAndreas Gohr $ok = $this->query($this->getConf('update-user-pass'), $params); 2844fb8dfabSAndreas Gohr if ($ok === false) goto FAIL; 2854fb8dfabSAndreas Gohr } 2864fb8dfabSAndreas Gohr 2874fb8dfabSAndreas Gohr // changing info? 2884fb8dfabSAndreas Gohr if (isset($changes['mail']) || isset($changes['name'])) { 2894fb8dfabSAndreas Gohr $params = $olddata; 2904fb8dfabSAndreas Gohr if (isset($changes['mail'])) $params['mail'] = $changes['mail']; 2914fb8dfabSAndreas Gohr if (isset($changes['name'])) $params['name'] = $changes['name']; 2924fb8dfabSAndreas Gohr 2933213bf4eSAndreas Gohr $ok = $this->query($this->getConf('update-user-info'), $params); 2944fb8dfabSAndreas Gohr if ($ok === false) goto FAIL; 2954fb8dfabSAndreas Gohr } 2964fb8dfabSAndreas Gohr 2974fb8dfabSAndreas Gohr // changing groups? 2984fb8dfabSAndreas Gohr if (isset($changes['grps'])) { 2993213bf4eSAndreas Gohr $allgroups = $this->selectGroups(); 3004fb8dfabSAndreas Gohr 3014fb8dfabSAndreas Gohr // remove membership for previous groups 3024fb8dfabSAndreas Gohr foreach ($oldgroups as $group) { 303358942b5SAndreas Gohr if (!in_array($group, $changes['grps']) && isset($allgroups[$group])) { 3043213bf4eSAndreas Gohr $ok = $this->leaveGroup($olddata, $allgroups[$group]); 3054fb8dfabSAndreas Gohr if ($ok === false) goto FAIL; 3064fb8dfabSAndreas Gohr } 3074fb8dfabSAndreas Gohr } 3084fb8dfabSAndreas Gohr 3094fb8dfabSAndreas Gohr // create all new groups that are missing 3104fb8dfabSAndreas Gohr $added = 0; 3114fb8dfabSAndreas Gohr foreach ($changes['grps'] as $group) { 3124fb8dfabSAndreas Gohr if (!isset($allgroups[$group])) { 3136459f496SAndreas Gohr $ok = $this->addGroup($group); 3144fb8dfabSAndreas Gohr if ($ok === false) goto FAIL; 3154fb8dfabSAndreas Gohr $added++; 3164fb8dfabSAndreas Gohr } 3174fb8dfabSAndreas Gohr } 3184fb8dfabSAndreas Gohr // reload group info 3193213bf4eSAndreas Gohr if ($added > 0) $allgroups = $this->selectGroups(); 3204fb8dfabSAndreas Gohr 3214fb8dfabSAndreas Gohr // add membership for new groups 3224fb8dfabSAndreas Gohr foreach ($changes['grps'] as $group) { 3234fb8dfabSAndreas Gohr if (!in_array($group, $oldgroups)) { 3243213bf4eSAndreas Gohr $ok = $this->joinGroup($olddata, $allgroups[$group]); 3254fb8dfabSAndreas Gohr if ($ok === false) goto FAIL; 3264fb8dfabSAndreas Gohr } 3274fb8dfabSAndreas Gohr } 3284fb8dfabSAndreas Gohr } 3294fb8dfabSAndreas Gohr 3304fb8dfabSAndreas Gohr } 3314fb8dfabSAndreas Gohr $this->pdo->commit(); 3324fb8dfabSAndreas Gohr return true; 3334fb8dfabSAndreas Gohr 3344fb8dfabSAndreas Gohr // something went wrong, rollback 3354fb8dfabSAndreas Gohr FAIL: 3364fb8dfabSAndreas Gohr $this->pdo->rollBack(); 3373213bf4eSAndreas Gohr $this->debugMsg('Transaction rolled back', 0, __LINE__); 338c27579a6SAndreas Gohr msg($this->getLang('writefail'), -1); 3394fb8dfabSAndreas Gohr return false; // return error 3404fb8dfabSAndreas Gohr } 341f64dbc90SAndreas Gohr 342f64dbc90SAndreas Gohr /** 343e19be516SAndreas Gohr * Delete one or more users 344f64dbc90SAndreas Gohr * 345f64dbc90SAndreas Gohr * Set delUser capability when implemented 346f64dbc90SAndreas Gohr * 347f64dbc90SAndreas Gohr * @param array $users 348f64dbc90SAndreas Gohr * @return int number of users deleted 349f64dbc90SAndreas Gohr */ 3503213bf4eSAndreas Gohr public function deleteUsers($users) 3513213bf4eSAndreas Gohr { 352e19be516SAndreas Gohr $count = 0; 353e19be516SAndreas Gohr foreach ($users as $user) { 3543213bf4eSAndreas Gohr if ($this->deleteUser($user)) $count++; 355e19be516SAndreas Gohr } 356e19be516SAndreas Gohr return $count; 357e19be516SAndreas Gohr } 358f64dbc90SAndreas Gohr 359f64dbc90SAndreas Gohr /** 360f64dbc90SAndreas Gohr * Bulk retrieval of user data [implement only where required/possible] 361f64dbc90SAndreas Gohr * 362f64dbc90SAndreas Gohr * Set getUsers capability when implemented 363f64dbc90SAndreas Gohr * 364f64dbc90SAndreas Gohr * @param int $start index of first user to be returned 365f64dbc90SAndreas Gohr * @param int $limit max number of users to be returned 366f64dbc90SAndreas Gohr * @param array $filter array of field/pattern pairs, null for no filter 367f64dbc90SAndreas Gohr * @return array list of userinfo (refer getUserData for internal userinfo details) 368f64dbc90SAndreas Gohr */ 3693213bf4eSAndreas Gohr public function retrieveUsers($start = 0, $limit = -1, $filter = null) 3703213bf4eSAndreas Gohr { 3716459f496SAndreas Gohr if ($limit < 0) $limit = 10000; // we don't support no limit 372ab9790caSAndreas Gohr if (is_null($filter)) $filter = []; 3736459f496SAndreas Gohr 37412c7f5c3SAndreas Gohr if (isset($filter['grps'])) $filter['group'] = $filter['grps']; 375ab9790caSAndreas Gohr foreach (['user', 'name', 'mail', 'group'] as $key) { 3766459f496SAndreas Gohr if (!isset($filter[$key])) { 3776459f496SAndreas Gohr $filter[$key] = '%'; 3786459f496SAndreas Gohr } else { 3796459f496SAndreas Gohr $filter[$key] = '%' . $filter[$key] . '%'; 3806459f496SAndreas Gohr } 3816459f496SAndreas Gohr } 38214119d44SAndreas Gohr $filter['start'] = (int)$start; 38314119d44SAndreas Gohr $filter['end'] = (int)$start + $limit; 38414119d44SAndreas Gohr $filter['limit'] = (int)$limit; 3856459f496SAndreas Gohr 3863213bf4eSAndreas Gohr $result = $this->query($this->getConf('list-users'), $filter); 387ab9790caSAndreas Gohr if (!$result) return []; 388ab9790caSAndreas Gohr $users = []; 38988ca2487SPhy if (is_array($result)) { 3906459f496SAndreas Gohr foreach ($result as $row) { 3916459f496SAndreas Gohr if (!isset($row['user'])) { 39231a58abaSAndreas Gohr $this->debugMsg("list-users statement did not return 'user' attribute", -1, __LINE__); 393ab9790caSAndreas Gohr return []; 3946459f496SAndreas Gohr } 3953e2a8145SAndreas Gohr $users[] = $this->getUserData($row['user']); 3966459f496SAndreas Gohr } 39788ca2487SPhy } else { 39831a58abaSAndreas Gohr $this->debugMsg("list-users statement did not return a list of result", -1, __LINE__); 39988ca2487SPhy } 4006459f496SAndreas Gohr return $users; 4016459f496SAndreas Gohr } 402f64dbc90SAndreas Gohr 403f64dbc90SAndreas Gohr /** 404f64dbc90SAndreas Gohr * Return a count of the number of user which meet $filter criteria 405f64dbc90SAndreas Gohr * 406f64dbc90SAndreas Gohr * @param array $filter array of field/pattern pairs, empty array for no filter 407f64dbc90SAndreas Gohr * @return int 408f64dbc90SAndreas Gohr */ 409ab9790caSAndreas Gohr public function getUserCount($filter = []) 4103213bf4eSAndreas Gohr { 411ab9790caSAndreas Gohr if (is_null($filter)) $filter = []; 4126459f496SAndreas Gohr 41312c7f5c3SAndreas Gohr if (isset($filter['grps'])) $filter['group'] = $filter['grps']; 414ab9790caSAndreas Gohr foreach (['user', 'name', 'mail', 'group'] as $key) { 4156459f496SAndreas Gohr if (!isset($filter[$key])) { 4166459f496SAndreas Gohr $filter[$key] = '%'; 4176459f496SAndreas Gohr } else { 4186459f496SAndreas Gohr $filter[$key] = '%' . $filter[$key] . '%'; 4196459f496SAndreas Gohr } 4206459f496SAndreas Gohr } 4216459f496SAndreas Gohr 4223213bf4eSAndreas Gohr $result = $this->query($this->getConf('count-users'), $filter); 4236459f496SAndreas Gohr if (!$result || !isset($result[0]['count'])) { 4243213bf4eSAndreas Gohr $this->debugMsg("Statement did not return 'count' attribute", -1, __LINE__); 4256459f496SAndreas Gohr } 426f3c1c207SAndreas Gohr return (int)$result[0]['count']; 4276459f496SAndreas Gohr } 428f64dbc90SAndreas Gohr 429f64dbc90SAndreas Gohr /** 4306459f496SAndreas Gohr * Create a new group with the given name 431f64dbc90SAndreas Gohr * 432f64dbc90SAndreas Gohr * @param string $group 433f64dbc90SAndreas Gohr * @return bool 434f64dbc90SAndreas Gohr */ 4353213bf4eSAndreas Gohr public function addGroup($group) 4363213bf4eSAndreas Gohr { 4376459f496SAndreas Gohr $sql = $this->getConf('insert-group'); 4386459f496SAndreas Gohr 439ab9790caSAndreas Gohr $result = $this->query($sql, [':group' => $group]); 4403213bf4eSAndreas Gohr $this->clearGroupCache(); 4416459f496SAndreas Gohr if ($result === false) return false; 4426459f496SAndreas Gohr return true; 4436459f496SAndreas Gohr } 444f64dbc90SAndreas Gohr 445f64dbc90SAndreas Gohr /** 4465de3a6a5SAndreas Gohr * Retrieve groups 447f64dbc90SAndreas Gohr * 448f64dbc90SAndreas Gohr * Set getGroups capability when implemented 449f64dbc90SAndreas Gohr * 450f64dbc90SAndreas Gohr * @param int $start 451f64dbc90SAndreas Gohr * @param int $limit 452f64dbc90SAndreas Gohr * @return array 453f64dbc90SAndreas Gohr */ 4543213bf4eSAndreas Gohr public function retrieveGroups($start = 0, $limit = 0) 4553213bf4eSAndreas Gohr { 4563213bf4eSAndreas Gohr $groups = array_keys($this->selectGroups()); 457ab9790caSAndreas Gohr if ($groups === false) return []; 458f64dbc90SAndreas Gohr 4595de3a6a5SAndreas Gohr if (!$limit) { 4605de3a6a5SAndreas Gohr return array_splice($groups, $start); 4615de3a6a5SAndreas Gohr } else { 4625de3a6a5SAndreas Gohr return array_splice($groups, $start, $limit); 463f64dbc90SAndreas Gohr } 464f64dbc90SAndreas Gohr } 465f64dbc90SAndreas Gohr 466f64dbc90SAndreas Gohr /** 467f64dbc90SAndreas Gohr * Select data of a specified user 468f64dbc90SAndreas Gohr * 4695de3a6a5SAndreas Gohr * @param string $user the user name 4705de3a6a5SAndreas Gohr * @return bool|array user data, false on error 471f64dbc90SAndreas Gohr */ 4723213bf4eSAndreas Gohr protected function selectUser($user) 4733213bf4eSAndreas Gohr { 474f64dbc90SAndreas Gohr $sql = $this->getConf('select-user'); 475f64dbc90SAndreas Gohr 476ab9790caSAndreas Gohr $result = $this->query($sql, [':user' => $user]); 47770a89417SAndreas Gohr if (!$result) return false; 478f64dbc90SAndreas Gohr 47970a89417SAndreas Gohr if (count($result) > 1) { 4803213bf4eSAndreas Gohr $this->debugMsg('Found more than one matching user', -1, __LINE__); 481f64dbc90SAndreas Gohr return false; 482f64dbc90SAndreas Gohr } 483f64dbc90SAndreas Gohr 484f64dbc90SAndreas Gohr $data = array_shift($result); 485f64dbc90SAndreas Gohr $dataok = true; 486f64dbc90SAndreas Gohr 487f64dbc90SAndreas Gohr if (!isset($data['user'])) { 4883213bf4eSAndreas Gohr $this->debugMsg("Statement did not return 'user' attribute", -1, __LINE__); 489f64dbc90SAndreas Gohr $dataok = false; 490f64dbc90SAndreas Gohr } 491ab9790caSAndreas Gohr if (!isset($data['hash']) && !isset($data['clear']) && !$this->checkConfig(['check-pass'])) { 4923213bf4eSAndreas Gohr $this->debugMsg("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__); 493f64dbc90SAndreas Gohr $dataok = false; 494f64dbc90SAndreas Gohr } 495f64dbc90SAndreas Gohr if (!isset($data['name'])) { 4963213bf4eSAndreas Gohr $this->debugMsg("Statement did not return 'name' attribute", -1, __LINE__); 497f64dbc90SAndreas Gohr $dataok = false; 498f64dbc90SAndreas Gohr } 499f64dbc90SAndreas Gohr if (!isset($data['mail'])) { 5003213bf4eSAndreas Gohr $this->debugMsg("Statement did not return 'mail' attribute", -1, __LINE__); 501f64dbc90SAndreas Gohr $dataok = false; 502f64dbc90SAndreas Gohr } 503f64dbc90SAndreas Gohr 504f64dbc90SAndreas Gohr if (!$dataok) return false; 505f64dbc90SAndreas Gohr return $data; 506f64dbc90SAndreas Gohr } 507f64dbc90SAndreas Gohr 508f64dbc90SAndreas Gohr /** 509e19be516SAndreas Gohr * Delete a user after removing all their group memberships 510e19be516SAndreas Gohr * 511e19be516SAndreas Gohr * @param string $user 512e19be516SAndreas Gohr * @return bool true when the user was deleted 513e19be516SAndreas Gohr */ 5143213bf4eSAndreas Gohr protected function deleteUser($user) 5153213bf4eSAndreas Gohr { 516e19be516SAndreas Gohr $this->pdo->beginTransaction(); 517e19be516SAndreas Gohr { 518e19be516SAndreas Gohr $userdata = $this->getUserData($user); 519e19be516SAndreas Gohr if ($userdata === false) goto FAIL; 5203213bf4eSAndreas Gohr $allgroups = $this->selectGroups(); 521e19be516SAndreas Gohr 522e19be516SAndreas Gohr // remove group memberships (ignore errors) 523e19be516SAndreas Gohr foreach ($userdata['grps'] as $group) { 524358942b5SAndreas Gohr if (isset($allgroups[$group])) { 5253213bf4eSAndreas Gohr $this->leaveGroup($userdata, $allgroups[$group]); 526e19be516SAndreas Gohr } 527358942b5SAndreas Gohr } 528e19be516SAndreas Gohr 5293213bf4eSAndreas Gohr $ok = $this->query($this->getConf('delete-user'), $userdata); 530e19be516SAndreas Gohr if ($ok === false) goto FAIL; 531e19be516SAndreas Gohr } 532e19be516SAndreas Gohr $this->pdo->commit(); 533e19be516SAndreas Gohr return true; 534e19be516SAndreas Gohr 535e19be516SAndreas Gohr FAIL: 536e19be516SAndreas Gohr $this->pdo->rollBack(); 537e19be516SAndreas Gohr return false; 538e19be516SAndreas Gohr } 539e19be516SAndreas Gohr 540e19be516SAndreas Gohr /** 54170a89417SAndreas Gohr * Select all groups of a user 54270a89417SAndreas Gohr * 54370a89417SAndreas Gohr * @param array $userdata The userdata as returned by _selectUser() 5445de3a6a5SAndreas Gohr * @return array|bool list of group names, false on error 54570a89417SAndreas Gohr */ 5463213bf4eSAndreas Gohr protected function selectUserGroups($userdata) 5473213bf4eSAndreas Gohr { 54870a89417SAndreas Gohr global $conf; 54970a89417SAndreas Gohr $sql = $this->getConf('select-user-groups'); 5503213bf4eSAndreas Gohr $result = $this->query($sql, $userdata); 5515de3a6a5SAndreas Gohr if ($result === false) return false; 55270a89417SAndreas Gohr 553ab9790caSAndreas Gohr $groups = [$conf['defaultgroup']]; // always add default config 55488ca2487SPhy if (is_array($result)) { 5555de3a6a5SAndreas Gohr foreach ($result as $row) { 5565de3a6a5SAndreas Gohr if (!isset($row['group'])) { 55731a58abaSAndreas Gohr $this->debugMsg("No 'group' field returned in select-user-groups statement", -1, __LINE__); 5585de3a6a5SAndreas Gohr return false; 5595de3a6a5SAndreas Gohr } 56070a89417SAndreas Gohr $groups[] = $row['group']; 56170a89417SAndreas Gohr } 56288ca2487SPhy } else { 56331a58abaSAndreas Gohr $this->debugMsg("select-user-groups statement did not return a list of result", -1, __LINE__); 56488ca2487SPhy } 56570a89417SAndreas Gohr 56670a89417SAndreas Gohr $groups = array_unique($groups); 5670489c64bSMoisés Braga Ribeiro Sort::sort($groups); 56870a89417SAndreas Gohr return $groups; 56970a89417SAndreas Gohr } 57070a89417SAndreas Gohr 57170a89417SAndreas Gohr /** 5725de3a6a5SAndreas Gohr * Select all available groups 5735de3a6a5SAndreas Gohr * 5745de3a6a5SAndreas Gohr * @return array|bool list of all available groups and their properties 5755de3a6a5SAndreas Gohr */ 5763213bf4eSAndreas Gohr protected function selectGroups() 5773213bf4eSAndreas Gohr { 5780cec3e2aSAndreas Gohr if ($this->groupcache) return $this->groupcache; 5790cec3e2aSAndreas Gohr 5805de3a6a5SAndreas Gohr $sql = $this->getConf('select-groups'); 5813213bf4eSAndreas Gohr $result = $this->query($sql); 5825de3a6a5SAndreas Gohr if ($result === false) return false; 5835de3a6a5SAndreas Gohr 584ab9790caSAndreas Gohr $groups = []; 58588ca2487SPhy if (is_array($result)) { 5865de3a6a5SAndreas Gohr foreach ($result as $row) { 5875de3a6a5SAndreas Gohr if (!isset($row['group'])) { 5883213bf4eSAndreas Gohr $this->debugMsg("No 'group' field returned from select-groups statement", -1, __LINE__); 5895de3a6a5SAndreas Gohr return false; 5905de3a6a5SAndreas Gohr } 5915de3a6a5SAndreas Gohr 5925de3a6a5SAndreas Gohr // relayout result with group name as key 5935de3a6a5SAndreas Gohr $group = $row['group']; 5945de3a6a5SAndreas Gohr $groups[$group] = $row; 5955de3a6a5SAndreas Gohr } 59688ca2487SPhy } else { 59731a58abaSAndreas Gohr $this->debugMsg("select-groups statement did not return a list of result", -1, __LINE__); 59888ca2487SPhy } 5995de3a6a5SAndreas Gohr 6000489c64bSMoisés Braga Ribeiro Sort::ksort($groups); 6015de3a6a5SAndreas Gohr return $groups; 6025de3a6a5SAndreas Gohr } 6035de3a6a5SAndreas Gohr 6045de3a6a5SAndreas Gohr /** 6050cec3e2aSAndreas Gohr * Remove all entries from the group cache 6060cec3e2aSAndreas Gohr */ 6073213bf4eSAndreas Gohr protected function clearGroupCache() 6083213bf4eSAndreas Gohr { 6090cec3e2aSAndreas Gohr $this->groupcache = null; 6100cec3e2aSAndreas Gohr } 6110cec3e2aSAndreas Gohr 6120cec3e2aSAndreas Gohr /** 6134fb8dfabSAndreas Gohr * Adds the user to the group 6145de3a6a5SAndreas Gohr * 6155de3a6a5SAndreas Gohr * @param array $userdata all the user data 6165de3a6a5SAndreas Gohr * @param array $groupdata all the group data 6175de3a6a5SAndreas Gohr * @return bool 6185de3a6a5SAndreas Gohr */ 6193213bf4eSAndreas Gohr protected function joinGroup($userdata, $groupdata) 6203213bf4eSAndreas Gohr { 6215de3a6a5SAndreas Gohr $data = array_merge($userdata, $groupdata); 6225de3a6a5SAndreas Gohr $sql = $this->getConf('join-group'); 6233213bf4eSAndreas Gohr $result = $this->query($sql, $data); 6245de3a6a5SAndreas Gohr if ($result === false) return false; 6255de3a6a5SAndreas Gohr return true; 6265de3a6a5SAndreas Gohr } 6275de3a6a5SAndreas Gohr 6285de3a6a5SAndreas Gohr /** 6294fb8dfabSAndreas Gohr * Removes the user from the group 6304fb8dfabSAndreas Gohr * 6314fb8dfabSAndreas Gohr * @param array $userdata all the user data 6324fb8dfabSAndreas Gohr * @param array $groupdata all the group data 6334fb8dfabSAndreas Gohr * @return bool 6344fb8dfabSAndreas Gohr */ 6353213bf4eSAndreas Gohr protected function leaveGroup($userdata, $groupdata) 6363213bf4eSAndreas Gohr { 6374fb8dfabSAndreas Gohr $data = array_merge($userdata, $groupdata); 6384fb8dfabSAndreas Gohr $sql = $this->getConf('leave-group'); 6393213bf4eSAndreas Gohr $result = $this->query($sql, $data); 6404fb8dfabSAndreas Gohr if ($result === false) return false; 6414fb8dfabSAndreas Gohr return true; 6424fb8dfabSAndreas Gohr } 6434fb8dfabSAndreas Gohr 6444fb8dfabSAndreas Gohr /** 64570a89417SAndreas Gohr * Executes a query 64670a89417SAndreas Gohr * 64770a89417SAndreas Gohr * @param string $sql The SQL statement to execute 64870a89417SAndreas Gohr * @param array $arguments Named parameters to be used in the statement 649f695c447SAndreas Gohr * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error 65070a89417SAndreas Gohr */ 651ab9790caSAndreas Gohr protected function query($sql, $arguments = []) 6523213bf4eSAndreas Gohr { 653f695c447SAndreas Gohr $sql = trim($sql); 6545de3a6a5SAndreas Gohr if (empty($sql)) { 6553213bf4eSAndreas Gohr $this->debugMsg('No SQL query given', -1, __LINE__); 6565de3a6a5SAndreas Gohr return false; 6575de3a6a5SAndreas Gohr } 6585de3a6a5SAndreas Gohr 65914119d44SAndreas Gohr // execute 660ab9790caSAndreas Gohr $params = []; 66114119d44SAndreas Gohr $sth = $this->pdo->prepare($sql); 66288ca2487SPhy $result = false; 66314119d44SAndreas Gohr try { 66414119d44SAndreas Gohr // prepare parameters - we only use those that exist in the SQL 66570a89417SAndreas Gohr foreach ($arguments as $key => $value) { 66670a89417SAndreas Gohr if (is_array($value)) continue; 66770a89417SAndreas Gohr if (is_object($value)) continue; 66870a89417SAndreas Gohr if ($key[0] != ':') $key = ":$key"; // prefix with colon if needed 669ab9790caSAndreas Gohr if (strpos($sql, (string) $key) === false) continue; // skip if parameter is missing 67014119d44SAndreas Gohr 67114119d44SAndreas Gohr if (is_int($value)) { 67214119d44SAndreas Gohr $sth->bindValue($key, $value, PDO::PARAM_INT); 67314119d44SAndreas Gohr } else { 67414119d44SAndreas Gohr $sth->bindValue($key, $value); 67514119d44SAndreas Gohr } 676f6cd8a7fSphjanderson $params[$key] = $value; //remember for debugging 67770a89417SAndreas Gohr } 67870a89417SAndreas Gohr 67914119d44SAndreas Gohr $sth->execute(); 6806a1b9bfeSPhy // only report last line's result 6816a1b9bfeSPhy $hasnextrowset = true; 6826a1b9bfeSPhy $currentsql = $sql; 6836a1b9bfeSPhy while ($hasnextrowset) { 6841b2deed9Sfiwswe if (str_starts_with(strtolower($currentsql), 'select')) { 68570a89417SAndreas Gohr $result = $sth->fetchAll(); 686f695c447SAndreas Gohr } else { 687f695c447SAndreas Gohr $result = $sth->rowCount(); 688f695c447SAndreas Gohr } 6896a1b9bfeSPhy $semi_pos = strpos($currentsql, ';'); 6906a1b9bfeSPhy if ($semi_pos) { 6916a1b9bfeSPhy $currentsql = trim(substr($currentsql, $semi_pos + 1)); 6926a1b9bfeSPhy } 6936a1b9bfeSPhy try { 6946a1b9bfeSPhy $hasnextrowset = $sth->nextRowset(); // run next rowset 6956a1b9bfeSPhy } catch (PDOException $rowset_e) { 6966a1b9bfeSPhy $hasnextrowset = false; // driver does not support multi-rowset, should be executed in one time 6976a1b9bfeSPhy } 6986a1b9bfeSPhy } 6995de3a6a5SAndreas Gohr } catch (Exception $e) { 7004fb8dfabSAndreas Gohr // report the caller's line 7014fb8dfabSAndreas Gohr $trace = debug_backtrace(); 7024fb8dfabSAndreas Gohr $line = $trace[0]['line']; 7033213bf4eSAndreas Gohr $dsql = $this->debugSQL($sql, $params, !defined('DOKU_UNITTEST')); 7043213bf4eSAndreas Gohr $this->debugMsg($e, -1, $line); 7053213bf4eSAndreas Gohr $this->debugMsg("SQL: <pre>$dsql</pre>", -1, $line); 7061600c7ccSAndreas Gohr } 70770a89417SAndreas Gohr $sth->closeCursor(); 70870a89417SAndreas Gohr 7095de3a6a5SAndreas Gohr return $result; 7105de3a6a5SAndreas Gohr } 71170a89417SAndreas Gohr 71270a89417SAndreas Gohr /** 713f64dbc90SAndreas Gohr * Wrapper around msg() but outputs only when debug is enabled 714f64dbc90SAndreas Gohr * 715f64dbc90SAndreas Gohr * @param string|Exception $message 716f64dbc90SAndreas Gohr * @param int $err 717f64dbc90SAndreas Gohr * @param int $line 718f64dbc90SAndreas Gohr */ 7193213bf4eSAndreas Gohr protected function debugMsg($message, $err = 0, $line = 0) 7203213bf4eSAndreas Gohr { 721f64dbc90SAndreas Gohr if (!$this->getConf('debug')) return; 722f64dbc90SAndreas Gohr if (is_a($message, 'Exception')) { 723f64dbc90SAndreas Gohr $err = -1; 724f64dbc90SAndreas Gohr $msg = $message->getMessage(); 7254fb8dfabSAndreas Gohr if (!$line) $line = $message->getLine(); 726f64dbc90SAndreas Gohr } else { 727f64dbc90SAndreas Gohr $msg = $message; 728f64dbc90SAndreas Gohr } 729f64dbc90SAndreas Gohr 730f64dbc90SAndreas Gohr if (defined('DOKU_UNITTEST')) { 731f64dbc90SAndreas Gohr printf("\n%s, %s:%d\n", $msg, __FILE__, $line); 732f64dbc90SAndreas Gohr } else { 733f64dbc90SAndreas Gohr msg('authpdo: ' . $msg, $err, $line, __FILE__); 734f64dbc90SAndreas Gohr } 735f64dbc90SAndreas Gohr } 7365de3a6a5SAndreas Gohr 7375de3a6a5SAndreas Gohr /** 7385de3a6a5SAndreas Gohr * Check if the given config strings are set 7395de3a6a5SAndreas Gohr * 7405de3a6a5SAndreas Gohr * @param string[] $keys 7415de3a6a5SAndreas Gohr * @return bool 74231a58abaSAndreas Gohr * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 74331a58abaSAndreas Gohr * 7445de3a6a5SAndreas Gohr */ 7453213bf4eSAndreas Gohr protected function checkConfig($keys) 7463213bf4eSAndreas Gohr { 7475de3a6a5SAndreas Gohr foreach ($keys as $key) { 748ad4d5631SAndreas Gohr $params = explode(':', $key); 749ad4d5631SAndreas Gohr $key = array_shift($params); 750ad4d5631SAndreas Gohr $sql = trim($this->getConf($key)); 751ad4d5631SAndreas Gohr 752ad4d5631SAndreas Gohr // check if sql is set 753ad4d5631SAndreas Gohr if (!$sql) return false; 754ad4d5631SAndreas Gohr // check if needed params are there 755ad4d5631SAndreas Gohr foreach ($params as $param) { 756ad4d5631SAndreas Gohr if (strpos($sql, ":$param") === false) return false; 757ad4d5631SAndreas Gohr } 7585de3a6a5SAndreas Gohr } 7595de3a6a5SAndreas Gohr 7605de3a6a5SAndreas Gohr return true; 7615de3a6a5SAndreas Gohr } 7625de3a6a5SAndreas Gohr 7635de3a6a5SAndreas Gohr /** 7645de3a6a5SAndreas Gohr * create an approximation of the SQL string with parameters replaced 7655de3a6a5SAndreas Gohr * 7665de3a6a5SAndreas Gohr * @param string $sql 7675de3a6a5SAndreas Gohr * @param array $params 7685de3a6a5SAndreas Gohr * @param bool $htmlescape Should the result be escaped for output in HTML? 7695de3a6a5SAndreas Gohr * @return string 7705de3a6a5SAndreas Gohr */ 7713213bf4eSAndreas Gohr protected function debugSQL($sql, $params, $htmlescape = true) 7723213bf4eSAndreas Gohr { 7735de3a6a5SAndreas Gohr foreach ($params as $key => $val) { 7745de3a6a5SAndreas Gohr if (is_int($val)) { 7755de3a6a5SAndreas Gohr $val = $this->pdo->quote($val, PDO::PARAM_INT); 7765de3a6a5SAndreas Gohr } elseif (is_bool($val)) { 7775de3a6a5SAndreas Gohr $val = $this->pdo->quote($val, PDO::PARAM_BOOL); 7785de3a6a5SAndreas Gohr } elseif (is_null($val)) { 7795de3a6a5SAndreas Gohr $val = 'NULL'; 7805de3a6a5SAndreas Gohr } else { 7815de3a6a5SAndreas Gohr $val = $this->pdo->quote($val); 7825de3a6a5SAndreas Gohr } 7835de3a6a5SAndreas Gohr $sql = str_replace($key, $val, $sql); 7845de3a6a5SAndreas Gohr } 7855de3a6a5SAndreas Gohr if ($htmlescape) $sql = hsc($sql); 7865de3a6a5SAndreas Gohr return $sql; 7875de3a6a5SAndreas Gohr } 788f64dbc90SAndreas Gohr} 789f64dbc90SAndreas Gohr 790f64dbc90SAndreas Gohr// vim:ts=4:sw=4:et: 791