PageRenderTime 68ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/program/lib/Roundcube/rcube_db.php

https://github.com/fretelweb/roundcubemail
PHP | 1051 lines | 543 code | 135 blank | 373 comment | 117 complexity | 68122a2aa34735e9a790586b181ce1d1 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2005-2012, The Roundcube Dev Team |
  6. | |
  7. | Licensed under the GNU General Public License version 3 or |
  8. | any later version with exceptions for skins & plugins. |
  9. | See the README file for a full license statement. |
  10. | |
  11. | PURPOSE: |
  12. | Database wrapper class that implements PHP PDO functions |
  13. +-----------------------------------------------------------------------+
  14. | Author: Aleksander Machniak <alec@alec.pl> |
  15. +-----------------------------------------------------------------------+
  16. */
  17. /**
  18. * Database independent query interface.
  19. * This is a wrapper for the PHP PDO.
  20. *
  21. * @package Framework
  22. * @subpackage Database
  23. */
  24. class rcube_db
  25. {
  26. public $db_provider;
  27. protected $db_dsnw; // DSN for write operations
  28. protected $db_dsnr; // DSN for read operations
  29. protected $db_connected = false; // Already connected ?
  30. protected $db_mode; // Connection mode
  31. protected $dbh; // Connection handle
  32. protected $db_error = false;
  33. protected $db_error_msg = '';
  34. protected $conn_failure = false;
  35. protected $db_index = 0;
  36. protected $last_result;
  37. protected $tables;
  38. protected $variables;
  39. protected $options = array(
  40. // column/table quotes
  41. 'identifier_start' => '"',
  42. 'identifier_end' => '"',
  43. );
  44. const DEBUG_LINE_LENGTH = 4096;
  45. /**
  46. * Factory, returns driver-specific instance of the class
  47. *
  48. * @param string $db_dsnw DSN for read/write operations
  49. * @param string $db_dsnr Optional DSN for read only operations
  50. * @param bool $pconn Enables persistent connections
  51. *
  52. * @return rcube_db Object instance
  53. */
  54. public static function factory($db_dsnw, $db_dsnr = '', $pconn = false)
  55. {
  56. $driver = strtolower(substr($db_dsnw, 0, strpos($db_dsnw, ':')));
  57. $driver_map = array(
  58. 'sqlite2' => 'sqlite',
  59. 'sybase' => 'mssql',
  60. 'dblib' => 'mssql',
  61. 'mysqli' => 'mysql',
  62. );
  63. $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver;
  64. $class = "rcube_db_$driver";
  65. if (!$driver || !class_exists($class)) {
  66. rcube::raise_error(array('code' => 600, 'type' => 'db',
  67. 'line' => __LINE__, 'file' => __FILE__,
  68. 'message' => "Configuration error. Unsupported database driver: $driver"),
  69. true, true);
  70. }
  71. return new $class($db_dsnw, $db_dsnr, $pconn);
  72. }
  73. /**
  74. * Object constructor
  75. *
  76. * @param string $db_dsnw DSN for read/write operations
  77. * @param string $db_dsnr Optional DSN for read only operations
  78. * @param bool $pconn Enables persistent connections
  79. */
  80. public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
  81. {
  82. if (empty($db_dsnr)) {
  83. $db_dsnr = $db_dsnw;
  84. }
  85. $this->db_dsnw = $db_dsnw;
  86. $this->db_dsnr = $db_dsnr;
  87. $this->db_pconn = $pconn;
  88. $this->db_dsnw_array = self::parse_dsn($db_dsnw);
  89. $this->db_dsnr_array = self::parse_dsn($db_dsnr);
  90. }
  91. /**
  92. * Connect to specific database
  93. *
  94. * @param array $dsn DSN for DB connections
  95. * @param string $mode Connection mode (r|w)
  96. */
  97. protected function dsn_connect($dsn, $mode)
  98. {
  99. $this->db_error = false;
  100. $this->db_error_msg = null;
  101. // Get database specific connection options
  102. $dsn_string = $this->dsn_string($dsn);
  103. $dsn_options = $this->dsn_options($dsn);
  104. if ($this->db_pconn) {
  105. $dsn_options[PDO::ATTR_PERSISTENT] = true;
  106. }
  107. // Connect
  108. try {
  109. // with this check we skip fatal error on PDO object creation
  110. if (!class_exists('PDO', false)) {
  111. throw new Exception('PDO extension not loaded. See http://php.net/manual/en/intro.pdo.php');
  112. }
  113. $this->conn_prepare($dsn);
  114. $dbh = new PDO($dsn_string, $dsn['username'], $dsn['password'], $dsn_options);
  115. // don't throw exceptions or warnings
  116. $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  117. }
  118. catch (Exception $e) {
  119. $this->db_error = true;
  120. $this->db_error_msg = $e->getMessage();
  121. rcube::raise_error(array('code' => 500, 'type' => 'db',
  122. 'line' => __LINE__, 'file' => __FILE__,
  123. 'message' => $this->db_error_msg), true, false);
  124. return null;
  125. }
  126. $this->dbh = $dbh;
  127. $this->db_mode = $mode;
  128. $this->db_connected = true;
  129. $this->conn_configure($dsn, $dbh);
  130. }
  131. /**
  132. * Driver-specific preparation of database connection
  133. *
  134. * @param array $dsn DSN for DB connections
  135. */
  136. protected function conn_prepare($dsn)
  137. {
  138. }
  139. /**
  140. * Driver-specific configuration of database connection
  141. *
  142. * @param array $dsn DSN for DB connections
  143. * @param PDO $dbh Connection handler
  144. */
  145. protected function conn_configure($dsn, $dbh)
  146. {
  147. }
  148. /**
  149. * Connect to appropriate database depending on the operation
  150. *
  151. * @param string $mode Connection mode (r|w)
  152. */
  153. public function db_connect($mode)
  154. {
  155. // previous connection failed, don't attempt to connect again
  156. if ($this->conn_failure) {
  157. return;
  158. }
  159. // no replication
  160. if ($this->db_dsnw == $this->db_dsnr) {
  161. $mode = 'w';
  162. }
  163. // Already connected
  164. if ($this->db_connected) {
  165. // connected to db with the same or "higher" mode
  166. if ($this->db_mode == 'w' || $this->db_mode == $mode) {
  167. return;
  168. }
  169. }
  170. $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
  171. $this->dsn_connect($dsn, $mode);
  172. // use write-master when read-only fails
  173. if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) {
  174. $this->dsn_connect($this->db_dsnw_array, 'w');
  175. }
  176. $this->conn_failure = !$this->db_connected;
  177. }
  178. /**
  179. * Activate/deactivate debug mode
  180. *
  181. * @param boolean $dbg True if SQL queries should be logged
  182. */
  183. public function set_debug($dbg = true)
  184. {
  185. $this->options['debug_mode'] = $dbg;
  186. }
  187. /**
  188. * Writes debug information/query to 'sql' log file
  189. *
  190. * @param string $query SQL query
  191. */
  192. protected function debug($query)
  193. {
  194. if ($this->options['debug_mode']) {
  195. if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) {
  196. $diff = $len - self::DEBUG_LINE_LENGTH;
  197. $query = substr($query, 0, self::DEBUG_LINE_LENGTH)
  198. . "... [truncated $diff bytes]";
  199. }
  200. rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
  201. }
  202. }
  203. /**
  204. * Getter for error state
  205. *
  206. * @param mixed $result Optional query result
  207. *
  208. * @return string Error message
  209. */
  210. public function is_error($result = null)
  211. {
  212. if ($result !== null) {
  213. return $result === false ? $this->db_error_msg : null;
  214. }
  215. return $this->db_error ? $this->db_error_msg : null;
  216. }
  217. /**
  218. * Connection state checker
  219. *
  220. * @return boolean True if in connected state
  221. */
  222. public function is_connected()
  223. {
  224. return !is_object($this->dbh) ? false : $this->db_connected;
  225. }
  226. /**
  227. * Is database replication configured?
  228. *
  229. * @return bool Returns true if dsnw != dsnr
  230. */
  231. public function is_replicated()
  232. {
  233. return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
  234. }
  235. /**
  236. * Get database runtime variables
  237. *
  238. * @param string $varname Variable name
  239. * @param mixed $default Default value if variable is not set
  240. *
  241. * @return mixed Variable value or default
  242. */
  243. public function get_variable($varname, $default = null)
  244. {
  245. // to be implemented by driver class
  246. return $default;
  247. }
  248. /**
  249. * Execute a SQL query
  250. *
  251. * @param string SQL query to execute
  252. * @param mixed Values to be inserted in query
  253. *
  254. * @return number Query handle identifier
  255. */
  256. public function query()
  257. {
  258. $params = func_get_args();
  259. $query = array_shift($params);
  260. // Support one argument of type array, instead of n arguments
  261. if (count($params) == 1 && is_array($params[0])) {
  262. $params = $params[0];
  263. }
  264. return $this->_query($query, 0, 0, $params);
  265. }
  266. /**
  267. * Execute a SQL query with limits
  268. *
  269. * @param string SQL query to execute
  270. * @param int Offset for LIMIT statement
  271. * @param int Number of rows for LIMIT statement
  272. * @param mixed Values to be inserted in query
  273. *
  274. * @return PDOStatement|bool Query handle or False on error
  275. */
  276. public function limitquery()
  277. {
  278. $params = func_get_args();
  279. $query = array_shift($params);
  280. $offset = array_shift($params);
  281. $numrows = array_shift($params);
  282. return $this->_query($query, $offset, $numrows, $params);
  283. }
  284. /**
  285. * Execute a SQL query with limits
  286. *
  287. * @param string $query SQL query to execute
  288. * @param int $offset Offset for LIMIT statement
  289. * @param int $numrows Number of rows for LIMIT statement
  290. * @param array $params Values to be inserted in query
  291. *
  292. * @return PDOStatement|bool Query handle or False on error
  293. */
  294. protected function _query($query, $offset, $numrows, $params)
  295. {
  296. $query = trim($query);
  297. // Read or write ?
  298. $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
  299. $this->db_connect($mode);
  300. // check connection before proceeding
  301. if (!$this->is_connected()) {
  302. return $this->last_result = false;
  303. }
  304. if ($numrows || $offset) {
  305. $query = $this->set_limit($query, $numrows, $offset);
  306. }
  307. $params = (array) $params;
  308. // Because in Roundcube we mostly use queries that are
  309. // executed only once, we will not use prepared queries
  310. $pos = 0;
  311. $idx = 0;
  312. while ($pos = strpos($query, '?', $pos)) {
  313. if ($query[$pos+1] == '?') { // skip escaped ?
  314. $pos += 2;
  315. }
  316. else {
  317. $val = $this->quote($params[$idx++]);
  318. unset($params[$idx-1]);
  319. $query = substr_replace($query, $val, $pos, 1);
  320. $pos += strlen($val);
  321. }
  322. }
  323. // replace escaped ? back to normal
  324. $query = rtrim(strtr($query, array('??' => '?')), ';');
  325. $this->debug($query);
  326. // destroy reference to previous result, required for SQLite driver (#1488874)
  327. $this->last_result = null;
  328. $this->db_error_msg = null;
  329. // send query
  330. $result = $this->dbh->query($query);
  331. if ($result === false) {
  332. $error = $this->dbh->errorInfo();
  333. if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
  334. $this->db_error = true;
  335. $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
  336. rcube::raise_error(array('code' => 500, 'type' => 'db',
  337. 'line' => __LINE__, 'file' => __FILE__,
  338. 'message' => $this->db_error_msg . " (SQL Query: $query)"
  339. ), true, false);
  340. }
  341. }
  342. $this->last_result = $result;
  343. return $result;
  344. }
  345. /**
  346. * Get number of affected rows for the last query
  347. *
  348. * @param mixed $result Optional query handle
  349. *
  350. * @return int Number of (matching) rows
  351. */
  352. public function affected_rows($result = null)
  353. {
  354. if ($result || ($result === null && ($result = $this->last_result))) {
  355. return $result->rowCount();
  356. }
  357. return 0;
  358. }
  359. /**
  360. * Get number of rows for a SQL query
  361. * If no query handle is specified, the last query will be taken as reference
  362. *
  363. * @param mixed $result Optional query handle
  364. * @return mixed Number of rows or false on failure
  365. * @deprecated This method shows very poor performance and should be avoided.
  366. */
  367. public function num_rows($result = null)
  368. {
  369. if ($result || ($result === null && ($result = $this->last_result))) {
  370. // repeat query with SELECT COUNT(*) ...
  371. if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) {
  372. $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM);
  373. return $query ? intval($query->fetchColumn(0)) : false;
  374. }
  375. else {
  376. $num = count($result->fetchAll());
  377. $result->execute(); // re-execute query because there's no seek(0)
  378. return $num;
  379. }
  380. }
  381. return false;
  382. }
  383. /**
  384. * Get last inserted record ID
  385. *
  386. * @param string $table Table name (to find the incremented sequence)
  387. *
  388. * @return mixed ID or false on failure
  389. */
  390. public function insert_id($table = '')
  391. {
  392. if (!$this->db_connected || $this->db_mode == 'r') {
  393. return false;
  394. }
  395. if ($table) {
  396. // resolve table name
  397. $table = $this->table_name($table);
  398. }
  399. $id = $this->dbh->lastInsertId($table);
  400. return $id;
  401. }
  402. /**
  403. * Get an associative array for one row
  404. * If no query handle is specified, the last query will be taken as reference
  405. *
  406. * @param mixed $result Optional query handle
  407. *
  408. * @return mixed Array with col values or false on failure
  409. */
  410. public function fetch_assoc($result = null)
  411. {
  412. return $this->_fetch_row($result, PDO::FETCH_ASSOC);
  413. }
  414. /**
  415. * Get an index array for one row
  416. * If no query handle is specified, the last query will be taken as reference
  417. *
  418. * @param mixed $result Optional query handle
  419. *
  420. * @return mixed Array with col values or false on failure
  421. */
  422. public function fetch_array($result = null)
  423. {
  424. return $this->_fetch_row($result, PDO::FETCH_NUM);
  425. }
  426. /**
  427. * Get col values for a result row
  428. *
  429. * @param mixed $result Optional query handle
  430. * @param int $mode Fetch mode identifier
  431. *
  432. * @return mixed Array with col values or false on failure
  433. */
  434. protected function _fetch_row($result, $mode)
  435. {
  436. if ($result || ($result === null && ($result = $this->last_result))) {
  437. return $result->fetch($mode);
  438. }
  439. return false;
  440. }
  441. /**
  442. * Adds LIMIT,OFFSET clauses to the query
  443. *
  444. * @param string $query SQL query
  445. * @param int $limit Number of rows
  446. * @param int $offset Offset
  447. *
  448. * @return string SQL query
  449. */
  450. protected function set_limit($query, $limit = 0, $offset = 0)
  451. {
  452. if ($limit) {
  453. $query .= ' LIMIT ' . intval($limit);
  454. }
  455. if ($offset) {
  456. $query .= ' OFFSET ' . intval($offset);
  457. }
  458. return $query;
  459. }
  460. /**
  461. * Returns list of tables in a database
  462. *
  463. * @return array List of all tables of the current database
  464. */
  465. public function list_tables()
  466. {
  467. // get tables if not cached
  468. if ($this->tables === null) {
  469. $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME');
  470. if ($q) {
  471. $this->tables = $q->fetchAll(PDO::FETCH_COLUMN, 0);
  472. }
  473. else {
  474. $this->tables = array();
  475. }
  476. }
  477. return $this->tables;
  478. }
  479. /**
  480. * Returns list of columns in database table
  481. *
  482. * @param string $table Table name
  483. *
  484. * @return array List of table cols
  485. */
  486. public function list_cols($table)
  487. {
  488. $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
  489. array($table));
  490. if ($q) {
  491. return $q->fetchAll(PDO::FETCH_COLUMN, 0);
  492. }
  493. return array();
  494. }
  495. /**
  496. * Formats input so it can be safely used in a query
  497. *
  498. * @param mixed $input Value to quote
  499. * @param string $type Type of data (integer, bool, ident)
  500. *
  501. * @return string Quoted/converted string for use in query
  502. */
  503. public function quote($input, $type = null)
  504. {
  505. // handle int directly for better performance
  506. if ($type == 'integer' || $type == 'int') {
  507. return intval($input);
  508. }
  509. if (is_null($input)) {
  510. return 'NULL';
  511. }
  512. if ($type == 'ident') {
  513. return $this->quote_identifier($input);
  514. }
  515. // create DB handle if not available
  516. if (!$this->dbh) {
  517. $this->db_connect('r');
  518. }
  519. if ($this->dbh) {
  520. $map = array(
  521. 'bool' => PDO::PARAM_BOOL,
  522. 'integer' => PDO::PARAM_INT,
  523. );
  524. $type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
  525. return strtr($this->dbh->quote($input, $type), array('?' => '??')); // escape ?
  526. }
  527. return 'NULL';
  528. }
  529. /**
  530. * Escapes a string so it can be safely used in a query
  531. *
  532. * @param string $str A string to escape
  533. *
  534. * @return string Escaped string for use in a query
  535. */
  536. public function escape($str)
  537. {
  538. if (is_null($str)) {
  539. return 'NULL';
  540. }
  541. return substr($this->quote($str), 1, -1);
  542. }
  543. /**
  544. * Quotes a string so it can be safely used as a table or column name
  545. *
  546. * @param string $str Value to quote
  547. *
  548. * @return string Quoted string for use in query
  549. * @deprecated Replaced by rcube_db::quote_identifier
  550. * @see rcube_db::quote_identifier
  551. */
  552. public function quoteIdentifier($str)
  553. {
  554. return $this->quote_identifier($str);
  555. }
  556. /**
  557. * Escapes a string so it can be safely used in a query
  558. *
  559. * @param string $str A string to escape
  560. *
  561. * @return string Escaped string for use in a query
  562. * @deprecated Replaced by rcube_db::escape
  563. * @see rcube_db::escape
  564. */
  565. public function escapeSimple($str)
  566. {
  567. return $this->escape($str);
  568. }
  569. /**
  570. * Quotes a string so it can be safely used as a table or column name
  571. *
  572. * @param string $str Value to quote
  573. *
  574. * @return string Quoted string for use in query
  575. */
  576. public function quote_identifier($str)
  577. {
  578. $start = $this->options['identifier_start'];
  579. $end = $this->options['identifier_end'];
  580. $name = array();
  581. foreach (explode('.', $str) as $elem) {
  582. $elem = str_replace(array($start, $end), '', $elem);
  583. $name[] = $start . $elem . $end;
  584. }
  585. return implode($name, '.');
  586. }
  587. /**
  588. * Return SQL function for current time and date
  589. *
  590. * @param int $interval Optional interval (in seconds) to add/subtract
  591. *
  592. * @return string SQL function to use in query
  593. */
  594. public function now($interval = 0)
  595. {
  596. if ($interval) {
  597. $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL ';
  598. $add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
  599. $add .= ' SECOND';
  600. }
  601. return "now()" . $add;
  602. }
  603. /**
  604. * Return list of elements for use with SQL's IN clause
  605. *
  606. * @param array $arr Input array
  607. * @param string $type Type of data (integer, bool, ident)
  608. *
  609. * @return string Comma-separated list of quoted values for use in query
  610. */
  611. public function array2list($arr, $type = null)
  612. {
  613. if (!is_array($arr)) {
  614. return $this->quote($arr, $type);
  615. }
  616. foreach ($arr as $idx => $item) {
  617. $arr[$idx] = $this->quote($item, $type);
  618. }
  619. return implode(',', $arr);
  620. }
  621. /**
  622. * Return SQL statement to convert a field value into a unix timestamp
  623. *
  624. * This method is deprecated and should not be used anymore due to limitations
  625. * of timestamp functions in Mysql (year 2038 problem)
  626. *
  627. * @param string $field Field name
  628. *
  629. * @return string SQL statement to use in query
  630. * @deprecated
  631. */
  632. public function unixtimestamp($field)
  633. {
  634. return "UNIX_TIMESTAMP($field)";
  635. }
  636. /**
  637. * Return SQL statement to convert from a unix timestamp
  638. *
  639. * @param int $timestamp Unix timestamp
  640. *
  641. * @return string Date string in db-specific format
  642. */
  643. public function fromunixtime($timestamp)
  644. {
  645. return date("'Y-m-d H:i:s'", $timestamp);
  646. }
  647. /**
  648. * Return SQL statement for case insensitive LIKE
  649. *
  650. * @param string $column Field name
  651. * @param string $value Search value
  652. *
  653. * @return string SQL statement to use in query
  654. */
  655. public function ilike($column, $value)
  656. {
  657. return $this->quote_identifier($column).' LIKE '.$this->quote($value);
  658. }
  659. /**
  660. * Abstract SQL statement for value concatenation
  661. *
  662. * @return string SQL statement to be used in query
  663. */
  664. public function concat(/* col1, col2, ... */)
  665. {
  666. $args = func_get_args();
  667. if (is_array($args[0])) {
  668. $args = $args[0];
  669. }
  670. return '(' . join(' || ', $args) . ')';
  671. }
  672. /**
  673. * Encodes non-UTF-8 characters in string/array/object (recursive)
  674. *
  675. * @param mixed $input Data to fix
  676. * @param bool $serialized Enable serialization
  677. *
  678. * @return mixed Properly UTF-8 encoded data
  679. */
  680. public static function encode($input, $serialized = false)
  681. {
  682. // use Base64 encoding to workaround issues with invalid
  683. // or null characters in serialized string (#1489142)
  684. if ($serialized) {
  685. return base64_encode(serialize($input));
  686. }
  687. if (is_object($input)) {
  688. foreach (get_object_vars($input) as $idx => $value) {
  689. $input->$idx = self::encode($value);
  690. }
  691. return $input;
  692. }
  693. else if (is_array($input)) {
  694. foreach ($input as $idx => $value) {
  695. $input[$idx] = self::encode($value);
  696. }
  697. return $input;
  698. }
  699. return utf8_encode($input);
  700. }
  701. /**
  702. * Decodes encoded UTF-8 string/object/array (recursive)
  703. *
  704. * @param mixed $input Input data
  705. * @param bool $serialized Enable serialization
  706. *
  707. * @return mixed Decoded data
  708. */
  709. public static function decode($input, $serialized = false)
  710. {
  711. // use Base64 encoding to workaround issues with invalid
  712. // or null characters in serialized string (#1489142)
  713. if ($serialized) {
  714. // Keep backward compatybility where base64 wasn't used
  715. if (strpos(substr($input, 0, 16), ':') !== false) {
  716. return self::decode(@unserialize($input));
  717. }
  718. return @unserialize(base64_decode($input));
  719. }
  720. if (is_object($input)) {
  721. foreach (get_object_vars($input) as $idx => $value) {
  722. $input->$idx = self::decode($value);
  723. }
  724. return $input;
  725. }
  726. else if (is_array($input)) {
  727. foreach ($input as $idx => $value) {
  728. $input[$idx] = self::decode($value);
  729. }
  730. return $input;
  731. }
  732. return utf8_decode($input);
  733. }
  734. /**
  735. * Return correct name for a specific database table
  736. *
  737. * @param string $table Table name
  738. *
  739. * @return string Translated table name
  740. */
  741. public function table_name($table)
  742. {
  743. $rcube = rcube::get_instance();
  744. // add prefix to the table name if configured
  745. if ($prefix = $rcube->config->get('db_prefix')) {
  746. return $prefix . $table;
  747. }
  748. return $table;
  749. }
  750. /**
  751. * Set class option value
  752. *
  753. * @param string $name Option name
  754. * @param mixed $value Option value
  755. */
  756. public function set_option($name, $value)
  757. {
  758. $this->options[$name] = $value;
  759. }
  760. /**
  761. * MDB2 DSN string parser
  762. *
  763. * @param string $sequence Secuence name
  764. *
  765. * @return array DSN parameters
  766. */
  767. public static function parse_dsn($dsn)
  768. {
  769. if (empty($dsn)) {
  770. return null;
  771. }
  772. // Find phptype and dbsyntax
  773. if (($pos = strpos($dsn, '://')) !== false) {
  774. $str = substr($dsn, 0, $pos);
  775. $dsn = substr($dsn, $pos + 3);
  776. }
  777. else {
  778. $str = $dsn;
  779. $dsn = null;
  780. }
  781. // Get phptype and dbsyntax
  782. // $str => phptype(dbsyntax)
  783. if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
  784. $parsed['phptype'] = $arr[1];
  785. $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
  786. }
  787. else {
  788. $parsed['phptype'] = $str;
  789. $parsed['dbsyntax'] = $str;
  790. }
  791. if (empty($dsn)) {
  792. return $parsed;
  793. }
  794. // Get (if found): username and password
  795. // $dsn => username:password@protocol+hostspec/database
  796. if (($at = strrpos($dsn,'@')) !== false) {
  797. $str = substr($dsn, 0, $at);
  798. $dsn = substr($dsn, $at + 1);
  799. if (($pos = strpos($str, ':')) !== false) {
  800. $parsed['username'] = rawurldecode(substr($str, 0, $pos));
  801. $parsed['password'] = rawurldecode(substr($str, $pos + 1));
  802. }
  803. else {
  804. $parsed['username'] = rawurldecode($str);
  805. }
  806. }
  807. // Find protocol and hostspec
  808. // $dsn => proto(proto_opts)/database
  809. if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
  810. $proto = $match[1];
  811. $proto_opts = $match[2] ? $match[2] : false;
  812. $dsn = $match[3];
  813. }
  814. // $dsn => protocol+hostspec/database (old format)
  815. else {
  816. if (strpos($dsn, '+') !== false) {
  817. list($proto, $dsn) = explode('+', $dsn, 2);
  818. }
  819. if ( strpos($dsn, '//') === 0
  820. && strpos($dsn, '/', 2) !== false
  821. && $parsed['phptype'] == 'oci8'
  822. ) {
  823. //oracle's "Easy Connect" syntax:
  824. //"username/password@[//]host[:port][/service_name]"
  825. //e.g. "scott/tiger@//mymachine:1521/oracle"
  826. $proto_opts = $dsn;
  827. $pos = strrpos($proto_opts, '/');
  828. $dsn = substr($proto_opts, $pos + 1);
  829. $proto_opts = substr($proto_opts, 0, $pos);
  830. }
  831. else if (strpos($dsn, '/') !== false) {
  832. list($proto_opts, $dsn) = explode('/', $dsn, 2);
  833. }
  834. else {
  835. $proto_opts = $dsn;
  836. $dsn = null;
  837. }
  838. }
  839. // process the different protocol options
  840. $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
  841. $proto_opts = rawurldecode($proto_opts);
  842. if (strpos($proto_opts, ':') !== false) {
  843. list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
  844. }
  845. if ($parsed['protocol'] == 'tcp') {
  846. $parsed['hostspec'] = $proto_opts;
  847. }
  848. else if ($parsed['protocol'] == 'unix') {
  849. $parsed['socket'] = $proto_opts;
  850. }
  851. // Get dabase if any
  852. // $dsn => database
  853. if ($dsn) {
  854. // /database
  855. if (($pos = strpos($dsn, '?')) === false) {
  856. $parsed['database'] = rawurldecode($dsn);
  857. // /database?param1=value1&param2=value2
  858. }
  859. else {
  860. $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
  861. $dsn = substr($dsn, $pos + 1);
  862. if (strpos($dsn, '&') !== false) {
  863. $opts = explode('&', $dsn);
  864. }
  865. else { // database?param1=value1
  866. $opts = array($dsn);
  867. }
  868. foreach ($opts as $opt) {
  869. list($key, $value) = explode('=', $opt);
  870. if (!array_key_exists($key, $parsed) || false === $parsed[$key]) {
  871. // don't allow params overwrite
  872. $parsed[$key] = rawurldecode($value);
  873. }
  874. }
  875. }
  876. }
  877. return $parsed;
  878. }
  879. /**
  880. * Returns PDO DSN string from DSN array
  881. *
  882. * @param array $dsn DSN parameters
  883. *
  884. * @return string DSN string
  885. */
  886. protected function dsn_string($dsn)
  887. {
  888. $params = array();
  889. $result = $dsn['phptype'] . ':';
  890. if ($dsn['hostspec']) {
  891. $params[] = 'host=' . $dsn['hostspec'];
  892. }
  893. if ($dsn['port']) {
  894. $params[] = 'port=' . $dsn['port'];
  895. }
  896. if ($dsn['database']) {
  897. $params[] = 'dbname=' . $dsn['database'];
  898. }
  899. if (!empty($params)) {
  900. $result .= implode(';', $params);
  901. }
  902. return $result;
  903. }
  904. /**
  905. * Returns driver-specific connection options
  906. *
  907. * @param array $dsn DSN parameters
  908. *
  909. * @return array Connection options
  910. */
  911. protected function dsn_options($dsn)
  912. {
  913. $result = array();
  914. return $result;
  915. }
  916. }