[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/lib/plugins/authpdo/ -> auth.php (source)

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