Permalink
Browse files

Merge branch 'master' of github.com:roundcube/roundcubemail

  • Loading branch information...
2 parents 09efab3 + 7c15137 commit fe847dfe092408c6f3c79bcab4c661ccf57f4646 @katiepru katiepru committed Aug 16, 2012
Showing with 2,080 additions and 21,057 deletions.
  1. +1 −1 .htaccess
  2. +40 −24 CHANGELOG
  3. +3 −4 INSTALL
  4. +1 −1 bin/update.sh
  5. +3 −3 config/main.inc.php.dist
  6. +20 −19 index.php
  7. +12 −13 installer/check.php
  8. +2 −7 installer/config.php
  9. +14 −82 installer/rcube_install.php
  10. +3 −3 installer/test.php
  11. +0 −2 installer/utils.php
  12. +1 −1 plugins/acl/acl.php
  13. +1 −1 plugins/enigma/enigma.php
  14. +2 −0 plugins/managesieve/Changelog
  15. +3 −1 plugins/managesieve/lib/Net/Sieve.php
  16. +10 −13 plugins/new_user_identity/new_user_identity.php
  17. +3 −3 plugins/new_user_identity/package.xml
  18. +1 −1 plugins/password/drivers/sql.php
  19. +0 −4 plugins/password/drivers/virtualmin.php
  20. +1 −1 plugins/squirrelmail_usercopy/config.inc.php.dist
  21. +3 −3 plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
  22. +1 −1 plugins/subscriptions_option/subscriptions_option.php
  23. +1 −1 plugins/vcard_attachments/vcard_attachments.php
  24. +26 −23 program/include/clisetup.php
  25. +16 −12 program/include/html.php
  26. +4 −6 program/include/rcube.php
  27. +10 −7 program/include/rcube_cache.php
  28. +7 −5 program/include/rcube_config.php
  29. +6 −5 program/include/rcube_contacts.php
  30. +1,002 −0 program/include/rcube_db.php
  31. +155 −0 program/include/rcube_db_mssql.php
  32. +154 −0 program/include/rcube_db_mysql.php
  33. +135 −0 program/include/rcube_db_pgsql.php
  34. +178 −0 program/include/rcube_db_sqlite.php
  35. +156 −0 program/include/rcube_db_sqlsrv.php
  36. +18 −15 program/include/rcube_imap.php
  37. +2 −2 program/include/rcube_imap_cache.php
  38. +2 −2 program/include/rcube_imap_generic.php
  39. +11 −6 program/include/rcube_ldap.php
  40. +0 −935 program/include/rcube_mdb2.php
  41. +1 −1 program/include/rcube_message.php
  42. +4 −1 program/include/rcube_output_html.php
  43. +2 −2 program/include/rcube_plugin_api.php
  44. +8 −6 program/include/rcube_session.php
  45. +24 −1 program/include/rcube_shared.inc
  46. +1 −1 program/include/rcube_smtp.php
  47. +6 −6 program/include/rcube_spellchecker.php
  48. +4 −1 program/include/rcube_storage.php
  49. +4 −5 program/include/rcube_user.php
  50. +5 −5 program/include/rcube_utils.php
  51. +1 −1 program/include/rcube_vcard.php
  52. +11 −33 program/js/app.js
  53. +1 −1 program/js/list.js
  54. +0 −4,270 program/lib/MDB2.php
  55. +0 −183 program/lib/MDB2/Date.php
  56. +0 −1,838 program/lib/MDB2/Driver/Datatype/Common.php
  57. +0 −496 program/lib/MDB2/Driver/Datatype/mssql.php
  58. +0 −562 program/lib/MDB2/Driver/Datatype/mysql.php
  59. +0 −599 program/lib/MDB2/Driver/Datatype/mysqli.php
  60. +0 −579 program/lib/MDB2/Driver/Datatype/pgsql.php
  61. +0 −418 program/lib/MDB2/Driver/Datatype/sqlite.php
  62. +0 −451 program/lib/MDB2/Driver/Datatype/sqlsrv.php
  63. +0 −293 program/lib/MDB2/Driver/Function/Common.php
  64. +0 −193 program/lib/MDB2/Driver/Function/mssql.php
  65. +0 −136 program/lib/MDB2/Driver/Function/mysql.php
  66. +0 −144 program/lib/MDB2/Driver/Function/mysqli.php
  67. +0 −115 program/lib/MDB2/Driver/Function/pgsql.php
  68. +0 −162 program/lib/MDB2/Driver/Function/sqlite.php
  69. +0 −189 program/lib/MDB2/Driver/Function/sqlsrv.php
  70. +0 −1,014 program/lib/MDB2/Driver/Manager/Common.php
  71. +0 −1,129 program/lib/MDB2/Driver/Manager/mssql.php
  72. +0 −1,438 program/lib/MDB2/Driver/Manager/mysql.php
  73. +0 −1,438 program/lib/MDB2/Driver/Manager/mysqli.php
  74. +0 −948 program/lib/MDB2/Driver/Manager/pgsql.php
  75. +0 −1,362 program/lib/MDB2/Driver/Manager/sqlite.php
  76. +0 −1,383 program/lib/MDB2/Driver/Manager/sqlsrv.php
  77. +0 −61 program/lib/MDB2/Driver/Native/Common.php
  78. +0 −60 program/lib/MDB2/Driver/Native/mssql.php
  79. +0 −60 program/lib/MDB2/Driver/Native/mysql.php
  80. +0 −60 program/lib/MDB2/Driver/Native/mysqli.php
  81. +0 −88 program/lib/MDB2/Driver/Native/pgsql.php
  82. +0 −60 program/lib/MDB2/Driver/Native/sqlite.php
  83. +0 −57 program/lib/MDB2/Driver/Native/sqlsrv.php
