PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/plugins/authpdo/auth.php

http://github.com/splitbrain/dokuwiki
PHP | 826 lines | 528 code | 90 blank | 208 comment | 109 complexity | 2d0484af819920bde6d4ff7eb87506a3 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * DokuWiki Plugin authpdo (Auth Component)
  4. *
  5. * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
  6. * @author Andreas Gohr <andi@splitbrain.org>
  7. */
  8. /**
  9. * Class auth_plugin_authpdo
  10. */
  11. class auth_plugin_authpdo extends DokuWiki_Auth_Plugin
  12. {
  13. /** @var PDO */
  14. protected $pdo;
  15. /** @var null|array The list of all groups */
  16. protected $groupcache = null;
  17. /**
  18. * Constructor.
  19. */
  20. public function __construct()
  21. {
  22. parent::__construct(); // for compatibility
  23. if (!class_exists('PDO')) {
  24. $this->debugMsg('PDO extension for PHP not found.', -1, __LINE__);
  25. $this->success = false;
  26. return;
  27. }
  28. if (!$this->getConf('dsn')) {
  29. $this->debugMsg('No DSN specified', -1, __LINE__);
  30. $this->success = false;
  31. return;
  32. }
  33. try {
  34. $this->pdo = new PDO(
  35. $this->getConf('dsn'),
  36. $this->getConf('user'),
  37. conf_decodeString($this->getConf('pass')),
  38. array(
  39. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array
  40. PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names
  41. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes
  42. )
  43. );
  44. } catch (PDOException $e) {
  45. $this->debugMsg($e);
  46. msg($this->getLang('connectfail'), -1);
  47. $this->success = false;
  48. return;
  49. }
  50. // can Users be created?
  51. $this->cando['addUser'] = $this->checkConfig(
  52. array(
  53. 'select-user',
  54. 'select-user-groups',
  55. 'select-groups',
  56. 'insert-user',
  57. 'insert-group',
  58. 'join-group'
  59. )
  60. );
  61. // can Users be deleted?
  62. $this->cando['delUser'] = $this->checkConfig(
  63. array(
  64. 'select-user',
  65. 'select-user-groups',
  66. 'select-groups',
  67. 'leave-group',
  68. 'delete-user'
  69. )
  70. );
  71. // can login names be changed?
  72. $this->cando['modLogin'] = $this->checkConfig(
  73. array(
  74. 'select-user',
  75. 'select-user-groups',
  76. 'update-user-login'
  77. )
  78. );
  79. // can passwords be changed?
  80. $this->cando['modPass'] = $this->checkConfig(
  81. array(
  82. 'select-user',
  83. 'select-user-groups',
  84. 'update-user-pass'
  85. )
  86. );
  87. // can real names be changed?
  88. $this->cando['modName'] = $this->checkConfig(
  89. array(
  90. 'select-user',
  91. 'select-user-groups',
  92. 'update-user-info:name'
  93. )
  94. );
  95. // can real email be changed?
  96. $this->cando['modMail'] = $this->checkConfig(
  97. array(
  98. 'select-user',
  99. 'select-user-groups',
  100. 'update-user-info:mail'
  101. )
  102. );
  103. // can groups be changed?
  104. $this->cando['modGroups'] = $this->checkConfig(
  105. array(
  106. 'select-user',
  107. 'select-user-groups',
  108. 'select-groups',
  109. 'leave-group',
  110. 'join-group',
  111. 'insert-group'
  112. )
  113. );
  114. // can a filtered list of users be retrieved?
  115. $this->cando['getUsers'] = $this->checkConfig(
  116. array(
  117. 'list-users'
  118. )
  119. );
  120. // can the number of users be retrieved?
  121. $this->cando['getUserCount'] = $this->checkConfig(
  122. array(
  123. 'count-users'
  124. )
  125. );
  126. // can a list of available groups be retrieved?
  127. $this->cando['getGroups'] = $this->checkConfig(
  128. array(
  129. 'select-groups'
  130. )
  131. );
  132. $this->success = true;
  133. }
  134. /**
  135. * Check user+password
  136. *
  137. * @param string $user the user name
  138. * @param string $pass the clear text password
  139. * @return bool
  140. */
  141. public function checkPass($user, $pass)
  142. {
  143. $userdata = $this->selectUser($user);
  144. if ($userdata == false) return false;
  145. // password checking done in SQL?
  146. if ($this->checkConfig(array('check-pass'))) {
  147. $userdata['clear'] = $pass;
  148. $userdata['hash'] = auth_cryptPassword($pass);
  149. $result = $this->query($this->getConf('check-pass'), $userdata);
  150. if ($result === false) return false;
  151. return (count($result) == 1);
  152. }
  153. // we do password checking on our own
  154. if (isset($userdata['hash'])) {
  155. // hashed password
  156. $passhash = new \dokuwiki\PassHash();
  157. return $passhash->verify_hash($pass, $userdata['hash']);
  158. } else {
  159. // clear text password in the database O_o
  160. return ($pass === $userdata['clear']);
  161. }
  162. }
  163. /**
  164. * Return user info
  165. *
  166. * Returns info about the given user needs to contain
  167. * at least these fields:
  168. *
  169. * name string full name of the user
  170. * mail string email addres of the user
  171. * grps array list of groups the user is in
  172. *
  173. * @param string $user the user name
  174. * @param bool $requireGroups whether or not the returned data must include groups
  175. * @return array|bool containing user data or false
  176. */
  177. public function getUserData($user, $requireGroups = true)
  178. {
  179. $data = $this->selectUser($user);
  180. if ($data == false) return false;
  181. if (isset($data['hash'])) unset($data['hash']);
  182. if (isset($data['clean'])) unset($data['clean']);
  183. if ($requireGroups) {
  184. $data['grps'] = $this->selectUserGroups($data);
  185. if ($data['grps'] === false) return false;
  186. }
  187. return $data;
  188. }
  189. /**
  190. * Create a new User [implement only where required/possible]
  191. *
  192. * Returns false if the user already exists, null when an error
  193. * occurred and true if everything went well.
  194. *
  195. * The new user HAS TO be added to the default group by this
  196. * function!
  197. *
  198. * Set addUser capability when implemented
  199. *
  200. * @param string $user
  201. * @param string $clear
  202. * @param string $name
  203. * @param string $mail
  204. * @param null|array $grps
  205. * @return bool|null
  206. */
  207. public function createUser($user, $clear, $name, $mail, $grps = null)
  208. {
  209. global $conf;
  210. if (($info = $this->getUserData($user, false)) !== false) {
  211. msg($this->getLang('userexists'), -1);
  212. return false; // user already exists
  213. }
  214. // prepare data
  215. if ($grps == null) $grps = array();
  216. array_unshift($grps, $conf['defaultgroup']);
  217. $grps = array_unique($grps);
  218. $hash = auth_cryptPassword($clear);
  219. $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
  220. // action protected by transaction
  221. $this->pdo->beginTransaction();
  222. {
  223. // insert the user
  224. $ok = $this->query($this->getConf('insert-user'), $userdata);
  225. if ($ok === false) goto FAIL;
  226. $userdata = $this->getUserData($user, false);
  227. if ($userdata === false) goto FAIL;
  228. // create all groups that do not exist, the refetch the groups
  229. $allgroups = $this->selectGroups();
  230. foreach ($grps as $group) {
  231. if (!isset($allgroups[$group])) {
  232. $ok = $this->addGroup($group);
  233. if ($ok === false) goto FAIL;
  234. }
  235. }
  236. $allgroups = $this->selectGroups();
  237. // add user to the groups
  238. foreach ($grps as $group) {
  239. $ok = $this->joinGroup($userdata, $allgroups[$group]);
  240. if ($ok === false) goto FAIL;
  241. }
  242. }
  243. $this->pdo->commit();
  244. return true;
  245. // something went wrong, rollback
  246. FAIL:
  247. $this->pdo->rollBack();
  248. $this->debugMsg('Transaction rolled back', 0, __LINE__);
  249. msg($this->getLang('writefail'), -1);
  250. return null; // return error
  251. }
  252. /**
  253. * Modify user data
  254. *
  255. * @param string $user nick of the user to be changed
  256. * @param array $changes array of field/value pairs to be changed (password will be clear text)
  257. * @return bool
  258. */
  259. public function modifyUser($user, $changes)
  260. {
  261. // secure everything in transaction
  262. $this->pdo->beginTransaction();
  263. {
  264. $olddata = $this->getUserData($user);
  265. $oldgroups = $olddata['grps'];
  266. unset($olddata['grps']);
  267. // changing the user name?
  268. if (isset($changes['user'])) {
  269. if ($this->getUserData($changes['user'], false)) goto FAIL;
  270. $params = $olddata;
  271. $params['newlogin'] = $changes['user'];
  272. $ok = $this->query($this->getConf('update-user-login'), $params);
  273. if ($ok === false) goto FAIL;
  274. }
  275. // changing the password?
  276. if (isset($changes['pass'])) {
  277. $params = $olddata;
  278. $params['clear'] = $changes['pass'];
  279. $params['hash'] = auth_cryptPassword($changes['pass']);
  280. $ok = $this->query($this->getConf('update-user-pass'), $params);
  281. if ($ok === false) goto FAIL;
  282. }
  283. // changing info?
  284. if (isset($changes['mail']) || isset($changes['name'])) {
  285. $params = $olddata;
  286. if (isset($changes['mail'])) $params['mail'] = $changes['mail'];
  287. if (isset($changes['name'])) $params['name'] = $changes['name'];
  288. $ok = $this->query($this->getConf('update-user-info'), $params);
  289. if ($ok === false) goto FAIL;
  290. }
  291. // changing groups?
  292. if (isset($changes['grps'])) {
  293. $allgroups = $this->selectGroups();
  294. // remove membership for previous groups
  295. foreach ($oldgroups as $group) {
  296. if (!in_array($group, $changes['grps']) && isset($allgroups[$group])) {
  297. $ok = $this->leaveGroup($olddata, $allgroups[$group]);
  298. if ($ok === false) goto FAIL;
  299. }
  300. }
  301. // create all new groups that are missing
  302. $added = 0;
  303. foreach ($changes['grps'] as $group) {
  304. if (!isset($allgroups[$group])) {
  305. $ok = $this->addGroup($group);
  306. if ($ok === false) goto FAIL;
  307. $added++;
  308. }
  309. }
  310. // reload group info
  311. if ($added > 0) $allgroups = $this->selectGroups();
  312. // add membership for new groups
  313. foreach ($changes['grps'] as $group) {
  314. if (!in_array($group, $oldgroups)) {
  315. $ok = $this->joinGroup($olddata, $allgroups[$group]);
  316. if ($ok === false) goto FAIL;
  317. }
  318. }
  319. }
  320. }
  321. $this->pdo->commit();
  322. return true;
  323. // something went wrong, rollback
  324. FAIL:
  325. $this->pdo->rollBack();
  326. $this->debugMsg('Transaction rolled back', 0, __LINE__);
  327. msg($this->getLang('writefail'), -1);
  328. return false; // return error
  329. }
  330. /**
  331. * Delete one or more users
  332. *
  333. * Set delUser capability when implemented
  334. *
  335. * @param array $users
  336. * @return int number of users deleted
  337. */
  338. public function deleteUsers($users)
  339. {
  340. $count = 0;
  341. foreach ($users as $user) {
  342. if ($this->deleteUser($user)) $count++;
  343. }
  344. return $count;
  345. }
  346. /**
  347. * Bulk retrieval of user data [implement only where required/possible]
  348. *
  349. * Set getUsers capability when implemented
  350. *
  351. * @param int $start index of first user to be returned
  352. * @param int $limit max number of users to be returned
  353. * @param array $filter array of field/pattern pairs, null for no filter
  354. * @return array list of userinfo (refer getUserData for internal userinfo details)
  355. */
  356. public function retrieveUsers($start = 0, $limit = -1, $filter = null)
  357. {
  358. if ($limit < 0) $limit = 10000; // we don't support no limit
  359. if (is_null($filter)) $filter = array();
  360. if (isset($filter['grps'])) $filter['group'] = $filter['grps'];
  361. foreach (array('user', 'name', 'mail', 'group') as $key) {
  362. if (!isset($filter[$key])) {
  363. $filter[$key] = '%';
  364. } else {
  365. $filter[$key] = '%' . $filter[$key] . '%';
  366. }
  367. }
  368. $filter['start'] = (int)$start;
  369. $filter['end'] = (int)$start + $limit;
  370. $filter['limit'] = (int)$limit;
  371. $result = $this->query($this->getConf('list-users'), $filter);
  372. if (!$result) return array();
  373. $users = array();
  374. if (is_array($result)) {
  375. foreach ($result as $row) {
  376. if (!isset($row['user'])) {
  377. $this->debugMsg("list-users statement did not return 'user' attribute", -1, __LINE__);
  378. return array();
  379. }
  380. $users[] = $this->getUserData($row['user']);
  381. }
  382. } else {
  383. $this->debugMsg("list-users statement did not return a list of result", -1, __LINE__);
  384. }
  385. return $users;
  386. }
  387. /**
  388. * Return a count of the number of user which meet $filter criteria
  389. *
  390. * @param array $filter array of field/pattern pairs, empty array for no filter
  391. * @return int
  392. */
  393. public function getUserCount($filter = array())
  394. {
  395. if (is_null($filter)) $filter = array();
  396. if (isset($filter['grps'])) $filter['group'] = $filter['grps'];
  397. foreach (array('user', 'name', 'mail', 'group') as $key) {
  398. if (!isset($filter[$key])) {
  399. $filter[$key] = '%';
  400. } else {
  401. $filter[$key] = '%' . $filter[$key] . '%';
  402. }
  403. }
  404. $result = $this->query($this->getConf('count-users'), $filter);
  405. if (!$result || !isset($result[0]['count'])) {
  406. $this->debugMsg("Statement did not return 'count' attribute", -1, __LINE__);
  407. }
  408. return (int)$result[0]['count'];
  409. }
  410. /**
  411. * Create a new group with the given name
  412. *
  413. * @param string $group
  414. * @return bool
  415. */
  416. public function addGroup($group)
  417. {
  418. $sql = $this->getConf('insert-group');
  419. $result = $this->query($sql, array(':group' => $group));
  420. $this->clearGroupCache();
  421. if ($result === false) return false;
  422. return true;
  423. }
  424. /**
  425. * Retrieve groups
  426. *
  427. * Set getGroups capability when implemented
  428. *
  429. * @param int $start
  430. * @param int $limit
  431. * @return array
  432. */
  433. public function retrieveGroups($start = 0, $limit = 0)
  434. {
  435. $groups = array_keys($this->selectGroups());
  436. if ($groups === false) return array();
  437. if (!$limit) {
  438. return array_splice($groups, $start);
  439. } else {
  440. return array_splice($groups, $start, $limit);
  441. }
  442. }
  443. /**
  444. * Select data of a specified user
  445. *
  446. * @param string $user the user name
  447. * @return bool|array user data, false on error
  448. */
  449. protected function selectUser($user)
  450. {
  451. $sql = $this->getConf('select-user');
  452. $result = $this->query($sql, array(':user' => $user));
  453. if (!$result) return false;
  454. if (count($result) > 1) {
  455. $this->debugMsg('Found more than one matching user', -1, __LINE__);
  456. return false;
  457. }
  458. $data = array_shift($result);
  459. $dataok = true;
  460. if (!isset($data['user'])) {
  461. $this->debugMsg("Statement did not return 'user' attribute", -1, __LINE__);
  462. $dataok = false;
  463. }
  464. if (!isset($data['hash']) && !isset($data['clear']) && !$this->checkConfig(array('check-pass'))) {
  465. $this->debugMsg("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
  466. $dataok = false;
  467. }
  468. if (!isset($data['name'])) {
  469. $this->debugMsg("Statement did not return 'name' attribute", -1, __LINE__);
  470. $dataok = false;
  471. }
  472. if (!isset($data['mail'])) {
  473. $this->debugMsg("Statement did not return 'mail' attribute", -1, __LINE__);
  474. $dataok = false;
  475. }
  476. if (!$dataok) return false;
  477. return $data;
  478. }
  479. /**
  480. * Delete a user after removing all their group memberships
  481. *
  482. * @param string $user
  483. * @return bool true when the user was deleted
  484. */
  485. protected function deleteUser($user)
  486. {
  487. $this->pdo->beginTransaction();
  488. {
  489. $userdata = $this->getUserData($user);
  490. if ($userdata === false) goto FAIL;
  491. $allgroups = $this->selectGroups();
  492. // remove group memberships (ignore errors)
  493. foreach ($userdata['grps'] as $group) {
  494. if (isset($allgroups[$group])) {
  495. $this->leaveGroup($userdata, $allgroups[$group]);
  496. }
  497. }
  498. $ok = $this->query($this->getConf('delete-user'), $userdata);
  499. if ($ok === false) goto FAIL;
  500. }
  501. $this->pdo->commit();
  502. return true;
  503. FAIL:
  504. $this->pdo->rollBack();
  505. return false;
  506. }
  507. /**
  508. * Select all groups of a user
  509. *
  510. * @param array $userdata The userdata as returned by _selectUser()
  511. * @return array|bool list of group names, false on error
  512. */
  513. protected function selectUserGroups($userdata)
  514. {
  515. global $conf;
  516. $sql = $this->getConf('select-user-groups');
  517. $result = $this->query($sql, $userdata);
  518. if ($result === false) return false;
  519. $groups = array($conf['defaultgroup']); // always add default config
  520. if (is_array($result)) {
  521. foreach ($result as $row) {
  522. if (!isset($row['group'])) {
  523. $this->debugMsg("No 'group' field returned in select-user-groups statement", -1, __LINE__);
  524. return false;
  525. }
  526. $groups[] = $row['group'];
  527. }
  528. } else {
  529. $this->debugMsg("select-user-groups statement did not return a list of result", -1, __LINE__);
  530. }
  531. $groups = array_unique($groups);
  532. sort($groups);
  533. return $groups;
  534. }
  535. /**
  536. * Select all available groups
  537. *
  538. * @return array|bool list of all available groups and their properties
  539. */
  540. protected function selectGroups()
  541. {
  542. if ($this->groupcache) return $this->groupcache;
  543. $sql = $this->getConf('select-groups');
  544. $result = $this->query($sql);
  545. if ($result === false) return false;
  546. $groups = array();
  547. if (is_array($result)) {
  548. foreach ($result as $row) {
  549. if (!isset($row['group'])) {
  550. $this->debugMsg("No 'group' field returned from select-groups statement", -1, __LINE__);
  551. return false;
  552. }
  553. // relayout result with group name as key
  554. $group = $row['group'];
  555. $groups[$group] = $row;
  556. }
  557. } else {
  558. $this->debugMsg("select-groups statement did not return a list of result", -1, __LINE__);
  559. }
  560. ksort($groups);
  561. return $groups;
  562. }
  563. /**
  564. * Remove all entries from the group cache
  565. */
  566. protected function clearGroupCache()
  567. {
  568. $this->groupcache = null;
  569. }
  570. /**
  571. * Adds the user to the group
  572. *
  573. * @param array $userdata all the user data
  574. * @param array $groupdata all the group data
  575. * @return bool
  576. */
  577. protected function joinGroup($userdata, $groupdata)
  578. {
  579. $data = array_merge($userdata, $groupdata);
  580. $sql = $this->getConf('join-group');
  581. $result = $this->query($sql, $data);
  582. if ($result === false) return false;
  583. return true;
  584. }
  585. /**
  586. * Removes the user from the group
  587. *
  588. * @param array $userdata all the user data
  589. * @param array $groupdata all the group data
  590. * @return bool
  591. */
  592. protected function leaveGroup($userdata, $groupdata)
  593. {
  594. $data = array_merge($userdata, $groupdata);
  595. $sql = $this->getConf('leave-group');
  596. $result = $this->query($sql, $data);
  597. if ($result === false) return false;
  598. return true;
  599. }
  600. /**
  601. * Executes a query
  602. *
  603. * @param string $sql The SQL statement to execute
  604. * @param array $arguments Named parameters to be used in the statement
  605. * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
  606. */
  607. protected function query($sql, $arguments = array())
  608. {
  609. $sql = trim($sql);
  610. if (empty($sql)) {
  611. $this->debugMsg('No SQL query given', -1, __LINE__);
  612. return false;
  613. }
  614. // execute
  615. $params = array();
  616. $sth = $this->pdo->prepare($sql);
  617. $result = false;
  618. try {
  619. // prepare parameters - we only use those that exist in the SQL
  620. foreach ($arguments as $key => $value) {
  621. if (is_array($value)) continue;
  622. if (is_object($value)) continue;
  623. if ($key[0] != ':') $key = ":$key"; // prefix with colon if needed
  624. if (strpos($sql, $key) === false) continue; // skip if parameter is missing
  625. if (is_int($value)) {
  626. $sth->bindValue($key, $value, PDO::PARAM_INT);
  627. } else {
  628. $sth->bindValue($key, $value);
  629. }
  630. $params[$key] = $value; //remember for debugging
  631. }
  632. $sth->execute();
  633. // only report last line's result
  634. $hasnextrowset = true;
  635. $currentsql = $sql;
  636. while ($hasnextrowset) {
  637. if (strtolower(substr($currentsql, 0, 6)) == 'select') {
  638. $result = $sth->fetchAll();
  639. } else {
  640. $result = $sth->rowCount();
  641. }
  642. $semi_pos = strpos($currentsql, ';');
  643. if ($semi_pos) {
  644. $currentsql = trim(substr($currentsql, $semi_pos + 1));
  645. }
  646. try {
  647. $hasnextrowset = $sth->nextRowset(); // run next rowset
  648. } catch (PDOException $rowset_e) {
  649. $hasnextrowset = false; // driver does not support multi-rowset, should be executed in one time
  650. }
  651. }
  652. } catch (Exception $e) {
  653. // report the caller's line
  654. $trace = debug_backtrace();
  655. $line = $trace[0]['line'];
  656. $dsql = $this->debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
  657. $this->debugMsg($e, -1, $line);
  658. $this->debugMsg("SQL: <pre>$dsql</pre>", -1, $line);
  659. }
  660. $sth->closeCursor();
  661. $sth = null;
  662. return $result;
  663. }
  664. /**
  665. * Wrapper around msg() but outputs only when debug is enabled
  666. *
  667. * @param string|Exception $message
  668. * @param int $err
  669. * @param int $line
  670. */
  671. protected function debugMsg($message, $err = 0, $line = 0)
  672. {
  673. if (!$this->getConf('debug')) return;
  674. if (is_a($message, 'Exception')) {
  675. $err = -1;
  676. $msg = $message->getMessage();
  677. if (!$line) $line = $message->getLine();
  678. } else {
  679. $msg = $message;
  680. }
  681. if (defined('DOKU_UNITTEST')) {
  682. printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
  683. } else {
  684. msg('authpdo: ' . $msg, $err, $line, __FILE__);
  685. }
  686. }
  687. /**
  688. * Check if the given config strings are set
  689. *
  690. * @param string[] $keys
  691. * @return bool
  692. * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
  693. *
  694. */
  695. protected function checkConfig($keys)
  696. {
  697. foreach ($keys as $key) {
  698. $params = explode(':', $key);
  699. $key = array_shift($params);
  700. $sql = trim($this->getConf($key));
  701. // check if sql is set
  702. if (!$sql) return false;
  703. // check if needed params are there
  704. foreach ($params as $param) {
  705. if (strpos($sql, ":$param") === false) return false;
  706. }
  707. }
  708. return true;
  709. }
  710. /**
  711. * create an approximation of the SQL string with parameters replaced
  712. *
  713. * @param string $sql
  714. * @param array $params
  715. * @param bool $htmlescape Should the result be escaped for output in HTML?
  716. * @return string
  717. */
  718. protected function debugSQL($sql, $params, $htmlescape = true)
  719. {
  720. foreach ($params as $key => $val) {
  721. if (is_int($val)) {
  722. $val = $this->pdo->quote($val, PDO::PARAM_INT);
  723. } elseif (is_bool($val)) {
  724. $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
  725. } elseif (is_null($val)) {
  726. $val = 'NULL';
  727. } else {
  728. $val = $this->pdo->quote($val);
  729. }
  730. $sql = str_replace($key, $val, $sql);
  731. }
  732. if ($htmlescape) $sql = hsc($sql);
  733. return $sql;
  734. }
  735. }
  736. // vim:ts=4:sw=4:et: