summaryrefslogtreecommitdiff
path: root/program/lib/Roundcube
diff options
context:
space:
mode:
Diffstat (limited to 'program/lib/Roundcube')
-rw-r--r--program/lib/Roundcube/bootstrap.php30
-rw-r--r--program/lib/Roundcube/html.php70
-rw-r--r--program/lib/Roundcube/rcube.php302
-rw-r--r--program/lib/Roundcube/rcube_addressbook.php32
-rw-r--r--program/lib/Roundcube/rcube_base_replacer.php2
-rw-r--r--program/lib/Roundcube/rcube_browser.php2
-rw-r--r--program/lib/Roundcube/rcube_cache.php45
-rw-r--r--program/lib/Roundcube/rcube_config.php251
-rw-r--r--program/lib/Roundcube/rcube_contacts.php4
-rw-r--r--program/lib/Roundcube/rcube_content_filter.php2
-rw-r--r--program/lib/Roundcube/rcube_csv2vcard.php49
-rw-r--r--program/lib/Roundcube/rcube_db.php107
-rw-r--r--program/lib/Roundcube/rcube_db_mssql.php32
-rw-r--r--program/lib/Roundcube/rcube_db_mysql.php23
-rw-r--r--program/lib/Roundcube/rcube_db_pgsql.php47
-rw-r--r--program/lib/Roundcube/rcube_db_sqlite.php47
-rw-r--r--program/lib/Roundcube/rcube_db_sqlsrv.php29
-rw-r--r--program/lib/Roundcube/rcube_enriched.php2
-rw-r--r--program/lib/Roundcube/rcube_image.php99
-rw-r--r--program/lib/Roundcube/rcube_imap.php279
-rw-r--r--program/lib/Roundcube/rcube_imap_cache.php219
-rw-r--r--program/lib/Roundcube/rcube_imap_generic.php282
-rw-r--r--program/lib/Roundcube/rcube_ldap.php1322
-rw-r--r--program/lib/Roundcube/rcube_ldap_generic.php6
-rw-r--r--program/lib/Roundcube/rcube_message.php107
-rw-r--r--program/lib/Roundcube/rcube_mime.php32
-rw-r--r--program/lib/Roundcube/rcube_plugin.php36
-rw-r--r--program/lib/Roundcube/rcube_plugin_api.php122
-rw-r--r--program/lib/Roundcube/rcube_result_set.php47
-rw-r--r--program/lib/Roundcube/rcube_session.php142
-rw-r--r--program/lib/Roundcube/rcube_smtp.php12
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_atd.php204
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_enchant.php182
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_engine.php91
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_googie.php176
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_pspell.php189
-rw-r--r--program/lib/Roundcube/rcube_spellchecker.php163
-rw-r--r--program/lib/Roundcube/rcube_storage.php22
-rw-r--r--program/lib/Roundcube/rcube_string_replacer.php13
-rw-r--r--program/lib/Roundcube/rcube_user.php9
-rw-r--r--program/lib/Roundcube/rcube_utils.php84
-rw-r--r--program/lib/Roundcube/rcube_vcard.php15
-rw-r--r--program/lib/Roundcube/rcube_washtml.php21
43 files changed, 2508 insertions, 2442 deletions
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 6e5143382..c3fac1f4d 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -54,11 +54,11 @@ foreach ($config as $optname => $optval) {
}
// framework constants
-define('RCUBE_VERSION', '1.0-git');
+define('RCUBE_VERSION', '0.9.5');
define('RCUBE_CHARSET', 'UTF-8');
if (!defined('RCUBE_LIB_DIR')) {
- define('RCUBE_LIB_DIR', dirname(__FILE__).DIRECTORY_SEPARATOR);
+ define('RCUBE_LIB_DIR', dirname(__FILE__).'/');
}
if (!defined('RCUBE_INSTALL_PATH')) {
@@ -303,6 +303,32 @@ function is_ascii($str, $control_chars = true)
/**
+ * Remove single and double quotes from a given string
+ *
+ * @param string Input value
+ *
+ * @return string Dequoted string
+ */
+function strip_quotes($str)
+{
+ return str_replace(array("'", '"'), '', $str);
+}
+
+
+/**
+ * Remove new lines characters from given string
+ *
+ * @param string $str Input value
+ *
+ * @return string Stripped string
+ */
+function strip_newlines($str)
+{
+ return preg_replace('/[\r\n]/', '', $str);
+}
+
+
+/**
* Compose a valid representation of name and e-mail address
*
* @param string $email E-mail address
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index a36711281..1a4c3beba 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -21,7 +21,7 @@
* Class for HTML code creation
*
* @package Framework
- * @subpackage View
+ * @subpackage HTML
*/
class html
{
@@ -218,7 +218,7 @@ class html
$attr = array('src' => $attr);
}
return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
- array('src','name','width','height','border','frameborder','onload')));
+ array('src','name','width','height','border','frameborder')));
}
/**
@@ -288,7 +288,7 @@ class html
}
// attributes with no value
- if (in_array($key, array('checked', 'multiple', 'disabled', 'selected', 'autofocus'))) {
+ if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) {
if ($value) {
$attrib_arr[] = $key . '="' . $key . '"';
}
@@ -350,18 +350,16 @@ class html
/**
* Class to create an HTML input field
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_inputfield extends html
{
protected $tagname = 'input';
protected $type = 'text';
protected $allowed = array(
- 'type','name','value','size','tabindex','autocapitalize','required',
+ 'type','name','value','size','tabindex','autocapitalize',
'autocomplete','checked','onchange','onclick','disabled','readonly',
- 'spellcheck','results','maxlength','src','multiple','accept',
- 'placeholder','autofocus',
+ 'spellcheck','results','maxlength','src','multiple','placeholder',
);
/**
@@ -407,8 +405,7 @@ class html_inputfield extends html
/**
* Class to create an HTML password field
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_passwordfield extends html_inputfield
{
@@ -418,9 +415,9 @@ class html_passwordfield extends html_inputfield
/**
* Class to create an hidden HTML input field
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
+
class html_hiddenfield extends html
{
protected $tagname = 'input';
@@ -468,8 +465,7 @@ class html_hiddenfield extends html
/**
* Class to create HTML radio buttons
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_radiobutton extends html_inputfield
{
@@ -499,8 +495,7 @@ class html_radiobutton extends html_inputfield
/**
* Class to create HTML checkboxes
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_checkbox extends html_inputfield
{
@@ -530,8 +525,7 @@ class html_checkbox extends html_inputfield
/**
* Class to create an HTML textarea
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_textarea extends html
{
@@ -589,8 +583,7 @@ class html_textarea extends html
* print $select->show('CH');
* </pre>
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_select extends html
{
@@ -655,8 +648,7 @@ class html_select extends html
/**
* Class to build an HTML table
*
- * @package Framework
- * @subpackage View
+ * @package HTML
*/
class html_table extends html
{
@@ -678,11 +670,6 @@ class html_table extends html
{
$default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
$this->attrib = array_merge($attrib, $default_attrib);
-
- if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
- $this->tagname = $attrib['tagname'];
- $this->allowed = self::$common_attrib;
- }
}
/**
@@ -826,20 +813,19 @@ class html_table extends html
if (!empty($this->header)) {
$rowcontent = '';
foreach ($this->header as $c => $col) {
- $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
+ $rowcontent .= self::tag('td', $col->attrib, $col->content);
}
- $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
- self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
+ $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
}
foreach ($this->rows as $r => $row) {
$rowcontent = '';
foreach ($row->cells as $c => $col) {
- $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
+ $rowcontent .= self::tag('td', $col->attrib, $col->content);
}
if ($r < $this->rowindex || count($row->cells)) {
- $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
+ $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
}
}
@@ -848,7 +834,7 @@ class html_table extends html
}
// add <tbody>
- $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
+ $this->content = $thead . self::tag('tbody', null, $tbody);
unset($this->attrib['cols'], $this->attrib['rowsonly']);
return parent::show();
@@ -873,22 +859,4 @@ class html_table extends html
$this->rowindex = 0;
}
- /**
- * Getter for the corresponding tag name for table row elements
- */
- private function _row_tagname()
- {
- static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
- return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
- }
-
- /**
- * Getter for the corresponding tag name for table cell elements
- */
- private function _col_tagname()
- {
- static $col_tagnames = array('table' => 'td', '*' => 'span');
- return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
- }
-
}
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index d9c3dd8b9..7329b09fb 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -99,20 +99,20 @@ class rcube
protected $texts;
protected $caches = array();
protected $shutdown_functions = array();
+ protected $expunge_cache = false;
/**
* This implements the 'singleton' design pattern
*
* @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
- * @param string Environment name to run (e.g. live, dev, test)
*
* @return rcube The one and only instance
*/
- static function get_instance($mode = 0, $env = '')
+ static function get_instance($mode = 0)
{
if (!self::$instance) {
- self::$instance = new rcube($env);
+ self::$instance = new rcube();
self::$instance->init($mode);
}
@@ -123,10 +123,10 @@ class rcube
/**
* Private constructor
*/
- protected function __construct($env = '')
+ protected function __construct()
{
// load configuration
- $this->config = new rcube_config($env);
+ $this->config = new rcube_config;
$this->plugins = new rcube_dummy_plugin_api;
register_shutdown_function(array($this, 'shutdown'));
@@ -258,39 +258,6 @@ class rcube
/**
- * Initialize and get shared cache object
- *
- * @param string $name Cache identifier
- * @param bool $packed Enables/disables data serialization
- *
- * @return rcube_cache_shared Cache object
- */
- public function get_cache_shared($name, $packed=true)
- {
- $shared_name = "shared_$name";
-
- if (!array_key_exists($shared_name, $this->caches)) {
- $opt = strtolower($name) . '_cache';
- $type = $this->config->get($opt);
- $ttl = $this->config->get($opt . '_ttl');
-
- if (!$type) {
- // cache is disabled
- return $this->caches[$shared_name] = null;
- }
-
- if ($ttl === null) {
- $ttl = $this->config->get('shared_cache_ttl', '10d');
- }
-
- $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed);
- }
-
- return $this->caches[$shared_name];
- }
-
-
- /**
* Create SMTP object and connect to server
*
* @param boolean True if connection should be established
@@ -378,7 +345,6 @@ class rcube
'auth_pw' => $this->config->get("{$driver}_auth_pw"),
'debug' => (bool) $this->config->get("{$driver}_debug"),
'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
- 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
'timeout' => (int) $this->config->get("{$driver}_timeout"),
'skip_deleted' => (bool) $this->config->get('skip_deleted'),
'driver' => $driver,
@@ -458,12 +424,15 @@ class rcube
ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
+ ini_set('session.serialize_handler', 'php');
ini_set('session.cookie_httponly', 1);
// use database for storing session data
$this->session = new rcube_session($this->get_dbh(), $this->config);
- $this->session->register_gc_handler(array($this, 'gc'));
+ $this->session->register_gc_handler(array($this, 'temp_gc'));
+ $this->session->register_gc_handler(array($this, 'cache_gc'));
+
$this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
$this->session->set_ip_check($this->config->get('ip_check'));
@@ -473,47 +442,27 @@ class rcube
// start PHP session (if not in CLI mode)
if ($_SERVER['REMOTE_ADDR']) {
- $this->session->start();
+ session_start();
}
}
/**
- * Garbage collector - cache/temp cleaner
- */
- public function gc()
- {
- rcube_cache::gc();
- rcube_cache_shared::gc();
- $this->get_storage()->cache_gc();
-
- $this->gc_temp();
- }
-
-
- /**
* Garbage collector function for temp files.
* Remove temp files older than two days
*/
- public function gc_temp()
+ public function temp_gc()
{
$tmp = unslashify($this->config->get('temp_dir'));
-
- // expire in 48 hours by default
- $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h');
- $temp_dir_ttl = get_offset_sec($temp_dir_ttl);
- if ($temp_dir_ttl < 6*3600)
- $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound.
-
- $expire = time() - $temp_dir_ttl;
+ $expire = time() - 172800; // expire in 48 hours
if ($tmp && ($dir = opendir($tmp))) {
while (($fname = readdir($dir)) !== false) {
- if ($fname[0] == '.') {
+ if ($fname{0} == '.') {
continue;
}
- if (@filemtime($tmp.'/'.$fname) < $expire) {
+ if (filemtime($tmp.'/'.$fname) < $expire) {
@unlink($tmp.'/'.$fname);
}
}
@@ -524,21 +473,14 @@ class rcube
/**
- * Runs garbage collector with probability based on
- * session settings. This is intended for environments
- * without a session.
+ * Garbage collector for cache entries.
+ * Set flag to expunge caches on shutdown
*/
- public function gc_run()
+ public function cache_gc()
{
- $probability = (int) ini_get('session.gc_probability');
- $divisor = (int) ini_get('session.gc_divisor');
-
- if ($divisor > 0 && $probability > 0) {
- $random = mt_rand(1, $divisor);
- if ($random <= $probability) {
- $this->gc();
- }
- }
+ // because this gc function is called before storage is initialized,
+ // we just set a flag to expunge storage cache on shutdown.
+ $this->expunge_cache = true;
}
@@ -797,7 +739,7 @@ class rcube
mcrypt_module_close($td);
}
else {
- @include_once 'des.inc';
+ // @include_once 'des.inc'; (not shipped with this distribution)
if (function_exists('des')) {
$des_iv_size = 8;
@@ -852,7 +794,7 @@ class rcube
mcrypt_module_close($td);
}
else {
- @include_once 'des.inc';
+ // @include_once 'des.inc'; (not shipped with this distribution)
if (function_exists('des')) {
$des_iv_size = 8;
@@ -922,14 +864,6 @@ class rcube
call_user_func($function);
}
- // write session data as soon as possible and before
- // closing database connection, don't do this before
- // registered shutdown functions, they may need the session
- // Note: this will run registered gc handlers (ie. cache gc)
- if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
- $this->session->write_close();
- }
-
if (is_object($this->smtp)) {
$this->smtp->disconnect();
}
@@ -941,6 +875,9 @@ class rcube
}
if (is_object($this->storage)) {
+ if ($this->expunge_cache) {
+ $this->storage->expunge_cache();
+ }
$this->storage->close();
}
}
@@ -1132,8 +1069,8 @@ class rcube
* - code: Error code (required)
* - type: Error type [php|db|imap|javascript] (required)
* - message: Error message
- * - file: File where error occured
- * - line: Line where error occured
+ * - file: File where error occurred
+ * - line: Line where error occurred
* @param boolean True to log the error
* @param boolean Terminate script execution
*/
@@ -1359,191 +1296,6 @@ class rcube
}
}
- /**
- * Unique Message-ID generator.
- *
- * @return string Message-ID
- */
- public function gen_message_id()
- {
- $local_part = md5(uniqid('rcube'.mt_rand(), true));
- $domain_part = $this->user->get_username('domain');
-
- // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
- if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
- foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
- $host = preg_replace('/:[0-9]+$/', '', $host);
- if ($host && preg_match('/\.[a-z]+$/i', $host)) {
- $domain_part = $host;
- }
- }
- }
-
- return sprintf('<%s@%s>', $local_part, $domain_part);
- }
-
- /**
- * Send the given message using the configured method.
- *
- * @param object $message Reference to Mail_MIME object
- * @param string $from Sender address string
- * @param array $mailto Array of recipient address strings
- * @param array $error SMTP error array (reference)
- * @param string $body_file Location of file with saved message body (reference),
- * used when delay_file_io is enabled
- * @param array $options SMTP options (e.g. DSN request)
- *
- * @return boolean Send status.
- */
- public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
- {
- $plugin = $this->plugins->exec_hook('message_before_send', array(
- 'message' => $message,
- 'from' => $from,
- 'mailto' => $mailto,
- 'options' => $options,
- ));
-
- if ($plugin['abort']) {
- return isset($plugin['result']) ? $plugin['result'] : false;
- }
-
- $from = $plugin['from'];
- $mailto = $plugin['mailto'];
- $options = $plugin['options'];
- $message = $plugin['message'];
- $headers = $message->headers();
-
- // send thru SMTP server using custom SMTP library
- if ($this->config->get('smtp_server')) {
- // generate list of recipients
- $a_recipients = array($mailto);
-
- if (strlen($headers['Cc']))
- $a_recipients[] = $headers['Cc'];
- if (strlen($headers['Bcc']))
- $a_recipients[] = $headers['Bcc'];
-
- // clean Bcc from header for recipients
- $send_headers = $headers;
- unset($send_headers['Bcc']);
- // here too, it because txtHeaders() below use $message->_headers not only $send_headers
- unset($message->_headers['Bcc']);
-
- $smtp_headers = $message->txtHeaders($send_headers, true);
-
- if ($message->getParam('delay_file_io')) {
- // use common temp dir
- $temp_dir = $this->config->get('temp_dir');
- $body_file = tempnam($temp_dir, 'rcmMsg');
- if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
- self::raise_error(array('code' => 650, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not create message: ".$mime_result->getMessage()),
- TRUE, FALSE);
- return false;
- }
- $msg_body = fopen($body_file, 'r');
- }
- else {
- $msg_body = $message->get();
- }
-
- // send message
- if (!is_object($this->smtp)) {
- $this->smtp_init(true);
- }
-
- $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options);
- $response = $this->smtp->get_response();
- $error = $this->smtp->get_error();
-
- // log error
- if (!$sent) {
- self::raise_error(array('code' => 800, 'type' => 'smtp',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE);
- }
- }
- // send mail using PHP's mail() function
- else {
- // unset some headers because they will be added by the mail() function
- $headers_enc = $message->headers($headers);
- $headers_php = $message->_headers;
- unset($headers_php['To'], $headers_php['Subject']);
-
- // reset stored headers and overwrite
- $message->_headers = array();
- $header_str = $message->txtHeaders($headers_php);
-
- // #1485779
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
- if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
- $headers_enc['To'] = implode(', ', $m[1]);
- }
- }
-
- $msg_body = $message->get();
-
- if (PEAR::isError($msg_body)) {
- self::raise_error(array('code' => 650, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not create message: ".$msg_body->getMessage()),
- TRUE, FALSE);
- }
- else {
- $delim = $this->config->header_delimiter();
- $to = $headers_enc['To'];
- $subject = $headers_enc['Subject'];
- $header_str = rtrim($header_str);
-
- if ($delim != "\r\n") {
- $header_str = str_replace("\r\n", $delim, $header_str);
- $msg_body = str_replace("\r\n", $delim, $msg_body);
- $to = str_replace("\r\n", $delim, $to);
- $subject = str_replace("\r\n", $delim, $subject);
- }
-
- if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
- $sent = mail($to, $subject, $msg_body, $header_str);
- else
- $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
- }
- }
-
- if ($sent) {
- $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
-
- // remove MDN headers after sending
- unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
-
- // get all recipients
- if ($headers['Cc'])
- $mailto .= $headers['Cc'];
- if ($headers['Bcc'])
- $mailto .= $headers['Bcc'];
- if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
- $mailto = implode(', ', array_unique($m[1]));
-
- if ($this->config->get('smtp_log')) {
- self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
- $this->user->get_username(),
- $_SERVER['REMOTE_ADDR'],
- $mailto,
- !empty($response) ? join('; ', $response) : ''));
- }
- }
-
- if (is_resource($msg_body)) {
- fclose($msg_body);
- }
-
- $message->_headers = array();
- $message->headers($headers);
-
- return $sent;
- }
-
}
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index 9301211ff..13016ecc7 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2013, The Roundcube Dev Team |
+ | Copyright (C) 2006-2012, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -35,7 +35,6 @@ abstract class rcube_addressbook
/** public properties (mandatory) */
public $primary_key;
public $groups = false;
- public $export_groups = true;
public $readonly = true;
public $searchonly = false;
public $undelete = false;
@@ -134,7 +133,7 @@ abstract class rcube_addressbook
abstract function get_record($id, $assoc=false);
/**
- * Returns the last error occured (e.g. when updating/inserting failed)
+ * Returns the last error occurred (e.g. when updating/inserting failed)
*
* @return array Hash array with the following fields: type, message
*/
@@ -424,7 +423,7 @@ abstract class rcube_addressbook
* @param boolean True to return one array with all values, False for hash array with values grouped by type
* @return array List of column values
*/
- public static function get_col_values($col, $data, $flat = false)
+ function get_col_values($col, $data, $flat = false)
{
$out = array();
foreach ((array)$data as $c => $values) {
@@ -433,7 +432,7 @@ abstract class rcube_addressbook
$out = array_merge($out, (array)$values);
}
else {
- list(, $type) = explode(':', $c);
+ list($f, $type) = explode(':', $c);
$out[$type] = array_merge((array)$out[$type], (array)$values);
}
}
@@ -477,8 +476,7 @@ abstract class rcube_addressbook
$fn = trim(join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']))));
// use email address part for name
- $email = self::get_col_values('email', $contact, true);
- $email = $email[0];
+ $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
if ($email && (empty($fn) || $fn == $email)) {
// return full email
@@ -525,9 +523,9 @@ abstract class rcube_addressbook
$fn = $contact['name'];
// fallback to email address
- if (empty($fn) && ($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
- return $email[0];
- }
+ $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
+ if (empty($fn) && $email)
+ return $email;
return $fn;
}
@@ -540,11 +538,11 @@ abstract class rcube_addressbook
$key = $contact[$sort_col] . ':' . $contact['sourceid'];
// add email to a key to not skip contacts with the same name (#1488375)
- if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
- $key .= ':' . implode(':', (array)$email);
- }
+ if (!empty($contact['email'])) {
+ $key .= ':' . implode(':', (array)$contact['email']);
+ }
- return $key;
+ return $key;
}
/**
@@ -563,9 +561,9 @@ abstract class rcube_addressbook
// use only strict comparison (mode = 1)
// @TODO: partial search, e.g. match only day and month
if (in_array($colname, $this->date_cols)) {
- return (($value = rcube_utils::anytodatetime($value))
- && ($search = rcube_utils::anytodatetime($search))
- && $value->format('Ymd') == $search->format('Ymd'));
+ return (($value = rcube_utils::strtotime($value))
+ && ($search = rcube_utils::strtotime($search))
+ && date('Ymd', $value) == date('Ymd', $search));
}
// composite field, e.g. address
diff --git a/program/lib/Roundcube/rcube_base_replacer.php b/program/lib/Roundcube/rcube_base_replacer.php
index a59bba926..aaaa2028c 100644
--- a/program/lib/Roundcube/rcube_base_replacer.php
+++ b/program/lib/Roundcube/rcube_base_replacer.php
@@ -21,7 +21,7 @@
* using a predefined base
*
* @package Framework
- * @subpackage Utils
+ * @subpackage Core
* @author Thomas Bruederli <roundcube@gmail.com>
*/
class rcube_base_replacer
diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php
index 34128291b..d10fe2a2c 100644
--- a/program/lib/Roundcube/rcube_browser.php
+++ b/program/lib/Roundcube/rcube_browser.php
@@ -20,7 +20,7 @@
* Provide details about the client's browser based on the User-Agent header
*
* @package Framework
- * @subpackage Utils
+ * @subpackage Core
*/
class rcube_browser
{
diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php
index a708cb292..deaba68e9 100644
--- a/program/lib/Roundcube/rcube_cache.php
+++ b/program/lib/Roundcube/rcube_cache.php
@@ -38,7 +38,6 @@ class rcube_cache
private $type;
private $userid;
private $prefix;
- private $table;
private $ttl;
private $packed;
private $index;
@@ -72,9 +71,8 @@ class rcube_cache
$this->db = function_exists('apc_exists'); // APC 3.1.4 required
}
else {
- $this->type = 'db';
- $this->db = $rcube->get_dbh();
- $this->table = $this->db->table_name('cache');
+ $this->type = 'db';
+ $this->db = $rcube->get_dbh();
}
// convert ttl string to seconds
@@ -194,31 +192,20 @@ class rcube_cache
*/
function expunge()
{
- if ($this->type == 'db' && $this->db && $this->ttl) {
+ if ($this->type == 'db' && $this->db) {
$this->db->query(
- "DELETE FROM ".$this->table.
+ "DELETE FROM ".$this->db->table_name('cache').
" WHERE user_id = ?".
" AND cache_key LIKE ?".
- " AND expires < " . $this->db->now(),
+ " AND " . $this->db->unixtimestamp('created')." < ?",
$this->userid,
- $this->prefix.'.%');
+ $this->prefix.'.%',
+ time() - $this->ttl);
}
}
/**
- * Remove expired records of all caches
- */
- static function gc()
- {
- $rcube = rcube::get_instance();
- $db = $rcube->get_dbh();
-
- $db->query("DELETE FROM " . $db->table_name('cache') . " WHERE expires < " . $db->now());
- }
-
-
- /**
* Writes the cache back to the DB.
*/
function close()
@@ -284,7 +271,7 @@ class rcube_cache
else {
$sql_result = $this->db->limitquery(
"SELECT data, cache_key".
- " FROM " . $this->table.
+ " FROM ".$this->db->table_name('cache').
" WHERE user_id = ?".
" AND cache_key = ?".
// for better performance we allow more records for one key
@@ -339,7 +326,7 @@ class rcube_cache
// Remove NULL rows (here we don't need to check if the record exist)
if ($data == 'N;') {
$this->db->query(
- "DELETE FROM " . $this->table.
+ "DELETE FROM ".$this->db->table_name('cache').
" WHERE user_id = ?".
" AND cache_key = ?",
$this->userid, $key);
@@ -350,10 +337,8 @@ class rcube_cache
// update existing cache record
if ($key_exists) {
$result = $this->db->query(
- "UPDATE " . $this->table.
- " SET created = " . $this->db->now().
- ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL').
- ", data = ?".
+ "UPDATE ".$this->db->table_name('cache').
+ " SET created = ". $this->db->now().", data = ?".
" WHERE user_id = ?".
" AND cache_key = ?",
$data, $this->userid, $key);
@@ -363,9 +348,9 @@ class rcube_cache
// for better performance we allow more records for one key
// so, no need to check if record exist (see rcube_cache::read_record())
$result = $this->db->query(
- "INSERT INTO " . $this->table.
- " (created, expires, user_id, cache_key, data)".
- " VALUES (" . $this->db->now() . ", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?, ?)",
+ "INSERT INTO ".$this->db->table_name('cache').
+ " (created, user_id, cache_key, data)".
+ " VALUES (".$this->db->now().", ?, ?, ?)",
$this->userid, $key, $data);
}
@@ -426,7 +411,7 @@ class rcube_cache
}
$this->db->query(
- "DELETE FROM " . $this->table.
+ "DELETE FROM ".$this->db->table_name('cache').
" WHERE user_id = ?" . $where,
$this->userid);
}
diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php
index ac3ea678c..3edec4242 100644
--- a/program/lib/Roundcube/rcube_config.php
+++ b/program/lib/Roundcube/rcube_config.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2013, The Roundcube Dev Team |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -26,8 +26,6 @@ class rcube_config
{
const DEFAULT_SKIN = 'larry';
- private $env = '';
- private $paths = array();
private $prop = array();
private $errors = array();
private $userprefs = array();
@@ -45,46 +43,14 @@ class rcube_config
'reply_mode' => 'top_posting',
'refresh_interval' => 'keep_alive',
'min_refresh_interval' => 'min_keep_alive',
- 'messages_cache_ttl' => 'message_cache_lifetime',
- 'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl',
);
/**
* Object constructor
- *
- * @param string Environment suffix for config files to load
*/
- public function __construct($env = '')
+ public function __construct()
{
- $this->env = $env;
-
- if ($paths = getenv('RCUBE_CONFIG_PATH')) {
- $this->paths = explode(PATH_SEPARATOR, $paths);
- // make all paths absolute
- foreach ($this->paths as $i => $path) {
- if (!$this->_is_absolute($path)) {
- if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) {
- $this->paths[$i] = unslashify($realpath) . '/';
- }
- else {
- unset($this->paths[$i]);
- }
- }
- else {
- $this->paths[$i] = unslashify($path) . '/';
- }
- }
- }
-
- if (defined('RCUBE_CONFIG_DIR') && !in_array(RCUBE_CONFIG_DIR, $this->paths)) {
- $this->paths[] = RCUBE_CONFIG_DIR;
- }
-
- if (empty($this->paths)) {
- $this->paths[] = RCUBE_INSTALL_PATH . 'config/';
- }
-
$this->load();
// Defaults, that we do not require you to configure,
@@ -101,26 +67,16 @@ class rcube_config
*/
private function load()
{
- // Load default settings
- if (!$this->load_from_file('defaults.inc.php')) {
- $this->errors[] = 'defaults.inc.php was not found.';
- }
-
// load main config file
- if (!$this->load_from_file('config.inc.php')) {
- // Old configuration files
- if (!$this->load_from_file('main.inc.php') ||
- !$this->load_from_file('db.inc.php')) {
- $this->errors[] = 'config.inc.php was not found.';
- }
- else if (rand(1,100) == 10) { // log warning on every 100th request (average)
- trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING);
- }
- }
+ if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'main.inc.php'))
+ $this->errors[] = 'main.inc.php was not found.';
+
+ // load database config
+ if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'db.inc.php'))
+ $this->errors[] = 'db.inc.php was not found.';
// load host-specific configuration
- if (!empty($_SERVER['HTTP_HOST']))
- $this->load_host_config();
+ $this->load_host_config();
// set skin (with fallback to old 'skin_path' property)
if (empty($this->prop['skin'])) {
@@ -163,6 +119,17 @@ class rcube_config
// enable display_errors in 'show' level, but not for ajax requests
ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
+ // set timezone auto settings values
+ if ($this->prop['timezone'] == 'auto') {
+ $this->prop['_timezone_value'] = $this->client_timezone();
+ }
+ else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) {
+ $this->prop['timezone'] = $tz;
+ }
+ else if (empty($this->prop['timezone'])) {
+ $this->prop['timezone'] = 'UTC';
+ }
+
// remove deprecated properties
unset($this->prop['dst_active']);
@@ -186,7 +153,7 @@ class rcube_config
}
if ($fname) {
- $this->load_from_file($fname);
+ $this->load_from_file(RCUBE_CONFIG_DIR . $fname);
}
}
@@ -195,78 +162,26 @@ class rcube_config
* Read configuration from a file
* and merge with the already stored config values
*
- * @param string $file Name of the config file to be loaded
+ * @param string $fpath Full path to the config file to be loaded
* @return booelan True on success, false on failure
*/
- public function load_from_file($file)
- {
- $success = false;
-
- foreach ($this->resolve_paths($file) as $fpath) {
- if ($fpath && is_file($fpath) && is_readable($fpath)) {
- // use output buffering, we don't need any output here
- ob_start();
- include($fpath);
- ob_end_clean();
-
- if (is_array($config)) {
- $this->merge($config);
- $success = true;
- }
- // deprecated name of config variable
- else if (is_array($rcmail_config)) {
- $this->merge($rcmail_config);
- $success = true;
- }
- }
- }
-
- return $success;
- }
-
- /**
- * Helper method to resolve absolute paths to the given config file.
- * This also takes the 'env' property into account.
- *
- * @param string Filename or absolute file path
- * @param boolean Return -$env file path if exists
- * @return array List of candidates in config dir path(s)
- */
- public function resolve_paths($file, $use_env = true)
+ public function load_from_file($fpath)
{
- $files = array();
- $abs_path = $this->_is_absolute($file);
-
- foreach ($this->paths as $basepath) {
- $realpath = $abs_path ? $file : realpath($basepath . '/' . $file);
-
- // check if <file>-env.ini exists
- if ($realpath && $use_env && !empty($this->env)) {
- $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $realpath);
- if (is_file($envfile))
- $realpath = $envfile;
- }
-
- if ($realpath) {
- $files[] = $realpath;
-
- // no need to continue the loop if an absolute file path is given
- if ($abs_path) {
- break;
- }
+ if (is_file($fpath) && is_readable($fpath)) {
+ // use output buffering, we don't need any output here
+ ob_start();
+ include($fpath);
+ ob_end_clean();
+
+ if (is_array($rcmail_config)) {
+ $this->merge($rcmail_config);
+ return true;
}
}
- return $files;
+ return false;
}
- /**
- * Determine whether the given file path is absolute or relative
- */
- private function _is_absolute($path)
- {
- return $path[0] == DIRECTORY_SEPARATOR || preg_match('!^[a-z]:[\\\\/]!i', $path);
- }
/**
* Getter for a specific config parameter
@@ -286,10 +201,8 @@ class rcube_config
$rcube = rcube::get_instance();
- if ($name == 'timezone') {
- if (empty($result) || $result == 'auto') {
- $result = $this->client_timezone();
- }
+ if ($name == 'timezone' && isset($this->prop['_timezone_value'])) {
+ $result = $this->prop['_timezone_value'];
}
else if ($name == 'client_mimetypes') {
if ($result == null && $def == null)
@@ -347,6 +260,11 @@ class rcube_config
}
}
+ // convert user's timezone into the new format
+ if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) {
+ $prefs['timezone'] = $tz;
+ }
+
// larry is the new default skin :-)
if ($prefs['skin'] == 'default') {
$prefs['skin'] = self::DEFAULT_SKIN;
@@ -354,6 +272,13 @@ class rcube_config
$this->userprefs = $prefs;
$this->prop = array_merge($this->prop, $prefs);
+
+ // override timezone settings with client values
+ if ($this->prop['timezone'] == 'auto') {
+ $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value'];
+ }
+ else if (isset($this->prop['_timezone_value']))
+ unset($this->prop['_timezone_value']);
}
@@ -494,12 +419,13 @@ class rcube_config
*/
private function client_timezone()
{
- // @TODO: remove this legacy timezone handling in the future
- $props = $this->fix_legacy_props(array('timezone' => $_SESSION['timezone']));
-
- if (!empty($props['timezone'])) {
+ if (isset($_SESSION['timezone']) && is_numeric($_SESSION['timezone'])
+ && ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0))) {
+ return $ctz;
+ }
+ else if (!empty($_SESSION['timezone'])) {
try {
- $tz = new DateTimeZone($props['timezone']);
+ $tz = timezone_open($_SESSION['timezone']);
return $tz->getName();
}
catch (Exception $e) { /* gracefully ignore */ }
@@ -527,77 +453,6 @@ class rcube_config
}
}
- // convert deprecated numeric timezone value
- if (isset($props['timezone']) && is_numeric($props['timezone'])) {
- if ($tz = self::timezone_name_from_abbr($props['timezone'])) {
- $props['timezone'] = $tz;
- }
- else {
- unset($props['timezone']);
- }
- }
-
return $props;
}
-
- /**
- * timezone_name_from_abbr() replacement. Converts timezone offset
- * into timezone name abbreviation.
- *
- * @param float $offset Timezone offset (in hours)
- *
- * @return string Timezone abbreviation
- */
- static public function timezone_name_from_abbr($offset)
- {
- // List of timezones here is not complete - https://bugs.php.net/bug.php?id=44780
- if ($tz = timezone_name_from_abbr('', $offset * 3600, 0)) {
- return $tz;
- }
-
- // try with more complete list (#1489261)
- $timezones = array(
- '-660' => "Pacific/Apia",
- '-600' => "Pacific/Honolulu",
- '-570' => "Pacific/Marquesas",
- '-540' => "America/Anchorage",
- '-480' => "America/Los_Angeles",
- '-420' => "America/Denver",
- '-360' => "America/Chicago",
- '-300' => "America/New_York",
- '-270' => "America/Caracas",
- '-240' => "America/Halifax",
- '-210' => "Canada/Newfoundland",
- '-180' => "America/Sao_Paulo",
- '-60' => "Atlantic/Azores",
- '0' => "Europe/London",
- '60' => "Europe/Paris",
- '120' => "Europe/Helsinki",
- '180' => "Europe/Moscow",
- '210' => "Asia/Tehran",
- '240' => "Asia/Dubai",
- '300' => "Asia/Karachi",
- '270' => "Asia/Kabul",
- '300' => "Asia/Karachi",
- '330' => "Asia/Kolkata",
- '345' => "Asia/Katmandu",
- '360' => "Asia/Yekaterinburg",
- '390' => "Asia/Rangoon",
- '420' => "Asia/Krasnoyarsk",
- '480' => "Asia/Shanghai",
- '525' => "Australia/Eucla",
- '540' => "Asia/Tokyo",
- '570' => "Australia/Adelaide",
- '600' => "Australia/Melbourne",
- '630' => "Australia/Lord_Howe",
- '660' => "Asia/Vladivostok",
- '690' => "Pacific/Norfolk",
- '720' => "Pacific/Auckland",
- '765' => "Pacific/Chatham",
- '780' => "Pacific/Enderbury",
- '840' => "Pacific/Kiritimati",
- );
-
- return $timezones[(string) intval($offset * 60)];
- }
}
diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index 6d01368a1..3919cdc6e 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -718,10 +718,6 @@ class rcube_contacts extends rcube_addressbook
foreach ($save_data as $key => $values) {
list($field, $section) = explode(':', $key);
$fulltext = in_array($field, $this->fulltext_cols);
- // avoid casting DateTime objects to array
- if (is_object($values) && is_a($values, 'DateTime')) {
- $values = array(0 => $values);
- }
foreach ((array)$values as $value) {
if (isset($value))
$vcard->set($field, $value, $section);
diff --git a/program/lib/Roundcube/rcube_content_filter.php b/program/lib/Roundcube/rcube_content_filter.php
index ae6617d1b..b814bb71d 100644
--- a/program/lib/Roundcube/rcube_content_filter.php
+++ b/program/lib/Roundcube/rcube_content_filter.php
@@ -20,7 +20,7 @@
* PHP stream filter to detect html/javascript code in attachments
*
* @package Framework
- * @subpackage Utils
+ * @subpackage Core
*/
class rcube_content_filter extends php_user_filter
{
diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index 00e6d4e20..506a4b740 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -130,22 +130,6 @@ class rcube_csv2vcard
'work_state' => 'region:work',
'home_city_short' => 'locality:home',
'home_state_short' => 'region:home',
-
- // Atmail
- 'date_of_birth' => 'birthday',
- 'email' => 'email:pref',
- 'home_mobile' => 'phone:cell',
- 'home_zip' => 'zipcode:home',
- 'info' => 'notes',
- 'user_photo' => 'photo',
- 'url' => 'website:homepage',
- 'work_company' => 'organization',
- 'work_dept' => 'departament',
- 'work_fax' => 'phone:work,fax',
- 'work_mobile' => 'phone:work,cell',
- 'work_title' => 'jobtitle',
- 'work_zip' => 'zipcode:work',
- 'group' => 'groups',
);
/**
@@ -246,30 +230,8 @@ class rcube_csv2vcard
'work_phone' => "Work Phone",
'work_address' => "Work Address",
//'work_address_2' => "Work Address 2",
- 'work_city' => "Work City",
'work_country' => "Work Country",
- 'work_state' => "Work State",
'work_zipcode' => "Work ZipCode",
-
- // Atmail
- 'date_of_birth' => "Date of Birth",
- 'email' => "Email",
- //'email_2' => "Email2",
- //'email_3' => "Email3",
- //'email_4' => "Email4",
- //'email_5' => "Email5",
- 'home_mobile' => "Home Mobile",
- 'home_zip' => "Home Zip",
- 'info' => "Info",
- 'user_photo' => "User Photo",
- 'url' => "URL",
- 'work_company' => "Work Company",
- 'work_dept' => "Work Dept",
- 'work_fax' => "Work Fax",
- 'work_mobile' => "Work Mobile",
- 'work_title' => "Work Title",
- 'work_zip' => "Work Zip",
- 'groups' => "Group",
);
protected $local_label_map = array();
@@ -306,6 +268,7 @@ class rcube_csv2vcard
{
// convert to UTF-8
$head = substr($csv, 0, 4096);
+ $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1?
$charset = rcube_charset::detect($head, RCUBE_CHARSET);
$csv = rcube_charset::convert($csv, $charset);
$head = '';
@@ -313,7 +276,7 @@ class rcube_csv2vcard
$this->map = array();
// Parse file
- foreach (preg_split("/[\r\n]+/", $csv) as $line) {
+ foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) {
$elements = $this->parse_line($line);
if (empty($elements)) {
continue;
@@ -427,13 +390,9 @@ class rcube_csv2vcard
$contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d'];
}
- // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00"
foreach (array('birthday', 'anniversary') as $key) {
- if (!empty($contact[$key])) {
- $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]);
- if (empty($date)) {
- unset($contact[$key]);
- }
+ if (!empty($contact[$key]) && $contact[$key] == '0/0/00') { // @TODO: localization?
+ unset($contact[$key]);
}
}
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 852070073..5083a0dfe 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -47,7 +47,6 @@ class rcube_db
'identifier_end' => '"',
);
- const DEBUG_LINE_LENGTH = 4096;
/**
* Factory, returns driver-specific instance of the class
@@ -63,6 +62,7 @@ class rcube_db
$driver = strtolower(substr($db_dsnw, 0, strpos($db_dsnw, ':')));
$driver_map = array(
'sqlite2' => 'sqlite',
+ 'sqlite3' => 'sqlite',
'sybase' => 'mssql',
'dblib' => 'mssql',
'mysqli' => 'mysql',
@@ -100,15 +100,27 @@ class rcube_db
$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
- * @param string $mode Connection mode (r|w)
+ * @param array $dsn DSN for DB connections
+ *
+ * @return PDO database handle
*/
- protected function dsn_connect($dsn, $mode)
+ protected function dsn_connect($dsn)
{
$this->db_error = false;
$this->db_error_msg = null;
@@ -146,10 +158,9 @@ class rcube_db
return null;
}
- $this->dbh = $dbh;
- $this->db_mode = $mode;
- $this->db_connected = true;
$this->conn_configure($dsn, $dbh);
+
+ return $dbh;
}
/**
@@ -172,6 +183,16 @@ class rcube_db
}
/**
+ * 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)
@@ -198,14 +219,23 @@ class rcube_db
$dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
- $this->dsn_connect($dsn, $mode);
+ $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' && $this->is_replicated()) {
- $this->dsn_connect($this->db_dsnw_array, 'w');
+ if (!$this->db_connected && $mode == 'r') {
+ $mode = 'w';
+ $this->dbh = $this->dsn_connect($this->db_dsnw_array);
+ $this->db_connected = is_object($this->dbh);
}
- $this->conn_failure = !$this->db_connected;
+ if ($this->db_connected) {
+ $this->db_mode = $mode;
+ $this->set_charset('utf8');
+ }
+ else {
+ $this->conn_failure = true;
+ }
}
/**
@@ -226,11 +256,6 @@ class rcube_db
protected function debug($query)
{
if ($this->options['debug_mode']) {
- if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) {
- $diff = $len - self::DEBUG_LINE_LENGTH;
- $query = substr($query, 0, self::DEBUG_LINE_LENGTH)
- . "... [truncated $diff bytes]";
- }
rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
}
}
@@ -338,10 +363,8 @@ class rcube_db
*/
protected function _query($query, $offset, $numrows, $params)
{
- $query = trim($query);
-
// Read or write ?
- $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
+ $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w';
$this->db_connect($mode);
@@ -387,16 +410,13 @@ class rcube_db
if ($result === false) {
$error = $this->dbh->errorInfo();
+ $this->db_error = true;
+ $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
- if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
- $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 . " (SQL Query: $query)"
- ), true, false);
- }
+ rcube::raise_error(array('code' => 500, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $this->db_error_msg . " (SQL Query: $query)"
+ ), true, false);
}
$this->last_result = $result;
@@ -683,19 +703,11 @@ class rcube_db
/**
* Return SQL function for current time and date
*
- * @param int $interval Optional interval (in seconds) to add/subtract
- *
* @return string SQL function to use in query
*/
- public function now($interval = 0)
+ public function now()
{
- if ($interval) {
- $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL ';
- $add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
- $add .= ' SECOND';
- }
-
- return "now()" . $add;
+ return "now()";
}
/**
@@ -856,26 +868,17 @@ class rcube_db
{
$rcube = rcube::get_instance();
- // add prefix to the table name if configured
- if ($prefix = $rcube->config->get('db_prefix')) {
- return $prefix . $table;
+ // return table name if configured
+ $config_key = 'db_table_'.$table;
+
+ if ($name = $rcube->config->get($config_key)) {
+ return $name;
}
return $table;
}
/**
- * Set class option value
- *
- * @param string $name Option name
- * @param mixed $value Option value
- */
- public function set_option($name, $value)
- {
- $this->options[$name] = $value;
- }
-
- /**
* MDB2 DSN string parser
*
* @param string $sequence Secuence name
diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php
index 3c1b9d71f..37a42678a 100644
--- a/program/lib/Roundcube/rcube_db_mssql.php
+++ b/program/lib/Roundcube/rcube_db_mssql.php
@@ -29,52 +29,38 @@ class rcube_db_mssql extends rcube_db
public $db_provider = 'mssql';
/**
- * 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
+ * Driver initialization
*/
- public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
+ protected function init()
{
- parent::__construct($db_dsnw, $db_dsnr, $pconn);
-
$this->options['identifier_start'] = '[';
$this->options['identifier_end'] = ']';
}
/**
- * Driver-specific configuration of database connection
- *
- * @param array $dsn DSN for DB connections
- * @param PDO $dbh Connection handler
+ * Character setting
*/
- protected function conn_configure($dsn, $dbh)
+ protected function set_charset($charset)
{
- // Set date format in case of non-default language (#1488918)
- $this->query("SET DATEFORMAT ymd");
+ // UTF-8 is default
}
/**
* Return SQL function for current time and date
*
- * @param int $interval Optional interval (in seconds) to add/subtract
- *
* @return string SQL function to use in query
*/
- public function now($interval = 0)
+ public function now()
{
- if ($interval) {
- $interval = intval($interval);
- return "dateadd(second, $interval, getdate())";
- }
-
return "getdate()";
}
/**
* 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
diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php
index 6fa5ad768..7f5ad2b36 100644
--- a/program/lib/Roundcube/rcube_db_mysql.php
+++ b/program/lib/Roundcube/rcube_db_mysql.php
@@ -30,13 +30,9 @@ class rcube_db_mysql extends rcube_db
public $db_provider = 'mysql';
/**
- * 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
+ * Driver initialization/configuration
*/
- public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
+ protected function init()
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
rcube::raise_error(array('code' => 600, 'type' => 'db',
@@ -45,25 +41,12 @@ class rcube_db_mysql extends rcube_db
true, true);
}
- parent::__construct($db_dsnw, $db_dsnr, $pconn);
-
// SQL identifiers quoting
$this->options['identifier_start'] = '`';
$this->options['identifier_end'] = '`';
}
/**
- * Driver-specific configuration of database connection
- *
- * @param array $dsn DSN for DB connections
- * @param PDO $dbh Connection handler
- */
- protected function conn_configure($dsn, $dbh)
- {
- $this->query("SET NAMES 'utf8'");
- }
-
- /**
* Abstract SQL statement for value concatenation
*
* @return string SQL statement to be used in query
@@ -151,7 +134,7 @@ class rcube_db_mysql extends rcube_db
$result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
// Enable AUTOCOMMIT mode (#1488902)
- $result[PDO::ATTR_AUTOCOMMIT] = true;
+ $dsn_options[PDO::ATTR_AUTOCOMMIT] = true;
return $result;
}
diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php
index d72c9d6b3..a06a37c10 100644
--- a/program/lib/Roundcube/rcube_db_pgsql.php
+++ b/program/lib/Roundcube/rcube_db_pgsql.php
@@ -29,17 +29,6 @@ class rcube_db_pgsql extends rcube_db
public $db_provider = 'postgres';
/**
- * Driver-specific configuration of database connection
- *
- * @param array $dsn DSN for DB connections
- * @param PDO $dbh Connection handler
- */
- protected function conn_configure($dsn, $dbh)
- {
- $this->query("SET NAMES 'utf8'");
- }
-
- /**
* Get last inserted record ID
*
* @param string $table Table name (to find the incremented sequence)
@@ -64,20 +53,19 @@ class rcube_db_pgsql extends rcube_db
/**
* Return correct name for a specific database sequence
*
- * @param string $table Table name
+ * @param string $sequence Secuence name
*
* @return string Translated sequence name
*/
- protected function sequence_name($table)
+ protected function sequence_name($sequence)
{
- // Note: we support only one sequence per table
- // Note: The sequence name must be <table_name>_seq
- $sequence = $table . '_seq';
- $rcube = rcube::get_instance();
+ $rcube = rcube::get_instance();
// return sequence name if configured
- if ($prefix = $rcube->config->get('db_prefix')) {
- return $prefix . $sequence;
+ $config_key = 'db_sequence_'.$sequence;
+
+ if ($name = $rcube->config->get($config_key)) {
+ return $name;
}
return $sequence;
@@ -86,6 +74,9 @@ class rcube_db_pgsql extends rcube_db
/**
* 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
@@ -97,24 +88,6 @@ class rcube_db_pgsql extends rcube_db
}
/**
- * Return SQL function for current time and date
- *
- * @param int $interval Optional interval (in seconds) to add/subtract
- *
- * @return string SQL function to use in query
- */
- public function now($interval = 0)
- {
- if ($interval) {
- $add = ' ' . ($interval > 0 ? '+' : '-') . " interval '";
- $add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
- $add .= " seconds'";
- }
-
- return "now()" . $add;
- }
-
- /**
* Return SQL statement for case insensitive LIKE
*
* @param string $column Field name
diff --git a/program/lib/Roundcube/rcube_db_sqlite.php b/program/lib/Roundcube/rcube_db_sqlite.php
index b66c56097..145b8a371 100644
--- a/program/lib/Roundcube/rcube_db_sqlite.php
+++ b/program/lib/Roundcube/rcube_db_sqlite.php
@@ -29,6 +29,13 @@ class rcube_db_sqlite extends rcube_db
public $db_provider = 'sqlite';
/**
+ * Database character set
+ */
+ protected function set_charset($charset)
+ {
+ }
+
+ /**
* Prepare connection
*/
protected function conn_prepare($dsn)
@@ -49,6 +56,10 @@ class rcube_db_sqlite extends rcube_db
*/
protected function conn_configure($dsn, $dbh)
{
+ // we emulate via callback some missing functions
+ $dbh->sqliteCreateFunction('unix_timestamp', array('rcube_db_sqlite', 'sqlite_unix_timestamp'), 1);
+ $dbh->sqliteCreateFunction('now', array('rcube_db_sqlite', 'sqlite_now'), 0);
+
// Initialize database structure in file is empty
if (!empty($dsn['database']) && !filesize($dsn['database'])) {
$data = file_get_contents(RCUBE_INSTALL_PATH . 'SQL/sqlite.initial.sql');
@@ -72,32 +83,30 @@ class rcube_db_sqlite extends rcube_db
}
/**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @deprecated
+ * Callback for sqlite: unix_timestamp()
*/
- public function unixtimestamp($field)
+ public static function sqlite_unix_timestamp($timestamp = '')
{
- return "strftime('%s', $field)";
+ $timestamp = trim($timestamp);
+ if (!$timestamp) {
+ $ret = time();
+ }
+ else if (!preg_match('/^[0-9]+$/s', $timestamp)) {
+ $ret = strtotime($timestamp);
+ }
+ else {
+ $ret = $timestamp;
+ }
+
+ return $ret;
}
/**
- * Return SQL function for current time and date
- *
- * @param int $interval Optional interval (in seconds) to add/subtract
- *
- * @return string SQL function to use in query
+ * Callback for sqlite: now()
*/
- public function now($interval = 0)
+ public static function sqlite_now()
{
- if ($interval) {
- $add = ($interval > 0 ? '+' : '') . intval($interval) . ' seconds';
- }
-
- return "datetime('now'" . ($add ? ",'$add'" : "") . ")";
+ return date("Y-m-d H:i:s");
}
/**
diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php
index 45c41cdaf..e5dfb1154 100644
--- a/program/lib/Roundcube/rcube_db_sqlsrv.php
+++ b/program/lib/Roundcube/rcube_db_sqlsrv.php
@@ -29,46 +29,29 @@ class rcube_db_sqlsrv extends rcube_db
public $db_provider = 'mssql';
/**
- * 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
+ * Driver initialization
*/
- public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
+ protected function init()
{
- parent::__construct($db_dsnw, $db_dsnr, $pconn);
-
$this->options['identifier_start'] = '[';
$this->options['identifier_end'] = ']';
}
/**
- * Driver-specific configuration of database connection
- *
- * @param array $dsn DSN for DB connections
- * @param PDO $dbh Connection handler
+ * Database character set setting
*/
- protected function conn_configure($dsn, $dbh)
+ protected function set_charset($charset)
{
- // Set date format in case of non-default language (#1488918)
- $this->query("SET DATEFORMAT ymd");
+ // UTF-8 is default
}
/**
* Return SQL function for current time and date
*
- * @param int $interval Optional interval (in seconds) to add/subtract
- *
* @return string SQL function to use in query
*/
- public function now($interval = 0)
+ public function now()
{
- if ($interval) {
- $interval = intval($interval);
- return "dateadd(second, $interval, getdate())";
- }
-
return "getdate()";
}
diff --git a/program/lib/Roundcube/rcube_enriched.php b/program/lib/Roundcube/rcube_enriched.php
index 12deb33ce..8c628c912 100644
--- a/program/lib/Roundcube/rcube_enriched.php
+++ b/program/lib/Roundcube/rcube_enriched.php
@@ -118,7 +118,7 @@ class rcube_enriched
$quoted = '';
$lines = explode('<br>', $a[2]);
- foreach ($lines as $line)
+ foreach ($lines as $n => $line)
$quoted .= '&gt;'.$line.'<br>';
$body = $a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php
index 4e4caae93..ffcfd4b1d 100644
--- a/program/lib/Roundcube/rcube_image.php
+++ b/program/lib/Roundcube/rcube_image.php
@@ -93,10 +93,6 @@ class rcube_image
$convert = $rcube->config->get('im_convert_path', false);
$props = $this->props();
- if (empty($props)) {
- return false;
- }
-
if (!$filename) {
$filename = $this->image_file;
}
@@ -105,6 +101,7 @@ class rcube_image
if ($convert) {
$p['out'] = $filename;
$p['in'] = $this->image_file;
+ $p['size'] = $size.'x'.$size;
$type = $props['type'];
if (!$type && ($data = $this->identify())) {
@@ -119,37 +116,11 @@ class rcube_image
$type = 'jpg';
}
- // If only one dimension is greater than the limit convert doesn't
- // work as expected, we need to calculate new dimensions
- $scale = $size / max($props['width'], $props['height']);
+ $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
+ $p['-opts'] = array('-resize' => $p['size'].'>');
- // if file is smaller than the limit, we do nothing
- // but copy original file to destination file
- if ($scale >= 1 && $p['intype'] == $type) {
- $result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false;
- }
- else {
- if ($scale >= 1) {
- $width = $props['width'];
- $height = $props['height'];
- }
- else {
- $width = intval($props['width'] * $scale);
- $height = intval($props['height'] * $scale);
- }
-
- $valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif";
-
- $p += array(
- 'type' => $type,
- 'quality' => 75,
- 'size' => $width . 'x' . $height,
- );
-
- if (in_array($type, explode(',', $valid_types))) { // Valid type?
- $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
- . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
- }
+ if (in_array($type, explode(',', $p['types']))) { // Valid type?
+ $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -quality {quality} {-opts} {intype}:{in} {type}:{out}', $p);
}
if ($result === '') {
@@ -177,43 +148,39 @@ class rcube_image
return false;
}
- if ($image === false) {
- return false;
- }
-
$scale = $size / max($props['width'], $props['height']);
// Imagemagick resize is implemented in shrinking mode (see -resize argument above)
// we do the same here, if an image is smaller than specified size
// we do nothing but copy original file to destination file
- if ($scale >= 1) {
- $result = $this->image_file == $filename || copy($this->image_file, $filename);
+ if ($scale > 1) {
+ return $this->image_file == $filename || copy($this->image_file, $filename) ? $type : false;
}
- else {
- $width = intval($props['width'] * $scale);
- $height = intval($props['height'] * $scale);
- $new_image = imagecreatetruecolor($width, $height);
-
- // Fix transparency of gif/png image
- if ($props['gd_type'] != IMAGETYPE_JPEG) {
- imagealphablending($new_image, false);
- imagesavealpha($new_image, true);
- $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
- imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
- }
-
- imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
- $image = $new_image;
-
- if ($props['gd_type'] == IMAGETYPE_JPEG) {
- $result = imagejpeg($image, $filename, 75);
- }
- elseif($props['gd_type'] == IMAGETYPE_GIF) {
- $result = imagegif($image, $filename);
- }
- elseif($props['gd_type'] == IMAGETYPE_PNG) {
- $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
- }
+
+ $width = $props['width'] * $scale;
+ $height = $props['height'] * $scale;
+
+ $new_image = imagecreatetruecolor($width, $height);
+
+ // Fix transparency of gif/png image
+ if ($props['gd_type'] != IMAGETYPE_JPEG) {
+ imagealphablending($new_image, false);
+ imagesavealpha($new_image, true);
+ $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
+ imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
+ }
+
+ imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
+ $image = $new_image;
+
+ if ($props['gd_type'] == IMAGETYPE_JPEG) {
+ $result = imagejpeg($image, $filename, 75);
+ }
+ elseif($props['gd_type'] == IMAGETYPE_GIF) {
+ $result = imagegif($image, $filename);
+ }
+ elseif($props['gd_type'] == IMAGETYPE_PNG) {
+ $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
}
if ($result) {
@@ -255,7 +222,7 @@ class rcube_image
$p['out'] = $filename;
$p['type'] = self::$extensions[$type];
- $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p);
+ $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -quality 75 {in} {type}:{out}', $p);
if ($result === '') {
@chmod($filename, 0600);
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index aa074233f..ca5e35f2c 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -308,7 +308,14 @@ class rcube_imap extends rcube_storage
*/
public function set_folder($folder)
{
+ if ($this->folder == $folder) {
+ return;
+ }
+
$this->folder = $folder;
+
+ // clear messagecount cache for this folder
+ $this->clear_messagecount($folder);
}
@@ -606,7 +613,7 @@ class rcube_imap extends rcube_storage
}
if ($mode == 'THREADS') {
- $res = $this->threads($folder);
+ $res = $this->fetch_threads($folder, $force);
$count = $res->count();
if ($status) {
@@ -636,11 +643,11 @@ class rcube_imap extends rcube_storage
$keys[] = 'ALL';
}
if ($status) {
- $keys[] = 'MAX';
+ $keys[] = 'MAX';
}
}
- // @TODO: if $mode == 'ALL' we could try to use cache index here
+ // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here
// get message count using (E)SEARCH
// not very performant but more precise (using UNDELETED)
@@ -771,7 +778,7 @@ class rcube_imap extends rcube_storage
$threads = $mcache->get_thread($folder);
}
else {
- $threads = $this->threads($folder);
+ $threads = $this->fetch_threads($folder);
}
return $this->fetch_thread_headers($folder, $threads, $page, $slice);
@@ -780,47 +787,32 @@ class rcube_imap extends rcube_storage
/**
* Method for fetching threads data
*
- * @param string $folder Folder name
+ * @param string $folder Folder name
+ * @param bool $force Use IMAP server, no cache
*
* @return rcube_imap_thread Thread data object
*/
- function threads($folder)
+ function fetch_threads($folder, $force = false)
{
- if ($mcache = $this->get_mcache_engine()) {
+ if (!$force && ($mcache = $this->get_mcache_engine())) {
// don't store in self's internal cache, cache has it's own internal cache
return $mcache->get_thread($folder);
}
- if (!empty($this->icache['threads'])) {
- if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) {
- return $this->icache['threads'];
+ if (empty($this->icache['threads'])) {
+ if (!$this->check_connection()) {
+ return new rcube_result_thread();
}
- }
- // get all threads
- $result = $this->threads_direct($folder);
+ // get all threads
+ $result = $this->conn->thread($folder, $this->threading,
+ $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
- // add to internal (fast) cache
- return $this->icache['threads'] = $result;
- }
-
-
- /**
- * Method for direct fetching of threads data
- *
- * @param string $folder Folder name
- *
- * @return rcube_imap_thread Thread data object
- */
- function threads_direct($folder)
- {
- if (!$this->check_connection()) {
- return new rcube_result_thread();
+ // add to internal (fast) cache
+ $this->icache['threads'] = $result;
}
- // get all threads
- return $this->conn->thread($folder, $this->threading,
- $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
+ return $this->icache['threads'];
}
@@ -1091,17 +1083,16 @@ class rcube_imap extends rcube_storage
/**
- * Returns current status of a folder (compared to the last time use)
+ * Returns current status of folder
*
* We compare the maximum UID to determine the number of
* new messages because the RECENT flag is not reliable.
*
* @param string $folder Folder name
- * @param array $diff Difference data
*
- * @return int Folder status
+ * @return int Folder status
*/
- public function folder_status($folder = null, &$diff = array())
+ public function folder_status($folder = null)
{
if (!strlen($folder)) {
$folder = $this->folder;
@@ -1122,9 +1113,6 @@ class rcube_imap extends rcube_storage
// got new messages
if ($new['maxuid'] > $old['maxuid']) {
$result += 1;
- // get new message UIDs range, that can be used for example
- // to get the data of these messages
- $diff['new'] = ($old['maxuid'] + 1 < $new['maxuid'] ? ($old['maxuid']+1).':' : '') . $new['maxuid'];
}
// some messages has been deleted
if ($new['cnt'] < $old['cnt']) {
@@ -1175,15 +1163,12 @@ class rcube_imap extends rcube_storage
* @param string $folder Folder to get index from
* @param string $sort_field Sort column
* @param string $sort_order Sort order [ASC, DESC]
- * @param bool $no_threads Get not threaded index
- * @param bool $no_search Get index not limited to search result (optionally)
*
* @return rcube_result_index|rcube_result_thread List of messages (UIDs)
*/
- public function index($folder = '', $sort_field = NULL, $sort_order = NULL,
- $no_threads = false, $no_search = false
- ) {
- if (!$no_threads && $this->threading) {
+ public function index($folder = '', $sort_field = NULL, $sort_order = NULL)
+ {
+ if ($this->threading) {
return $this->thread_index($folder, $sort_field, $sort_order);
}
@@ -1195,50 +1180,43 @@ class rcube_imap extends rcube_storage
// we have a saved search result, get index from there
if ($this->search_string) {
- if ($this->search_set->is_empty()) {
- return new rcube_result_index($folder, '* SORT');
+ if ($this->search_threads) {
+ $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
}
- // search result is an index with the same sorting?
- if (($this->search_set instanceof rcube_result_index)
- && ((!$this->sort_field && !$this->search_sorted) ||
- ($this->search_sorted && $this->search_sort_field == $this->sort_field))
- ) {
+ // use message index sort as default sorting
+ if (!$this->sort_field || $this->search_sorted) {
+ if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
+ $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
+ }
$index = $this->search_set;
}
- // $no_search is enabled when we are not interested in
- // fetching index for search result, e.g. to sort
- // threaded search result we can use full mailbox index.
- // This makes possible to use index from cache
- else if (!$no_search) {
- if (!$this->sort_field) {
- // No sorting needed, just build index from the search result
- // @TODO: do we need to sort by UID here?
- $search = $this->search_set->get_compressed();
- $index = new rcube_result_index($folder, '* ESEARCH ALL ' . $search);
- }
- else {
- $index = $this->index_direct($folder, $this->search_charset,
- $this->sort_field, $this->search_set);
- }
+ else if (!$this->check_connection()) {
+ return new rcube_result_index();
+ }
+ else {
+ $index = $this->conn->index($folder, $this->search_set->get(),
+ $this->sort_field, $this->options['skip_deleted'], true, true);
}
- if (isset($index)) {
- if ($this->sort_order != $index->get_parameters('ORDER')) {
- $index->revert();
- }
-
- return $index;
+ if ($this->sort_order != $index->get_parameters('ORDER')) {
+ $index->revert();
}
+
+ return $index;
}
// check local cache
if ($mcache = $this->get_mcache_engine()) {
- return $mcache->get_index($folder, $this->sort_field, $this->sort_order);
+ $index = $mcache->get_index($folder, $this->sort_field, $this->sort_order);
}
-
// fetch from IMAP server
- return $this->index_direct($folder, $this->sort_field, $this->sort_order);
+ else {
+ $index = $this->index_direct(
+ $folder, $this->sort_field, $this->sort_order);
+ }
+
+ return $index;
}
@@ -1246,24 +1224,22 @@ class rcube_imap extends rcube_storage
* Return sorted list of message UIDs ignoring current search settings.
* Doesn't uses cache by default.
*
- * @param string $folder Folder to get index from
- * @param string $sort_field Sort column
- * @param string $sort_order Sort order [ASC, DESC]
- * @param rcube_result_* $search Optional messages set to limit the result
+ * @param string $folder Folder to get index from
+ * @param string $sort_field Sort column
+ * @param string $sort_order Sort order [ASC, DESC]
+ * @param bool $skip_cache Disables cache usage
*
* @return rcube_result_index Sorted list of message UIDs
*/
- public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null)
+ public function index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
{
- if (!empty($search)) {
- $search = $this->search_set->get_compressed();
+ if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
+ $index = $mcache->get_index($folder, $sort_field, $sort_order);
}
-
// use message index sort as default sorting
- if (!$sort_field) {
+ else if (!$sort_field) {
// use search result from count() if possible
- if (empty($search) && $this->options['skip_deleted']
- && !empty($this->icache['undeleted_idx'])
+ if ($this->options['skip_deleted'] && !empty($this->icache['undeleted_idx'])
&& $this->icache['undeleted_idx']->get_parameters('ALL') !== null
&& $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
) {
@@ -1273,12 +1249,8 @@ class rcube_imap extends rcube_storage
return new rcube_result_index();
}
else {
- $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
- if ($search) {
- $query = trim($query . ' UID ' . $search);
- }
-
- $index = $this->conn->search($folder, $query, true);
+ $index = $this->conn->search($folder,
+ 'ALL' .($this->options['skip_deleted'] ? ' UNDELETED' : ''), true);
}
}
else if (!$this->check_connection()) {
@@ -1287,18 +1259,13 @@ class rcube_imap extends rcube_storage
// fetch complete message index
else {
if ($this->get_capability('SORT')) {
- $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
- if ($search) {
- $query = trim($query . ' UID ' . $search);
- }
-
- $index = $this->conn->sort($folder, $sort_field, $query, true);
+ $index = $this->conn->sort($folder, $sort_field,
+ $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
}
if (empty($index) || $index->is_error()) {
- $index = $this->conn->index($folder, $search ? $search : "1:*",
- $sort_field, $this->options['skip_deleted'],
- $search ? true : false, true);
+ $index = $this->conn->index($folder, "1:*", $sort_field,
+ $this->options['skip_deleted'], false, true);
}
}
@@ -1331,7 +1298,7 @@ class rcube_imap extends rcube_storage
}
else {
// get all threads (default sort order)
- $threads = $this->threads($folder);
+ $threads = $this->fetch_threads($folder);
}
$this->set_sort_order($sort_field, $sort_order);
@@ -1342,10 +1309,9 @@ class rcube_imap extends rcube_storage
/**
- * Sort threaded result, using THREAD=REFS method if available.
- * If not, use any method and re-sort the result in THREAD=REFS way.
+ * Sort threaded result, using THREAD=REFS method
*
- * @param rcube_result_thread $threads Threads result set
+ * @param rcube_result_thread $threads Threads result set
*/
protected function sort_threads($threads)
{
@@ -1359,7 +1325,7 @@ class rcube_imap extends rcube_storage
if ($this->threading != 'REFS' || ($this->sort_field && $this->sort_field != 'date')) {
$sortby = $this->sort_field ? $this->sort_field : 'date';
- $index = $this->index($this->folder, $sortby, $this->sort_order, true, true);
+ $index = $this->index_direct($this->folder, $sortby, $this->sort_order, false);
if (!$index->is_empty()) {
$threads->sort($index);
@@ -1439,6 +1405,8 @@ class rcube_imap extends rcube_storage
*/
protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL)
{
+ $orig_criteria = $criteria;
+
if (!$this->check_connection()) {
if ($this->threading) {
return new rcube_result_thread();
@@ -2079,18 +2047,17 @@ class rcube_imap extends rcube_storage
/**
* Fetch message body of a specific message from the server
*
- * @param int Message UID
- * @param string Part number
- * @param rcube_message_part Part object created by get_structure()
- * @param mixed True to print part, resource to write part contents in
- * @param resource File pointer to save the message part
- * @param boolean Disables charset conversion
- * @param int Only read this number of bytes
- * @param boolean Enables formatting of text/* parts bodies
+ * @param int $uid Message UID
+ * @param string $part Part number
+ * @param rcube_message_part $o_part Part object created by get_structure()
+ * @param mixed $print True to print part, ressource to write part contents in
+ * @param resource $fp File pointer to save the message part
+ * @param boolean $skip_charset_conv Disables charset conversion
+ * @param int $max_bytes Only read this number of bytes
*
* @return string Message/part body if not printed
*/
- public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0, $formatted=true)
+ public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0)
{
if (!$this->check_connection()) {
return null;
@@ -2109,9 +2076,8 @@ class rcube_imap extends rcube_storage
}
if ($o_part && $o_part->size) {
- $formatted = $formatted && $o_part->ctype_primary == 'text';
$body = $this->conn->handlePartBody($this->folder, $uid, true,
- $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $formatted, $max_bytes);
+ $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text', $max_bytes);
}
if ($fp || $print) {
@@ -2256,14 +2222,13 @@ class rcube_imap extends rcube_storage
/**
* Append a mail message (source) to a specific folder
*
- * @param string $folder Target folder
- * @param string|array $message The message source string or filename
- * or array (of strings and file pointers)
- * @param string $headers Headers string if $message contains only the body
- * @param boolean $is_file True if $message is a filename
- * @param array $flags Message flags
- * @param mixed $date Message internal date
- * @param bool $binary Enables BINARY append
+ * @param string $folder Target folder
+ * @param string $message The message source string or filename
+ * @param string $headers Headers string if $message contains only the body
+ * @param boolean $is_file True if $message is a filename
+ * @param array $flags Message flags
+ * @param mixed $date Message internal date
+ * @param bool $binary Enables BINARY append
*
* @return int|bool Appended message UID or True on success, False on error
*/
@@ -2354,7 +2319,10 @@ class rcube_imap extends rcube_storage
// move messages
$moved = $this->conn->move($uids, $from_mbox, $to_mbox);
+ // send expunge command in order to have the moved message
+ // really deleted from the source folder
if ($moved) {
+ $this->expunge_message($uids, $from_mbox, false);
$this->clear_messagecount($from_mbox);
$this->clear_messagecount($to_mbox);
}
@@ -2656,6 +2624,7 @@ class rcube_imap extends rcube_storage
if ($list_extended) {
// unsubscribe non-existent folders, remove from the list
+ // we can do this only when LIST response is available
if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
foreach ($a_folders as $idx => $folder) {
if (($opts = $this->conn->data['LIST'][$folder])
@@ -2668,14 +2637,19 @@ class rcube_imap extends rcube_storage
}
}
else {
- // unsubscribe non-existent folders, remove them from the list
- if (is_array($a_folders) && !empty($a_folders) && $name == '*') {
- $existing = $this->list_folders($root, $name);
- $nonexisting = array_diff($a_folders, $existing);
- $a_folders = array_diff($a_folders, $nonexisting);
-
- foreach ($nonexisting as $folder) {
- $this->conn->unsubscribe($folder);
+ // unsubscribe non-existent folders, remove them from the list,
+ // we can do this only when LIST response is available
+ if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
+ foreach ($a_folders as $idx => $folder) {
+ if (!isset($this->conn->data['LIST'][$folder])
+ || in_array('\\Noselect', $this->conn->data['LIST'][$folder])
+ ) {
+ // Some servers returns \Noselect for existing folders
+ if (!$this->folder_exists($folder)) {
+ $this->conn->unsubscribe($folder);
+ unset($a_folders[$idx]);
+ }
+ }
}
}
}
@@ -2794,6 +2768,7 @@ class rcube_imap extends rcube_storage
*/
private function list_folders_update(&$result, $type = null)
{
+ $delim = $this->get_hierarchy_delimiter();
$namespace = $this->get_namespace();
$search = array();
@@ -3704,7 +3679,7 @@ class rcube_imap extends rcube_storage
{
if ($this->caching && !$this->cache) {
$rcube = rcube::get_instance();
- $ttl = $rcube->config->get('imap_cache_ttl', '10d');
+ $ttl = $rcube->config->get('message_cache_lifetime', '10d');
$this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl);
}
@@ -3752,6 +3727,21 @@ class rcube_imap extends rcube_storage
}
}
+ /**
+ * Delete outdated cache entries
+ */
+ public function expunge_cache()
+ {
+ if ($this->mcache) {
+ $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d');
+ $this->mcache->expunge($ttl);
+ }
+
+ if ($this->cache) {
+ $this->cache->expunge();
+ }
+ }
+
/* --------------------------------
* message caching methods
@@ -3785,10 +3775,8 @@ class rcube_imap extends rcube_storage
if ($this->messages_caching && !$this->mcache) {
$rcube = rcube::get_instance();
if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
- $ttl = $rcube->config->get('messages_cache_ttl', '10d');
- $threshold = $rcube->config->get('messages_cache_threshold', 50);
$this->mcache = new rcube_imap_cache(
- $dbh, $this, $userid, $this->options['skip_deleted'], $ttl, $threshold);
+ $dbh, $this, $userid, $this->options['skip_deleted']);
}
}
@@ -3810,15 +3798,6 @@ class rcube_imap extends rcube_storage
}
- /**
- * Delete outdated cache entries
- */
- function cache_gc()
- {
- rcube_imap_cache::gc();
- }
-
-
/* --------------------------------
* protected methods
* --------------------------------*/
@@ -3852,7 +3831,7 @@ class rcube_imap extends rcube_storage
$delimiter = $this->get_hierarchy_delimiter();
// find default folders and skip folders starting with '.'
- foreach ($a_folders as $folder) {
+ foreach ($a_folders as $i => $folder) {
if ($folder[0] == '.') {
continue;
}
@@ -4112,9 +4091,9 @@ class rcube_imap extends rcube_storage
return $this->index($folder, $sort_field, $sort_order);
}
- public function message_index_direct($folder, $sort_field = null, $sort_order = null)
+ public function message_index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
{
- return $this->index_direct($folder, $sort_field, $sort_order);
+ return $this->index_direct($folder, $sort_field, $sort_order, $skip_cache);
}
public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php
index d72bfe0ab..5170e9e21 100644
--- a/program/lib/Roundcube/rcube_imap_cache.php
+++ b/program/lib/Roundcube/rcube_imap_cache.php
@@ -49,20 +49,6 @@ class rcube_imap_cache
private $userid;
/**
- * Expiration time in seconds
- *
- * @var int
- */
- private $ttl;
-
- /**
- * Maximum cached message size
- *
- * @var int
- */
- private $threshold;
-
- /**
* Internal (in-memory) cache
*
* @var array
@@ -97,26 +83,13 @@ class rcube_imap_cache
/**
* Object constructor.
- *
- * @param rcube_db $db DB handler
- * @param rcube_imap $imap IMAP handler
- * @param int $userid User identifier
- * @param bool $skip_deleted skip_deleted flag
- * @param string $ttl Expiration time of memcache/apc items
- * @param int $threshold Maximum cached message size
*/
- function __construct($db, $imap, $userid, $skip_deleted, $ttl=0, $threshold=0)
+ function __construct($db, $imap, $userid, $skip_deleted)
{
- // convert ttl string to seconds
- $ttl = get_offset_sec($ttl);
- if ($ttl > 2592000) $ttl = 2592000;
-
$this->db = $db;
$this->imap = $imap;
$this->userid = $userid;
$this->skip_deleted = $skip_deleted;
- $this->ttl = $ttl;
- $this->threshold = $threshold;
}
@@ -242,7 +215,9 @@ class rcube_imap_cache
* Return messages thread.
* If threaded index doesn't exist or is invalid, will be updated.
*
- * @param string $mailbox Folder name
+ * @param string $mailbox Folder name
+ * @param string $sort_field Sorting column
+ * @param string $sort_order Sorting order (ASC|DESC)
*
* @return array Messages threaded index
*/
@@ -281,11 +256,19 @@ class rcube_imap_cache
if ($index === null) {
// Get mailbox data (UIDVALIDITY, counters, etc.) for status check
$mbox_data = $this->imap->folder_data($mailbox);
- // Get THREADS result
- $index['object'] = $this->get_thread_data($mailbox, $mbox_data);
+
+ if ($mbox_data['EXISTS']) {
+ // get all threads (default sort order)
+ $threads = $this->imap->fetch_threads($mailbox, true);
+ }
+ else {
+ $threads = new rcube_result_thread($mailbox, '* THREAD');
+ }
+
+ $index['object'] = $threads;
// insert/update
- $this->add_thread_row($mailbox, $index['object'], $mbox_data, $exists);
+ $this->add_thread_row($mailbox, $threads, $mbox_data, $exists);
}
$this->icache[$mailbox]['thread'] = $index;
@@ -443,40 +426,23 @@ class rcube_imap_cache
if (!$force) {
$res = $this->db->query(
"UPDATE ".$this->db->table_name('cache_messages')
- ." SET flags = ?, data = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
+ ." SET flags = ?, data = ?, changed = ".$this->db->now()
." WHERE user_id = ?"
." AND mailbox = ?"
." AND uid = ?",
$flags, $msg, $this->userid, $mailbox, (int) $message->uid);
- if ($this->db->affected_rows($res)) {
+ if ($this->db->affected_rows()) {
return;
}
}
- $this->db->set_option('ignore_key_errors', true);
-
// insert new record
- $res = $this->db->query(
+ $this->db->query(
"INSERT INTO ".$this->db->table_name('cache_messages')
- ." (user_id, mailbox, uid, flags, expires, data)"
- ." VALUES (?, ?, ?, ?, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?)",
+ ." (user_id, mailbox, uid, flags, changed, data)"
+ ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)",
$this->userid, $mailbox, (int) $message->uid, $flags, $msg);
-
- // race-condition, insert failed so try update (#1489146)
- // thanks to ignore_key_errors "duplicate row" errors will be ignored
- if ($force && !$res && !$this->db->is_error($res)) {
- $this->db->query(
- "UPDATE ".$this->db->table_name('cache_messages')
- ." SET expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
- .", flags = ?, data = ?"
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- ." AND uid = ?",
- $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
- }
-
- $this->db->set_option('ignore_key_errors', false);
}
@@ -517,7 +483,7 @@ class rcube_imap_cache
$this->db->query(
"UPDATE ".$this->db->table_name('cache_messages')
- ." SET expires = ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
+ ." SET changed = ".$this->db->now()
.", flags = flags ".($enabled ? "+ $idx" : "- $idx")
." WHERE user_id = ?"
." AND mailbox = ?"
@@ -640,21 +606,23 @@ class rcube_imap_cache
/**
- * Delete expired cache entries
+ * Delete cache entries older than TTL
+ *
+ * @param string $ttl Lifetime of message cache entries
*/
- static function gc()
+ function expunge($ttl)
{
- $rcube = rcube::get_instance();
- $db = $rcube->get_dbh();
+ // get expiration timestamp
+ $ts = get_offset_time($ttl, -1);
- $db->query("DELETE FROM ".$db->table_name('cache_messages')
- ." WHERE expires < " . $db->now());
+ $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages')
+ ." WHERE changed < " . $this->db->fromunixtime($ts));
- $db->query("DELETE FROM ".$db->table_name('cache_index')
- ." WHERE expires < " . $db->now());
+ $this->db->query("DELETE FROM ".$this->db->table_name('cache_index')
+ ." WHERE changed < " . $this->db->fromunixtime($ts));
- $db->query("DELETE FROM ".$db->table_name('cache_thread')
- ." WHERE expires < " . $db->now());
+ $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread')
+ ." WHERE changed < " . $this->db->fromunixtime($ts));
}
@@ -746,38 +714,20 @@ class rcube_imap_cache
$data = implode('@', $data);
if ($exists) {
- $res = $this->db->query(
+ $sql_result = $this->db->query(
"UPDATE ".$this->db->table_name('cache_index')
- ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
+ ." SET data = ?, valid = 1, changed = ".$this->db->now()
." WHERE user_id = ?"
." AND mailbox = ?",
$data, $this->userid, $mailbox);
-
- if ($this->db->affected_rows($res)) {
- return;
- }
}
-
- $this->db->set_option('ignore_key_errors', true);
-
- $res = $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache_index')
- ." (user_id, mailbox, valid, expires, data)"
- ." VALUES (?, ?, 1, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .", ?)",
- $this->userid, $mailbox, $data);
-
- // race-condition, insert failed so try update (#1489146)
- // thanks to ignore_key_errors "duplicate row" errors will be ignored
- if (!$exists && !$res && !$this->db->is_error($res)) {
- $res = $this->db->query(
- "UPDATE ".$this->db->table_name('cache_index')
- ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $data, $this->userid, $mailbox);
+ else {
+ $sql_result = $this->db->query(
+ "INSERT INTO ".$this->db->table_name('cache_index')
+ ." (user_id, mailbox, data, valid, changed)"
+ ." VALUES (?, ?, ?, 1, ".$this->db->now().")",
+ $this->userid, $mailbox, $data);
}
-
- $this->db->set_option('ignore_key_errors', false);
}
@@ -794,41 +744,21 @@ class rcube_imap_cache
);
$data = implode('@', $data);
- $expires = ($this->ttl ? $this->db->now($this->ttl) : 'NULL');
-
if ($exists) {
- $res = $this->db->query(
+ $sql_result = $this->db->query(
"UPDATE ".$this->db->table_name('cache_thread')
- ." SET data = ?, expires = $expires"
+ ." SET data = ?, changed = ".$this->db->now()
." WHERE user_id = ?"
." AND mailbox = ?",
$data, $this->userid, $mailbox);
-
- if ($this->db->affected_rows($res)) {
- return;
- }
}
-
- $this->db->set_option('ignore_key_errors', true);
-
- $res = $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache_thread')
- ." (user_id, mailbox, expires, data)"
- ." VALUES (?, ?, $expires, ?)",
- $this->userid, $mailbox, $data);
-
- // race-condition, insert failed so try update (#1489146)
- // thanks to ignore_key_errors "duplicate row" errors will be ignored
- if (!$exists && !$res && !$this->db->is_error($res)) {
- $this->db->query(
- "UPDATE ".$this->db->table_name('cache_thread')
- ." SET expires = $expires, data = ?"
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $data, $this->userid, $mailbox);
+ else {
+ $sql_result = $this->db->query(
+ "INSERT INTO ".$this->db->table_name('cache_thread')
+ ." (user_id, mailbox, data, changed)"
+ ." VALUES (?, ?, ?, ".$this->db->now().")",
+ $this->userid, $mailbox, $data);
}
-
- $this->db->set_option('ignore_key_errors', false);
}
@@ -1055,7 +985,7 @@ class rcube_imap_cache
$uids, true, array('FLAGS'), $index['modseq'], $qresync);
if (!empty($result)) {
- foreach ($result as $msg) {
+ foreach ($result as $id => $msg) {
$uid = $msg->uid;
// Remove deleted message
if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
@@ -1076,7 +1006,7 @@ class rcube_imap_cache
$this->db->query(
"UPDATE ".$this->db->table_name('cache_messages')
- ." SET flags = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
+ ." SET flags = ?, changed = ".$this->db->now()
." WHERE user_id = ?"
." AND mailbox = ?"
." AND uid = ?"
@@ -1104,18 +1034,17 @@ class rcube_imap_cache
}
}
+ // Invalidate thread index (?)
+ if (!$index['valid']) {
+ $this->remove_thread($mailbox);
+ }
+
$sort_field = $index['sort_field'];
$sort_order = $index['object']->get_parameters('ORDER');
$exists = true;
// Validate index
if (!$this->validate($mailbox, $index, $exists)) {
- // Invalidate (remove) thread index
- // if $exists=false it was already removed in validate()
- if ($exists) {
- $this->remove_thread($mailbox);
- }
-
// Update index
$data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
}
@@ -1182,16 +1111,11 @@ class rcube_imap_cache
*
* @param rcube_message_header|rcube_message_part
*/
- private function message_object_prepare(&$msg, &$size = 0)
+ private function message_object_prepare(&$msg)
{
- // Remove body too big
- if ($msg->body && ($length = strlen($msg->body))) {
- $size += $length;
-
- if ($size > $this->threshold * 1024) {
- $size -= $length;
- unset($msg->body);
- }
+ // Remove body too big (>25kB)
+ if ($msg->body && strlen($msg->body) > 25 * 1024) {
+ unset($msg->body);
}
// Fix mimetype which might be broken by some code when message is displayed
@@ -1205,13 +1129,13 @@ class rcube_imap_cache
if (is_array($msg->structure->parts)) {
foreach ($msg->structure->parts as $part) {
- $this->message_object_prepare($part, $size);
+ $this->message_object_prepare($part);
}
}
if (is_array($msg->parts)) {
foreach ($msg->parts as $part) {
- $this->message_object_prepare($part, $size);
+ $this->message_object_prepare($part);
}
}
}
@@ -1236,25 +1160,6 @@ class rcube_imap_cache
return $index;
}
-
-
- /**
- * Fetches thread data from IMAP server
- */
- private function get_thread_data($mailbox, $mbox_data = array())
- {
- if (empty($mbox_data)) {
- $mbox_data = $this->imap->folder_data($mailbox);
- }
-
- if ($mbox_data['EXISTS']) {
- // get all threads (default sort order)
- return $this->imap->threads_direct($mailbox);
- }
-
- return new rcube_result_thread($mailbox, '* THREAD');
- }
-
}
// for backward compat.
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index bce4cd4e2..1b28c3bd7 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -72,8 +72,6 @@ class rcube_imap_generic
const COMMAND_CAPABILITY = 2;
const COMMAND_LASTLINE = 4;
- const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
-
/**
* Object constructor
*/
@@ -789,21 +787,23 @@ class rcube_imap_generic
// TLS connection
if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
- $res = $this->execute('STARTTLS');
+ if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
+ $res = $this->execute('STARTTLS');
- if ($res[0] != self::ERROR_OK) {
- $this->closeConnection();
- return false;
- }
+ if ($res[0] != self::ERROR_OK) {
+ $this->closeConnection();
+ return false;
+ }
- if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
- $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
- $this->closeConnection();
- return false;
- }
+ if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+ $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
+ $this->closeConnection();
+ return false;
+ }
- // Now we're secure, capabilities need to be reread
- $this->clearCapability();
+ // Now we're secure, capabilities need to be reread
+ $this->clearCapability();
+ }
}
// Send ID info
@@ -902,11 +902,6 @@ class rcube_imap_generic
$this->prefs['auth_type'] = 'CHECK';
}
- // disabled capabilities
- if (!empty($this->prefs['disabled_caps'])) {
- $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']);
- }
-
// additional message flags
if (!empty($this->prefs['message_flags'])) {
$this->flags = array_merge($this->flags, $this->prefs['message_flags']);
@@ -1088,8 +1083,8 @@ class rcube_imap_generic
/**
* Executes EXPUNGE command
*
- * @param string $mailbox Mailbox name
- * @param string|array $messages Message UIDs to expunge
+ * @param string $mailbox Mailbox name
+ * @param string $messages Message UIDs to expunge
*
* @return boolean True on success, False on error
*/
@@ -1107,13 +1102,10 @@ class rcube_imap_generic
// Clear internal status cache
unset($this->data['STATUS:'.$mailbox]);
- if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
- $messages = self::compressMessageSet($messages);
- $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
- }
- else {
+ if ($messages)
+ $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
+ else
$result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
- }
if ($result == self::ERROR_OK) {
$this->selected = null; // state has changed, need to reselect
@@ -1350,8 +1342,9 @@ class rcube_imap_generic
$folders[$mailbox] = array();
}
- // store folder options
- if ($cmd == 'LIST') {
+ // store LSUB options only if not empty, this way
+ // we can detect a situation when LIST doesn't return specified folder
+ if (!empty($opts) || $cmd == 'LIST') {
// Add to options array
if (empty($this->data['LIST'][$mailbox]))
$this->data['LIST'][$mailbox] = $opts;
@@ -1583,12 +1576,11 @@ class rcube_imap_generic
}
// message IDs
- if (!empty($add)) {
+ if (!empty($add))
$add = $this->compressMessageSet($add);
- }
list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT',
- array("($field)", $encoding, !empty($add) ? $add : 'ALL'));
+ array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));
if ($code != self::ERROR_OK) {
$response = null;
@@ -1675,6 +1667,7 @@ class rcube_imap_generic
}
if (!empty($criteria)) {
+ $modseq = stripos($criteria, 'MODSEQ') !== false;
$params .= ($params ? ' ' : '') . $criteria;
}
else {
@@ -1813,6 +1806,7 @@ class rcube_imap_generic
if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
$flags = explode(' ', strtoupper($matches[1]));
if (in_array('\\DELETED', $flags)) {
+ $deleted[$id] = $id;
continue;
}
}
@@ -2004,6 +1998,7 @@ class rcube_imap_generic
/**
* Moves message(s) from one folder to another.
+ * Original message(s) will be marked as deleted.
*
* @param string|array $messages Message UID(s)
* @param string $from Mailbox name
@@ -2022,41 +2017,15 @@ class rcube_imap_generic
return false;
}
- // use MOVE command (RFC 6851)
- if ($this->hasCapability('MOVE')) {
- // Clear last COPYUID data
- unset($this->data['COPYUID']);
+ $r = $this->copy($messages, $from, $to);
+ if ($r) {
// Clear internal status cache
- unset($this->data['STATUS:'.$to]);
unset($this->data['STATUS:'.$from]);
- $result = $this->execute('UID MOVE', array(
- $this->compressMessageSet($messages), $this->escape($to)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE
- $result = $this->copy($messages, $from, $to);
-
- if ($result) {
- // Clear internal status cache
- unset($this->data['STATUS:'.$from]);
-
- $result = $this->flag($from, $messages, 'DELETED');
-
- if ($messages == '*') {
- // CLOSE+SELECT should be faster than EXPUNGE
- $this->close();
- }
- else {
- $this->expunge($from, $messages);
- }
+ return $this->flag($from, $messages, 'DELETED');
}
-
- return $result;
+ return $r;
}
/**
@@ -2197,7 +2166,7 @@ class rcube_imap_generic
// create array with header field:data
if (!empty($headers)) {
$headers = explode("\n", trim($headers));
- foreach ($headers as $resln) {
+ foreach ($headers as $hid => $resln) {
if (ord($resln[0]) <= 32) {
$lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
} else {
@@ -2205,7 +2174,7 @@ class rcube_imap_generic
}
}
- foreach ($lines as $str) {
+ while (list($lines_key, $str) = each($lines)) {
list($field, $string) = explode(':', $str, 2);
$field = strtolower($field);
@@ -2509,7 +2478,7 @@ class rcube_imap_generic
}
if ($binary) {
- // WARNING: Use $formatted argument with care, this may break binary data stream
+ // WARNING: Use $formatting argument with care, this may break binary data stream
$mode = -1;
}
@@ -2530,7 +2499,6 @@ class rcube_imap_generic
// handle one line response
if ($line[0] == '(' && substr($line, -1) == ')') {
// tokenize content inside brackets
- // the content can be e.g.: (UID 9844 BODY[2.4] NIL)
$tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
for ($i=0; $i<count($tokens); $i+=2) {
@@ -2645,11 +2613,11 @@ class rcube_imap_generic
/**
* Handler for IMAP APPEND command
*
- * @param string $mailbox Mailbox name
- * @param string|array $message The message source string or array (of strings and file pointers)
- * @param array $flags Message flags
- * @param string $date Message internal date
- * @param bool $binary Enable BINARY append (RFC3516)
+ * @param string $mailbox Mailbox name
+ * @param string $message Message content
+ * @param array $flags Message flags
+ * @param string $date Message internal date
+ * @param bool $binary Enable BINARY append (RFC3516)
*
* @return string|bool On success APPENDUID response (if available) or True, False on failure
*/
@@ -2663,28 +2631,13 @@ class rcube_imap_generic
$binary = $binary && $this->getCapability('BINARY');
$literal_plus = !$binary && $this->prefs['literal+'];
- $len = 0;
- $msg = is_array($message) ? $message : array(&$message);
- $chunk_size = 512000;
-
- for ($i=0, $cnt=count($msg); $i<$cnt; $i++) {
- if (is_resource($msg[$i])) {
- $stat = fstat($msg[$i]);
- if ($stat === false) {
- return false;
- }
- $len += $stat['size'];
- }
- else {
- if (!$binary) {
- $msg[$i] = str_replace("\r", '', $msg[$i]);
- $msg[$i] = str_replace("\n", "\r\n", $msg[$i]);
- }
- $len += strlen($msg[$i]);
- }
+ if (!$binary) {
+ $message = str_replace("\r", '', $message);
+ $message = str_replace("\n", "\r\n", $message);
}
+ $len = strlen($message);
if (!$len) {
return false;
}
@@ -2709,32 +2662,7 @@ class rcube_imap_generic
}
}
- foreach ($msg as $msg_part) {
- // file pointer
- if (is_resource($msg_part)) {
- rewind($msg_part);
- while (!feof($msg_part) && $this->fp) {
- $buffer = fread($msg_part, $chunk_size);
- $this->putLine($buffer, false);
- }
- fclose($msg_part);
- }
- // string
- else {
- $size = strlen($msg_part);
-
- // Break up the data by sending one chunk (up to 512k) at a time.
- // This approach reduces our peak memory usage
- for ($offset = 0; $offset < $size; $offset += $chunk_size) {
- $chunk = substr($msg_part, $offset, $chunk_size);
- if (!$this->putLine($chunk, false)) {
- return false;
- }
- }
- }
- }
-
- if (!$this->putLine('')) { // \r\n
+ if (!$this->putLine($message)) {
return false;
}
@@ -2773,23 +2701,94 @@ class rcube_imap_generic
*/
function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
{
+ unset($this->data['APPENDUID']);
+
+ if ($mailbox === null || $mailbox === '') {
+ return false;
+ }
+
// open message file
+ $in_fp = false;
if (file_exists(realpath($path))) {
- $fp = fopen($path, 'r');
+ $in_fp = fopen($path, 'r');
}
- if (!$fp) {
+ if (!$in_fp) {
$this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
return false;
}
- $message = array();
+ $body_separator = "\r\n\r\n";
+ $len = filesize($path);
+
+ if (!$len) {
+ return false;
+ }
+
if ($headers) {
- $message[] = trim($headers, "\r\n") . "\r\n\r\n";
+ $headers = preg_replace('/[\r\n]+$/', '', $headers);
+ $len += strlen($headers) + strlen($body_separator);
+ }
+
+ $binary = $binary && $this->getCapability('BINARY');
+ $literal_plus = !$binary && $this->prefs['literal+'];
+
+ // build APPEND command
+ $key = $this->nextTag();
+ $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
+ if (!empty($date)) {
+ $request .= ' ' . $this->escape($date);
}
- $message[] = $fp;
+ $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
+
+ // send APPEND command
+ if ($this->putLine($request)) {
+ // Don't wait when LITERAL+ is supported
+ if (!$literal_plus) {
+ $line = $this->readReply();
- return $this->append($mailbox, $message, $flags, $date, $binary);
+ if ($line[0] != '+') {
+ $this->parseResult($line, 'APPEND: ');
+ return false;
+ }
+ }
+
+ // send headers with body separator
+ if ($headers) {
+ $this->putLine($headers . $body_separator, false);
+ }
+
+ // send file
+ while (!feof($in_fp) && $this->fp) {
+ $buffer = fgets($in_fp, 4096);
+ $this->putLine($buffer, false);
+ }
+ fclose($in_fp);
+
+ if (!$this->putLine('')) { // \r\n
+ return false;
+ }
+
+ // read response
+ do {
+ $line = $this->readLine();
+ } while (!$this->startsWith($line, $key, true, true));
+
+ // Clear internal status cache
+ unset($this->data['STATUS:'.$mailbox]);
+
+ if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
+ return false;
+ else if (!empty($this->data['APPENDUID']))
+ return $this->data['APPENDUID'];
+ else
+ return true;
+ }
+ else {
+ $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
+ }
+
+ return false;
}
/**
@@ -3538,7 +3537,7 @@ class rcube_imap_generic
if (is_array($element)) {
reset($element);
- foreach ($element as $value) {
+ while (list($key, $value) = each($element)) {
$string .= ' ' . self::r_implode($value);
}
}
@@ -3566,7 +3565,7 @@ class rcube_imap_generic
// if less than 255 bytes long, let's not bother
if (!$force && strlen($messages)<255) {
return $messages;
- }
+ }
// see if it's already been compressed
if (strpos($messages, ':') !== false) {
@@ -3674,20 +3673,8 @@ class rcube_imap_generic
*/
static function strToTime($date)
{
- // Clean malformed data
- $date = preg_replace(
- array(
- '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal
- '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters
- '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names
- ),
- array(
- '\\1',
- '',
- '',
- ), $date);
-
- $date = trim($date);
+ // support non-standard "GMTXXXX" literal
+ $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
// if date parsing fails, we have a date in non-rfc format
// remove token from the end and try again
@@ -3712,10 +3699,6 @@ class rcube_imap_generic
$this->capability = explode(' ', strtoupper($str));
- if (!empty($this->prefs['disabled_caps'])) {
- $this->capability = array_diff($this->capability, $this->prefs['disabled_caps']);
- }
-
if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
$this->prefs['literal+'] = true;
}
@@ -3761,10 +3744,9 @@ class rcube_imap_generic
/**
* Set the value of the debugging flag.
*
- * @param boolean $debug New value for the debugging flag.
- * @param callback $handler Logging handler function
+ * @param boolean $debug New value for the debugging flag.
*
- * @since 0.5-stable
+ * @since 0.5-stable
*/
function setDebug($debug, $handler = null)
{
@@ -3775,18 +3757,12 @@ class rcube_imap_generic
/**
* Write the given debug text to the current debug output handler.
*
- * @param string $message Debug mesage text.
+ * @param string $message Debug mesage text.
*
- * @since 0.5-stable
+ * @since 0.5-stable
*/
private function debug($message)
{
- if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
- $diff = $len - self::DEBUG_LINE_LENGTH;
- $message = substr($message, 0, self::DEBUG_LINE_LENGTH)
- . "... [truncated $diff bytes]";
- }
-
if ($this->resourceid) {
$message = sprintf('[%s] %s', $this->resourceid, $message);
}
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index 78573789b..7c4002337 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -3,8 +3,8 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2013, The Roundcube Dev Team |
- | Copyright (C) 2011-2013, Kolab Systems AG |
+ | Copyright (C) 2006-2012, The Roundcube Dev Team |
+ | Copyright (C) 2011-2012, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -27,51 +27,38 @@
*/
class rcube_ldap extends rcube_addressbook
{
- // public properties
+ /** public properties */
public $primary_key = 'ID';
- public $groups = false;
- public $readonly = true;
- public $ready = false;
- public $group_id = 0;
- public $coltypes = array();
- public $export_groups = false;
-
- // private properties
- protected $ldap;
- protected $prop = array();
+ public $groups = false;
+ public $readonly = true;
+ public $ready = false;
+ public $group_id = 0;
+ public $coltypes = array();
+
+ /** private properties */
+ protected $conn;
+ protected $prop = array();
protected $fieldmap = array();
- protected $filter = '';
protected $sub_filter;
- protected $result;
- protected $ldap_result;
+ protected $filter = '';
+ protected $result = null;
+ protected $ldap_result = null;
protected $mail_domain = '';
protected $debug = false;
- /**
- * Group objectclass (lowercase) to member attribute mapping
- *
- * @var array
- */
- private static $group_types = array(
- 'group' => 'member',
- 'groupofnames' => 'member',
- 'kolabgroupofnames' => 'member',
- 'groupofuniquenames' => 'uniqueMember',
- 'kolabgroupofuniquenames' => 'uniqueMember',
- 'univentiongroup' => 'uniqueMember',
- 'groupofurls' => null,
- );
-
- private $base_dn = '';
+ private $base_dn = '';
private $groups_base_dn = '';
- private $group_url;
+ private $group_url = null;
private $cache;
+ private $vlv_active = false;
+ private $vlv_count = 0;
+
/**
* Object constructor
*
- * @param array $p LDAP connection properties
+ * @param array $p LDAP connection properties
* @param boolean $debug Enables debug mode
* @param string $mail_domain Current user mail domain name
*/
@@ -79,7 +66,8 @@ class rcube_ldap extends rcube_addressbook
{
$this->prop = $p;
- $fetch_attributes = array('objectClass');
+ if (isset($p['searchonly']))
+ $this->searchonly = $p['searchonly'];
// check if groups are configured
if (is_array($p['groups']) && count($p['groups'])) {
@@ -94,21 +82,6 @@ class rcube_ldap extends rcube_addressbook
$this->prop['groups']['name_attr'] = 'cn';
if (empty($this->prop['groups']['scope']))
$this->prop['groups']['scope'] = 'sub';
-
- // add group name attrib to the list of attributes to be fetched
- $fetch_attributes[] = $this->prop['groups']['name_attr'];
- }
- if (is_array($p['group_filters']) && count($p['group_filters'])) {
- $this->groups = true;
-
- foreach ($p['group_filters'] as $k => $group_filter) {
- // set default name attribute to cn
- if (empty($group_filter['name_attr']) && empty($this->prop['groups']['name_attr']))
- $this->prop['group_filters'][$k]['name_attr'] = $group_filter['name_attr'] = 'cn';
-
- if ($group_filter['name_attr'])
- $fetch_attributes[] = $group_filter['name_attr'];
- }
}
// fieldmap property is given
@@ -196,7 +169,7 @@ class rcube_ldap extends rcube_addressbook
// Build sub_fields filter
if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
$this->sub_filter = '';
- foreach ($this->prop['sub_fields'] as $class) {
+ foreach ($this->prop['sub_fields'] as $attr => $class) {
if (!empty($class)) {
$class = is_array($class) ? array_pop($class) : $class;
$this->sub_filter .= '(objectClass=' . $class . ')';
@@ -213,24 +186,7 @@ class rcube_ldap extends rcube_addressbook
// initialize cache
$rcube = rcube::get_instance();
- if ($cache_type = $rcube->config->get('ldap_cache', 'db')) {
- $cache_ttl = $rcube->config->get('ldap_cache_ttl', '10m');
- $cache_name = 'LDAP.' . asciiwords($this->prop['name']);
-
- $this->cache = $rcube->get_cache($cache_name, $cache_type, $cache_ttl);
- }
-
- // determine which attributes to fetch
- $this->prop['list_attributes'] = array_unique($fetch_attributes);
- $this->prop['attributes'] = array_merge(array_values($this->fieldmap), $fetch_attributes);
- foreach ($rcube->config->get('contactlist_fields') as $col) {
- $this->prop['list_attributes'] = array_merge($this->prop['list_attributes'], $this->_map_field($col));
- }
-
- // initialize ldap wrapper object
- $this->ldap = new rcube_ldap_generic($this->prop);
- $this->ldap->set_cache($this->cache);
- $this->ldap->set_debug($this->debug);
+ $this->cache = $rcube->get_cache('LDAP.' . asciiwords($this->prop['name']), 'db', 600);
$this->_connect();
}
@@ -243,18 +199,49 @@ class rcube_ldap extends rcube_addressbook
{
$rcube = rcube::get_instance();
- if ($this->ready)
+ if (!function_exists('ldap_connect'))
+ rcube::raise_error(array('code' => 100, 'type' => 'ldap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "No ldap support in this installation of PHP"),
+ true, true);
+
+ if (is_resource($this->conn))
return true;
if (!is_array($this->prop['hosts']))
$this->prop['hosts'] = array($this->prop['hosts']);
+ if (empty($this->prop['ldap_version']))
+ $this->prop['ldap_version'] = 3;
+
// try to connect + bind for every host configured
// with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable
// see http://www.php.net/manual/en/function.ldap-connect.php
foreach ($this->prop['hosts'] as $host) {
- // skip host if connection failed
- if (!$this->ldap->connect($host)) {
+ $host = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host));
+ $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : '');
+
+ $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]");
+
+ if ($lc = @ldap_connect($host, $this->prop['port'])) {
+ if ($this->prop['use_tls'] === true)
+ if (!ldap_start_tls($lc))
+ continue;
+
+ $this->_debug("S: OK");
+
+ ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['ldap_version']);
+ $this->prop['host'] = $host;
+ $this->conn = $lc;
+
+ if (!empty($this->prop['network_timeout']))
+ ldap_set_option($lc, LDAP_OPT_NETWORK_TIMEOUT, $this->prop['network_timeout']);
+
+ if (isset($this->prop['referrals']))
+ ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->prop['referrals']);
+ }
+ else {
+ $this->_debug("S: NOT OK");
continue;
}
@@ -269,7 +256,7 @@ class rcube_ldap extends rcube_addressbook
$this->base_dn = $this->prop['base_dn'];
$this->groups_base_dn = ($this->prop['groups']['base_dn']) ?
- $this->prop['groups']['base_dn'] : $this->base_dn;
+ $this->prop['groups']['base_dn'] : $this->base_dn;
// User specific access, generate the proper values to use.
if ($this->prop['user_specific']) {
@@ -288,47 +275,30 @@ class rcube_ldap extends rcube_addressbook
$replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u);
- // Search for the dn to use to authenticate
if ($this->prop['search_base_dn'] && $this->prop['search_filter']) {
- $search_bind_dn = strtr($this->prop['search_bind_dn'], $replaces);
- $search_base_dn = strtr($this->prop['search_base_dn'], $replaces);
- $search_filter = strtr($this->prop['search_filter'], $replaces);
-
- $cache_key = 'DN.' . md5("$host:$search_bind_dn:$search_base_dn:$search_filter:"
- .$this->prop['search_bind_pw']);
-
- if ($this->cache && ($dn = $this->cache->get($cache_key))) {
- $replaces['%dn'] = $dn;
+ if (!empty($this->prop['search_bind_dn']) && !empty($this->prop['search_bind_pw'])) {
+ $this->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']);
}
- else {
- $ldap = $this->ldap;
- if (!empty($search_bind_dn) && !empty($this->prop['search_bind_pw'])) {
- // To protect from "Critical extension is unavailable" error
- // we need to use a separate LDAP connection
- if (!empty($this->prop['vlv'])) {
- $ldap = new rcube_ldap_generic($this->prop);
- $ldap->set_debug($this->debug);
- $ldap->set_cache($this->cache);
- if (!$ldap->connect($host)) {
- continue;
- }
- }
-
- if (!$ldap->bind($search_bind_dn, $this->prop['search_bind_pw'])) {
- continue; // bind failed, try next host
- }
- }
- $res = $ldap->search($search_base_dn, $search_filter, 'sub', array('uid'));
- if ($res) {
- $res->rewind();
- $replaces['%dn'] = $res->get_dn();
- }
+ // Search for the dn to use to authenticate
+ $this->prop['search_base_dn'] = strtr($this->prop['search_base_dn'], $replaces);
+ $this->prop['search_filter'] = strtr($this->prop['search_filter'], $replaces);
+
+ $this->_debug("S: searching with base {$this->prop['search_base_dn']} for {$this->prop['search_filter']}");
- if ($ldap != $this->ldap) {
- $ldap->close();
+ $res = @ldap_search($this->conn, $this->prop['search_base_dn'], $this->prop['search_filter'], array('uid'));
+ if ($res) {
+ if (($entry = ldap_first_entry($this->conn, $res))
+ && ($bind_dn = ldap_get_dn($this->conn, $entry))
+ ) {
+ $this->_debug("S: search returned dn: $bind_dn");
+ $dn = ldap_explode_dn($bind_dn, 1);
+ $replaces['%dn'] = $dn[0];
}
}
+ else {
+ $this->_debug("S: ".ldap_error($this->conn));
+ }
// DN not found
if (empty($replaces['%dn'])) {
@@ -339,13 +309,9 @@ class rcube_ldap extends rcube_addressbook
'code' => 100, 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "DN not found using LDAP search."), true);
- continue;
+ return false;
}
}
-
- if ($this->cache && !empty($replaces['%dn'])) {
- $this->cache->set($cache_key, $replaces['%dn']);
- }
}
// Replace the bind_dn and base_dn variables.
@@ -363,13 +329,13 @@ class rcube_ldap extends rcube_addressbook
}
else {
if (!empty($bind_dn)) {
- $this->ready = $this->ldap->bind($bind_dn, $bind_pass);
+ $this->ready = $this->bind($bind_dn, $bind_pass);
}
else if (!empty($this->prop['auth_cid'])) {
- $this->ready = $this->ldap->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user);
+ $this->ready = $this->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user);
}
else {
- $this->ready = $this->ldap->sasl_bind($bind_user, $bind_pass);
+ $this->ready = $this->sasl_bind($bind_user, $bind_pass);
}
}
@@ -380,10 +346,10 @@ class rcube_ldap extends rcube_addressbook
} // end foreach hosts
- if (!is_resource($this->ldap->conn)) {
+ if (!is_resource($this->conn)) {
rcube::raise_error(array('code' => 100, 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not connect to any LDAP server, last tried $host"), true);
+ 'message' => "Could not connect to any LDAP server, last tried $hostname"), true);
return false;
}
@@ -393,47 +359,112 @@ class rcube_ldap extends rcube_addressbook
/**
- * Close connection to LDAP server
+ * Bind connection with (SASL-) user and password
+ *
+ * @param string $authc Authentication user
+ * @param string $pass Bind password
+ * @param string $authz Autorization user
+ *
+ * @return boolean True on success, False on error
*/
- function close()
+ public function sasl_bind($authc, $pass, $authz=null)
{
- if ($this->ldap) {
- $this->ldap->close();
+ if (!$this->conn) {
+ return false;
+ }
+
+ if (!function_exists('ldap_sasl_bind')) {
+ rcube::raise_error(array('code' => 100, 'type' => 'ldap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Unable to bind: ldap_sasl_bind() not exists"),
+ true, true);
}
+
+ if (!empty($authz)) {
+ $authz = 'u:' . $authz;
+ }
+
+ if (!empty($this->prop['auth_method'])) {
+ $method = $this->prop['auth_method'];
+ }
+ else {
+ $method = 'DIGEST-MD5';
+ }
+
+ $this->_debug("C: Bind [mech: $method, authc: $authc, authz: $authz] [pass: $pass]");
+
+ if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) {
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ $this->_debug("S: ".ldap_error($this->conn));
+
+ rcube::raise_error(array(
+ 'code' => ldap_errno($this->conn), 'type' => 'ldap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)),
+ true);
+
+ return false;
}
/**
- * Returns address book name
+ * Bind connection with DN and password
*
- * @return string Address book name
+ * @param string Bind DN
+ * @param string Bind password
+ *
+ * @return boolean True on success, False on error
*/
- function get_name()
+ public function bind($dn, $pass)
{
- return $this->prop['name'];
+ if (!$this->conn) {
+ return false;
+ }
+
+ $this->_debug("C: Bind [dn: $dn] [pass: $pass]");
+
+ if (@ldap_bind($this->conn, $dn, $pass)) {
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ $this->_debug("S: ".ldap_error($this->conn));
+
+ rcube::raise_error(array(
+ 'code' => ldap_errno($this->conn), 'type' => 'ldap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
+ true);
+
+ return false;
}
/**
- * Set internal list page
- *
- * @param number Page number to list
+ * Close connection to LDAP server
*/
- function set_page($page)
+ function close()
{
- $this->list_page = (int)$page;
- $this->ldap->set_vlv_page($this->list_page, $this->page_size);
+ if ($this->conn)
+ {
+ $this->_debug("C: Close");
+ ldap_unbind($this->conn);
+ $this->conn = null;
+ }
}
+
/**
- * Set internal page size
+ * Returns address book name
*
- * @param number Number of records to display on one page
+ * @return string Address book name
*/
- function set_pagesize($size)
+ function get_name()
{
- $this->page_size = (int)$size;
- $this->ldap->set_vlv_page($this->list_page, $this->page_size);
+ return $this->prop['name'];
}
@@ -493,14 +524,16 @@ class rcube_ldap extends rcube_addressbook
*/
function list_records($cols=null, $subset=0)
{
- if ($this->prop['searchonly'] && empty($this->filter) && !$this->group_id) {
+ if ($this->prop['searchonly'] && empty($this->filter) && !$this->group_id)
+ {
$this->result = new rcube_result_set(0);
$this->result->searchonly = true;
return $this->result;
}
// fetch group members recursively
- if ($this->group_id && $this->group_data['dn']) {
+ if ($this->group_id && $this->group_data['dn'])
+ {
$entries = $this->list_group_members($this->group_data['dn']);
// make list of entries unique and sort it
@@ -514,35 +547,34 @@ class rcube_ldap extends rcube_addressbook
$entries['count'] = count($entries);
$this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size);
}
- else {
- $prop = $this->group_id ? $this->group_data : $this->prop;
- $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn;
-
- // use global search filter
- if (!empty($this->filter))
- $prop['filter'] = $this->filter;
+ else
+ {
+ // add general filter to query
+ if (!empty($this->prop['filter']) && empty($this->filter))
+ $this->set_search_set($this->prop['filter']);
// exec LDAP search if no result resource is stored
- if ($this->ready && !$this->ldap_result)
- $this->ldap_result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop);
+ if ($this->conn && !$this->ldap_result)
+ $this->_exec_search();
// count contacts for this user
$this->result = $this->count();
// we have a search result resource
- if ($this->ldap_result && $this->result->count > 0) {
+ if ($this->ldap_result && $this->result->count > 0)
+ {
// sorting still on the ldap server
- if ($this->sort_col && $prop['scope'] !== 'base' && !$this->ldap->vlv_active)
- $this->ldap_result->sort($this->sort_col);
+ if ($this->sort_col && $this->prop['scope'] !== 'base' && !$this->vlv_active)
+ ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
// get all entries from the ldap server
- $entries = $this->ldap_result->entries();
+ $entries = ldap_get_entries($this->conn, $this->ldap_result);
}
} // end else
// start and end of the page
- $start_row = $this->ldap->vlv_active ? 0 : $this->result->first;
+ $start_row = $this->vlv_active ? 0 : $this->result->first;
$start_row = $subset < 0 ? $start_row + $this->page_size + $subset : $start_row;
$last_row = $this->result->first + $this->page_size;
$last_row = $subset != 0 ? $start_row + abs($subset) : $last_row;
@@ -567,34 +599,43 @@ class rcube_ldap extends rcube_addressbook
// fetch group object
if (empty($entries)) {
- $attribs = array('dn','objectClass','member','uniqueMember','memberURL');
- $entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs);
- if ($entries === false) {
+ $result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL'));
+ if ($result === false)
+ {
+ $this->_debug("S: ".ldap_error($this->conn));
return $group_members;
}
+
+ $entries = @ldap_get_entries($this->conn, $result);
}
- for ($i=0; $i < $entries['count']; $i++) {
+ for ($i=0; $i < $entries['count']; $i++)
+ {
$entry = $entries[$i];
- $attrs = array();
- foreach ((array)$entry['objectclass'] as $objectclass) {
- if (strtolower($objectclass) == 'groupofurls') {
- $members = $this->_list_group_memberurl($dn, $entry, $count);
- $group_members = array_merge($group_members, $members);
- }
- else if (($member_attr = $this->get_group_member_attr(array($objectclass), ''))
- && ($member_attr = strtolower($member_attr)) && !in_array($member_attr, $attrs)
- ) {
- $members = $this->_list_group_members($dn, $entry, $member_attr, $count);
- $group_members = array_merge($group_members, $members);
- $attrs[] = $member_attr;
- }
+ if (empty($entry['objectclass']))
+ continue;
- if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) {
- break 2;
+ foreach ((array)$entry['objectclass'] as $objectclass)
+ {
+ switch (strtolower($objectclass)) {
+ case "group":
+ case "groupofnames":
+ case "kolabgroupofnames":
+ $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'member', $count));
+ break;
+ case "groupofuniquenames":
+ case "kolabgroupofuniquenames":
+ $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'uniquemember', $count));
+ break;
+ case "groupofurls":
+ $group_members = array_merge($group_members, $this->_list_group_memberurl($dn, $entry, $count));
+ break;
}
}
+
+ if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit'])
+ break;
}
return array_filter($group_members);
@@ -613,24 +654,28 @@ class rcube_ldap extends rcube_addressbook
// Use the member attributes to return an array of member ldap objects
// NOTE that the member attribute is supposed to contain a DN
$group_members = array();
- if (empty($entry[$attr])) {
+ if (empty($entry[$attr]))
return $group_members;
- }
// read these attributes for all members
- $attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes'];
+ $attrib = $count ? array('dn') : array_values($this->fieldmap);
+ $attrib[] = 'objectClass';
$attrib[] = 'member';
$attrib[] = 'uniqueMember';
$attrib[] = 'memberURL';
- $filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)';
-
- for ($i=0; $i < $entry[$attr]['count']; $i++) {
+ for ($i=0; $i < $entry[$attr]['count']; $i++)
+ {
if (empty($entry[$attr][$i]))
continue;
- $members = $this->ldap->read_entries($entry[$attr][$i], $filter, $attrib);
- if ($members == false) {
+ $result = @ldap_read($this->conn, $entry[$attr][$i], '(objectclass=*)',
+ $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']);
+
+ $members = @ldap_get_entries($this->conn, $result);
+ if ($members == false)
+ {
+ $this->_debug("S: ".ldap_error($this->conn));
$members = array();
}
@@ -656,22 +701,34 @@ class rcube_ldap extends rcube_addressbook
{
$group_members = array();
- for ($i=0; $i < $entry['memberurl']['count']; $i++) {
+ for ($i=0; $i < $entry['memberurl']['count']; $i++)
+ {
// extract components from url
if (!preg_match('!ldap:///([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m))
continue;
// add search filter if any
$filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3];
- $attrs = $count ? array('dn','objectClass') : $this->prop['list_attributes'];
- if ($result = $this->ldap->search($m[1], $filter, $m[2], $attrs, $this->group_data)) {
- $entries = $result->entries();
- for ($j = 0; $j < $entries['count']; $j++) {
- if (self::is_group_entry($entries[$j]) && ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count)))
- $group_members = array_merge($group_members, $nested_group_members);
- else
- $group_members[] = $entries[$j];
- }
+ $func = $m[2] == 'sub' ? 'ldap_search' : ($m[2] == 'base' ? 'ldap_read' : 'ldap_list');
+
+ $attrib = $count ? array('dn') : array_values($this->fieldmap);
+ if ($result = @$func($this->conn, $m[1], $filter,
+ $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
+ ) {
+ $this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]);
+ }
+ else {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return $group_members;
+ }
+
+ $entries = @ldap_get_entries($this->conn, $result);
+ for ($j = 0; $j < $entries['count']; $j++)
+ {
+ if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))
+ $group_members = array_merge($group_members, $nested_group_members);
+ else
+ $group_members[] = $entries[$j];
}
}
@@ -707,11 +764,14 @@ class rcube_ldap extends rcube_addressbook
$mode = intval($mode);
// special treatment for ID-based search
- if ($fields == 'ID' || $fields == $this->primary_key) {
+ if ($fields == 'ID' || $fields == $this->primary_key)
+ {
$ids = !is_array($value) ? explode(',', $value) : $value;
$result = new rcube_result_set();
- foreach ($ids as $id) {
- if ($rec = $this->get_record($id, true)) {
+ foreach ($ids as $id)
+ {
+ if ($rec = $this->get_record($id, true))
+ {
$result->add($rec);
$result->count++;
}
@@ -723,20 +783,34 @@ class rcube_ldap extends rcube_addressbook
$rcube = rcube::get_instance();
$list_fields = $rcube->config->get('contactlist_fields');
- if ($this->prop['vlv_search'] && $this->ready && join(',', (array)$fields) == join(',', $list_fields)) {
+ if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $list_fields))
+ {
+ // add general filter to query
+ if (!empty($this->prop['filter']) && empty($this->filter))
+ $this->set_search_set($this->prop['filter']);
+
+ // set VLV controls with encoded search string
+ $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size, $value);
+
+ $function = $this->_scope2func($this->prop['scope']);
+ $this->ldap_result = @$function($this->conn, $this->base_dn, $this->filter ? $this->filter : '(objectclass=*)',
+ array_values($this->fieldmap), 0, $this->page_size, (int)$this->prop['timelimit']);
+
$this->result = new rcube_result_set(0);
- $search_suffix = $this->prop['fuzzy_search'] && $mode != 1 ? '*' : '';
- $ldap_data = $this->ldap->search($this->base_dn, $this->prop['filter'], $this->prop['scope'], $this->prop['attributes'],
- array('search' => $value . $search_suffix /*, 'sort' => $this->prop['sort'] */));
- if ($ldap_data === false) {
+ if (!$this->ldap_result) {
+ $this->_debug("S: ".ldap_error($this->conn));
return $this->result;
}
+ $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)");
+
// get all entries of this page and post-filter those that really match the query
- $search = mb_strtolower($value);
- foreach ($ldap_data as $i => $entry) {
- $rec = $this->_ldap2result($entry);
+ $search = mb_strtolower($value);
+ $entries = ldap_get_entries($this->conn, $this->ldap_result);
+
+ for ($i = 0; $i < $entries['count']; $i++) {
+ $rec = $this->_ldap2result($entries[$i]);
foreach ($fields as $f) {
foreach ((array)$rec[$f] as $val) {
if ($this->compare_search_value($f, $val, $search, $mode)) {
@@ -762,27 +836,31 @@ class rcube_ldap extends rcube_addressbook
}
}
- if ($fields == '*') {
+ if ($fields == '*')
+ {
// search_fields are required for fulltext search
- if (empty($this->prop['search_fields'])) {
+ if (empty($this->prop['search_fields']))
+ {
$this->set_error(self::ERROR_SEARCH, 'nofulltextsearch');
$this->result = new rcube_result_set();
return $this->result;
}
- if (is_array($this->prop['search_fields'])) {
+ if (is_array($this->prop['search_fields']))
+ {
foreach ($this->prop['search_fields'] as $field) {
- $filter .= "($field=$wp" . rcube_ldap_generic::quote_string($value) . "$ws)";
+ $filter .= "($field=$wp" . $this->_quote_string($value) . "$ws)";
}
}
}
- else {
+ else
+ {
foreach ((array)$fields as $idx => $field) {
$val = is_array($value) ? $value[$idx] : $value;
if ($attrs = $this->_map_field($field)) {
if (count($attrs) > 1)
$filter .= '(|';
foreach ($attrs as $f)
- $filter .= "($f=$wp" . rcube_ldap_generic::quote_string($val) . "$ws)";
+ $filter .= "($f=$wp" . $this->_quote_string($val) . "$ws)";
if (count($attrs) > 1)
$filter .= ')';
}
@@ -817,6 +895,7 @@ class rcube_ldap extends rcube_addressbook
// set filter string and execute search
$this->set_search_set($filter);
+ $this->_exec_search();
if ($select)
$this->list_records();
@@ -835,21 +914,20 @@ class rcube_ldap extends rcube_addressbook
function count()
{
$count = 0;
- if ($this->ldap_result) {
- $count = $this->ldap_result->count();
+ if ($this->conn && $this->ldap_result) {
+ $count = $this->vlv_active ? $this->vlv_count : ldap_count_entries($this->conn, $this->ldap_result);
}
else if ($this->group_id && $this->group_data['dn']) {
$count = count($this->list_group_members($this->group_data['dn'], true));
}
- // We have a connection but no result set, attempt to get one.
- else if ($this->ready) {
- $prop = $this->group_id ? $this->group_data : $this->prop;
- $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn;
-
- if (!empty($this->filter)) { // Use global search filter
- $prop['filter'] = $this->filter;
+ else if ($this->conn) {
+ // We have a connection but no result set, attempt to get one.
+ if (empty($this->filter)) {
+ // The filter is not set, set it.
+ $this->filter = $this->prop['filter'];
}
- $count = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], array('dn'), $prop, true);
+
+ $count = (int) $this->_exec_search(true);
}
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
@@ -879,16 +957,28 @@ class rcube_ldap extends rcube_addressbook
{
$res = $this->result = null;
- if ($this->ready && $dn) {
+ if ($this->conn && $dn)
+ {
$dn = self::dn_decode($dn);
- if ($rec = $this->ldap->get_entry($dn)) {
- $rec = array_change_key_case($rec, CASE_LOWER);
+ $this->_debug("C: Read [dn: $dn] [(objectclass=*)]");
+
+ if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', array_values($this->fieldmap))) {
+ $this->_debug("S: OK");
+
+ $entry = ldap_first_entry($this->conn, $ldap_result);
+
+ if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) {
+ $rec = array_change_key_case($rec, CASE_LOWER);
+ }
+ }
+ else {
+ $this->_debug("S: ".ldap_error($this->conn));
}
// Use ldap_list to get subentries like country (c) attribute (#1488123)
if (!empty($rec) && $this->sub_filter) {
- if ($entries = $this->ldap->list_entries($dn, $this->sub_filter, array_keys($this->prop['sub_fields']))) {
+ if ($entries = $this->ldap_list($dn, $this->sub_filter, array_keys($this->prop['sub_fields']))) {
foreach ($entries as $entry) {
$lrec = array_change_key_case($entry, CASE_LOWER);
$rec = array_merge($lrec, $rec);
@@ -900,7 +990,7 @@ class rcube_ldap extends rcube_addressbook
// Add in the dn for the entry.
$rec['dn'] = $dn;
$res = $this->_ldap2result($rec);
- $this->result = new rcube_result_set(1);
+ $this->result = new rcube_result_set();
$this->result->add($res);
}
}
@@ -947,6 +1037,7 @@ class rcube_ldap extends rcube_addressbook
$mail_field = $this->fieldmap['email'];
// try to extract surname and firstname from displayname
+ $reverse_map = array_flip($this->fieldmap);
$name_parts = preg_split('/[\s,.]+/', $save_data['name']);
if ($sn_field && $missing[$sn_field]) {
@@ -1013,12 +1104,12 @@ class rcube_ldap extends rcube_addressbook
}
// Build the new entries DN.
- $dn = $this->prop['LDAP_rdn'].'='.rcube_ldap_generic::quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->base_dn;
+ $dn = $this->prop['LDAP_rdn'].'='.$this->_quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->base_dn;
// Remove attributes that need to be added separately (child objects)
$xfields = array();
if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
- foreach (array_keys($this->prop['sub_fields']) as $xf) {
+ foreach ($this->prop['sub_fields'] as $xf => $xclass) {
if (!empty($newentry[$xf])) {
$xfields[$xf] = $newentry[$xf];
unset($newentry[$xf]);
@@ -1026,19 +1117,19 @@ class rcube_ldap extends rcube_addressbook
}
}
- if (!$this->ldap->add($dn, $newentry)) {
+ if (!$this->ldap_add($dn, $newentry)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
foreach ($xfields as $xidx => $xf) {
- $xdn = $xidx.'='.rcube_ldap_generic::quote_string($xf).','.$dn;
+ $xdn = $xidx.'='.$this->_quote_string($xf).','.$dn;
$xf = array(
$xidx => $xf,
'objectClass' => (array) $this->prop['sub_fields'][$xidx],
);
- $this->ldap->add($xdn, $xf);
+ $this->ldap_add($xdn, $xf);
}
$dn = self::dn_encode($dn);
@@ -1081,7 +1172,7 @@ class rcube_ldap extends rcube_addressbook
}
}
- foreach ($this->fieldmap as $fld) {
+ foreach ($this->fieldmap as $col => $fld) {
if ($fld) {
$val = $ldap_data[$fld];
$old = $old_data[$fld];
@@ -1144,7 +1235,7 @@ class rcube_ldap extends rcube_addressbook
// Update the entry as required.
if (!empty($deletedata)) {
// Delete the fields.
- if (!$this->ldap->mod_del($dn, $deletedata)) {
+ if (!$this->ldap_mod_del($dn, $deletedata)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
@@ -1154,17 +1245,17 @@ class rcube_ldap extends rcube_addressbook
// Handle RDN change
if ($replacedata[$this->prop['LDAP_rdn']]) {
$newdn = $this->prop['LDAP_rdn'].'='
- .rcube_ldap_generic::quote_string($replacedata[$this->prop['LDAP_rdn']], true)
+ .$this->_quote_string($replacedata[$this->prop['LDAP_rdn']], true)
.','.$this->base_dn;
if ($dn != $newdn) {
$newrdn = $this->prop['LDAP_rdn'].'='
- .rcube_ldap_generic::quote_string($replacedata[$this->prop['LDAP_rdn']], true);
+ .$this->_quote_string($replacedata[$this->prop['LDAP_rdn']], true);
unset($replacedata[$this->prop['LDAP_rdn']]);
}
}
// Replace the fields.
if (!empty($replacedata)) {
- if (!$this->ldap->mod_replace($dn, $replacedata)) {
+ if (!$this->ldap_mod_replace($dn, $replacedata)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
@@ -1180,8 +1271,8 @@ class rcube_ldap extends rcube_addressbook
// remove sub-entries
if (!empty($subdeldata)) {
foreach ($subdeldata as $fld => $val) {
- $subdn = $fld.'='.rcube_ldap_generic::quote_string($val).','.$dn;
- if (!$this->ldap->delete($subdn)) {
+ $subdn = $fld.'='.$this->_quote_string($val).','.$dn;
+ if (!$this->ldap_delete($subdn)) {
return false;
}
}
@@ -1189,7 +1280,7 @@ class rcube_ldap extends rcube_addressbook
if (!empty($newdata)) {
// Add the fields.
- if (!$this->ldap->mod_add($dn, $newdata)) {
+ if (!$this->ldap_mod_add($dn, $newdata)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
@@ -1197,7 +1288,7 @@ class rcube_ldap extends rcube_addressbook
// Handle RDN change
if (!empty($newrdn)) {
- if (!$this->ldap->rename($dn, $newrdn, null, true)) {
+ if (!$this->ldap_rename($dn, $newrdn, null, true)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
@@ -1208,7 +1299,8 @@ class rcube_ldap extends rcube_addressbook
// change the group membership of the contact
if ($this->groups) {
$group_ids = $this->get_record_groups($dn);
- foreach (array_keys($group_ids) as $group_id) {
+ foreach ($group_ids as $group_id)
+ {
$this->remove_from_group($group_id, $dn);
$this->add_to_group($group_id, $newdn);
}
@@ -1220,12 +1312,12 @@ class rcube_ldap extends rcube_addressbook
// add sub-entries
if (!empty($subnewdata)) {
foreach ($subnewdata as $fld => $val) {
- $subdn = $fld.'='.rcube_ldap_generic::quote_string($val).','.$dn;
+ $subdn = $fld.'='.$this->_quote_string($val).','.$dn;
$xf = array(
$fld => $val,
'objectClass' => (array) $this->prop['sub_fields'][$fld],
);
- $this->ldap->add($subdn, $xf);
+ $this->ldap_add($subdn, $xf);
}
}
@@ -1253,9 +1345,9 @@ class rcube_ldap extends rcube_addressbook
// Need to delete all sub-entries first
if ($this->sub_filter) {
- if ($entries = $this->ldap->list_entries($dn, $this->sub_filter)) {
+ if ($entries = $this->ldap_list($dn, $this->sub_filter)) {
foreach ($entries as $entry) {
- if (!$this->ldap->delete($entry['dn'])) {
+ if (!$this->ldap_delete($entry['dn'])) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
@@ -1264,7 +1356,7 @@ class rcube_ldap extends rcube_addressbook
}
// Delete the record.
- if (!$this->ldap->delete($dn)) {
+ if (!$this->ldap_delete($dn)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
@@ -1273,7 +1365,7 @@ class rcube_ldap extends rcube_addressbook
if ($this->groups) {
$dn = self::dn_encode($dn);
$group_ids = $this->get_record_groups($dn);
- foreach (array_keys($group_ids) as $group_id) {
+ foreach ($group_ids as $group_id) {
$this->remove_from_group($group_id, $dn);
}
}
@@ -1288,8 +1380,8 @@ class rcube_ldap extends rcube_addressbook
*/
function delete_all()
{
- // searching for contact entries
- $dn_list = $this->ldap->list_entries($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)');
+ //searching for contact entries
+ $dn_list = $this->ldap_list($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)');
if (!empty($dn_list)) {
foreach ($dn_list as $idx => $entry) {
@@ -1306,10 +1398,6 @@ class rcube_ldap extends rcube_addressbook
*/
protected function add_autovalues(&$attrs)
{
- if (empty($this->prop['autovalues'])) {
- return;
- }
-
$attrvals = array();
foreach ($attrs as $k => $v) {
$attrvals['{'.$k.'}'] = is_array($v) ? $v[0] : $v;
@@ -1320,16 +1408,7 @@ class rcube_ldap extends rcube_addressbook
if (strpos($templ, '(') !== false) {
// replace {attr} placeholders with (escaped!) attribute values to be safely eval'd
$code = preg_replace('/\{\w+\}/', '', strtr($templ, array_map('addslashes', $attrvals)));
- $fn = create_function('', "return ($code);");
- if (!$fn) {
- rcube::raise_error(array(
- 'code' => 505, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Expression parse error on: ($code)"), true, false);
- continue;
- }
-
- $attrs[$lf] = $fn();
+ $attrs[$lf] = eval("return ($code);");
}
else {
// replace {attr} placeholders with concrete attribute values
@@ -1339,26 +1418,120 @@ class rcube_ldap extends rcube_addressbook
}
}
+ /**
+ * Execute the LDAP search based on the stored credentials
+ */
+ private function _exec_search($count = false)
+ {
+ if ($this->ready)
+ {
+ $filter = $this->filter ? $this->filter : '(objectclass=*)';
+ $function = $this->_scope2func($this->prop['scope'], $ns_function);
+
+ $this->_debug("C: Search [$filter][dn: $this->base_dn]");
+
+ // when using VLV, we get the total count by...
+ if (!$count && $function != 'ldap_read' && $this->prop['vlv'] && !$this->group_id) {
+ // ...either reading numSubOrdinates attribute
+ if ($this->prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $this->base_dn, $this->prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) {
+ $counts = ldap_get_entries($this->conn, $result_count);
+ for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++)
+ $this->vlv_count += $counts[$j]['numsubordinates'][0];
+ $this->_debug("D: total numsubordinates = " . $this->vlv_count);
+ }
+ else if (!function_exists('ldap_parse_virtuallist_control')) // ...or by fetching all records dn and count them
+ $this->vlv_count = $this->_exec_search(true);
+
+ $this->vlv_active = $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size);
+ }
+
+ // only fetch dn for count (should keep the payload low)
+ $attrs = $count ? array('dn') : array_values($this->fieldmap);
+ if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter,
+ $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
+ ) {
+ // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result
+ if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
+ if (ldap_parse_result($this->conn, $this->ldap_result,
+ $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
+ && $serverctrls // can be null e.g. in case of adm. limit error
+ ) {
+ ldap_parse_virtuallist_control($this->conn, $serverctrls,
+ $last_offset, $this->vlv_count, $vresult);
+ $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count");
+ }
+ else {
+ $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
+ }
+ }
+
+ $entries_count = ldap_count_entries($this->conn, $this->ldap_result);
+ $this->_debug("S: $entries_count record(s)");
+
+ return $count ? $entries_count : true;
+ }
+ else {
+ $this->_debug("S: ".ldap_error($this->conn));
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Choose the right PHP function according to scope property
+ */
+ private function _scope2func($scope, &$ns_function = null)
+ {
+ switch ($scope) {
+ case 'sub':
+ $function = $ns_function = 'ldap_search';
+ break;
+ case 'base':
+ $function = $ns_function = 'ldap_read';
+ break;
+ default:
+ $function = 'ldap_list';
+ $ns_function = 'ldap_read';
+ break;
+ }
+
+ return $function;
+ }
+
+ /**
+ * Set server controls for Virtual List View (paginated listing)
+ */
+ private function _vlv_set_controls($prop, $list_page, $page_size, $search = null)
+ {
+ $sort_ctrl = array('oid' => "1.2.840.113556.1.4.473", 'value' => $this->_sort_ber_encode((array)$prop['sort']));
+ $vlv_ctrl = array('oid' => "2.16.840.1.113730.3.4.9", 'value' => $this->_vlv_ber_encode(($offset = ($list_page-1) * $page_size + 1), $page_size, $search), 'iscritical' => true);
+
+ $sort = (array)$prop['sort'];
+ $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value'])) . " ($sort[0]);"
+ . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)");
+
+ if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ $this->set_error(self::ERROR_SEARCH, 'vlvnotsupported');
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Converts LDAP entry into an array
*/
private function _ldap2result($rec)
{
- $out = array('_type' => 'person');
- $fieldmap = $this->fieldmap;
+ $out = array();
if ($rec['dn'])
$out[$this->primary_key] = self::dn_encode($rec['dn']);
- // determine record type
- if (self::is_group_entry($rec)) {
- $out['_type'] = 'group';
- $out['readonly'] = true;
- $fieldmap['name'] = $this->group_data['name_attr'] ? $this->group_data['name_attr'] : $this->prop['groups']['name_attr'];
- }
-
- foreach ($fieldmap as $rf => $lf)
+ foreach ($this->fieldmap as $rf => $lf)
{
for ($i=0; $i < $rec[$lf]['count']; $i++) {
if (!($value = $rec[$lf][$i]))
@@ -1420,10 +1593,8 @@ class rcube_ldap extends rcube_addressbook
if (is_array($colprop['serialized'])) {
foreach ($colprop['serialized'] as $subtype => $delim) {
$key = $col.':'.$subtype;
- foreach ((array)$save_cols[$key] as $i => $val) {
- $values = array($val['street'], $val['locality'], $val['zipcode'], $val['country']);
- $save_cols[$key][$i] = count(array_filter($values)) ? join($delim, $values) : null;
- }
+ foreach ((array)$save_cols[$key] as $i => $val)
+ $save_cols[$key][$i] = join($delim, array($val['street'], $val['locality'], $val['zipcode'], $val['country']));
}
}
}
@@ -1461,11 +1632,11 @@ class rcube_ldap extends rcube_addressbook
{
// list of known attribute aliases
static $aliases = array(
- 'gn' => 'givenname',
+ 'gn' => 'givenname',
'rfc822mailbox' => 'email',
- 'userid' => 'uid',
- 'emailaddress' => 'email',
- 'pkcs9email' => 'email',
+ 'userid' => 'uid',
+ 'emailaddress' => 'email',
+ 'pkcs9email' => 'email',
);
list($name, $limit) = explode(':', $namev, 2);
@@ -1474,15 +1645,6 @@ class rcube_ldap extends rcube_addressbook
return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix;
}
- /**
- * Determines whether the given LDAP entry is a group record
- */
- private static function is_group_entry($entry)
- {
- $classes = array_map('strtolower', (array)$entry['objectclass']);
-
- return count(array_intersect(array_keys(self::$group_types), $classes)) > 0;
- }
/**
* Prints debug info to the log
@@ -1499,27 +1661,55 @@ class rcube_ldap extends rcube_addressbook
* Activate/deactivate debug mode
*
* @param boolean $dbg True if LDAP commands should be logged
+ * @access public
*/
function set_debug($dbg = true)
{
$this->debug = $dbg;
+ }
- if ($this->ldap) {
- $this->ldap->set_debug($dbg);
- }
+
+ /**
+ * Quotes attribute value string
+ *
+ * @param string $str Attribute value
+ * @param bool $dn True if the attribute is a DN
+ *
+ * @return string Quoted string
+ */
+ private static function _quote_string($str, $dn=false)
+ {
+ // take firt entry if array given
+ if (is_array($str))
+ $str = reset($str);
+
+ if ($dn)
+ $replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c',
+ '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23');
+ else
+ $replace = array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c',
+ '/'=>'\2f');
+
+ return strtr($str, $replace);
}
/**
* Setter for the current group
+ * (empty, has to be re-implemented by extending class)
*/
function set_group($group_id)
{
- if ($group_id) {
+ if ($group_id)
+ {
+ if (($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
+
$this->group_id = $group_id;
- $this->group_data = $this->get_group_entry($group_id);
+ $this->group_data = $group_cache[$group_id];
}
- else {
+ else
+ {
$this->group_id = 0;
$this->group_data = null;
}
@@ -1538,13 +1728,15 @@ class rcube_ldap extends rcube_addressbook
*/
function list_groups($search = null, $mode = 0)
{
- if (!$this->groups) {
+ if (!$this->groups)
return array();
- }
- $group_cache = $this->_fetch_groups();
- $groups = array();
+ // use cached list for searching
+ $this->cache->expunge();
+ if (!$search || ($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
+ $groups = array();
if ($search) {
foreach ($group_cache as $group) {
if ($this->compare_search_value('name', $group['name'], $search, $mode)) {
@@ -1552,9 +1744,8 @@ class rcube_ldap extends rcube_addressbook
}
}
}
- else {
+ else
$groups = $group_cache;
- }
return array_values($groups);
}
@@ -1562,140 +1753,80 @@ class rcube_ldap extends rcube_addressbook
/**
* Fetch groups from server
*/
- private function _fetch_groups($vlv_page = null)
+ private function _fetch_groups($vlv_page = 0)
{
- // special case: list groups from 'group_filters' config
- if ($vlv_page === null && !empty($this->prop['group_filters'])) {
- $groups = array();
-
- // list regular groups configuration as special filter
- if (!empty($this->prop['groups']['filter'])) {
- $id = '__groups__';
- $groups[$id] = array('ID' => $id, 'name' => rcube_label('groups'), 'virtual' => true) + $this->prop['groups'];
- }
-
- foreach ($this->prop['group_filters'] as $id => $prop) {
- $groups[$id] = $prop + array('ID' => $id, 'name' => ucfirst($id), 'virtual' => true, 'base_dn' => $this->base_dn);
- }
-
- return $groups;
- }
-
- if ($this->cache && $vlv_page === null && ($groups = $this->cache->get('groups')) !== null) {
- return $groups;
- }
-
- $base_dn = $this->groups_base_dn;
- $filter = $this->prop['groups']['filter'];
- $scope = $this->prop['groups']['scope'];
- $name_attr = $this->prop['groups']['name_attr'];
+ $base_dn = $this->groups_base_dn;
+ $filter = $this->prop['groups']['filter'];
+ $name_attr = $this->prop['groups']['name_attr'];
$email_attr = $this->prop['groups']['email_attr'] ? $this->prop['groups']['email_attr'] : 'mail';
$sort_attrs = $this->prop['groups']['sort'] ? (array)$this->prop['groups']['sort'] : array($name_attr);
- $sort_attr = $sort_attrs[0];
+ $sort_attr = $sort_attrs[0];
- $ldap = $this->ldap;
+ $this->_debug("C: Search [$filter][dn: $base_dn]");
// use vlv to list groups
if ($this->prop['groups']['vlv']) {
$page_size = 200;
- if (!$this->prop['groups']['sort']) {
+ if (!$this->prop['groups']['sort'])
$this->prop['groups']['sort'] = $sort_attrs;
- }
-
- $ldap = clone $this->ldap;
- $ldap->set_config($this->prop['groups']);
- $ldap->set_vlv_page($vlv_page+1, $page_size);
+ $vlv_active = $this->_vlv_set_controls($this->prop['groups'], $vlv_page+1, $page_size);
}
- $attrs = array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr));
- $ldap_data = $ldap->search($base_dn, $filter, $scope, $attrs, $this->prop['groups']);
-
- if ($ldap_data === false) {
+ $function = $this->_scope2func($this->prop['groups']['scope'], $ns_function);
+ $res = @$function($this->conn, $base_dn, $filter, array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)));
+ if ($res === false)
+ {
+ $this->_debug("S: ".ldap_error($this->conn));
return array();
}
- $groups = array();
- $group_sortnames = array();
- $group_count = $ldap_data->count();
-
- foreach ($ldap_data as $entry) {
- if (!$entry['dn']) // DN is mandatory
- $entry['dn'] = $ldap_data->get_dn();
+ $ldap_data = ldap_get_entries($this->conn, $res);
+ $this->_debug("S: ".ldap_count_entries($this->conn, $res)." record(s)");
- $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr];
- $group_id = self::dn_encode($entry['dn']);
+ $groups = array();
+ $group_sortnames = array();
+ $group_count = $ldap_data["count"];
+ for ($i=0; $i < $group_count; $i++)
+ {
+ $group_name = is_array($ldap_data[$i][$name_attr]) ? $ldap_data[$i][$name_attr][0] : $ldap_data[$i][$name_attr];
+ $group_id = self::dn_encode($group_name);
$groups[$group_id]['ID'] = $group_id;
- $groups[$group_id]['dn'] = $entry['dn'];
+ $groups[$group_id]['dn'] = $ldap_data[$i]['dn'];
$groups[$group_id]['name'] = $group_name;
- $groups[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']);
+ $groups[$group_id]['member_attr'] = $this->get_group_member_attr($ldap_data[$i]['objectclass']);
// list email attributes of a group
- for ($j=0; $entry[$email_attr] && $j < $entry[$email_attr]['count']; $j++) {
- if (strpos($entry[$email_attr][$j], '@') > 0)
- $groups[$group_id]['email'][] = $entry[$email_attr][$j];
+ for ($j=0; $ldap_data[$i][$email_attr] && $j < $ldap_data[$i][$email_attr]['count']; $j++) {
+ if (strpos($ldap_data[$i][$email_attr][$j], '@') > 0)
+ $groups[$group_id]['email'][] = $ldap_data[$i][$email_attr][$j];
}
- $group_sortnames[] = mb_strtolower($entry[$sort_attr][0]);
+ $group_sortnames[] = mb_strtolower($ldap_data[$i][$sort_attr][0]);
}
// recursive call can exit here
- if ($vlv_page > 0) {
+ if ($vlv_page > 0)
return $groups;
- }
// call recursively until we have fetched all groups
- while ($this->prop['groups']['vlv'] && $group_count == $page_size) {
- $next_page = $this->_fetch_groups(++$vlv_page);
- $groups = array_merge($groups, $next_page);
+ while ($vlv_active && $group_count == $page_size)
+ {
+ $next_page = $this->_fetch_groups(++$vlv_page);
+ $groups = array_merge($groups, $next_page);
$group_count = count($next_page);
}
// when using VLV the list of groups is already sorted
- if (!$this->prop['groups']['vlv']) {
+ if (!$this->prop['groups']['vlv'])
array_multisort($group_sortnames, SORT_ASC, SORT_STRING, $groups);
- }
// cache this
- if ($this->cache) {
- $this->cache->set('groups', $groups);
- }
+ $this->cache->set('groups', $groups);
return $groups;
}
/**
- * Fetch a group entry from LDAP and save in local cache
- */
- private function get_group_entry($group_id)
- {
- $group_cache = $this->_fetch_groups();
-
- // add group record to cache if it isn't yet there
- if (!isset($group_cache[$group_id])) {
- $name_attr = $this->prop['groups']['name_attr'];
- $dn = self::dn_decode($group_id);
-
- if ($list = $this->ldap->read_entries($dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL',$name_attr,$this->fieldmap['email']))) {
- $entry = $list[0];
- $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr];
- $group_cache[$group_id]['ID'] = $group_id;
- $group_cache[$group_id]['dn'] = $dn;
- $group_cache[$group_id]['name'] = $group_name;
- $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']);
- }
- else {
- $group_cache[$group_id] = false;
- }
-
- if ($this->cache) {
- $this->cache->set('groups', $group_cache);
- }
- }
-
- return $group_cache[$group_id];
- }
-
- /**
* Get group properties such as name and email address(es)
*
* @param string Group identifier
@@ -1703,7 +1834,10 @@ class rcube_ldap extends rcube_addressbook
*/
function get_group($group_id)
{
- $group_data = $this->get_group_entry($group_id);
+ if (($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
+
+ $group_data = $group_cache[$group_id];
unset($group_data['dn'], $group_data['member_attr']);
return $group_data;
@@ -1717,24 +1851,24 @@ class rcube_ldap extends rcube_addressbook
*/
function create_group($group_name)
{
- $new_dn = 'cn=' . rcube_ldap_generic::quote_string($group_name, true) . ',' . $this->groups_base_dn;
- $new_gid = self::dn_encode($new_dn);
+ $base_dn = $this->groups_base_dn;
+ $new_dn = "cn=$group_name,$base_dn";
+ $new_gid = self::dn_encode($group_name);
$member_attr = $this->get_group_member_attr();
- $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn';
- $new_entry = array(
+ $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn';
+
+ $new_entry = array(
'objectClass' => $this->prop['groups']['object_classes'],
$name_attr => $group_name,
$member_attr => '',
);
- if (!$this->ldap->add($new_dn, $new_entry)) {
+ if (!$this->ldap_add($new_dn, $new_entry)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
- if ($this->cache) {
- $this->cache->remove('groups');
- }
+ $this->cache->remove('groups');
return array('id' => $new_gid, 'name' => $group_name);
}
@@ -1747,18 +1881,19 @@ class rcube_ldap extends rcube_addressbook
*/
function delete_group($group_id)
{
- $group_cache = $this->_fetch_groups();
- $del_dn = $group_cache[$group_id]['dn'];
+ if (($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
- if (!$this->ldap->delete($del_dn)) {
+ $base_dn = $this->groups_base_dn;
+ $group_name = $group_cache[$group_id]['name'];
+ $del_dn = "cn=$group_name,$base_dn";
+
+ if (!$this->ldap_delete($del_dn)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
- if ($this->cache) {
- unset($group_cache[$group_id]);
- $this->cache->set('groups', $group_cache);
- }
+ $this->cache->remove('groups');
return true;
}
@@ -1773,19 +1908,21 @@ class rcube_ldap extends rcube_addressbook
*/
function rename_group($group_id, $new_name, &$new_gid)
{
- $group_cache = $this->_fetch_groups();
- $old_dn = $group_cache[$group_id]['dn'];
- $new_rdn = "cn=" . rcube_ldap_generic::quote_string($new_name, true);
- $new_gid = self::dn_encode($new_rdn . ',' . $this->groups_base_dn);
+ if (($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
+
+ $base_dn = $this->groups_base_dn;
+ $group_name = $group_cache[$group_id]['name'];
+ $old_dn = "cn=$group_name,$base_dn";
+ $new_rdn = "cn=$new_name";
+ $new_gid = self::dn_encode($new_name);
- if (!$this->ldap->rename($old_dn, $new_rdn, null, true)) {
+ if (!$this->ldap_rename($old_dn, $new_rdn, null, true)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
- if ($this->cache) {
- $this->cache->remove('groups');
- }
+ $this->cache->remove('groups');
return $new_name;
}
@@ -1800,27 +1937,27 @@ class rcube_ldap extends rcube_addressbook
*/
function add_to_group($group_id, $contact_ids)
{
- $group_cache = $this->_fetch_groups();
- $member_attr = $group_cache[$group_id]['member_attr'];
- $group_dn = $group_cache[$group_id]['dn'];
- $new_attrs = array();
+ if (($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
- if (!is_array($contact_ids)) {
+ if (!is_array($contact_ids))
$contact_ids = explode(',', $contact_ids);
- }
- foreach ($contact_ids as $id) {
+ $base_dn = $this->groups_base_dn;
+ $group_name = $group_cache[$group_id]['name'];
+ $member_attr = $group_cache[$group_id]['member_attr'];
+ $group_dn = "cn=$group_name,$base_dn";
+ $new_attrs = array();
+
+ foreach ($contact_ids as $id)
$new_attrs[$member_attr][] = self::dn_decode($id);
- }
- if (!$this->ldap->mod_add($group_dn, $new_attrs)) {
+ if (!$this->ldap_mod_add($group_dn, $new_attrs)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return 0;
}
- if ($this->cache) {
- $this->cache->remove('groups');
- }
+ $this->cache->remove('groups');
return count($new_attrs[$member_attr]);
}
@@ -1835,27 +1972,27 @@ class rcube_ldap extends rcube_addressbook
*/
function remove_from_group($group_id, $contact_ids)
{
- $group_cache = $this->_fetch_groups();
- $member_attr = $group_cache[$group_id]['member_attr'];
- $group_dn = $group_cache[$group_id]['dn'];
- $del_attrs = array();
+ if (($group_cache = $this->cache->get('groups')) === null)
+ $group_cache = $this->_fetch_groups();
- if (!is_array($contact_ids)) {
+ if (!is_array($contact_ids))
$contact_ids = explode(',', $contact_ids);
- }
- foreach ($contact_ids as $id) {
+ $base_dn = $this->groups_base_dn;
+ $group_name = $group_cache[$group_id]['name'];
+ $member_attr = $group_cache[$group_id]['member_attr'];
+ $group_dn = "cn=$group_name,$base_dn";
+ $del_attrs = array();
+
+ foreach ($contact_ids as $id)
$del_attrs[$member_attr][] = self::dn_decode($id);
- }
- if (!$this->ldap->mod_del($group_dn, $del_attrs)) {
+ if (!$this->ldap_mod_del($group_dn, $del_attrs)) {
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return 0;
}
- if ($this->cache) {
- $this->cache->remove('groups');
- }
+ $this->cache->remove('groups');
return count($del_attrs[$member_attr]);
}
@@ -1870,63 +2007,206 @@ class rcube_ldap extends rcube_addressbook
*/
function get_record_groups($contact_id)
{
- if (!$this->groups) {
+ if (!$this->groups)
return array();
- }
$base_dn = $this->groups_base_dn;
$contact_dn = self::dn_decode($contact_id);
$name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn';
$member_attr = $this->get_group_member_attr();
$add_filter = '';
-
if ($member_attr != 'member' && $member_attr != 'uniqueMember')
$add_filter = "($member_attr=$contact_dn)";
$filter = strtr("(|(member=$contact_dn)(uniqueMember=$contact_dn)$add_filter)", array('\\' => '\\\\'));
- $ldap_data = $this->ldap->search($base_dn, $filter, 'sub', array('dn', $name_attr));
- if ($res === false) {
+ $this->_debug("C: Search [$filter][dn: $base_dn]");
+
+ $res = @ldap_search($this->conn, $base_dn, $filter, array($name_attr));
+ if ($res === false)
+ {
+ $this->_debug("S: ".ldap_error($this->conn));
return array();
}
+ $ldap_data = ldap_get_entries($this->conn, $res);
+ $this->_debug("S: ".ldap_count_entries($this->conn, $res)." record(s)");
$groups = array();
- foreach ($ldap_data as $entry) {
- if (!$entry['dn'])
- $entry['dn'] = $ldap_data->get_dn();
- $group_name = $entry[$name_attr][0];
- $group_id = self::dn_encode($entry['dn']);
- $groups[$group_id] = $group_name;
+ for ($i=0; $i<$ldap_data["count"]; $i++)
+ {
+ $group_name = $ldap_data[$i][$name_attr][0];
+ $group_id = self::dn_encode($group_name);
+ $groups[$group_id] = $group_id;
}
-
return $groups;
}
/**
* Detects group member attribute name
*/
- private function get_group_member_attr($object_classes = array(), $default = 'member')
+ private function get_group_member_attr($object_classes = array())
{
if (empty($object_classes)) {
$object_classes = $this->prop['groups']['object_classes'];
}
-
if (!empty($object_classes)) {
foreach ((array)$object_classes as $oc) {
- if ($attr = self::$group_types[strtolower($oc)]) {
- return $attr;
+ switch (strtolower($oc)) {
+ case 'group':
+ case 'groupofnames':
+ case 'kolabgroupofnames':
+ $member_attr = 'member';
+ break;
+
+ case 'groupofuniquenames':
+ case 'kolabgroupofuniquenames':
+ $member_attr = 'uniqueMember';
+ break;
}
}
}
+ if (!empty($member_attr)) {
+ return $member_attr;
+ }
+
if (!empty($this->prop['groups']['member_attr'])) {
return $this->prop['groups']['member_attr'];
}
- return $default;
+ return 'member';
}
/**
+ * Generate BER encoded string for Virtual List View option
+ *
+ * @param integer List offset (first record)
+ * @param integer Records per page
+ * @return string BER encoded option value
+ */
+ private function _vlv_ber_encode($offset, $rpp, $search = '')
+ {
+ # this string is ber-encoded, php will prefix this value with:
+ # 04 (octet string) and 10 (length of 16 bytes)
+ # the code behind this string is broken down as follows:
+ # 30 = ber sequence with a length of 0e (14) bytes following
+ # 02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0)
+ # 02 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24)
+ # a0 = type context-specific/constructed with a length of 06 (6) bytes following
+ # 02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
+ # 02 = type integer with 2 bytes following (contentCount): 01 00
+
+ # whith a search string present:
+ # 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
+ # 81 indicates a user string is present where as a a0 indicates just a offset search
+ # 81 = type context-specific/constructed with a length of 06 (6) bytes following
+
+ # the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
+ # encoding of integer values (note: these values are in
+ # two-complement form so since offset will never be negative bit 8 of the
+ # leftmost octet should never by set to 1):
+ # 8.3.2: If the contents octets of an integer value encoding consist
+ # of more than one octet, then the bits of the first octet (rightmost) and bit 8
+ # of the second (to the left of first octet) octet:
+ # a) shall not all be ones; and
+ # b) shall not all be zero
+
+ if ($search)
+ {
+ $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
+ $ber_val = self::_string2hex($search);
+ $str = self::_ber_addseq($ber_val, '81');
+ }
+ else
+ {
+ # construct the string from right to left
+ $str = "020100"; # contentCount
+
+ $ber_val = self::_ber_encode_int($offset); // returns encoded integer value in hex format
+
+ // calculate octet length of $ber_val
+ $str = self::_ber_addseq($ber_val, '02') . $str;
+
+ // now compute length over $str
+ $str = self::_ber_addseq($str, 'a0');
+ }
+
+ // now tack on records per page
+ $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;
+
+ // now tack on sequence identifier and length
+ $str = self::_ber_addseq($str, '30');
+
+ return pack('H'.strlen($str), $str);
+ }
+
+
+ /**
+ * create ber encoding for sort control
+ *
+ * @param array List of cols to sort by
+ * @return string BER encoded option value
+ */
+ private function _sort_ber_encode($sortcols)
+ {
+ $str = '';
+ foreach (array_reverse((array)$sortcols) as $col) {
+ $ber_val = self::_string2hex($col);
+
+ # 30 = ber sequence with a length of octet value
+ # 04 = octet string with a length of the ascii value
+ $oct = self::_ber_addseq($ber_val, '04');
+ $str = self::_ber_addseq($oct, '30') . $str;
+ }
+
+ // now tack on sequence identifier and length
+ $str = self::_ber_addseq($str, '30');
+
+ return pack('H'.strlen($str), $str);
+ }
+
+ /**
+ * Add BER sequence with correct length and the given identifier
+ */
+ private static function _ber_addseq($str, $identifier)
+ {
+ $len = dechex(strlen($str)/2);
+ if (strlen($len) % 2 != 0)
+ $len = '0'.$len;
+
+ return $identifier . $len . $str;
+ }
+
+ /**
+ * Returns BER encoded integer value in hex format
+ */
+ private static function _ber_encode_int($offset)
+ {
+ $val = dechex($offset);
+ $prefix = '';
+
+ // check if bit 8 of high byte is 1
+ if (preg_match('/^[89abcdef]/', $val))
+ $prefix = '00';
+
+ if (strlen($val)%2 != 0)
+ $prefix .= '0';
+
+ return $prefix . $val;
+ }
+
+ /**
+ * Returns ascii string encoded in hex
+ */
+ private static function _string2hex($str)
+ {
+ $hex = '';
+ for ($i=0; $i < strlen($str); $i++)
+ $hex .= dechex(ord($str[$i]));
+ return $hex;
+ }
+
+ /**
* HTML-safe DN string encoding
*
* @param string $str DN string
@@ -1953,4 +2233,130 @@ class rcube_ldap extends rcube_addressbook
return base64_decode($str);
}
+ /**
+ * Wrapper for ldap_add()
+ */
+ protected function ldap_add($dn, $entry)
+ {
+ $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true));
+
+ $res = ldap_add($this->conn, $dn, $entry);
+ if ($res === false) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return false;
+ }
+
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ /**
+ * Wrapper for ldap_delete()
+ */
+ protected function ldap_delete($dn)
+ {
+ $this->_debug("C: Delete [dn: $dn]");
+
+ $res = ldap_delete($this->conn, $dn);
+ if ($res === false) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return false;
+ }
+
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ /**
+ * Wrapper for ldap_mod_replace()
+ */
+ protected function ldap_mod_replace($dn, $entry)
+ {
+ $this->_debug("C: Replace [dn: $dn]: ".print_r($entry, true));
+
+ if (!ldap_mod_replace($this->conn, $dn, $entry)) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return false;
+ }
+
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ /**
+ * Wrapper for ldap_mod_add()
+ */
+ protected function ldap_mod_add($dn, $entry)
+ {
+ $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true));
+
+ if (!ldap_mod_add($this->conn, $dn, $entry)) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return false;
+ }
+
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ /**
+ * Wrapper for ldap_mod_del()
+ */
+ protected function ldap_mod_del($dn, $entry)
+ {
+ $this->_debug("C: Delete [dn: $dn]: ".print_r($entry, true));
+
+ if (!ldap_mod_del($this->conn, $dn, $entry)) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return false;
+ }
+
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ /**
+ * Wrapper for ldap_rename()
+ */
+ protected function ldap_rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true)
+ {
+ $this->_debug("C: Rename [dn: $dn] [dn: $newrdn]");
+
+ if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return false;
+ }
+
+ $this->_debug("S: OK");
+ return true;
+ }
+
+ /**
+ * Wrapper for ldap_list()
+ */
+ protected function ldap_list($dn, $filter, $attrs = array(''))
+ {
+ $list = array();
+ $this->_debug("C: List [dn: $dn] [{$filter}]");
+
+ if ($result = ldap_list($this->conn, $dn, $filter, $attrs)) {
+ $list = ldap_get_entries($this->conn, $result);
+
+ if ($list === false) {
+ $this->_debug("S: ".ldap_error($this->conn));
+ return array();
+ }
+
+ $count = $list['count'];
+ unset($list['count']);
+
+ $this->_debug("S: $count record(s)");
+ }
+ else {
+ $this->_debug("S: ".ldap_error($this->conn));
+ }
+
+ return $list;
+ }
+
}
diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php
index 88378dc22..923a12a41 100644
--- a/program/lib/Roundcube/rcube_ldap_generic.php
+++ b/program/lib/Roundcube/rcube_ldap_generic.php
@@ -696,11 +696,17 @@ class rcube_ldap_generic
* Turn an LDAP entry into a regular PHP array with attributes as keys.
*
* @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries()
+ *
* @return array Hash array with attributes as keys
*/
public static function normalize_entry($entry)
{
+ if (!isset($entry['count'])) {
+ return $entry;
+ }
+
$rec = array();
+
for ($i=0; $i < $entry['count']; $i++) {
$attr = $entry[$i];
if ($entry[$attr]['count'] == 1) {
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index 0d33ea44d..a8bcf6afc 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -168,11 +168,10 @@ class rcube_message
* @param resource $fp File pointer to save the message part
* @param boolean $skip_charset_conv Disables charset conversion
* @param int $max_bytes Only read this number of bytes
- * @param boolean $formatted Enables formatting of text/* parts bodies
*
* @return string Part content
*/
- public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0, $formatted = true)
+ public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0)
{
if ($part = $this->mime_parts[$mime_id]) {
// stored in message structure (winmail/inline-uuencode)
@@ -186,89 +185,47 @@ class rcube_message
// get from IMAP
$this->storage->set_folder($this->folder);
- return $this->storage->get_message_part($this->uid, $mime_id, $part,
- NULL, $fp, $skip_charset_conv, $max_bytes, $formatted);
+ return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv, $max_bytes);
}
}
/**
- * Determine if the message contains a HTML part. This must to be
- * a real part not an attachment (or its part)
- * This must to be
- * a real part not an attachment (or its part)
+ * Determine if the message contains a HTML part
*
- * @param bool $enriched Enables checking for text/enriched parts too
+ * @param bool $recursive Enables checking in all levels of the structure
+ * @param bool $enriched Enables checking for text/enriched parts too
*
* @return bool True if a HTML is available, False if not
*/
- function has_html_part($enriched = false)
+ function has_html_part($recursive = true, $enriched = false)
{
// check all message parts
- foreach ($this->mime_parts as $part) {
+ foreach ($this->parts as $part) {
if ($part->mimetype == 'text/html' || ($enriched && $part->mimetype == 'text/enriched')) {
- // Skip if part is an attachment, don't use is_attachment() here
- if ($part->filename) {
- continue;
- }
-
- $level = explode('.', $part->mime_id);
-
- // Check if the part belongs to higher-level's alternative/related
- while (array_pop($level) !== null) {
- if (!count($level)) {
- return true;
- }
+ // Level check, we'll skip e.g. HTML attachments
+ if (!$recursive) {
+ $level = explode('.', $part->mime_id);
- $parent = $this->mime_parts[join('.', $level)];
- if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
- continue 2;
+ // Skip if part is an attachment
+ if ($this->is_attachment($part)) {
+ continue;
}
- }
- if ($part->size) {
- return true;
- }
- }
- }
-
- return false;
- }
-
-
- /**
- * Determine if the message contains a text/plain part. This must to be
- * a real part not an attachment (or its part)
- *
- * @return bool True if a plain text part is available, False if not
- */
- function has_text_part()
- {
- // check all message parts
- foreach ($this->mime_parts as $part) {
- if ($part->mimetype == 'text/plain') {
- // Skip if part is an attachment, don't use is_attachment() here
- if ($part->filename) {
- continue;
- }
-
- $level = explode('.', $part->mime_id);
-
- // Check if the part belongs to higher-level's alternative/related
- while (array_pop($level) !== null) {
- if (!count($level)) {
- return true;
- }
+ // Check if the part belongs to higher-level's alternative/related
+ while (array_pop($level) !== null) {
+ if (!count($level)) {
+ return true;
+ }
- $parent = $this->mime_parts[join('.', $level)];
- if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
- continue 2;
+ $parent = $this->mime_parts[join('.', $level)];
+ if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
+ continue 2;
+ }
}
}
- if ($part->size) {
- return true;
- }
+ return true;
}
}
@@ -363,8 +320,8 @@ class rcube_message
$mimetype = $structure->real_mimetype;
// parse headers from message/rfc822 part
- if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) {
- list($headers, ) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768));
+ if (!isset($structure->headers['subject'])) {
+ list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768));
$structure->headers = rcube_mime::parse_headers($headers);
}
}
@@ -373,7 +330,7 @@ class rcube_message
// show message headers
if ($recursive && is_array($structure->headers) &&
- (isset($structure->headers['subject']) || $structure->headers['from'] || $structure->headers['to'])) {
+ ($structure->headers['subject'] || $structure->headers['from'] || $structure->headers['to'])) {
$c = new stdClass;
$c->type = 'headers';
$c->headers = $structure->headers;
@@ -487,6 +444,14 @@ class rcube_message
$this->parts[] = $c;
}
+ // add html part as attachment
+ if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
+ $html_part = $structure->parts[$html_part];
+ $html_part->mimetype = 'text/html';
+
+ $this->attachments[] = $html_part;
+ }
+
// add unsupported/unrecognized parts to attachments list
if ($attach_part) {
$this->attachments[] = $structure->parts[$attach_part];
@@ -571,6 +536,10 @@ class rcube_message
if (!empty($mail_part->filename)) {
$this->attachments[] = $mail_part;
}
+ // list html part as attachment (here the part is most likely inside a multipart/related part)
+ else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) {
+ $this->attachments[] = $mail_part;
+ }
}
// part message/*
else if ($primary_type == 'message') {
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 96a8eac61..323a5e900 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -637,7 +637,8 @@ class rcube_mime
if ($nextChar === ' ' || $nextChar === $separator) {
$afterNextChar = mb_substr($string, $width + 1, 1);
- if ($afterNextChar === false) {
+ // Note: mb_substr() does never return False
+ if ($afterNextChar === false || $afterNextChar === '') {
$subString .= $nextChar;
}
@@ -650,24 +651,23 @@ class rcube_mime
$subString = mb_substr($subString, 0, $spacePos);
$cutLength = $spacePos + 1;
}
- else if ($cut === false && $breakPos === false) {
- $subString = $string;
- $cutLength = null;
- }
else if ($cut === false) {
$spacePos = mb_strpos($string, ' ', 0);
- if ($spacePos !== false && $spacePos < $breakPos) {
+ if ($spacePos !== false && ($breakPos === false || $spacePos < $breakPos)) {
$subString = mb_substr($string, 0, $spacePos);
$cutLength = $spacePos + 1;
}
+ else if ($breakPos === false) {
+ $subString = $string;
+ $cutLength = null;
+ }
else {
$subString = mb_substr($string, 0, $breakPos);
$cutLength = $breakPos + 1;
}
}
else {
- $subString = mb_substr($subString, 0, $width);
$cutLength = $width;
}
}
@@ -708,20 +708,12 @@ class rcube_mime
*/
public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false)
{
- static $mime_ext = array();
-
$mime_type = null;
- $config = rcube::get_instance()->config;
- $mime_magic = $config->get('mime_magic');
-
- if (!$skip_suffix && empty($mime_ext)) {
- foreach ($config->resolve_paths('mimetypes.php') as $fpath) {
- $mime_ext = array_merge($mime_ext, (array) @include($fpath));
- }
- }
+ $mime_magic = rcube::get_instance()->config->get('mime_magic');
+ $mime_ext = $skip_suffix ? null : @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
// use file name suffix with hard-coded mime-type map
- if (!$skip_suffix && is_array($mime_ext) && $name) {
+ if (is_array($mime_ext) && $name) {
if ($suffix = substr($name, strrpos($name, '.')+1)) {
$mime_type = $mime_ext[strtolower($suffix)];
}
@@ -826,9 +818,7 @@ class rcube_mime
// fallback to some well-known types most important for daily emails
if (empty($mime_types)) {
- foreach (rcube::get_instance()->config->resolve_paths('mimetypes.php') as $fpath) {
- $mime_extensions = array_merge($mime_extensions, (array) @include($fpath));
- }
+ $mime_extensions = (array) @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
foreach ($mime_extensions as $ext => $mime) {
$mime_types[$mime][] = $ext;
diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index 3153a8410..34720cfd7 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -92,16 +92,6 @@ abstract class rcube_plugin
abstract function init();
/**
- * Provide information about this
- *
- * @return array Meta information about a plugin or false if not implemented
- */
- public static function info()
- {
- return false;
- }
-
- /**
* Attempt to load the given plugin which is required for the current plugin
*
* @param string Plugin name
@@ -227,7 +217,7 @@ abstract class rcube_plugin
$rcube->load_language($lang, $add);
// add labels to client
- if ($add2client && method_exists($rcube->output, 'add_label')) {
+ if ($add2client) {
if (is_array($add2client)) {
$js_labels = array_map(array($this, 'label_map_callback'), $add2client);
}
@@ -240,24 +230,6 @@ abstract class rcube_plugin
}
/**
- * Wrapper for add_label() adding the plugin ID as domain
- */
- public function add_label()
- {
- $rcube = rcube::get_instance();
-
- if (method_exists($rcube->output, 'add_label')) {
- $args = func_get_args();
- if (count($args) == 1 && is_array($args[0])) {
- $args = $args[0];
- }
-
- $args = array_map(array($this, 'label_map_callback'), $args);
- $rcube->output->add_label($args);
- }
- }
-
- /**
* Wrapper for rcube::gettext() adding the plugin ID as domain
*
* @param string $p Message identifier
@@ -273,7 +245,7 @@ abstract class rcube_plugin
/**
* Register this plugin to be responsible for a specific task
*
- * @param string $task Task name (only characters [a-z0-9_-] are allowed)
+ * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
*/
public function register_task($task)
{
@@ -408,10 +380,6 @@ abstract class rcube_plugin
*/
private function label_map_callback($key)
{
- if (strpos($key, $this->ID.'.') === 0) {
- return $key;
- }
-
return $this->ID.'.'.$key;
}
}
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 33f04eaa5..c9602d912 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -35,8 +35,9 @@ class rcube_plugin_api
public $url = 'plugins/';
public $task = '';
public $output;
- public $handlers = array();
- public $allowed_prefs = array();
+ public $handlers = array();
+ public $allowed_prefs = array();
+ public $allowed_session_prefs = array();
protected $plugins = array();
protected $tasks = array();
@@ -228,119 +229,6 @@ class rcube_plugin_api
}
/**
- * Get information about a specific plugin.
- * This is either provided my a plugin's info() method or extracted from a package.xml or a composer.json file
- *
- * @param string Plugin name
- * @return array Meta information about a plugin or False if plugin was not found
- */
- public function get_info($plugin_name)
- {
- static $composer_lock, $license_uris = array(
- 'Apache' => 'http://www.apache.org/licenses/LICENSE-2.0.html',
- 'Apache-2' => 'http://www.apache.org/licenses/LICENSE-2.0.html',
- 'Apache-1' => 'http://www.apache.org/licenses/LICENSE-1.0',
- 'Apache-1.1' => 'http://www.apache.org/licenses/LICENSE-1.1',
- 'GPL' => 'http://www.gnu.org/licenses/gpl.html',
- 'GPLv2' => 'http://www.gnu.org/licenses/gpl-2.0.html',
- 'GPL-2.0' => 'http://www.gnu.org/licenses/gpl-2.0.html',
- 'GPLv3' => 'http://www.gnu.org/licenses/gpl-3.0.html',
- 'GPL-3.0' => 'http://www.gnu.org/licenses/gpl-3.0.html',
- 'GPL-3.0+' => 'http://www.gnu.org/licenses/gpl.html',
- 'GPL-2.0+' => 'http://www.gnu.org/licenses/gpl.html',
- 'LGPL' => 'http://www.gnu.org/licenses/lgpl.html',
- 'LGPLv2' => 'http://www.gnu.org/licenses/lgpl-2.0.html',
- 'LGPLv2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html',
- 'LGPLv3' => 'http://www.gnu.org/licenses/lgpl.html',
- 'LGPL-2.0' => 'http://www.gnu.org/licenses/lgpl-2.0.html',
- 'LGPL-2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html',
- 'LGPL-3.0' => 'http://www.gnu.org/licenses/lgpl.html',
- 'LGPL-3.0+' => 'http://www.gnu.org/licenses/lgpl.html',
- 'BSD' => 'http://opensource.org/licenses/bsd-license.html',
- 'BSD-2-Clause' => 'http://opensource.org/licenses/BSD-2-Clause',
- 'BSD-3-Clause' => 'http://opensource.org/licenses/BSD-3-Clause',
- 'FreeBSD' => 'http://opensource.org/licenses/BSD-2-Clause',
- 'MIT' => 'http://www.opensource.org/licenses/mit-license.php',
- 'PHP' => 'http://opensource.org/licenses/PHP-3.0',
- 'PHP-3' => 'http://www.php.net/license/3_01.txt',
- 'PHP-3.0' => 'http://www.php.net/license/3_0.txt',
- 'PHP-3.01' => 'http://www.php.net/license/3_01.txt',
- );
-
- $dir = dir($this->dir);
- $fn = unslashify($dir->path) . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
- $info = false;
-
- if (!class_exists($plugin_name))
- include($fn);
-
- if (class_exists($plugin_name))
- $info = $plugin_name::info();
-
- // fall back to composer.json file
- if (!$info) {
- $composer = INSTALL_PATH . "/plugins/$plugin_name/composer.json";
- if (file_exists($composer) && ($json = @json_decode(file_get_contents($composer), true))) {
- list($info['vendor'], $info['name']) = explode('/', $json['name']);
- $info['license'] = $json['license'];
- if ($license_uri = $license_uris[$info['license']])
- $info['license_uri'] = $license_uri;
- }
-
- // read local composer.lock file (once)
- if (!isset($composer_lock)) {
- $composer_lock = @json_decode(@file_get_contents(INSTALL_PATH . "/composer.lock"), true);
- if ($composer_lock['packages']) {
- foreach ($composer_lock['packages'] as $i => $package) {
- $composer_lock['installed'][$package['name']] = $package;
- }
- }
- }
-
- // load additional information from local composer.lock file
- if ($lock = $composer_lock['installed'][$json['name']]) {
- $info['version'] = $lock['version'];
- $info['uri'] = $lock['homepage'] ? $lock['homepage'] : $lock['source']['uri'];
- $info['src_uri'] = $lock['dist']['uri'] ? $lock['dist']['uri'] : $lock['source']['uri'];
- }
- }
-
- // fall back to package.xml file
- if (!$info) {
- $package = INSTALL_PATH . "/plugins/$plugin_name/package.xml";
- if (file_exists($package) && ($file = file_get_contents($package))) {
- $doc = new DOMDocument();
- $doc->loadXML($file);
- $xpath = new DOMXPath($doc);
- $xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0");
-
- // XPaths of plugin metadata elements
- $metadata = array(
- 'name' => 'string(//rc:package/rc:name)',
- 'version' => 'string(//rc:package/rc:version/rc:release)',
- 'license' => 'string(//rc:package/rc:license)',
- 'license_uri' => 'string(//rc:package/rc:license/@uri)',
- 'src_uri' => 'string(//rc:package/rc:srcuri)',
- 'uri' => 'string(//rc:package/rc:uri)',
- );
-
- foreach ($metadata as $key => $path) {
- $info[$key] = $xpath->evaluate($path);
- }
-
- // dependent required plugins (can be used, but not included in config)
- $deps = $xpath->evaluate('//rc:package/rc:dependencies/rc:required/rc:package/rc:name');
- for ($i = 0; $i < $deps->length; $i++) {
- $dn = $deps->item($i)->nodeValue;
- $info['requires'][] = $dn;
- }
- }
- }
-
- return $info;
- }
-
- /**
* Allows a plugin object to register a callback for a certain hook
*
* @param string $hook Hook name
@@ -491,7 +379,7 @@ class rcube_plugin_api
/**
* Register this plugin to be responsible for a specific task
*
- * @param string $task Task name (only characters [a-z0-9_-] are allowed)
+ * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
* @param string $owner Plugin name that registers this action
*/
public function register_task($task, $owner)
@@ -501,7 +389,7 @@ class rcube_plugin_api
return true;
}
- if ($task != asciiwords($task, true)) {
+ if ($task != asciiwords($task)) {
rcube::raise_error(array('code' => 526, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid task name: $task."
diff --git a/program/lib/Roundcube/rcube_result_set.php b/program/lib/Roundcube/rcube_result_set.php
index a4b070e28..1391e5e4b 100644
--- a/program/lib/Roundcube/rcube_result_set.php
+++ b/program/lib/Roundcube/rcube_result_set.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2013, The Roundcube Dev Team |
+ | Copyright (C) 2006-2011, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -17,22 +17,20 @@
*/
/**
- * Roundcube result set class
- *
+ * Roundcube result set class.
* Representing an address directory result set.
- * Implenets Iterator and thus be used in foreach() loops.
*
* @package Framework
* @subpackage Addressbook
*/
-class rcube_result_set implements Iterator
+class rcube_result_set
{
- public $count = 0;
- public $first = 0;
- public $searchonly = false;
- public $records = array();
+ var $count = 0;
+ var $first = 0;
+ var $current = 0;
+ var $searchonly = false;
+ var $records = array();
- private $current = 0;
function __construct($c=0, $f=0)
{
@@ -53,39 +51,18 @@ class rcube_result_set implements Iterator
function first()
{
$this->current = 0;
- return $this->records[$this->current];
- }
-
- function seek($i)
- {
- $this->current = $i;
- }
-
- /*** PHP 5 Iterator interface ***/
-
- function rewind()
- {
- $this->current = 0;
- }
-
- function current()
- {
- return $this->records[$this->current];
- }
-
- function key()
- {
- return $this->current;
+ return $this->records[$this->current++];
}
+ // alias for iterate()
function next()
{
return $this->iterate();
}
- function valid()
+ function seek($i)
{
- return isset($this->records[$this->current]);
+ $this->current = $i;
}
}
diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index 67072df41..ee4db6e86 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -32,7 +32,6 @@ class rcube_session
private $ip;
private $start;
private $changed;
- private $time_diff = 0;
private $reloaded = false;
private $unsets = array();
private $gc_handlers = array();
@@ -43,7 +42,6 @@ class rcube_session
private $secret = '';
private $ip_check = false;
private $logging = false;
- private $storage;
private $memcache;
@@ -54,21 +52,18 @@ class rcube_session
{
$this->db = $db;
$this->start = microtime(true);
- $this->ip = rcube_utils::remote_addr();
+ $this->ip = $_SERVER['REMOTE_ADDR'];
$this->logging = $config->get('log_session', false);
$lifetime = $config->get('session_lifetime', 1) * 60;
$this->set_lifetime($lifetime);
// use memcache backend
- $this->storage = $config->get('session_storage', 'db');
- if ($this->storage == 'memcache') {
+ if ($config->get('session_storage', 'db') == 'memcache') {
$this->memcache = rcube::get_instance()->get_memcache();
// set custom functions for PHP session management if memcache is available
if ($this->memcache) {
- ini_set('session.serialize_handler', 'php');
-
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
@@ -84,9 +79,7 @@ class rcube_session
true, true);
}
}
- else if ($this->storage != 'php') {
- ini_set('session.serialize_handler', 'php');
-
+ else {
// set custom functions for PHP session management
session_set_save_handler(
array($this, 'open'),
@@ -94,23 +87,7 @@ class rcube_session
array($this, 'db_read'),
array($this, 'db_write'),
array($this, 'db_destroy'),
- array($this, 'gc'));
- }
- }
-
-
- /**
- * Wrapper for session_start()
- */
- public function start()
- {
- session_start();
-
- // copy some session properties to object vars
- if ($this->storage == 'php') {
- $this->key = session_id();
- $this->ip = $_SESSION['__IP'];
- $this->changed = $_SESSION['__MTIME'];
+ array($this, 'db_gc'));
}
}
@@ -139,25 +116,6 @@ class rcube_session
/**
- * Wrapper for session_write_close()
- */
- public function write_close()
- {
- if ($this->storage == 'php') {
- $_SESSION['__IP'] = $this->ip;
- $_SESSION['__MTIME'] = time();
- }
-
- session_write_close();
-
- // write_close() is called on script shutdown, see rcube::shutdown()
- // execute cleanup functionality if enabled by session gc handler
- // we do this after closing the session for better performance
- $this->gc_shutdown();
- }
-
-
- /**
* Read session data from database
*
* @param string Session ID
@@ -167,16 +125,14 @@ class rcube_session
public function db_read($key)
{
$sql_result = $this->db->query(
- "SELECT vars, ip, changed, " . $this->db->now() . " AS ts"
- . " FROM " . $this->db->table_name('session')
- . " WHERE sess_id = ?", $key);
+ "SELECT vars, ip, changed FROM ".$this->db->table_name('session')
+ ." WHERE sess_id = ?", $key);
if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $this->time_diff = time() - strtotime($sql_arr['ts']);
- $this->changed = strtotime($sql_arr['changed']);
- $this->ip = $sql_arr['ip'];
- $this->vars = base64_decode($sql_arr['vars']);
- $this->key = $key;
+ $this->changed = strtotime($sql_arr['changed']);
+ $this->ip = $sql_arr['ip'];
+ $this->vars = base64_decode($sql_arr['vars']);
+ $this->key = $key;
return !empty($this->vars) ? (string) $this->vars : '';
}
@@ -196,9 +152,8 @@ class rcube_session
*/
public function db_write($key, $vars)
{
- $now = $this->db->now();
- $table = $this->db->table_name('session');
- $ts = microtime(true);
+ $ts = microtime(true);
+ $now = $this->db->fromunixtime((int)$ts);
// no session row in DB (db_read() returns false)
if (!$this->key) {
@@ -216,19 +171,22 @@ class rcube_session
$newvars = $this->_fixvars($vars, $oldvars);
if ($newvars !== $oldvars) {
- $this->db->query("UPDATE $table "
- . "SET changed = $now, vars = ? WHERE sess_id = ?",
- base64_encode($newvars), $key);
+ $this->db->query(
+ sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
+ $this->db->table_name('session'), $now),
+ base64_encode($newvars), $key);
}
- else if ($ts - $this->changed + $this->time_diff > $this->lifetime / 2) {
- $this->db->query("UPDATE $table SET changed = $now"
- . " WHERE sess_id = ?", $key);
+ else if ($ts - $this->changed > $this->lifetime / 2) {
+ $this->db->query("UPDATE ".$this->db->table_name('session')
+ ." SET changed=$now WHERE sess_id=?", $key);
}
}
else {
- $this->db->query("INSERT INTO $table (sess_id, vars, ip, created, changed)"
- . " VALUES (?, ?, ?, $now, $now)",
- $key, base64_encode($vars), (string)$this->ip);
+ $this->db->query(
+ sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
+ "VALUES (?, ?, ?, %s, %s)",
+ $this->db->table_name('session'), $now, $now),
+ $key, base64_encode($vars), (string)$this->ip);
}
return true;
@@ -288,6 +246,25 @@ class rcube_session
/**
+ * Garbage collecting function
+ *
+ * @param string Session lifetime in seconds
+ * @return boolean True on success
+ */
+ public function db_gc($maxlifetime)
+ {
+ // just delete all expired sessions
+ $this->db->query(
+ sprintf("DELETE FROM %s WHERE changed < %s",
+ $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
+
+ $this->gc();
+
+ return true;
+ }
+
+
+ /**
* Read session data from memcache
*
* @param string Session ID
@@ -363,11 +340,11 @@ class rcube_session
/**
* Execute registered garbage collector routines
*/
- public function gc($maxlifetime)
+ public function gc()
{
- // move gc execution to the script shutdown function
- // see rcube::shutdown() and rcube_session::write_close()
- return $this->gc_enabled = $maxlifetime;
+ foreach ($this->gc_handlers as $fct) {
+ call_user_func($fct);
+ }
}
@@ -389,25 +366,6 @@ class rcube_session
/**
- * Garbage collector handler to run on script shutdown
- */
- protected function gc_shutdown()
- {
- if ($this->gc_enabled) {
- // just delete all expired sessions
- if ($this->storage == 'db') {
- $this->db->query("DELETE FROM " . $this->db->table_name('session')
- . " WHERE changed < " . $this->db->now(-$this->gc_enabled));
- }
-
- foreach ($this->gc_handlers as $fct) {
- call_user_func($fct);
- }
- }
- }
-
-
- /**
* Generate and set new session id
*
* @param boolean $destroy If enabled the current session will be destroyed
@@ -480,7 +438,7 @@ class rcube_session
public function kill()
{
$this->vars = null;
- $this->ip = rcube_utils::remote_addr(); // update IP (might have changed)
+ $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
$this->destroy(session_id());
rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
}
@@ -694,10 +652,10 @@ class rcube_session
function check_auth()
{
$this->cookie = $_COOKIE[$this->cookiename];
- $result = $this->ip_check ? rcube_utils::remote_addr() == $this->ip : true;
+ $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
if (!$result) {
- $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . rcube_utils::remote_addr());
+ $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
}
if ($result && $this->_mkcookie($this->now) != $this->cookie) {
diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php
index 60b1389ea..201e8269e 100644
--- a/program/lib/Roundcube/rcube_smtp.php
+++ b/program/lib/Roundcube/rcube_smtp.php
@@ -33,8 +33,6 @@ class rcube_smtp
// define headers delimiter
const SMTP_MIME_CRLF = "\r\n";
- const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
-
/**
* SMTP Connection and authentication
@@ -329,12 +327,6 @@ class rcube_smtp
*/
public function debug_handler(&$smtp, $message)
{
- if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
- $diff = $len - self::DEBUG_LINE_LENGTH;
- $message = substr($message, 0, self::DEBUG_LINE_LENGTH)
- . "... [truncated $diff bytes]";
- }
-
rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
}
@@ -441,9 +433,9 @@ class rcube_smtp
$recipients = rcube_utils::explode_quoted_string(',', $recipients);
reset($recipients);
- foreach ($recipients as $recipient) {
+ while (list($k, $recipient) = each($recipients)) {
$a = rcube_utils::explode_quoted_string(' ', $recipient);
- foreach ($a as $word) {
+ while (list($k2, $word) = each($a)) {
if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') {
$word = preg_replace('/^<|>$/', '', trim($word));
if (in_array($word, $addresses) === false) {
diff --git a/program/lib/Roundcube/rcube_spellcheck_atd.php b/program/lib/Roundcube/rcube_spellcheck_atd.php
new file mode 100644
index 000000000..9f073f56f
--- /dev/null
+++ b/program/lib/Roundcube/rcube_spellcheck_atd.php
@@ -0,0 +1,204 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | |
+ | Copyright (C) 2013, 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: |
+ | Spellchecking backend implementation for afterthedeadline services |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Spellchecking backend implementation to work with an After the Deadline service
+ * See http://www.afterthedeadline.com/ for more information
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_spellcheck_atd extends rcube_spellcheck_engine
+{
+ const SERVICE_HOST = 'service.afterthedeadline.com';
+ const SERVICE_PORT = 80;
+
+ private $matches = array();
+ private $content;
+ private $langhosts = array(
+ 'fr' => 'fr.',
+ 'de' => 'de.',
+ 'pt' => 'pt.',
+ 'es' => 'es.',
+ );
+
+ /**
+ * Return a list of languages supported by this backend
+ *
+ * @see rcube_spellcheck_engine::languages()
+ */
+ function languages()
+ {
+ $langs = array_values($this->langhosts);
+ $langs[] = 'en';
+ return $langs;
+ }
+
+ /**
+ * Set content and check spelling
+ *
+ * @see rcube_spellcheck_engine::check()
+ */
+ function check($text)
+ {
+ $this->content = $text;
+
+ // spell check uri is configured
+ $rcube = rcube::get_instance();
+ $url = $rcube->config->get('spellcheck_uri');
+ $key = $rcube->config->get('spellcheck_atd_key');
+
+ if ($url) {
+ $a_uri = parse_url($url);
+ $ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl');
+ $port = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80);
+ $host = ($ssl ? 'ssl://' : '') . $a_uri['host'];
+ $path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang;
+ }
+ else {
+ $host = self::SERVICE_HOST;
+ $port = self::SERVICE_PORT;
+ $path = '/checkDocument';
+
+ // prefix host for other languages than 'en'
+ $lang = substr($this->lang, 0, 2);
+ if ($this->langhosts[$lang])
+ $host = $this->langhosts[$lang] . $host;
+ }
+
+ $postdata = 'data=' . urlencode($text);
+
+ if (!empty($key))
+ $postdata .= '&key=' . urlencode($key);
+
+ $response = $headers = '';
+ $in_header = true;
+ if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) {
+ $out = "POST $path HTTP/1.0\r\n";
+ $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n";
+ $out .= "Content-Length: " . strlen($postdata) . "\r\n";
+ $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
+ $out .= "Connection: Close\r\n\r\n";
+ $out .= $postdata;
+ fwrite($fp, $out);
+
+ while (!feof($fp)) {
+ if ($in_header) {
+ $line = fgets($fp, 512);
+ $headers .= $line;
+ if (trim($line) == '')
+ $in_header = false;
+ }
+ else {
+ $response .= fgets($fp, 1024);
+ }
+ }
+ fclose($fp);
+ }
+
+ // parse HTTP response headers
+ if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $headers, $m)) {
+ $http_status = $m[1];
+ if ($http_status != '200')
+ $this->error = 'HTTP ' . $m[1] . $m[2];
+ }
+
+ if (!$response) {
+ $this->error = "Empty result from spelling engine";
+ }
+
+ try {
+ $result = new SimpleXMLElement($response);
+ }
+ catch (Exception $e) {
+ $thid->error = "Unexpected response from server: " . $store;
+ return array();
+ }
+
+ foreach ($result->error as $error) {
+ if (strval($error->type) == 'spelling') {
+ $word = strval($error->string);
+
+ // skip exceptions
+ if ($this->dictionary->is_exception($word)) {
+ continue;
+ }
+
+ $prefix = strval($error->precontext);
+ $start = $prefix ? mb_strpos($text, $prefix) : 0;
+ $pos = mb_strpos($text, $word, $start);
+ $len = mb_strlen($word);
+ $num = 0;
+
+ $match = array($word, $pos, $len, null, array());
+ foreach ($error->suggestions->option as $option) {
+ $match[4][] = strval($option);
+ if (++$num == self::MAX_SUGGESTIONS)
+ break;
+ }
+ $matches[] = $match;
+ }
+ }
+
+ $this->matches = $matches;
+ return $matches;
+ }
+
+ /**
+ * Returns suggestions for the specified word
+ *
+ * @see rcube_spellcheck_engine::get_words()
+ */
+ function get_suggestions($word)
+ {
+ $matches = $word ? $this->check($word) : $this->matches;
+
+ if ($matches[0][4]) {
+ return $matches[0][4];
+ }
+
+ return array();
+ }
+
+ /**
+ * Returns misspelled words
+ *
+ * @see rcube_spellcheck_engine::get_suggestions()
+ */
+ function get_words($text = null)
+ {
+ if ($text) {
+ $matches = $this->check($text);
+ }
+ else {
+ $matches = $this->matches;
+ $text = $this->content;
+ }
+
+ $result = array();
+
+ foreach ($matches as $m) {
+ $result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET);
+ }
+
+ return $result;
+ }
+
+}
+
diff --git a/program/lib/Roundcube/rcube_spellcheck_enchant.php b/program/lib/Roundcube/rcube_spellcheck_enchant.php
new file mode 100644
index 000000000..14d6fff46
--- /dev/null
+++ b/program/lib/Roundcube/rcube_spellcheck_enchant.php
@@ -0,0 +1,182 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | |
+ | Copyright (C) 2011-2013, Kolab Systems AG |
+ | Copyright (C) 20011-2013, 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: |
+ | Spellchecking backend implementation to work with Enchant |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Spellchecking backend implementation to work with Pspell
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_spellcheck_enchant extends rcube_spellcheck_engine
+{
+ private $enchant_broker;
+ private $enchant_dictionary;
+ private $matches = array();
+
+ /**
+ * Return a list of languages supported by this backend
+ *
+ * @see rcube_spellcheck_engine::languages()
+ */
+ function languages()
+ {
+ $this->init();
+
+ $langs = array();
+ $dicts = enchant_broker_list_dicts($this->enchant_broker);
+ foreach ($dicts as $dict) {
+ $langs[] = preg_replace('/-.*$/', '', $dict['lang_tag']);
+ }
+
+ return array_unique($langs);
+ }
+
+ /**
+ * Initializes Enchant dictionary
+ */
+ private function init()
+ {
+ if (!$this->enchant_broker) {
+ if (!extension_loaded('enchant')) {
+ $this->error = "Enchant extension not available";
+ return;
+ }
+
+ $this->enchant_broker = enchant_broker_init();
+ }
+
+ if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) {
+ $this->error = "Unable to load dictionary for selected language using Enchant";
+ return;
+ }
+
+ $this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang);
+ }
+
+ /**
+ * Set content and check spelling
+ *
+ * @see rcube_spellcheck_engine::check()
+ */
+ function check($text)
+ {
+ $this->init();
+
+ if (!$this->enchant_dictionary) {
+ return array();
+ }
+
+ // tokenize
+ $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
+
+ $diff = 0;
+ $matches = array();
+
+ foreach ($text as $w) {
+ $word = trim($w[0]);
+ $pos = $w[1] - $diff;
+ $len = mb_strlen($word);
+
+ // skip exceptions
+ if ($this->dictionary->is_exception($word)) {
+ }
+ else if (!enchant_dict_check($this->enchant_dictionary, $word)) {
+ $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
+
+ if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
+ $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
+ }
+
+ $matches[] = array($word, $pos, $len, null, $suggestions);
+ }
+
+ $diff += (strlen($word) - $len);
+ }
+
+ $this->matches = $matches;
+ return $matches;
+ }
+
+ /**
+ * Returns suggestions for the specified word
+ *
+ * @see rcube_spellcheck_engine::get_words()
+ */
+ function get_suggestions($word)
+ {
+ $this->init();
+
+ if (!$this->enchant_dictionary) {
+ return array();
+ }
+
+ $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
+
+ if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
+ $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
+
+ return is_array($suggestions) ? $suggestions : array();
+ }
+
+ /**
+ * Returns misspelled words
+ *
+ * @see rcube_spellcheck_engine::get_suggestions()
+ */
+ function get_words($text = null)
+ {
+ $result = array();
+
+ if ($text) {
+ // init spellchecker
+ $this->init();
+
+ if (!$this->enchant_dictionary) {
+ return array();
+ }
+
+ // With Enchant we don't need to get suggestions to return misspelled words
+ $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
+
+ foreach ($text as $w) {
+ $word = trim($w[0]);
+
+ // skip exceptions
+ if ($this->dictionary->is_exception($word)) {
+ continue;
+ }
+
+ if (!enchant_dict_check($this->enchant_dictionary, $word)) {
+ $result[] = $word;
+ }
+ }
+
+ return $result;
+ }
+
+ foreach ($this->matches as $m) {
+ $result[] = $m[0];
+ }
+
+ return $result;
+ }
+
+}
+
diff --git a/program/lib/Roundcube/rcube_spellcheck_engine.php b/program/lib/Roundcube/rcube_spellcheck_engine.php
new file mode 100644
index 000000000..3cb4ca3de
--- /dev/null
+++ b/program/lib/Roundcube/rcube_spellcheck_engine.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | |
+ | Copyright (C) 2011-2013, Kolab Systems AG |
+ | Copyright (C) 2008-2013, 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: |
+ | Interface class for a spell-checking backend |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Interface class for a spell-checking backend
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+abstract class rcube_spellcheck_engine
+{
+ const MAX_SUGGESTIONS = 10;
+
+ protected $lang;
+ protected $error;
+ protected $dictionary;
+ protected $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.](?=\W|$)/';
+
+ /**
+ * Default constructor
+ */
+ public function __construct($dict, $lang)
+ {
+ $this->dictionary = $dict;
+ $this->lang = $lang;
+ }
+
+ /**
+ * Return a list of languages supported by this backend
+ *
+ * @return array Indexed list of language codes
+ */
+ abstract function languages();
+
+ /**
+ * Set content and check spelling
+ *
+ * @param string $text Text content for spellchecking
+ *
+ * @return bool True when no mispelling found, otherwise false
+ */
+ abstract function check($text);
+
+ /**
+ * Returns suggestions for the specified word
+ *
+ * @param string $word The word
+ *
+ * @return array Suggestions list
+ */
+ abstract function get_suggestions($word);
+
+ /**
+ * Returns misspelled words
+ *
+ * @param string $text The content for spellchecking. If empty content
+ * used for check() method will be used.
+ *
+ * @return array List of misspelled words
+ */
+ abstract function get_words($text = null);
+
+ /**
+ * Returns error message
+ *
+ * @return string Error message
+ */
+ public function error()
+ {
+ return $this->error;
+ }
+
+}
+
diff --git a/program/lib/Roundcube/rcube_spellcheck_googie.php b/program/lib/Roundcube/rcube_spellcheck_googie.php
new file mode 100644
index 000000000..3777942a6
--- /dev/null
+++ b/program/lib/Roundcube/rcube_spellcheck_googie.php
@@ -0,0 +1,176 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | |
+ | Copyright (C) 2008-2013, 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: |
+ | Spellchecking backend implementation to work with Googiespell |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Spellchecking backend implementation to work with a Googiespell service
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_spellcheck_googie extends rcube_spellcheck_engine
+{
+ const GOOGIE_HOST = 'ssl://spell.roundcube.net';
+ const GOOGIE_PORT = 443;
+
+ private $matches = array();
+ private $content;
+
+ /**
+ * Return a list of languages supported by this backend
+ *
+ * @see rcube_spellcheck_engine::languages()
+ */
+ function languages()
+ {
+ return array('am','ar','ar','bg','br','ca','cs','cy','da',
+ 'de_CH','de_DE','el','en_GB','en_US',
+ 'eo','es','et','eu','fa','fi','fr_FR','ga','gl','gl',
+ 'he','hr','hu','hy','is','it','ku','lt','lv','nl',
+ 'pl','pt_BR','pt_PT','ro','ru',
+ 'sk','sl','sv','uk');
+ }
+
+ /**
+ * Set content and check spelling
+ *
+ * @see rcube_spellcheck_engine::check()
+ */
+ function check($text)
+ {
+ $this->content = $text;
+
+ // spell check uri is configured
+ $url = rcube::get_instance()->config->get('spellcheck_uri');
+
+ if ($url) {
+ $a_uri = parse_url($url);
+ $ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl');
+ $port = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80);
+ $host = ($ssl ? 'ssl://' : '') . $a_uri['host'];
+ $path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang;
+ }
+ else {
+ $host = self::GOOGIE_HOST;
+ $port = self::GOOGIE_PORT;
+ $path = '/tbproxy/spell?lang=' . $this->lang;
+ }
+
+ $path .= sprintf('&key=%06d', $_SESSION['user_id']);
+
+ $gtext = '<?xml version="1.0" encoding="utf-8" ?>'
+ .'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
+ .'<text>' . htmlspecialchars($text, ENT_QUOTES, RCUBE_CHARSET) . '</text>'
+ .'</spellrequest>';
+
+ $store = '';
+ if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) {
+ $out = "POST $path HTTP/1.0\r\n";
+ $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n";
+ $out .= "User-Agent: Roundcube Webmail/" . RCMAIL_VERSION . " (Googiespell Wrapper)\r\n";
+ $out .= "Content-Length: " . strlen($gtext) . "\r\n";
+ $out .= "Content-Type: text/xml\r\n";
+ $out .= "Connection: Close\r\n\r\n";
+ $out .= $gtext;
+ fwrite($fp, $out);
+
+ while (!feof($fp))
+ $store .= fgets($fp, 128);
+ fclose($fp);
+ }
+
+ // parse HTTP response
+ if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) {
+ $http_status = $m[1];
+ if ($http_status != '200') {
+ $this->error = 'HTTP ' . $m[1] . $m[2];
+ $this->error .= "\n" . $store;
+ }
+ }
+
+ if (!$store) {
+ $this->error = "Empty result from spelling engine";
+ }
+ else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m) && $m[1]) {
+ $this->error = "Error code $m[1] returned";
+ $this->error .= preg_match('/<errortext>([^<]+)/', $store, $m) ? ": " . html_entity_decode($m[1]) : '';
+ }
+
+ preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
+
+ // skip exceptions (if appropriate options are enabled)
+ foreach ($matches as $idx => $m) {
+ $word = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET);
+ // skip exceptions
+ if ($this->dictionary->is_exception($word)) {
+ unset($matches[$idx]);
+ }
+ }
+
+ $this->matches = $matches;
+ return $matches;
+ }
+
+ /**
+ * Returns suggestions for the specified word
+ *
+ * @see rcube_spellcheck_engine::get_words()
+ */
+ function get_suggestions($word)
+ {
+ $matches = $word ? $this->check($word) : $this->matches;
+
+ if ($matches[0][4]) {
+ $suggestions = explode("\t", $matches[0][4]);
+ if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
+ $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
+ }
+
+ return $suggestions;
+ }
+
+ return array();
+ }
+
+ /**
+ * Returns misspelled words
+ *
+ * @see rcube_spellcheck_engine::get_suggestions()
+ */
+ function get_words($text = null)
+ {
+ if ($text) {
+ $matches = $this->check($text);
+ }
+ else {
+ $matches = $this->matches;
+ $text = $this->content;
+ }
+
+ $result = array();
+
+ foreach ($matches as $m) {
+ $result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET);
+ }
+
+ return $result;
+ }
+
+}
+
diff --git a/program/lib/Roundcube/rcube_spellcheck_pspell.php b/program/lib/Roundcube/rcube_spellcheck_pspell.php
new file mode 100644
index 000000000..b12684e43
--- /dev/null
+++ b/program/lib/Roundcube/rcube_spellcheck_pspell.php
@@ -0,0 +1,189 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | |
+ | Copyright (C) 2008-2013, 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: |
+ | Spellchecking backend implementation to work with Pspell |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak@kolabsys.com> |
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Spellchecking backend implementation to work with Pspell
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_spellcheck_pspell extends rcube_spellcheck_engine
+{
+ private $plink;
+ private $matches = array();
+
+ /**
+ * Return a list of languages supported by this backend
+ *
+ * @see rcube_spellcheck_engine::languages()
+ */
+ function languages()
+ {
+ $defaults = array('en');
+ $langs = array();
+
+ // get aspell dictionaries
+ exec('aspell dump dicts', $dicts);
+ if (!empty($dicts)) {
+ $seen = array();
+ foreach ($dicts as $lang) {
+ $lang = preg_replace('/-.*$/', '', $lang);
+ $langc = strlen($lang) == 2 ? $lang.'_'.strtoupper($lang) : $lang;
+ if (!$seen[$langc]++)
+ $langs[] = $lang;
+ }
+ $langs = array_unique($langs);
+ }
+ else {
+ $langs = $defaults;
+ }
+
+ return $langs;
+ }
+
+ /**
+ * Initializes PSpell dictionary
+ */
+ private function init()
+ {
+ if (!$this->plink) {
+ if (!extension_loaded('pspell')) {
+ $this->error = "Pspell extension not available";
+ return;
+ }
+
+ $this->plink = pspell_new($this->lang, null, null, RCUBE_CHARSET, PSPELL_FAST);
+ }
+
+ if (!$this->plink) {
+ $this->error = "Unable to load Pspell engine for selected language";
+ }
+ }
+
+ /**
+ * Set content and check spelling
+ *
+ * @see rcube_spellcheck_engine::check()
+ */
+ function check($text)
+ {
+ $this->init();
+
+ if (!$this->plink) {
+ return array();
+ }
+
+ // tokenize
+ $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
+
+ $diff = 0;
+ $matches = array();
+
+ foreach ($text as $w) {
+ $word = trim($w[0]);
+ $pos = $w[1] - $diff;
+ $len = mb_strlen($word);
+
+ // skip exceptions
+ if ($this->dictionary->is_exception($word)) {
+ }
+ else if (!pspell_check($this->plink, $word)) {
+ $suggestions = pspell_suggest($this->plink, $word);
+
+ if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
+ $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
+ }
+
+ $matches[] = array($word, $pos, $len, null, $suggestions);
+ }
+
+ $diff += (strlen($word) - $len);
+ }
+
+ $this->matches = $matches;
+ return $matches;
+ }
+
+ /**
+ * Returns suggestions for the specified word
+ *
+ * @see rcube_spellcheck_engine::get_words()
+ */
+ function get_suggestions($word)
+ {
+ $this->init();
+
+ if (!$this->plink) {
+ return array();
+ }
+
+ $suggestions = pspell_suggest($this->plink, $word);
+
+ if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
+ $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
+
+ return is_array($suggestions) ? $suggestions : array();
+ }
+
+ /**
+ * Returns misspelled words
+ *
+ * @see rcube_spellcheck_engine::get_suggestions()
+ */
+ function get_words($text = null)
+ {
+ $result = array();
+
+ if ($text) {
+ // init spellchecker
+ $this->init();
+
+ if (!$this->plink) {
+ return array();
+ }
+
+ // With PSpell we don't need to get suggestions to return misspelled words
+ $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
+
+ foreach ($text as $w) {
+ $word = trim($w[0]);
+
+ // skip exceptions
+ if ($this->dictionary->is_exception($word)) {
+ continue;
+ }
+
+ if (!pspell_check($this->plink, $word)) {
+ $result[] = $word;
+ }
+ }
+
+ return $result;
+ }
+
+ foreach ($this->matches as $m) {
+ $result[] = $m[0];
+ }
+
+ return $result;
+ }
+
+}
+
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index df4365223..672515204 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -38,7 +38,7 @@ class rcube_spellchecker
// default settings
- const GOOGLE_HOST = 'ssl://www.google.com';
+ const GOOGLE_HOST = 'ssl://spell.roundcube.net';
const GOOGLE_PORT = 443;
const MAX_SUGGESTIONS = 10;
@@ -84,9 +84,6 @@ class rcube_spellchecker
if ($this->engine == 'pspell') {
$this->matches = $this->_pspell_check($this->content);
}
- else if ($this->engine == 'enchant') {
- $this->matches = $this->_enchant_check($this->content);
- }
else {
$this->matches = $this->_googie_check($this->content);
}
@@ -118,9 +115,6 @@ class rcube_spellchecker
if ($this->engine == 'pspell') {
return $this->_pspell_suggestions($word);
}
- else if ($this->engine == 'enchant') {
- return $this->_enchant_suggestions($word);
- }
return $this->_googie_suggestions($word);
}
@@ -139,9 +133,6 @@ class rcube_spellchecker
if ($this->engine == 'pspell') {
return $this->_pspell_words($text, $is_html);
}
- else if ($this->engine == 'enchant') {
- return $this->_enchant_words($text, $is_html);
- }
return $this->_googie_words($text, $is_html);
}
@@ -323,6 +314,11 @@ class rcube_spellchecker
if (!$this->plink) {
if (!extension_loaded('pspell')) {
$this->error = "Pspell extension not available";
+ rcube::raise_error(array(
+ 'code' => 500, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => $this->error), true, false);
+
return;
}
@@ -335,141 +331,6 @@ class rcube_spellchecker
}
- /**
- * Checks the text using enchant
- *
- * @param string $text Text content for spellchecking
- */
- private function _enchant_check($text)
- {
- // init spellchecker
- $this->_enchant_init();
-
- if (!$this->enchant_dictionary) {
- return array();
- }
-
- // tokenize
- $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
-
- $diff = 0;
- $matches = array();
-
- foreach ($text as $w) {
- $word = trim($w[0]);
- $pos = $w[1] - $diff;
- $len = mb_strlen($word);
-
- // skip exceptions
- if ($this->is_exception($word)) {
- }
- else if (!enchant_dict_check($this->enchant_dictionary, $word)) {
- $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
-
- if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
- $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
- }
-
- $matches[] = array($word, $pos, $len, null, $suggestions);
- }
-
- $diff += (strlen($word) - $len);
- }
-
- return $matches;
- }
-
-
- /**
- * Returns the misspelled words
- */
- private function _enchant_words($text = null, $is_html=false)
- {
- $result = array();
-
- if ($text) {
- // init spellchecker
- $this->_enchant_init();
-
- if (!$this->enchant_dictionary) {
- return array();
- }
-
- // With Enchant we don't need to get suggestions to return misspelled words
- if ($is_html) {
- $text = $this->html2text($text);
- }
-
- $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
-
- foreach ($text as $w) {
- $word = trim($w[0]);
-
- // skip exceptions
- if ($this->is_exception($word)) {
- continue;
- }
-
- if (!enchant_dict_check($this->enchant_dictionary, $word)) {
- $result[] = $word;
- }
- }
-
- return $result;
- }
-
- foreach ($this->matches as $m) {
- $result[] = $m[0];
- }
-
- return $result;
- }
-
-
- /**
- * Returns suggestions for misspelled word
- */
- private function _enchant_suggestions($word)
- {
- // init spellchecker
- $this->_enchant_init();
-
- if (!$this->enchant_dictionary) {
- return array();
- }
-
- $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
-
- if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
- $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
-
- return is_array($suggestions) ? $suggestions : array();
- }
-
-
- /**
- * Initializes PSpell dictionary
- */
- private function _enchant_init()
- {
- if (!$this->enchant_broker) {
- if (!extension_loaded('enchant')) {
- $this->error = "Enchant extension not available";
- return;
- }
-
- $this->enchant_broker = enchant_broker_init();
- }
-
- if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) {
- $this->error = "Unable to load dictionary for selected language using Enchant";
- return;
- }
-
- $this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang);
- }
-
-
private function _googie_check($text)
{
// spell check uri is configured
@@ -493,7 +354,7 @@ class rcube_spellchecker
$gtext = '<?xml version="1.0" encoding="utf-8" ?>'
.'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
- .'<text>' . $gtext . '</text>'
+ .'<text>' . htmlspecialchars($gtext) . '</text>'
.'</spellrequest>';
$store = '';
@@ -511,19 +372,9 @@ class rcube_spellchecker
fclose($fp);
}
- // parse HTTP response
- if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) {
- $http_status = $m[1];
- if ($http_status != '200')
- $this->error = 'HTTP ' . $m[1] . $m[2];
- }
-
if (!$store) {
$this->error = "Empty result from spelling engine";
}
- else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m) && $m[1]) {
- $this->error = "Error code $m[1] returned";
- }
preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index e697b2c73..8193e540c 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -61,6 +61,8 @@ abstract class rcube_storage
'MAIL-FOLLOWUP-TO',
'MAIL-REPLY-TO',
'RETURN-PATH',
+ 'DELIVERED-TO',
+ 'ENVELOPE-TO',
);
const UNKNOWN = 0;
@@ -538,13 +540,12 @@ abstract class rcube_storage
/**
* Append a mail message (source) to a specific folder.
*
- * @param string $folder Target folder
- * @param string|array $message The message source string or filename
- * or array (of strings and file pointers)
- * @param string $headers Headers string if $message contains only the body
- * @param boolean $is_file True if $message is a filename
- * @param array $flags Message flags
- * @param mixed $date Message internal date
+ * @param string $folder Target folder
+ * @param string $message The message source string or filename
+ * @param string $headers Headers string if $message contains only the body
+ * @param boolean $is_file True if $message is a filename
+ * @param array $flags Message flags
+ * @param mixed $date Message internal date
*
* @return int|bool Appended message UID or True on success, False on error
*/
@@ -806,14 +807,13 @@ abstract class rcube_storage
/**
- * Returns current status of a folder (compared to the last time use)
+ * Returns current status of a folder
*
* @param string $folder Folder name
- * @param array $diff Difference data
*
* @return int Folder status
*/
- abstract function folder_status($folder = null, &$diff = array());
+ abstract function folder_status($folder = null);
/**
@@ -985,6 +985,6 @@ abstract class rcube_storage
/**
* Delete outdated cache entries
*/
- abstract function cache_gc();
+ abstract function expunge_cache();
} // end class rcube_storage
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 354b4596d..bd26f8e7d 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -28,10 +28,9 @@ class rcube_string_replacer
public $mailto_pattern;
public $link_pattern;
private $values = array();
- private $options = array();
- function __construct($options = array())
+ function __construct()
{
// Simplified domain expression for UTF8 characters handling
// Support unicode/punycode in top-level domain part
@@ -45,8 +44,6 @@ class rcube_string_replacer
."@$utf_domain" // domain-part
."(\?[$url1$url2]+)?" // e.g. ?subject=test...
.")/";
-
- $this->options = $options;
}
/**
@@ -92,10 +89,10 @@ class rcube_string_replacer
if ($url) {
$suffix = $this->parse_url_brackets($url);
- $attrib = (array)$this->options['link_attribs'];
- $attrib['href'] = $url_prefix . $url;
-
- $i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix);
+ $i = $this->add(html::a(array(
+ 'href' => $url_prefix . $url,
+ 'target' => '_blank'
+ ), rcube::Q($url)) . $suffix);
}
// Return valid link for recognized schemes, otherwise
diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php
index 5e9c9af80..505b190d1 100644
--- a/program/lib/Roundcube/rcube_user.php
+++ b/program/lib/Roundcube/rcube_user.php
@@ -495,9 +495,9 @@ class rcube_user
"INSERT INTO ".$dbh->table_name('users').
" (created, last_login, username, mail_host, language)".
" VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
- $data['user'],
- $data['host'],
- $data['language']);
+ strip_newlines($data['user']),
+ strip_newlines($data['host']),
+ strip_newlines($data['language']));
if ($user_id = $dbh->insert_id('users')) {
// create rcube_user instance to make plugin hooks work
@@ -517,7 +517,7 @@ class rcube_user
if (empty($user_email)) {
$user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
}
- $email_list[] = $user_email;
+ $email_list[] = strip_newlines($user_email);
}
// identities_level check
else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
@@ -547,6 +547,7 @@ class rcube_user
$record['name'] = $user_name != $record['email'] ? $user_name : '';
}
+ $record['name'] = strip_newlines($record['name']);
$record['user_id'] = $user_id;
$record['standard'] = $standard;
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index 1d76ae508..4dadbb8bd 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -400,7 +400,7 @@ class rcube_utils
$out = array();
$src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
- foreach (array_keys($src) as $key) {
+ foreach ($src as $key => $value) {
$fname = $key[0] == '_' ? substr($key, 1) : $key;
if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
$out[$fname] = self::get_input_value($key, $mode);
@@ -476,9 +476,9 @@ class rcube_utils
// remove html comments and add #container to each tag selector.
// also replace body definition because we also stripped off the <body> tag
- $styles = preg_replace(
+ $source = preg_replace(
array(
- '/(^\s*<!--)|(-->\s*$)/',
+ '/(^\s*<\!--)|(-->\s*$)/m',
'/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
'/'.preg_quote($container_id, '/').'\s+body/i',
),
@@ -490,9 +490,9 @@ class rcube_utils
$source);
// put block contents back in
- $styles = $replacements->resolve($styles);
+ $source = $replacements->resolve($source);
- return $styles;
+ return $source;
}
@@ -506,24 +506,17 @@ class rcube_utils
*/
public static function file2class($mimetype, $filename)
{
- $mimetype = strtolower($mimetype);
- $filename = strtolower($filename);
-
list($primary, $secondary) = explode('/', $mimetype);
$classes = array($primary ? $primary : 'unknown');
-
if ($secondary) {
$classes[] = $secondary;
}
-
- if (preg_match('/\.([a-z0-9]+)$/', $filename, $m)) {
- if (!in_array($m[1], $classes)) {
- $classes[] = $m[1];
- }
+ if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
+ $classes[] = $m[1];
}
- return join(" ", $classes);
+ return strtolower(join(" ", $classes));
}
@@ -666,21 +659,6 @@ class rcube_utils
/**
- * Returns the real remote IP address
- *
- * @return string Remote IP address
- */
- public static function remote_addr()
- {
- foreach (array('HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','REMOTE_ADDR') as $prop) {
- if (!empty($_SERVER[$prop]))
- return $_SERVER[$prop];
- }
-
- return '';
- }
-
- /**
* Read a specific HTTP request header.
*
* @param string $name Header name
@@ -761,9 +739,9 @@ class rcube_utils
// Clean malformed data
$date = preg_replace(
array(
- '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal
- '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters
- '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names
+ '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal
+ '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters
+ '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names
),
array(
'\\1',
@@ -771,8 +749,6 @@ class rcube_utils
'',
), $date);
- $date = trim($date);
-
// if date parsing fails, we have a date in non-rfc format.
// remove token from the end and try again
while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
@@ -787,44 +763,6 @@ class rcube_utils
return (int) $ts;
}
- /**
- * Date parsing function that turns the given value into a DateTime object
- *
- * @param string $date Date string
- *
- * @return object DateTime instance or false on failure
- */
- public static function anytodatetime($date)
- {
- if (is_object($date) && is_a($date, 'DateTime')) {
- return $date;
- }
-
- $dt = false;
- $date = trim($date);
-
- // try to parse string with DateTime first
- if (!empty($date)) {
- try {
- $dt = new DateTime($date);
- }
- catch (Exception $e) {
- // ignore
- }
- }
-
- // try our advanced strtotime() method
- if (!$dt && ($timestamp = self::strtotime($date))) {
- try {
- $dt = new DateTime("@".$timestamp);
- }
- catch (Exception $e) {
- // ignore
- }
- }
-
- return $dt;
- }
/*
* Idn_to_ascii wrapper.
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index d54dc56ad..f76c4f08d 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -47,7 +47,6 @@ class rcube_vcard
'manager' => 'X-MANAGER',
'spouse' => 'X-SPOUSE',
'edit' => 'X-AB-EDIT',
- 'groups' => 'CATEGORIES',
);
private $typemap = array(
'IPHONE' => 'mobile',
@@ -358,8 +357,8 @@ class rcube_vcard
case 'birthday':
case 'anniversary':
- if (($val = rcube_utils::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) {
- $this->raw[$fn][] = array(0 => $val->format('Y-m-d'), 'value' => array('date'));
+ if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) {
+ $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
}
break;
@@ -482,7 +481,7 @@ class rcube_vcard
$vcard_block = '';
$in_vcard_block = false;
- foreach (preg_split("/[\r\n]+/", $data) as $line) {
+ foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
if ($in_vcard_block && !empty($line)) {
$vcard_block .= $line . "\n";
}
@@ -612,8 +611,8 @@ class rcube_vcard
$enc = null;
foreach($regs2[1] as $attrid => $attr) {
+ $attr = preg_replace('/[\s\t\n\r\0\x0B]/', '', $attr);
if ((list($key, $value) = explode('=', $attr)) && $value) {
- $value = trim($value);
if ($key == 'ENCODING') {
$value = strtoupper($value);
// add next line(s) to value string if QP line end detected
@@ -757,7 +756,7 @@ class rcube_vcard
*
* @return string Joined and quoted string
*/
- public static function vcard_quote($s, $sep = ';')
+ private static function vcard_quote($s, $sep = ';')
{
if (is_array($s)) {
foreach($s as $part) {
@@ -766,7 +765,7 @@ class rcube_vcard
return(implode($sep, (array)$r));
}
- return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep));
+ return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
}
/**
@@ -792,7 +791,7 @@ class rcube_vcard
return $result;
}
- $s = strtr($s, $rep2);
+ $s = trim(strtr($s, $rep2));
}
// some implementations (GMail) use non-standard backslash before colon (#1489085)
diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
index 8f7fe9749..f964f8b35 100644
--- a/program/lib/Roundcube/rcube_washtml.php
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -410,25 +410,6 @@ class rcube_washtml
);
$html = preg_replace($html_search, $html_replace, trim($html));
- //-> Replace all of those weird MS Word quotes and other high characters
- $badwordchars=array(
- "\xe2\x80\x98", // left single quote
- "\xe2\x80\x99", // right single quote
- "\xe2\x80\x9c", // left double quote
- "\xe2\x80\x9d", // right double quote
- "\xe2\x80\x94", // em dash
- "\xe2\x80\xa6" // elipses
- );
- $fixedwordchars=array(
- "'",
- "'",
- '"',
- '"',
- '&mdash;',
- '...'
- );
- $html = str_replace($badwordchars,$fixedwordchars, $html);
-
// PCRE errors handling (#1486856), should we use something like for every preg_* use?
if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
$errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
@@ -448,7 +429,7 @@ class rcube_washtml
}
// fix (unknown/malformed) HTML tags before "wash"
- $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html);
+ $html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html);
// Remove invalid HTML comments (#1487759)
// Don't remove valid conditional comments