Sorry, we could not display the entire diff because it was too big.
View
2 .htaccess
@@ -28,7 +28,7 @@ php_value mbstring.func_overload 0
<IfModule mod_rewrite.c>
RewriteEngine On
-RewriteRule ^favicon\.ico$ skins/default/images/favicon.ico
+RewriteRule ^favicon\.ico$ skins/larry/images/favicon.ico
# security rules
RewriteRule .git - [F]
RewriteRule ^/?(README(.md)?|INSTALL|LICENSE|SQL|bin|CHANGELOG)$ - [F]
View
64 CHANGELOG
@@ -1,10 +1,48 @@
CHANGELOG Roundcube Webmail
===========================
+- Rewritten test scripts for PHPUnit
+- Fix lower-casing email address on replies (#1488598)
+- Fix line separator in exported messages (#1488603)
+- Fix XSS issue where plain signatures wasn't secured in HTML mode (#1488613)
+- Fix XSS issue where href="javascript:" wasn't secured (#1488613)
+- Fix impossible to create message with empty plain text part (#1488610)
+- Fix stripped apostrophes when replying in plain text to HTML message (#1488606)
+- Fix inactive Save search option after advanced search (#1488607)
+- Fix Remove from group option is active for contact search result (#1488608)
+- Disable autocapitalization in login form on iPad/iPhone (#1488609)
+- Fix focus on the list when list row is clicked (#1488600)
+- Added separate From and To columns apart from smart From/To column (#1486891)
+- Fix fallback to Larry skin when configured skin isn't available (#1488591)
+- Fix (workaround) delete operations with some versions of memcache (#1488592)
+- Fix (disable) request validation for spell and spell_html actions
+- Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332)
+- Removed PEAR::MDB2 package
- Removed users.alias column, added option ('user_aliases')
to use email address from identities as username (#1488581)
- Removed redundant cache.cache_id column (#1488528)
- Fix order of attachments in sent mail (#1488423)
+- Fix Shift + delete button does not permanently delete messages (#1488243)
+- Add Content-Length for attachments where possible (#1485478)
+- Fix attachment sizes in message print page and attachment preview page (#1488515)
+- Add mail attachments using drag & drop on HTML5 enabled browsers
+- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
+- Display Tiff as Jpeg in browsers without Tiff support (#1488452)
+- Don't display Pdf/Tiff/Flash attachments inline without browser support (#1488452, #1487929)
+- Add is_escaped attribute for html_select and html_textarea (#1488485)
+- Fix issue where draft auto-save wasn't executed after some inactivity time
+- Add vCard import from multiple files at once (#1488015)
+- Roundcube Framework:
+ Add possibility to replace IMAP driver with custom class
+ Add IMAP auto-connection feature, improving performance with caching enabled
+ Replace imap_init hook with storage_init (with additional 'driver' argument)
+ Improved performance by caching IMAP server's capabilities in session
+ Unified global functions naming (rcube_ prefix)
+ Move global functions from main.inc and rcube_shared.inc into classes
+ Better classes separation
+
+RELEASE 0.8.0
+-------------
- Don't show product version on login screen (can be enabled by config)
- Renamed old default skin to 'classic'. Larry is the new default skin.
- Support connections to memcached socket file (#1488577)
@@ -26,43 +64,25 @@ CHANGELOG Roundcube Webmail
- Fix removing contact photo using LDAP addressbook (#1488420)
- Fix storing X-ANNIVERSARY date in vCard format (#1488527)
- Update to Mail_Mime-1.8.5 (#1488521)
-- Fix Shift + delete button does not permanently delete messages (#1488243)
-- Add Content-Length for attachments where possible (#1485478)
-- Fix attachment sizes in message print page and attachment preview page (#1488515)
- Fix XSS vulnerability in message subject handling using Larry skin (#1488519)
- Fix handling of links with various URI schemes e.g. "skype:" (#1488106)
- Fix handling of links inside PRE elements on html to text conversion
- Fix indexing of links on html to text conversion
-- Add mail attachments using drag & drop on HTML5 enabled browsers
-- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
- Decode header value in rcube_mime::get() by default (#1488511)
- Fix errors with enabled PHP magic_quotes_sybase option (#1488506)
- Fix SQL query for contacts listing on MS SQL Server (#1488505)
-- Update to TinyMCE 3.5.2
- Fix window.resize handler on IE8 and Opera (#1488453)
- Don't let error message popups cover the login form (#1488500)
-- Display Tiff as Jpeg in browsers without Tiff support (#1488452)
-- Don't display Pdf/Tiff/Flash attachments inline without browser support (#1488452, #1487929)
+- Update to TinyMCE 3.5.2
- Don't show errors when moving contacts into groups they are already in (#1488493)
- Make folders with unread messages in subfolders bold again (#1486793)
- Abbreviate long attachment file names with ellipsis (#1488499)
- Fix html2text conversion of strong|b|a|th|h tags when used in upper case
- Add listcontrols template container in Larry skin (#1488498)
- Fix host autoselection when default_host is an array (#1488495)
- Move messages forwarding mode setting into Preferences
-- Add is_escaped attribute for html_select and html_textarea (#1488485)
- Fix HTML entities handling in HTML editor (#1488483)
- Fix listing shared folders on Courier IMAP (#1488466)
-- Fix issue where draft auto-save wasn't executed after some inactivity time
-- Add vCard import from multiple files at once (#1488015)
-- Roundcube Framework:
- Add possibility to replace IMAP driver with custom class
- Add IMAP auto-connection feature, improving performance with caching enabled
- Replace imap_init hook with storage_init (with additional 'driver' argument)
- Improved performance by caching IMAP server's capabilities in session
- Unified global functions naming (rcube_ prefix)
- Move global functions from main.inc and rcube_shared.inc into classes
- Better classes separation
RELEASE 0.8-rc
--------------
@@ -181,7 +201,7 @@ RELEASE 0.7
- Fix handling contact photo url with https:// prefix (#1488202)
- Fix possible infinite redirect on attachment preview (#1488199)
- Improved clickjacking protection for browsers which don't support X-Frame-Options headers
-- Fixed bug where similiar folder names were highlighted wrong (#1487860)
+- Fixed bug where similar folder names were highlighted wrong (#1487860)
- Fixed bug in handling link with '!' character in it (#1488195)
- Fixed bug where session ID's length was limited to 40 characters (#1488196)
- TinyMCE security issue: removed moxieplayer (embedding flv and mp4 is not supported anymore)
@@ -1384,7 +1404,3 @@ RELEASE 0.1-RC1
Now based on the message structure delivered by the IMAP server.
- Fixed some XSS and SQL injection issues
- Fixed charset problems with folder renaming
-
-
-
-
View
7 INSTALL
@@ -12,14 +12,13 @@ REQUIREMENTS
* The Apache, Lighttpd, Cherokee or Hiawatha web server
* .htaccess support allowing overrides for DirectoryIndex
* PHP Version 5.2.1 or greater including
- - PCRE, DOM, JSON, XML, Session, Sockets (required)
+ - PDO, PCRE, DOM, JSON, XML, Session, Sockets (required)
- libiconv (recommended)
- mbstring, fileinfo, mcrypt (optional)
* PEAR packages distributed with Roundcube or external:
- - MDB2 2.5.0 or newer
- Mail_Mime 1.8.1 or newer
- Mail_mimeDecode 1.5.5 or newer
- - Net_SMTP 1.4.2 or newer
+ - Net_SMTP (latest from https://github.com/pear/Net_SMTP/)
- Net_IDNA2 0.1.1 or newer
- Auth_SASL 1.0.6 or newer
* php.ini options (see .htaccess file):
@@ -34,7 +33,7 @@ REQUIREMENTS
- magic_quotes_sybase disabled
* PHP compiled with OpenSSL to connect to IMAPS and to use the spell checker
* A MySQL (4.0.8 or newer), PostgreSQL, MSSQL database engine
- or the SQLite extension for PHP
+ or SQLite support in PHP
* One of the above databases with permission to create tables
* An SMTP server (recommended) or PHP configured for mail delivery
View
2 bin/update.sh
@@ -143,7 +143,7 @@ if ($RCI->configured) {
// check database schema
if ($RCI->config['db_dsnw']) {
- $DB = new rcube_mdb2($RCI->config['db_dsnw'], '', false);
+ $DB = rcube_db::factory($RCI->config['db_dsnw'], '', false);
$DB->db_connect('w');
if ($db_error_msg = $DB->is_error()) {
echo "Error connecting to database: $db_error_msg\n";
View
6 config/main.inc.php.dist
@@ -387,15 +387,15 @@ $rcmail_config['plugins'] = array();
// ----------------------------------
// default messages sort column. Use empty value for default server's sorting,
-// or 'arrival', 'date', 'subject', 'from', 'to', 'size', 'cc'
+// or 'arrival', 'date', 'subject', 'from', 'to', 'fromto', 'size', 'cc'
$rcmail_config['message_sort_col'] = '';
// default messages sort order
$rcmail_config['message_sort_order'] = 'DESC';
// These cols are shown in the message list. Available cols are:
-// subject, from, to, cc, replyto, date, size, status, flag, attachment, 'priority'
-$rcmail_config['list_cols'] = array('subject', 'status', 'from', 'date', 'size', 'flag', 'attachment');
+// subject, from, to, fromto, cc, replyto, date, size, status, flag, attachment, 'priority'
+$rcmail_config['list_cols'] = array('subject', 'status', 'fromto', 'date', 'size', 'flag', 'attachment');
// the default locale setting (leave empty for auto-detection)
// RFC1766 formatted language name like en_US, de_DE, de_CH, fr_FR, pt_BR
View
39 index.php
@@ -219,27 +219,28 @@
// CSRF prevention
else {
// don't check for valid request tokens in these actions
- $request_check_whitelist = array('login'=>1, 'spell'=>1);
-
- // check client X-header to verify request origin
- if ($OUTPUT->ajax_call) {
- if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) {
- header('HTTP/1.1 403 Forbidden');
- die("Invalid Request");
+ $request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1);
+
+ if (!$request_check_whitelist[$RCMAIL->action]) {
+ // check client X-header to verify request origin
+ if ($OUTPUT->ajax_call) {
+ if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) {
+ header('HTTP/1.1 403 Forbidden');
+ die("Invalid Request");
+ }
+ }
+ // check request token in POST form submissions
+ else if (!empty($_POST) && !$RCMAIL->check_request()) {
+ $OUTPUT->show_message('invalidrequest', 'error');
+ $OUTPUT->send($RCMAIL->task);
}
- }
- // check request token in POST form submissions
- else if (!empty($_POST) && !$request_check_whitelist[$RCMAIL->action] && !$RCMAIL->check_request()) {
- $OUTPUT->show_message('invalidrequest', 'error');
- $OUTPUT->send($RCMAIL->task);
- }
- // check referer if configured
- if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcmail::check_referer()) {
- raise_error(array(
- 'code' => 403,
- 'type' => 'php',
- 'message' => "Referer check failed"), true, true);
+ // check referer if configured
+ if ($RCMAIL->config->get('referer_check') && !rcmail::check_referer()) {
+ raise_error(array(
+ 'code' => 403, 'type' => 'php',
+ 'message' => "Referer check failed"), true, true);
+ }
}
}
View
25 installer/check.php
@@ -6,7 +6,8 @@
'DOM' => 'dom',
'Session' => 'session',
'XML' => 'xml',
- 'JSON' => 'json'
+ 'JSON' => 'json',
+ 'PDO' => 'PDO',
);
$optional_php_exts = array(
@@ -21,19 +22,11 @@
$required_libs = array(
'PEAR' => 'PEAR.php',
- 'MDB2' => 'MDB2.php',
'Net_SMTP' => 'Net/SMTP.php',
'Net_IDNA2' => 'Net/IDNA2.php',
'Mail_mime' => 'Mail/mime.php',
);
-$supported_dbs = array(
- 'MySQL' => 'mysql',
- 'MySQLi' => 'mysqli',
- 'PostgreSQL' => 'pgsql',
- 'SQLite (v2)' => 'sqlite',
-);
-
$ini_checks = array(
'file_uploads' => 1,
'session.auto_start' => 0,
@@ -63,8 +56,14 @@
'DOM' => 'http://www.php.net/manual/en/book.dom.php',
'Intl' => 'http://www.php.net/manual/en/book.intl.php',
'Exif' => 'http://www.php.net/manual/en/book.exif.php',
+ 'PDO' => 'http://www.php.net/manual/en/book.pdo.php',
+ 'pdo_mysql' => 'http://www.php.net/manual/en/book.pdo-mysql.php',
+ 'pdo_pgsql' => 'http://www.php.net/manual/en/book.pdo-pgsql.php',
+ 'pdo_sqlite' => 'http://www.php.net/manual/en/book.pdo-sqlite.php',
+ 'pdo_sqlite2' => 'http://www.php.net/manual/en/book.pdo-sqlite.php',
+ 'pdo_sqlsrv' => 'http://www.php.net/manual/en/book.pdo-sqlsrv.php',
+ 'pdo_dblib' => 'http://www.php.net/manual/en/book.pdo-dblib.php',
'PEAR' => 'http://pear.php.net',
- 'MDB2' => 'http://pear.php.net/package/MDB2',
'Net_SMTP' => 'http://pear.php.net/package/Net_SMTP',
'Mail_mime' => 'http://pear.php.net/package/Mail_mime',
'Net_IDNA2' => 'http://pear.php.net/package/Net_IDNA2',
@@ -129,14 +128,14 @@
<?php
$prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : '';
-foreach ($supported_dbs as $database => $ext) {
+foreach ($RCI->supported_dbs as $database => $ext) {
if (extension_loaded($ext)) {
$RCI->pass($database);
}
else {
$_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX;
- $msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : 'Not installed';
- $RCI->na($database, $msg, $source_urls[$database]);
+ $msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : '';
+ $RCI->na($database, $msg, $source_urls[$ext]);
}
echo '<br />';
}
View
9 installer/config.php
@@ -267,13 +267,8 @@
<p>Database settings for read/write operations:</p>
<?php
-require_once 'MDB2.php';
-
-$supported_dbs = array('MySQL' => 'mysql', 'MySQLi' => 'mysqli',
- 'PgSQL' => 'pgsql', 'SQLite' => 'sqlite');
-
$select_dbtype = new html_select(array('name' => '_dbtype', 'id' => "cfgdbtype"));
-foreach ($supported_dbs AS $database => $ext) {
+foreach ($RCI->supported_dbs as $database => $ext) {
if (extension_loaded($ext)) {
$select_dbtype->add($database, $ext);
}
@@ -284,7 +279,7 @@
$input_dbuser = new html_inputfield(array('name' => '_dbuser', 'size' => 20, 'id' => "cfgdbuser"));
$input_dbpass = new html_passwordfield(array('name' => '_dbpass', 'size' => 20, 'id' => "cfgdbpass"));
-$dsnw = MDB2::parseDSN($RCI->getprop('db_dsnw'));
+$dsnw = rcube_db::parse_dsn($RCI->getprop('db_dsnw'));
echo $select_dbtype->show($RCI->is_post ? $_POST['_dbtype'] : $dsnw['phptype']);
echo '<label for="cfgdbtype">Database type</label><br />';
View
96 installer/rcube_install.php
@@ -50,6 +50,17 @@ class rcube_install
'des_key', 'session_lifetime', 'support_url',
);
+ // list of supported database drivers
+ var $supported_dbs = array(
+ 'MySQL' => 'pdo_mysql',
+ 'PostgreSQL' => 'pdo_pgsql',
+ 'SQLite' => 'pdo_sqlite',
+ 'SQLite (v2)' => 'pdo_sqlite2',
+ 'SQL Server (SQLSRV)' => 'pdo_sqlsrv',
+ 'SQL Server (DBLIB)' => 'pdo_dblib',
+ );
+
+
/**
* Constructor
*/
@@ -371,7 +382,7 @@ function db_schema_check($DB, $update = false)
$errors[] = "Missing columns in table '$table': " . join(',', $diff);
}
}
-
+
return !empty($errors) ? $errors : false;
}
@@ -394,87 +405,8 @@ private function db_read_schema($schemafile)
}
}
}
-
- return $schema;
- }
-
-
- /**
- * Compare the local database schema with the reference schema
- * required for this version of Roundcube
- *
- * @param boolean True if the schema schould be updated
- * @return boolean True if the schema is up-to-date, false if not or an error occured
- */
- function mdb2_schema_check($update = false)
- {
- if (!$this->configured)
- return false;
-
- $options = array(
- 'use_transactions' => false,
- 'log_line_break' => "\n",
- 'idxname_format' => '%s',
- 'debug' => false,
- 'quote_identifier' => true,
- 'force_defaults' => false,
- 'portability' => true
- );
-
- $dsnw = $this->config['db_dsnw'];
- $schema = MDB2_Schema::factory($dsnw, $options);
- $schema->db->supported['transactions'] = false;
-
- if (PEAR::isError($schema)) {
- $this->raise_error(array('code' => $schema->getCode(), 'message' => $schema->getMessage() . ' ' . $schema->getUserInfo()));
- return false;
- }
- else {
- $definition = $schema->getDefinitionFromDatabase();
- $definition['charset'] = 'utf8';
-
- if (PEAR::isError($definition)) {
- $this->raise_error(array('code' => $definition->getCode(), 'message' => $definition->getMessage() . ' ' . $definition->getUserInfo()));
- return false;
- }
-
- // load reference schema
- $dsn_arr = MDB2::parseDSN($this->config['db_dsnw']);
-
- $ref_schema = INSTALL_PATH . 'SQL/' . $dsn_arr['phptype'] . '.schema.xml';
-
- if (is_readable($ref_schema)) {
- $reference = $schema->parseDatabaseDefinition($ref_schema, false, array(), $schema->options['fail_on_invalid_names']);
-
- if (PEAR::isError($reference)) {
- $this->raise_error(array('code' => $reference->getCode(), 'message' => $reference->getMessage() . ' ' . $reference->getUserInfo()));
- }
- else {
- $diff = $schema->compareDefinitions($reference, $definition);
-
- if (empty($diff)) {
- return true;
- }
- else if ($update) {
- // update database schema with the diff from the above check
- $success = $schema->alterDatabase($reference, $definition, $diff);
-
- if (PEAR::isError($success)) {
- $this->raise_error(array('code' => $success->getCode(), 'message' => $success->getMessage() . ' ' . $success->getUserInfo()));
- }
- else
- return true;
- }
- echo '<pre>'; var_dump($diff); echo '</pre>';
- return false;
- }
- }
- else
- $this->raise_error(array('message' => "Could not find reference schema file ($ref_schema)"));
- return false;
- }
- return false;
+ return $schema;
}
@@ -521,7 +453,7 @@ function versions_select($attrib = array())
'0.5-beta', '0.5', '0.5.1',
'0.6-beta', '0.6',
'0.7-beta', '0.7', '0.7.1', '0.7.2',
- '0.8-beta', '0.8-rc',
+ '0.8-beta', '0.8-rc', '0.8.0',
));
return $select;
}
View
6 installer/test.php
@@ -125,9 +125,9 @@
$db_working = false;
if ($RCI->configured) {
if (!empty($RCI->config['db_dsnw'])) {
-
- $DB = new rcube_mdb2($RCI->config['db_dsnw'], '', false);
+ $DB = rcube_db::factory($RCI->config['db_dsnw'], '', false);
$DB->db_connect('w');
+
if (!($db_error_msg = $DB->is_error())) {
$RCI->pass('DSN (write)');
echo '<br />';
@@ -167,7 +167,7 @@
// test database
if ($db_working) {
$db_read = $DB->query("SELECT count(*) FROM {$RCI->config['db_table_users']}");
- if ($DB->db_error) {
+ if ($DB->is_error()) {
$RCI->fail('DB Schema', "Database not initialized");
echo '<p><input type="submit" name="initdb" value="Initialize database" /></p>';
$db_working = false;
View
2 installer/utils.php
@@ -23,15 +23,13 @@ function __autoload($classname)
{
$filename = preg_replace(
array(
- '/MDB2_(.+)/',
'/Mail_(.+)/',
'/Net_(.+)/',
'/Auth_(.+)/',
'/^html_.+/',
'/^utf8$/'
),
array(
- 'MDB2/\\1',
'Mail/\\1',
'Net/\\1',
'Auth/\\1',
View
2 plugins/acl/acl.php
@@ -615,7 +615,7 @@ function rights_supported()
private function get_realm()
{
// When user enters a username without domain part, realm
- // alows to add it to the username (and display correct username in the table)
+ // allows to add it to the username (and display correct username in the table)
if (isset($_SESSION['acl_username_realm'])) {
return $_SESSION['acl_username_realm'];
View
2 plugins/enigma/enigma.php
@@ -429,7 +429,7 @@ function message_output($p)
$style = "margin:0 1em; padding:0.2em 0.5em; border:1px solid #999; width: auto"
." border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px";
- // add box below messsage body
+ // add box below message body
$p['content'] .= html::p(array('style' => $style),
html::a(array(
'href' => "#",
View
2 plugins/managesieve/Changelog
@@ -1,3 +1,5 @@
+- Fixed issue with DBMail bug [http://pear.php.net/bugs/bug.php?id=19077] (#1488594)
+
* version 5.2 [2012-07-24]
-----------------------------------------------------------
- Added GUI for variables setting - RFC5229 (patch from Paweł Słowik)
View
4 plugins/managesieve/lib/Net/Sieve.php
@@ -1098,7 +1098,9 @@ function _doCmd($cmd = '', $auth = false)
return PEAR::raiseError(trim($response . $line), 6);
}
- if (preg_match('/^{([0-9]+)}/i', $line, $matches)) {
+ // "\+?" is added in the regexp to workaround DBMail bug
+ // http://dbmail.org/mantis/view.php?id=963
+ if (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) {
// Matches literal string responses.
$line = $this->_recvBytes($matches[1] + 2);
View
23 plugins/new_user_identity/new_user_identity.php
@@ -19,10 +19,6 @@
* // When automatically setting a new users's full name in their
* // new identity, match the user's login name against this field.
* $rcmail_config['new_user_identity_match'] = 'uid';
- *
- * // Use this field (from fieldmap configuration) to fill alias col of
- * // the new user record.
- * $rcmail_config['new_user_identity_alias'] = 'alias';
*/
class new_user_identity extends rcube_plugin
{
@@ -38,16 +34,16 @@ function init()
function lookup_user_name($args)
{
$rcmail = rcmail::get_instance();
-
+
if ($this->init_ldap($args['host'])) {
- $results = $this->ldap->search('*', $args['user'], TRUE);
+ $results = $this->ldap->search('*', $args['user'], true);
if (count($results->records) == 1) {
- $args['user_name'] = $results->records[0]['name'];
- if (!$args['user_email'] && strpos($results->records[0]['email'], '@')) {
- $args['user_email'] = rcube_idn_to_ascii($results->records[0]['email']);
- }
- if (($alias_col = $rcmail->config->get('new_user_identity_alias')) && $results->records[0][$alias_col]) {
- $args['alias'] = $results->records[0][$alias_col];
+ $user_name = is_array($results->records[0]['name']) ? $results->records[0]['name'][0] : $results->records[0]['name'];
+ $user_email = is_array($results->records[0]['email']) ? $results->records[0]['email'][0] : $results->records[0]['email'];
+
+ $args['user_name'] = $user_name;
+ if (!$args['user_email'] && strpos($user_email, '@')) {
+ $args['user_email'] = rcube_idn_to_ascii($user_email);
}
}
}
@@ -56,8 +52,9 @@ function lookup_user_name($args)
private function init_ldap($host)
{
- if ($this->ldap)
+ if ($this->ldap) {
return $this->ldap->ready;
+ }
$rcmail = rcmail::get_instance();
View
6 plugins/new_user_identity/package.xml
@@ -15,10 +15,10 @@
<email>alec@alec.pl</email>
<active>yes</active>
</lead>
- <date>2011-11-21</date>
+ <date>2012-08-13</date>
<version>
- <release>1.0.5</release>
- <api>1.0</api>
+ <release>1.0.7</release>
+ <api>1.1</api>
</version>
<stability>
<release>stable</release>
View
2 plugins/password/drivers/sql.php
@@ -26,7 +26,7 @@ function save($curpass, $passwd)
else if (!is_array($dsn) && !preg_match('/\?new_link=true/', $dsn))
$dsn .= '?new_link=true';
- $db = new rcube_mdb2($dsn, '', FALSE);
+ $db = rcube_db::factory($dsn, '', false);
$db->set_debug((bool)$rcmail->config->get('sql_debug'));
$db->db_connect('w');
}
View
4 plugins/password/drivers/virtualmin.php
@@ -48,10 +48,6 @@ function save($currpass, $newpass)
$pieces = explode("_", $username);
$domain = $pieces[0];
break;
- case 8: // domain taken from alias, username left as it was
- $email = $rcmail->user->data['alias'];
- $domain = substr(strrchr($email, "@"), 1);
- break
default: // username@domain
$domain = substr(strrchr($username, "@"), 1);
}
View
2 plugins/squirrelmail_usercopy/config.inc.php.dist
@@ -22,4 +22,4 @@ $rcmail_config['squirrelmail_identities_level'] = null;
// Set to false if you don't want the email address of the default identity
// (squirrelmail preference "email_address") to be saved as alias.
// Recommended: set to false if your squirrelmail config setting $edit_identity has been true.
-$rcmail_config['squirrelmail_set_alias'] = true;
+$rcmail_config['squirrelmail_set_alias'] = true;
View
6 plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
@@ -151,10 +151,10 @@ private function read_squirrel_prefs($uname)
$this->prefs = array();
/* connect to squirrelmail database */
- $db = new rcube_mdb2($rcmail->config->get('squirrelmail_dsn'));
- $db->db_connect('r'); // connect in read mode
+ $db = rcube_db::factory($rcmail->config->get('squirrelmail_dsn'));
- // $db->set_debug(true);
+ $db->set_debug($rcmail->config->get('sql_debug'));
+ $db->db_connect('r'); // connect in read mode
/* retrieve prefs */
$userprefs_table = $rcmail->config->get('squirrelmail_userprefs_table');
View
2 plugins/subscriptions_option/subscriptions_option.php
@@ -9,7 +9,7 @@
*
* Add it to the plugins list in config/main.inc.php to enable the user option
* The user option can be hidden and set globally by adding 'use_subscriptions'
- * to the the 'dont_override' configure line:
+ * to the 'dont_override' configure line:
* $rcmail_config['dont_override'] = array('use_subscriptions');
* and then set the global preference
* $rcmail_config['use_subscriptions'] = true; // or false
View
2 plugins/vcard_attachments/vcard_attachments.php
@@ -81,7 +81,7 @@ function html_output($p)
if ($vcard->email[0])
$display .= ' <'.$vcard->email[0].'>';
- // add box below messsage body
+ // add box below message body
$p['content'] .= html::p(array('class' => 'vcardattachment'),
html::a(array(
'href' => "#",
View
49 program/include/clisetup.php
@@ -33,33 +33,36 @@
*/
function get_opt($aliases = array())
{
- $args = array();
- for ($i=1; $i < count($_SERVER['argv']); $i++) {
- $arg = $_SERVER['argv'][$i];
- $value = true;
- $key = null;
+ $args = array();
- if ($arg[0] == '-') {
- $key = preg_replace('/^-+/', '', $arg);
- $sp = strpos($arg, '=');
- if ($sp > 0) {
- $key = substr($key, 0, $sp - 2);
- $value = substr($arg, $sp+1);
- }
- else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
- $value = $_SERVER['argv'][++$i];
- }
+ for ($i=1; $i < count($_SERVER['argv']); $i++) {
+ $arg = $_SERVER['argv'][$i];
+ $value = true;
+ $key = null;
- $args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
- }
- else
- $args[] = $arg;
+ if ($arg[0] == '-') {
+ $key = preg_replace('/^-+/', '', $arg);
+ $sp = strpos($arg, '=');
+ if ($sp > 0) {
+ $key = substr($key, 0, $sp - 2);
+ $value = substr($arg, $sp+1);
+ }
+ else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
+ $value = $_SERVER['argv'][++$i];
+ }
- if ($alias = $aliases[$key])
- $args[$alias] = $args[$key];
- }
+ $args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
+ }
+ else {
+ $args[] = $arg;
+ }
- return $args;
+ if ($alias = $aliases[$key]) {
+ $args[$alias] = $args[$key];
+ }
+ }
+
+ return $args;
}
View
28 program/include/html.php
@@ -171,7 +171,7 @@ public static function a($attr, $cont)
$attr = array('href' => $attr);
}
return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
- array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
+ array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
}
/**
@@ -358,7 +358,7 @@ class html_inputfield extends html
protected $tagname = 'input';
protected $type = 'text';
protected $allowed = array(
- 'type','name','value','size','tabindex',
+ 'type','name','value','size','tabindex','autocapitalize',
'autocomplete','checked','onchange','onclick','disabled','readonly',
'spellcheck','results','maxlength','src','multiple','placeholder',
);
@@ -532,7 +532,7 @@ class html_textarea extends html
{
protected $tagname = 'textarea';
protected $allowed = array('name','rows','cols','wrap','tabindex',
- 'onchange','disabled','readonly','spellcheck');
+ 'onchange','disabled','readonly','spellcheck');
/**
* Get HTML code for this object
@@ -563,7 +563,7 @@ public function show($value = '', $attrib = null)
}
return self::tag($this->tagname, $this->attrib, $value,
- array_merge(self::$common_attrib, $this->allowed));
+ array_merge(self::$common_attrib, $this->allowed));
}
}
@@ -591,7 +591,7 @@ class html_select extends html
protected $tagname = 'select';
protected $options = array();
protected $allowed = array('name','size','tabindex','autocomplete',
- 'multiple','onchange','disabled','rel');
+ 'multiple','onchange','disabled','rel');
/**
* Add a new option to this drop-down
@@ -655,7 +655,7 @@ class html_table extends html
{
protected $tagname = 'table';
protected $allowed = array('id','class','style','width','summary',
- 'cellpadding','cellspacing','border');
+ 'cellpadding','cellspacing','border');
private $header = array();
private $rows = array();
@@ -705,8 +705,9 @@ public function add($attr, $cont)
*/
public function add_header($attr, $cont)
{
- if (is_string($attr))
- $attr = array('class' => $attr);
+ if (is_string($attr)) {
+ $attr = array('class' => $attr);
+ }
$cell = new stdClass;
$cell->attrib = $attr;
@@ -763,11 +764,13 @@ public function add_row($attr = array())
*/
public function set_row_attribs($attr = array(), $index = null)
{
- if (is_string($attr))
- $attr = array('class' => $attr);
+ if (is_string($attr)) {
+ $attr = array('class' => $attr);
+ }
- if ($index === null)
+ if ($index === null) {
$index = $this->rowindex;
+ }
$this->rows[$index]->attrib = $attr;
}
@@ -781,8 +784,9 @@ public function set_row_attribs($attr = array(), $index = null)
*/
public function get_row_attribs($index = null)
{
- if ($index === null)
+ if ($index === null) {
$index = $this->rowindex;
+ }
return $this->rows[$index] ? $this->rows[$index]->attrib : null;
}
View
10 program/include/rcube.php
@@ -49,14 +49,14 @@ class rcube
/**
* Instace of database class.
*
- * @var rcube_mdb2
+ * @var rcube_pdo
*/
public $db;
/**
* Instace of Memcache class.
*
- * @var rcube_mdb2
+ * @var Memcache
*/
public $memcache;
@@ -160,15 +160,13 @@ protected function init($mode = 0)
/**
* Get the current database connection
*
- * @return rcube_mdb2 Database connection object
+ * @return rcube_pdo Database connection object
*/
public function get_dbh()
{
if (!$this->db) {
$config_all = $this->config->all();
-
- $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
- $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
+ $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
$this->db->set_debug((bool)$config_all['sql_debug']);
}
View
17 program/include/rcube_cache.php
@@ -33,9 +33,9 @@
class rcube_cache
{
/**
- * Instance of rcube_mdb2 or Memcache class
+ * Instance of database handler
*
- * @var rcube_mdb2/Memcache
+ * @var rcube_db|Memcache|bool
*/
private $db;
private $type;
@@ -254,7 +254,7 @@ private function read_record($key, $nostore=false)
}
else if ($this->type == 'apc') {
$data = apc_fetch($this->ckey($key));
- }
+ }
if ($data) {
$md5sum = md5($data);
@@ -294,7 +294,7 @@ private function read_record($key, $nostore=false)
}
$this->cache[$key] = $data;
- $this->cache_sums[$key] = $md5sum;
+ $this->cache_sums[$key] = $md5sum;
}
else {
$this->cache[$key] = null;
@@ -463,10 +463,13 @@ private function add_record($key, $data, $index=false)
*/
private function delete_record($key, $index=true)
{
- if ($this->type == 'memcache')
- $this->db->delete($this->ckey($key));
- else
+ if ($this->type == 'memcache') {
+ // #1488592: use 2nd argument
+ $this->db->delete($this->ckey($key), 0);
+ }
+ else {
apc_delete($this->ckey($key));
+ }
if ($index) {
if (($idx = array_search($key, $this->index)) !== false) {
View
12 program/include/rcube_config.php
@@ -26,6 +26,8 @@
*/
class rcube_config
{
+ const DEFAULT_SKIN = 'larry';
+
private $prop = array();
private $errors = array();
private $userprefs = array();
@@ -81,13 +83,13 @@ private function load()
$this->prop['skin'] = str_replace('skins/', '', unslashify($this->prop['skin_path']));
}
else {
- $this->prop['skin'] = 'larry';
+ $this->prop['skin'] = self::DEFAULT_SKIN;
}
}
// larry is the new default skin :-)
if ($this->prop['skin'] == 'default')
- $this->prop['skin'] = 'larry';
+ $this->prop['skin'] = self::DEFAULT_SKIN;
// fix paths
$this->prop['log_dir'] = $this->prop['log_dir'] ? realpath(unslashify($this->prop['log_dir'])) : INSTALL_PATH . 'logs';
@@ -254,7 +256,7 @@ public function set_user_prefs($prefs)
// larry is the new default skin :-)
if ($prefs['skin'] == 'default') {
- $prefs['skin'] = 'larry';
+ $prefs['skin'] = self::DEFAULT_SKIN;
}
$this->userprefs = $prefs;
@@ -322,7 +324,7 @@ public function get_crypto_key($key)
if (strlen($key) != 24) {
rcube::raise_error(array(
'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
+ 'file' => __FILE__, 'line' => __LINE__,
'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
), true, true);
}
@@ -346,7 +348,7 @@ public function header_delimiter()
else
rcube::raise_error(array(
'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
+ 'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid mail_header_delimiter setting"
), true, false);
}
View
11 program/include/rcube_contacts.php
@@ -36,7 +36,7 @@ class rcube_contacts extends rcube_addressbook
/**
* Store database connection.
*
- * @var rcube_mdb2
+ * @var rcube_db
*/
private $db = null;
private $user_id = 0;
@@ -934,8 +934,8 @@ function add_to_group($group_id, $ids)
$contact_id
);
- if ($this->db->db_error)
- $this->set_error(self::ERROR_SAVING, $this->db->db_error_msg);
+ if ($error = $this->db->is_error())
+ $this->set_error(self::ERROR_SAVING, $error);
else
$added++;
}
@@ -990,9 +990,10 @@ private function unique_groupname($name)
$checkname);
// append number to make name unique
- if ($hit = $this->db->num_rows($sql_result))
+ if ($hit = $this->db->fetch_array($sql_result)) {
$checkname = $name . ' ' . $num++;
- } while ($hit > 0);
+ }
+ } while ($hit);
return $checkname;
}
View
1,002 program/include/rcube_db.php
@@ -0,0 +1,1002 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_db.php |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Database wrapper class that implements PHP PDO functions |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl> |
+ +-----------------------------------------------------------------------+
+*/
+
+
+/**
+ * Database independent query interface
+ *
+ * This is a wrapper for the PHP PDO
+ *
+ * @package Database
+ * @version 1.0
+ */
+class rcube_db
+{
+ protected $db_dsnw; // DSN for write operations
+ protected $db_dsnr; // DSN for read operations
+ protected $db_connected = false; // Already connected ?
+ protected $db_mode; // Connection mode
+ protected $dbh; // Connection handle
+
+ protected $db_error = false;
+ protected $db_error_msg = '';
+ protected $conn_failure = false;
+ protected $a_query_results = array('dummy');
+ protected $last_res_id = 0;
+ protected $db_index = 0;
+ protected $tables;
+ protected $variables;
+
+ protected $options = array(
+ // column/table quotes
+ 'identifier_start' => '"',
+ 'identifier_end' => '"',
+ );
+
+
+ /**
+ * Factory, returns driver-specific instance of the class
+ *
+ * @param string $db_dsnw DSN for read/write operations
+ * @param string $db_dsnr Optional DSN for read only operations
+ * @param bool $pconn Enables persistent connections
+ *
+ * @return rcube_db Object instance
+ */
+ public static function factory($db_dsnw, $db_dsnr = '', $pconn = false)
+ {
+ $driver = strtolower(substr($db_dsnw, 0, strpos($db_dsnw, ':')));
+ $driver_map = array(
+ 'sqlite2' => 'sqlite',
+ 'sybase' => 'mssql',
+ 'dblib' => 'mssql',
+ 'mysqli' => 'mysql',
+ );
+
+ $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver;
+ $class = "rcube_db_$driver";
+
+ if (!class_exists($class)) {
+ rcube::raise_error(array('code' => 600, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => "Configuration error. Unsupported database driver: $driver"),
+ true, true);
+ }
+
+ return new $class($db_dsnw, $db_dsnr, $pconn);
+ }
+
+ /**
+ * Object constructor
+ *
+ * @param string $db_dsnw DSN for read/write operations
+ * @param string $db_dsnr Optional DSN for read only operations
+ * @param bool $pconn Enables persistent connections
+ */
+ public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
+ {
+ if (empty($db_dsnr)) {
+ $db_dsnr = $db_dsnw;
+ }
+
+ $this->db_dsnw = $db_dsnw;
+ $this->db_dsnr = $db_dsnr;
+ $this->db_pconn = $pconn;
+
+ $this->db_dsnw_array = self::parse_dsn($db_dsnw);
+ $this->db_dsnr_array = self::parse_dsn($db_dsnr);
+
+ // Initialize driver class
+ $this->init();
+ }
+
+ /**
+ * Initialization of the object with driver specific code
+ */
+ protected function init()
+ {
+ // To be used by driver classes
+ }
+
+ /**
+ * Connect to specific database
+ *
+ * @param array $dsn DSN for DB connections
+ *
+ * @return PDO database handle
+ */
+ protected function dsn_connect($dsn)
+ {
+ $this->db_error = false;
+ $this->db_error_msg = null;
+
+ // Get database specific connection options
+ $dsn_string = $this->dsn_string($dsn);
+ $dsn_options = $this->dsn_options($dsn);
+
+ if ($db_pconn) {
+ $dsn_options[PDO::ATTR_PERSISTENT] = true;
+ }
+
+ // Connect
+ try {
+ // with this check we skip fatal error on PDO object creation
+ if (!class_exists('PDO', false)) {
+ throw new Exception('PDO extension not loaded. See http://php.net/manual/en/intro.pdo.php');
+ }
+
+ $this->conn_prepare($dsn);
+
+ $dbh = new PDO($dsn_string, $dsn['username'], $dsn['password'], $dsn_options);
+
+ // don't throw exceptions or warnings
+ $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
+ }
+ catch (Exception $e) {
+ $this->db_error = true;
+ $this->db_error_msg = $e->getMessage();
+
+ rcube::raise_error(array('code' => 500, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $this->db_error_msg), true, false);
+
+ return null;
+ }
+
+ $this->conn_configure($dsn, $dbh);
+
+ return $dbh;
+ }
+
+ /**
+ * Driver-specific preparation of database connection
+ *
+ * @param array $dsn DSN for DB connections
+ */
+ protected function conn_prepare($dsn)
+ {
+ }
+
+ /**
+ * Driver-specific configuration of database connection
+ *
+ * @param array $dsn DSN for DB connections
+ * @param PDO $dbh Connection handler
+ */
+ protected function conn_configure($dsn, $dbh)
+ {
+ }
+
+ /**
+ * Driver-specific database character set setting
+ *
+ * @param string $charset Character set name
+ */
+ protected function set_charset($charset)
+ {
+ $this->query("SET NAMES 'utf8'");
+ }
+
+ /**
+ * Connect to appropriate database depending on the operation
+ *
+ * @param string $mode Connection mode (r|w)
+ */
+ public function db_connect($mode)
+ {
+ // previous connection failed, don't attempt to connect again
+ if ($this->conn_failure) {
+ return;
+ }
+
+ // no replication
+ if ($this->db_dsnw == $this->db_dsnr) {
+ $mode = 'w';
+ }
+
+ // Already connected
+ if ($this->db_connected) {
+ // connected to db with the same or "higher" mode
+ if ($this->db_mode == 'w' || $this->db_mode == $mode) {
+ return;
+ }
+ }
+
+ $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
+
+ $this->dbh = $this->dsn_connect($dsn);
+ $this->db_connected = is_object($this->dbh);
+
+ // use write-master when read-only fails
+ if (!$this->db_connected && $mode == 'r') {
+ $mode = 'w';
+ $this->dbh = $this->dsn_connect($this->db_dsnw_array);
+ $this->db_connected = is_object($this->dbh);
+ }
+
+ if ($this->db_connected) {
+ $this->db_mode = $mode;
+ $this->set_charset('utf8');
+ }
+ else {
+ $this->conn_failure = true;
+ }
+ }
+
+ /**
+ * Activate/deactivate debug mode
+ *
+ * @param boolean $dbg True if SQL queries should be logged
+ */
+ public function set_debug($dbg = true)
+ {
+ $this->options['debug_mode'] = $dbg;
+ }
+
+ /**
+ * Writes debug information/query to 'sql' log file
+ *
+ * @param string $query SQL query
+ */
+ protected function debug($query)
+ {
+ if ($this->options['debug_mode']) {
+ rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
+ }
+ }
+
+ /**
+ * Getter for error state
+ *
+ * @param int $res_id Optional query result identifier
+ *
+ * @return string Error message
+ */
+ public function is_error($res_id = null)
+ {
+ if ($res_id !== null) {
+ return $this->_get_result($res_id) === false ? $this->db_error_msg : null;
+ }
+
+ return $this->db_error ? $this->db_error_msg : null;
+ }
+
+ /**
+ * Connection state checker
+ *
+ * @return boolean True if in connected state
+ */
+ public function is_connected()
+ {
+ return !is_object($this->dbh) ? false : $this->db_connected;
+ }
+
+ /**
+ * Is database replication configured?
+ *
+ * @return bool Returns true if dsnw != dsnr
+ */
+ public function is_replicated()
+ {
+ return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
+ }
+
+ /**
+ * Get database runtime variables
+ *
+ * @param string $varname Variable name
+ * @param mixed $default Default value if variable is not set
+ *
+ * @return mixed Variable value or default
+ */
+ public function get_variable($varname, $default = null)
+ {
+ // to be implemented by driver class
+ return $default;
+ }
+
+ /**
+ * Execute a SQL query
+ *
+ * @param string SQL query to execute
+ * @param mixed Values to be inserted in query
+ *
+ * @return number Query handle identifier
+ */
+ public function query()
+ {
+ $params = func_get_args();
+ $query = array_shift($params);
+
+ // Support one argument of type array, instead of n arguments
+ if (count($params) == 1 && is_array($params[0])) {
+ $params = $params[0];
+ }
+
+ return $this->_query($query, 0, 0, $params);
+ }
+
+ /**
+ * Execute a SQL query with limits
+ *
+ * @param string SQL query to execute
+ * @param int Offset for LIMIT statement
+ * @param int Number of rows for LIMIT statement
+ * @param mixed Values to be inserted in query
+ *
+ * @return int Query handle identifier
+ */
+ public function limitquery()
+ {
+ $params = func_get_args();
+ $query = array_shift($params);
+ $offset = array_shift($params);
+ $numrows = array_shift($params);
+
+ return $this->_query($query, $offset, $numrows, $params);
+ }
+
+ /**
+ * Execute a SQL query with limits
+ *
+ * @param string $query SQL query to execute
+ * @param int $offset Offset for LIMIT statement
+ * @param int $numrows Number of rows for LIMIT statement
+ * @param array $params Values to be inserted in query
+ *
+ * @return int Query handle identifier
+ */
+ protected function _query($query, $offset, $numrows, $params)
+ {
+ // Read or write ?
+ $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w';
+
+ $this->db_connect($mode);
+
+ // check connection before proceeding
+ if (!$this->is_connected()) {
+ return null;
+ }
+
+ if ($numrows || $offset) {
+ $query = $this->set_limit($query, $numrows, $offset);
+ }
+
+ $params = (array) $params;
+
+ // Because in Roundcube we mostly use queries that are
+ // executed only once, we will not use prepared queries
+ $pos = 0;
+ $idx = 0;
+
+ while ($pos = strpos($query, '?', $pos)) {
+ $val = $this->quote($params[$idx++]);
+ unset($params[$idx-1]);
+ $query = substr_replace($query, $val, $pos, 1);
+ $pos += strlen($val);
+ }
+
+ $query = rtrim($query, ';');
+
+ $this->debug($query);
+
+ $query = $this->dbh->query($query);
+
+ if ($query === false) {
+ $error = $this->dbh->errorInfo();
+ $this->db_error = true;
+ $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
+
+ rcube::raise_error(array('code' => 500, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $this->db_error_msg), true, false);
+ }
+
+ // add result, even if it's an error
+ return $this->_add_result($query);
+ }
+
+ /**
+ * Get number of affected rows for the last query
+ *
+ * @param number $res_id Optional query handle identifier
+ *
+ * @return int Number of rows or false on failure
+ */
+ public function affected_rows($res_id = null)
+ {
+ if ($result = $this->_get_result($res_id)) {
+ return $result->rowCount();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get last inserted record ID
+ *
+ * @param string $table Table name (to find the incremented sequence)
+ *
+ * @return mixed ID or false on failure
+ */
+ public function insert_id($table = '')
+ {
+ if (!$this->db_connected || $this->db_mode == 'r') {
+ return false;
+ }
+
+ if ($table) {
+ // resolve table name
+ $table = $this->table_name($table);
+ }
+
+ $id = $this->dbh->lastInsertId($table);
+
+ return $id;
+ }
+
+ /**
+ * Get an associative array for one row
+ * If no query handle is specified, the last query will be taken as reference
+ *
+ * @param int $res_id Optional query handle identifier
+ *
+ * @return mixed Array with col values or false on failure
+ */
+ public function fetch_assoc($res_id = null)
+ {
+ $result = $this->_get_result($res_id);
+ return $this->_fetch_row($result, PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Get an index array for one row
+ * If no query handle is specified, the last query will be taken as reference
+ *
+ * @param int $res_id Optional query handle identifier
+ *
+ * @return mixed Array with col values or false on failure
+ */
+ public function fetch_array($res_id = null)
+ {
+ $result = $this->_get_result($res_id);
+ return $this->_fetch_row($result, PDO::FETCH_NUM);
+ }
+
+ /**
+ * Get col values for a result row
+ *
+ * @param PDOStatement $result Result handle
+ * @param int $mode Fetch mode identifier
+ *
+ * @return mixed Array with col values or false on failure
+ */
+ protected function _fetch_row($result, $mode)
+ {
+ if (!is_object($result) || !$this->is_connected()) {
+ return false;
+ }
+
+ return $result->fetch($mode);
+ }
+
+ /**
+ * Adds LIMIT,OFFSET clauses to the query
+ *
+ * @param string $query SQL query
+ * @param int $limit Number of rows
+ * @param int $offset Offset
+ *
+ * @return string SQL query
+ */
+ protected function set_limit($query, $limit = 0, $offset = 0)
+ {
+ if ($limit) {
+ $query .= ' LIMIT ' . intval($limit);
+ }
+
+ if ($offset) {
+ $query .= ' OFFSET ' . intval($offset);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Returns list of tables in a database
+ *
+ * @return array List of all tables of the current database
+ */
+ public function list_tables()
+ {
+ // get tables if not cached
+ if ($this->tables === null) {
+ $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME');
+
+ if ($res = $this->_get_result($q)) {
+ $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
+ }
+ else {
+ $this->tables = array();
+ }
+ }
+
+ return $this->tables;
+ }
+
+ /**
+ * Returns list of columns in database table
+ *
+ * @param string $table Table name
+ *
+ * @return array List of table cols
+ */
+ public function list_cols($table)
+ {
+ $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
+ array($table));
+
+ if ($res = $this->_get_result($q)) {
+ return $res->fetchAll(PDO::FETCH_COLUMN, 0);
+ }
+
+ return array();
+ }
+
+ /**
+ * Formats input so it can be safely used in a query
+ *
+ * @param mixed $input Value to quote
+ * @param string $type Type of data
+ *
+ * @return string Quoted/converted string for use in query
+ */
+ public function quote($input, $type = null)
+ {
+ // handle int directly for better performance
+ if ($type == 'integer' || $type == 'int') {
+ return intval($input);
+ }
+
+ if (is_null($input)) {
+ return 'NULL';
+ }
+
+ // create DB handle if not available
+ if (!$this->dbh) {
+ $this->db_connect('r');
+ }
+
+ if ($this->dbh) {
+ $map = array(
+ 'bool' => PDO::PARAM_BOOL,
+ 'integer' => PDO::PARAM_INT,
+ );
+ $type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
+ return $this->dbh->quote($input, $type);
+ }
+
+ return 'NULL';
+ }
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ *
+ * @param string $str Value to quote
+ *
+ * @return string Quoted string for use in query
+ * @deprecated Replaced by rcube_db::quote_identifier
+ * @see rcube_db::quote_identifier
+ */
+ public function quoteIdentifier($str)
+ {
+ return $this->quote_identifier($str);
+ }
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ *
+ * @param string $str Value to quote
+ *
+ * @return string Quoted string for use in query
+ */
+ public function quote_identifier($str)
+ {
+ $start = $this->options['identifier_start'];
+ $end = $this->options['identifier_end'];
+ $name = array();
+
+ foreach (explode('.', $str) as $elem) {
+ $elem = str_replace(array($start, $end), '', $elem);
+ $name[] = $start . $elem . $end;
+ }
+
+ return implode($name, '.');
+ }
+
+ /**
+ * Return SQL function for current time and date
+ *
+ * @return string SQL function to use in query
+ */
+ public function now()
+ {
+ return "now()";
+ }
+
+ /**
+ * Return list of elements for use with SQL's IN clause
+ *
+ * @param array $arr Input array
+ * @param string $type Type of data
+ *
+ * @return string Comma-separated list of quoted values for use in query
+ */
+ public function array2list($arr, $type = null)
+ {
+ if (!is_array($arr)) {
+ return $this->quote($arr, $type);
+ }
+
+ foreach ($arr as $idx => $item) {
+ $arr[$idx] = $this->quote($item, $type);
+ }
+
+ return implode(',', $arr);
+ }
+
+ /**
+ * Return SQL statement to convert a field value into a unix timestamp
+ *
+ * This method is deprecated and should not be used anymore due to limitations
+ * of timestamp functions in Mysql (year 2038 problem)
+ *
+ * @param string $field Field name
+ *
+ * @return string SQL statement to use in query
+ * @deprecated
+ */
+ public function unixtimestamp($field)
+ {
+ return "UNIX_TIMESTAMP($field)";
+ }
+
+ /**
+ * Return SQL statement to convert from a unix timestamp
+ *
+ * @param int $timestamp Unix timestamp
+ *
+ * @return string Date string in db-specific format
+ */
+ public function fromunixtime($timestamp)
+ {
+ return date("'Y-m-d H:i:s'", $timestamp);
+ }
+
+ /**
+ * Return SQL statement for case insensitive LIKE
+ *
+ * @param string $column Field name
+ * @param string $value Search value
+ *
+ * @return string SQL statement to use in query
+ */
+ public function ilike($column, $value)
+ {
+ return $this->quote_identifier($column).' LIKE '.$this->quote($value);
+ }
+
+ /**
+ * Abstract SQL statement for value concatenation
+ *
+ * @return string SQL statement to be used in query
+ */
+ public function concat(/* col1, col2, ... */)
+ {
+ $args = func_get_args();
+ if (is_array($args[0])) {
+ $args = $args[0];
+ }
+
+ return '(' . join(' || ', $args) . ')';
+ }
+
+ /**
+ * Encodes non-UTF-8 characters in string/array/object (recursive)
+ *
+ * @param mixed $input Data to fix
+ *
+ * @return mixed Properly UTF-8 encoded data
+ */
+ public static function encode($input)
+ {
+ if (is_object($input)) {
+ foreach (get_object_vars($input) as $idx => $value) {