summaryrefslogtreecommitdiff
path: root/program/lib/Roundcube
diff options
context:
space:
mode:
Diffstat (limited to 'program/lib/Roundcube')
-rw-r--r--program/lib/Roundcube/html.php8
-rw-r--r--program/lib/Roundcube/rcube_addressbook.php27
-rw-r--r--program/lib/Roundcube/rcube_contacts.php4
-rw-r--r--program/lib/Roundcube/rcube_html2text.php4
-rw-r--r--program/lib/Roundcube/rcube_ldap.php73
-rw-r--r--program/lib/Roundcube/rcube_mime.php2
-rw-r--r--program/lib/Roundcube/rcube_plugin_api.php5
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_atd.php12
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_enchant.php18
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_engine.php7
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_googie.php36
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_pspell.php29
-rw-r--r--program/lib/Roundcube/rcube_spellchecker.php50
-rw-r--r--program/lib/Roundcube/rcube_user.php12
-rw-r--r--program/lib/Roundcube/rcube_utils.php82
-rw-r--r--program/lib/Roundcube/rcube_vcard.php31
16 files changed, 312 insertions, 88 deletions
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index 5911c04d7..f6f744cb2 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2011, The Roundcube Dev Team |
+ | Copyright (C) 2005-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -32,8 +32,8 @@ class html
public static $doctype = 'xhtml';
public static $lc_tags = true;
- public static $common_attrib = array('id','class','style','title','align');
- public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
+ public static $common_attrib = array('id','class','style','title','align','unselectable');
+ public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script');
/**
@@ -645,7 +645,7 @@ class html_select extends html
$option_content = self::quote($option_content);
}
- $this->content .= self::tag('option', $attr + $option, $option_content, array('class','style','title','disabled'));
+ $this->content .= self::tag('option', $attr + $option, $option_content, array('value','label','class','style','title','disabled','selected'));
}
return parent::show();
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index 6e2b439d8..886f65cb9 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -209,6 +209,7 @@ abstract class rcube_addressbook
public function validate(&$save_data, $autofix = false)
{
$rcube = rcube::get_instance();
+ $valid = true;
// check validity of email addresses
foreach ($this->get_col_values('email', $save_data, true) as $email) {
@@ -216,12 +217,28 @@ abstract class rcube_addressbook
if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
$error = $rcube->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
$this->set_error(self::ERROR_VALIDATE, $error);
- return false;
+ $valid = false;
+ break;
}
}
}
- return true;
+ // allow plugins to do contact validation and auto-fixing
+ $plugin = $rcube->plugins->exec_hook('contact_validate', array(
+ 'record' => $save_data,
+ 'autofix' => $autofix,
+ 'valid' => $valid,
+ ));
+
+ if ($valid && !$plugin['valid']) {
+ $this->set_error(self::ERROR_VALIDATE, $plugin['error']);
+ }
+
+ if (is_array($plugin['record'])) {
+ $save_data = $plugin['record'];
+ }
+
+ return $plugin['valid'];
}
/**
@@ -515,8 +532,12 @@ abstract class rcube_addressbook
$fn = join(' ', array($contact['surname'], $contact['firstname'], $contact['middlename']));
else if ($compose_mode == 1)
$fn = join(' ', array($contact['firstname'], $contact['middlename'], $contact['surname']));
- else
+ else if ($compose_mode == 0)
$fn = !empty($contact['name']) ? $contact['name'] : join(' ', array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']));
+ else {
+ $plugin = rcube::get_instance()->plugins->exec_hook('contact_listname', array('contact' => $contact));
+ $fn = $plugin['fn'];
+ }
$fn = trim($fn, ', ');
diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index 6d01368a1..5c9e5ab39 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -592,8 +592,8 @@ class rcube_contacts extends rcube_addressbook
// validate e-mail addresses
$valid = parent::validate($save_data, $autofix);
- // require at least one e-mail address (syntax check is already done)
- if ($valid && !array_filter($this->get_col_values('email', $save_data, true))) {
+ // require at least one email address or a name
+ if ($valid && !strlen($save_data['firstname'].$save_data['surname'].$save_data['name']) && !array_filter($this->get_col_values('email', $save_data, true))) {
$this->set_error(self::ERROR_VALIDATE, 'noemailwarning');
$valid = false;
}
diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
index 6f79e2f8e..01362e6fb 100644
--- a/program/lib/Roundcube/rcube_html2text.php
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -608,7 +608,7 @@ class rcube_html2text
$this->width = $p_width;
// Add citation markers and create <pre> block
- $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_ballback'), trim($body));
+ $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_callback'), trim($body));
$body = '<pre>' . htmlspecialchars($body) . '</pre>';
$text = substr_replace($text, $body . "\n", $start, $end + 13 - $start);
@@ -624,7 +624,7 @@ class rcube_html2text
/**
* Callback function to correctly add citation markers for blockquote contents
*/
- public function blockquote_citation_ballback($m)
+ public function blockquote_citation_callback($m)
{
$line = ltrim($m[2]);
$space = $line[0] == '>' ? '' : ' ';
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index 64288f973..b733e2465 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -52,7 +52,7 @@ class rcube_ldap extends rcube_addressbook
*
* @var array
*/
- private static $group_types = array(
+ private $group_types = array(
'group' => 'member',
'groupofnames' => 'member',
'kolabgroupofnames' => 'member',
@@ -94,6 +94,9 @@ class rcube_ldap extends rcube_addressbook
$this->prop['groups']['name_attr'] = 'cn';
if (empty($this->prop['groups']['scope']))
$this->prop['groups']['scope'] = 'sub';
+ // extend group objectclass => member attribute mapping
+ if (!empty($this->prop['groups']['class_member_attr']))
+ $this->group_types = array_merge($this->group_types, $this->prop['groups']['class_member_attr']);
// add group name attrib to the list of attributes to be fetched
$fetch_attributes[] = $this->prop['groups']['name_attr'];
@@ -292,6 +295,14 @@ class rcube_ldap extends rcube_addressbook
if ($this->prop['search_base_dn'] && $this->prop['search_filter']
&& (strstr($bind_dn, '%dn') || strstr($this->base_dn, '%dn') || strstr($this->groups_base_dn, '%dn'))
) {
+ $search_attribs = array('uid');
+ if ($search_bind_attrib = (array)$this->prop['search_bind_attrib']) {
+ foreach ($search_bind_attrib as $r => $attr) {
+ $search_attribs[] = $attr;
+ $replaces[$r] = '';
+ }
+ }
+
$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);
@@ -321,10 +332,18 @@ class rcube_ldap extends rcube_addressbook
}
}
- $res = $ldap->search($search_base_dn, $search_filter, 'sub', array('uid'));
+ $res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs);
if ($res) {
$res->rewind();
$replaces['%dn'] = $res->get_dn();
+
+ // add more replacements from 'search_bind_attrib' config
+ if ($search_bind_attrib) {
+ $res = $res->current();
+ foreach ($search_bind_attrib as $r => $attr) {
+ $replaces[$r] = $res[$attr][0];
+ }
+ }
}
if ($ldap != $this->ldap) {
@@ -355,6 +374,23 @@ class rcube_ldap extends rcube_addressbook
$this->base_dn = strtr($this->base_dn, $replaces);
$this->groups_base_dn = strtr($this->groups_base_dn, $replaces);
+ // replace placeholders in filter settings
+ if (!empty($this->prop['filter']))
+ $this->prop['filter'] = strtr($this->prop['filter'], $replaces);
+ if (!empty($this->prop['groups']['filter']))
+ $this->prop['groups']['filter'] = strtr($this->prop['groups']['filter'], $replaces);
+ if (!empty($this->prop['groups']['member_filter']))
+ $this->prop['groups']['member_filter'] = strtr($this->prop['groups']['member_filter'], $replaces);
+
+ if (!empty($this->prop['group_filters'])) {
+ foreach ($this->prop['group_filters'] as $i => $gf) {
+ if (!empty($gf['base_dn']))
+ $this->prop['group_filters'][$i]['base_dn'] = strtr($gf['base_dn'], $replaces);
+ if (!empty($gf['filter']))
+ $this->prop['group_filters'][$i]['filter'] = strtr($gf['filter'], $replaces);
+ }
+ }
+
if (empty($bind_user)) {
$bind_user = $u;
}
@@ -559,9 +595,10 @@ class rcube_ldap extends rcube_addressbook
/**
* Get all members of the given group
*
- * @param string Group DN
- * @param array Group entries (if called recursively)
- * @return array Accumulated group members
+ * @param string Group DN
+ * @param boolean Count only
+ * @param array Group entries (if called recursively)
+ * @return array Accumulated group members
*/
function list_group_members($dn, $count = false, $entries = null)
{
@@ -569,7 +606,7 @@ class rcube_ldap extends rcube_addressbook
// fetch group object
if (empty($entries)) {
- $attribs = array('dn','objectClass','member','uniqueMember','memberURL');
+ $attribs = array_merge(array('dn','objectClass','memberURL'), array_values($this->group_types));
$entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs);
if ($entries === false) {
return $group_members;
@@ -581,17 +618,17 @@ class rcube_ldap extends rcube_addressbook
$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), ''))
+ 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;
}
+ else if (!empty($entry['memberurl'])) {
+ $members = $this->_list_group_memberurl($dn, $entry, $count);
+ $group_members = array_merge($group_members, $members);
+ }
if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) {
break 2;
@@ -608,6 +645,7 @@ class rcube_ldap extends rcube_addressbook
* @param string Group DN
* @param array Group entry
* @param string Member attribute to use
+ * @param boolean Count only
* @return array Accumulated group members
*/
private function _list_group_members($dn, $entry, $attr, $count)
@@ -621,8 +659,7 @@ class rcube_ldap extends rcube_addressbook
// read these attributes for all members
$attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes'];
- $attrib[] = 'member';
- $attrib[] = 'uniqueMember';
+ $attrib = array_merge($attrib, array_values($this->group_types));
$attrib[] = 'memberURL';
$filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)';
@@ -669,7 +706,7 @@ class rcube_ldap extends rcube_addressbook
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)))
+ if ($this->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];
@@ -1354,7 +1391,7 @@ class rcube_ldap extends rcube_addressbook
$out[$this->primary_key] = self::dn_encode($rec['dn']);
// determine record type
- if (self::is_group_entry($rec)) {
+ if ($this->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'];
@@ -1479,11 +1516,11 @@ class rcube_ldap extends rcube_addressbook
/**
* Determines whether the given LDAP entry is a group record
*/
- private static function is_group_entry($entry)
+ private function is_group_entry($entry)
{
$classes = array_map('strtolower', (array)$entry['objectclass']);
- return count(array_intersect(array_keys(self::$group_types), $classes)) > 0;
+ return count(array_intersect(array_keys($this->group_types), $classes)) > 0;
}
/**
@@ -1914,7 +1951,7 @@ class rcube_ldap extends rcube_addressbook
if (!empty($object_classes)) {
foreach ((array)$object_classes as $oc) {
- if ($attr = self::$group_types[strtolower($oc)]) {
+ if ($attr = $this->group_types[strtolower($oc)]) {
return $attr;
}
}
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 9c2220328..a931c27c1 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -810,7 +810,7 @@ class rcube_mime
}
$mime_types = $mime_extensions = array();
- $regex = "/([\w\+\-\.\/]+)\t+([\w\s]+)/i";
+ $regex = "/([\w\+\-\.\/]+)\s+([\w\s]+)/i";
foreach((array)$lines as $line) {
// skip comments or mime types w/o any extensions
if ($line[0] == '#' || !preg_match($regex, $line, $matches))
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 5a25ada02..2258f1486 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();
diff --git a/program/lib/Roundcube/rcube_spellcheck_atd.php b/program/lib/Roundcube/rcube_spellcheck_atd.php
index 68e8b7cb8..9f073f56f 100644
--- a/program/lib/Roundcube/rcube_spellcheck_atd.php
+++ b/program/lib/Roundcube/rcube_spellcheck_atd.php
@@ -39,6 +39,18 @@ class rcube_spellcheck_atd extends rcube_spellcheck_engine
);
/**
+ * 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()
diff --git a/program/lib/Roundcube/rcube_spellcheck_enchant.php b/program/lib/Roundcube/rcube_spellcheck_enchant.php
index a22251e00..14d6fff46 100644
--- a/program/lib/Roundcube/rcube_spellcheck_enchant.php
+++ b/program/lib/Roundcube/rcube_spellcheck_enchant.php
@@ -31,6 +31,24 @@ class rcube_spellcheck_enchant extends rcube_spellcheck_engine
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()
diff --git a/program/lib/Roundcube/rcube_spellcheck_engine.php b/program/lib/Roundcube/rcube_spellcheck_engine.php
index 88e10ac05..3cb4ca3de 100644
--- a/program/lib/Roundcube/rcube_spellcheck_engine.php
+++ b/program/lib/Roundcube/rcube_spellcheck_engine.php
@@ -43,6 +43,13 @@ abstract class rcube_spellcheck_engine
}
/**
+ * 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
diff --git a/program/lib/Roundcube/rcube_spellcheck_googie.php b/program/lib/Roundcube/rcube_spellcheck_googie.php
index 70507dc23..3777942a6 100644
--- a/program/lib/Roundcube/rcube_spellcheck_googie.php
+++ b/program/lib/Roundcube/rcube_spellcheck_googie.php
@@ -26,13 +26,28 @@
*/
class rcube_spellcheck_googie extends rcube_spellcheck_engine
{
- const GOOGLE_HOST = 'ssl://www.google.com';
- const GOOGLE_PORT = 443;
+ 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()
@@ -52,25 +67,25 @@ class rcube_spellcheck_googie extends rcube_spellcheck_engine
$path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang;
}
else {
- $host = self::GOOGLE_HOST;
- $port = self::GOOGLE_PORT;
+ $host = self::GOOGIE_HOST;
+ $port = self::GOOGIE_PORT;
$path = '/tbproxy/spell?lang=' . $this->lang;
}
- // Google has some problem with spaces, use \n instead
- $gtext = str_replace(' ', "\n", $text);
+ $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>' . $gtext . '</text>'
+ .'<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: application/x-www-form-urlencoded\r\n";
+ $out .= "Content-Type: text/xml\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= $gtext;
fwrite($fp, $out);
@@ -83,8 +98,10 @@ class rcube_spellcheck_googie extends rcube_spellcheck_engine
// parse HTTP response
if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) {
$http_status = $m[1];
- if ($http_status != '200')
+ if ($http_status != '200') {
$this->error = 'HTTP ' . $m[1] . $m[2];
+ $this->error .= "\n" . $store;
+ }
}
if (!$store) {
@@ -92,6 +109,7 @@ class rcube_spellcheck_googie extends rcube_spellcheck_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);
diff --git a/program/lib/Roundcube/rcube_spellcheck_pspell.php b/program/lib/Roundcube/rcube_spellcheck_pspell.php
index ce089ed8f..b12684e43 100644
--- a/program/lib/Roundcube/rcube_spellcheck_pspell.php
+++ b/program/lib/Roundcube/rcube_spellcheck_pspell.php
@@ -30,6 +30,35 @@ class rcube_spellcheck_pspell extends rcube_spellcheck_engine
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()
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index 31835dbb5..3182ff378 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -65,6 +65,52 @@ class rcube_spellchecker
}
}
+ /**
+ * Return a list of supported languages
+ */
+ function languages()
+ {
+ // trust configuration
+ $configured = $this->rc->config->get('spellcheck_languages');
+ if (!empty($configured) && is_array($configured) && !$configured[0]) {
+ return $configured;
+ }
+ else if (!empty($configured)) {
+ $langs = (array)$configured;
+ }
+ else if ($this->backend) {
+ $langs = $this->backend->languages();
+ }
+
+ // load index
+ @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
+
+ // add correct labels
+ $languages = array();
+ foreach ($langs as $lang) {
+ $langc = strtolower(substr($lang, 0, 2));
+ $alias = $rcube_language_aliases[$langc];
+ if (!$alias) {
+ $alias = $langc.'_'.strtoupper($langc);
+ }
+ if ($rcube_languages[$lang]) {
+ $languages[$lang] = $rcube_languages[$lang];
+ }
+ else if ($rcube_languages[$alias]) {
+ $languages[$lang] = $rcube_languages[$alias];
+ }
+ else {
+ $languages[$lang] = ucfirst($lang);
+ }
+ }
+
+ // remove possible duplicates (#1489395)
+ $languages = array_unique($languages);
+
+ asort($languages);
+
+ return $languages;
+ }
/**
* Set content and check spelling
@@ -152,7 +198,7 @@ class rcube_spellchecker
// send output
$out = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">';
- foreach ($this->matches as $item) {
+ foreach ((array)$this->matches as $item) {
$out .= '<c o="'.$item[1].'" l="'.$item[2].'">';
$out .= is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
$out .= '</c>';
@@ -173,7 +219,7 @@ class rcube_spellchecker
{
$result = array();
- foreach ($this->matches as $item) {
+ foreach ((array)$this->matches as $item) {
if ($this->engine == 'pspell') {
$word = $item[0];
}
diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php
index 5e9c9af80..57f63361d 100644
--- a/program/lib/Roundcube/rcube_user.php
+++ b/program/lib/Roundcube/rcube_user.php
@@ -163,8 +163,16 @@ class rcube_user
if (!$this->ID)
return false;
- $config = $this->rc->config;
- $old_prefs = (array)$this->get_prefs();
+ $plugin = $this->rc->plugins->exec_hook('preferences_update', array(
+ 'userid' => $this->ID, 'prefs' => $a_user_prefs, 'old' => (array)$this->get_prefs()));
+
+ if (!empty($plugin['abort'])) {
+ return;
+ }
+
+ $a_user_prefs = $plugin['prefs'];
+ $old_prefs = $plugin['old'];
+ $config = $this->rc->config;
// merge (partial) prefs array with existing settings
$save_prefs = $a_user_prefs + $old_prefs;
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index b73bc0812..27a618d83 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -454,6 +454,9 @@ class rcube_utils
// cut out all contents between { and }
while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
+ $nested = strpos($source, '{', $pos+1);
+ if ($nested && $nested < $pos2) // when dealing with nested blocks (e.g. @media), take the inner one
+ $pos = $nested;
$length = $pos2 - $pos - 1;
$styles = substr($source, $pos+1, $length);
@@ -744,40 +747,13 @@ class rcube_utils
*/
public static function strtotime($date)
{
- $date = trim($date);
-
- // check for MS Outlook vCard date format YYYYMMDD
- if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) {
- return mktime(0,0,0, intval($m[2]), intval($m[3]), intval($m[1]));
- }
-
- // common little-endian formats, e.g. dd/mm/yyyy (not all are supported by strtotime)
- if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})$/', $date, $m)
- && $m[1] > 0 && $m[1] <= 31 && $m[2] > 0 && $m[2] <= 12 && $m[3] >= 1970
- ) {
- return mktime(0,0,0, intval($m[2]), intval($m[1]), intval($m[3]));
- }
+ $date = self::clean_datestr($date);
// unix timestamp
if (is_numeric($date)) {
return (int) $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);
-
// 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)) {
@@ -805,8 +781,8 @@ class rcube_utils
return $date;
}
- $dt = false;
- $date = trim($date);
+ $dt = false;
+ $date = self::clean_datestr($date);
// try to parse string with DateTime first
if (!empty($date)) {
@@ -831,6 +807,52 @@ class rcube_utils
return $dt;
}
+ /**
+ * Clean up date string for strtotime() input
+ *
+ * @param string $date Date string
+ *
+ * @return string Date string
+ */
+ public static function clean_datestr($date)
+ {
+ $date = trim($date);
+
+ // check for MS Outlook vCard date format YYYYMMDD
+ if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) {
+ return sprintf('%04d-%02d-%02d 00:00:00', intval($m[1]), intval($m[2]), intval($m[3]));
+ }
+
+ // 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);
+
+ // try to fix dd/mm vs. mm/dd discrepancy, we can't do more here
+ if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})$/', $date, $m)) {
+ $mdy = $m[2] > 12 && $m[1] <= 12;
+ $day = $mdy ? $m[2] : $m[1];
+ $month = $mdy ? $m[1] : $m[2];
+ $date = sprintf('%04d-%02d-%02d 00:00:00', intval($m[3]), $month, $day);
+ }
+ // I've found that YYYY.MM.DD is recognized wrong, so here's a fix
+ else if (preg_match('/^(\d{4})\.(\d{1,2})\.(\d{1,2})$/', $date)) {
+ $date = str_replace('.', '-', $date) . ' 00:00:00';
+ }
+
+ return $date;
+ }
+
/*
* Idn_to_ascii wrapper.
* Intl/Idn modules version of this function doesn't work with e-mail address
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index d54dc56ad..5f74ccbd4 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -518,20 +518,28 @@ class rcube_vcard
*/
public static function cleanup($vcard)
{
- // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
- $vcard = preg_replace(
- '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
- '\2;type=\5\3:\4',
- $vcard);
-
// convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
$vcard = preg_replace_callback(
'/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
array('self', 'x_abrelatednames_callback'),
$vcard);
- // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
- $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
+ // Cleanup
+ $vcard = preg_replace(array(
+ // convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
+ '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
+ '/^item\d*\.X-AB.*$/m', // remove cruft like item1.X-AB*
+ '/^item\d*\./m', // remove item1.ADR instead of ADR
+ '/\n+/', // remove empty lines
+ '/^(N:[^;\R]*)$/m', // if N doesn't have any semicolons, add some
+ ),
+ array(
+ '\2;type=\5\3:\4',
+ '',
+ '',
+ "\n",
+ '\1;;;;',
+ ), $vcard);
// convert X-WAB-GENDER to X-GENDER
if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
@@ -539,9 +547,6 @@ class rcube_vcard
$vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
}
- // if N doesn't have any semicolons, add some
- $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
-
return $vcard;
}
@@ -612,8 +617,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
@@ -792,7 +797,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)