lib/ext lib/kolab_sync_body_converter.php lib/kolab_sync_data_email.php
Aleksander Machniak
machniak at kolabsys.com
Thu Jan 17 15:02:47 CET 2013
lib/ext/Roundcube/bootstrap.php | 21
lib/ext/Roundcube/html.php | 22
lib/ext/Roundcube/rcube.php | 21
lib/ext/Roundcube/rcube_addressbook.php | 57 -
lib/ext/Roundcube/rcube_base_replacer.php | 3
lib/ext/Roundcube/rcube_browser.php | 3
lib/ext/Roundcube/rcube_cache.php | 3
lib/ext/Roundcube/rcube_charset.php | 88 +
lib/ext/Roundcube/rcube_config.php | 3
lib/ext/Roundcube/rcube_contacts.php | 28
lib/ext/Roundcube/rcube_content_filter.php | 3
lib/ext/Roundcube/rcube_csv2vcard.php | 54 -
lib/ext/Roundcube/rcube_db.php | 13
lib/ext/Roundcube/rcube_db_mssql.php | 4
lib/ext/Roundcube/rcube_db_mysql.php | 7
lib/ext/Roundcube/rcube_db_pgsql.php | 4
lib/ext/Roundcube/rcube_db_sqlite.php | 11
lib/ext/Roundcube/rcube_db_sqlsrv.php | 4
lib/ext/Roundcube/rcube_enriched.php | 4
lib/ext/Roundcube/rcube_html2text.php | 691 +++++++++++++
lib/ext/Roundcube/rcube_image.php | 31
lib/ext/Roundcube/rcube_imap.php | 27
lib/ext/Roundcube/rcube_imap_cache.php | 4
lib/ext/Roundcube/rcube_imap_generic.php | 41
lib/ext/Roundcube/rcube_ldap.php | 22
lib/ext/Roundcube/rcube_message.php | 69 -
lib/ext/Roundcube/rcube_message_header.php | 22
lib/ext/Roundcube/rcube_message_part.php | 4
lib/ext/Roundcube/rcube_mime.php | 7
lib/ext/Roundcube/rcube_output.php | 4
lib/ext/Roundcube/rcube_plugin.php | 649 ++++++------
lib/ext/Roundcube/rcube_plugin_api.php | 893 ++++++++--------
lib/ext/Roundcube/rcube_result_index.php | 4
lib/ext/Roundcube/rcube_result_set.php | 4
lib/ext/Roundcube/rcube_result_thread.php | 4
lib/ext/Roundcube/rcube_session.php | 1162 +++++++++++-----------
lib/ext/Roundcube/rcube_smtp.php | 768 +++++++-------
lib/ext/Roundcube/rcube_spellchecker.php | 6
lib/ext/Roundcube/rcube_storage.php | 8
lib/ext/Roundcube/rcube_string_replacer.php | 327 +++---
lib/ext/Roundcube/rcube_user.php | 4
lib/ext/Roundcube/rcube_utils.php | 3
lib/ext/Roundcube/rcube_vcard.php | 1472 ++++++++++++++--------------
lib/ext/Roundcube/rcube_washtml.php | 451 ++++++++
lib/ext/html2text.php | 746 --------------
lib/kolab_sync_body_converter.php | 2
lib/kolab_sync_data_email.php | 2
47 files changed, 4171 insertions(+), 3609 deletions(-)
New commits:
commit a6efcf32ca8a96de397551b7497eaacccbb76991
Author: Aleksander Machniak <alec at alec.pl>
Date: Thu Jan 17 14:58:46 2013 +0100
Update Roundcube Framework
diff --git a/lib/ext/Roundcube/bootstrap.php b/lib/ext/Roundcube/bootstrap.php
index eed7db8..8cea481 100644
--- a/lib/ext/Roundcube/bootstrap.php
+++ b/lib/ext/Roundcube/bootstrap.php
@@ -2,10 +2,8 @@
/*
+-----------------------------------------------------------------------+
- | program/include/bootstrap.php |
- | |
| This file is part of the Roundcube PHP suite |
- | Copyright (C) 2005-2012, 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. |
@@ -13,7 +11,6 @@
| |
| CONTENTS: |
| Roundcube Framework Initialization |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
@@ -361,6 +358,22 @@ function format_email($email)
/**
+ * Fix version number so it can be used correctly in version_compare()
+ *
+ * @param string $version Version number string
+ *
+ * @param return Version number string
+ */
+function version_parse($version)
+{
+ return str_replace(
+ array('-stable', '-git'),
+ array('.0', '.99'),
+ $version);
+}
+
+
+/**
* mbstring replacement functions
*/
if (!extension_loaded('mbstring'))
diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php
index 5fb574b..522a823 100644
--- a/lib/ext/Roundcube/html.php
+++ b/lib/ext/Roundcube/html.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/html.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| Helper class to create valid XHTML code |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
@@ -172,7 +169,7 @@ class html
$attr = array('href' => $attr);
}
return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
- array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
+ array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
}
/**
@@ -678,7 +675,7 @@ class html_table extends html
}
$cell = new stdClass;
- $cell->attrib = $attr;
+ $cell->attrib = $attr;
$cell->content = $cont;
$this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
@@ -702,16 +699,16 @@ class html_table extends html
}
$cell = new stdClass;
- $cell->attrib = $attr;
- $cell->content = $cont;
+ $cell->attrib = $attr;
+ $cell->content = $cont;
$this->header[] = $cell;
}
- /**
+ /**
* Remove a column from a table
* Useful for plugins making alterations
- *
- * @param string $class
+ *
+ * @param string $class
*/
public function remove_column($class)
{
@@ -791,8 +788,9 @@ class html_table extends html
*/
public function show($attrib = null)
{
- if (is_array($attrib))
+ if (is_array($attrib)) {
$this->attrib = array_merge($this->attrib, $attrib);
+ }
$thead = $tbody = "";
@@ -834,7 +832,7 @@ class html_table extends html
*/
public function size()
{
- return count($this->rows);
+ return count($this->rows);
}
/**
diff --git a/lib/ext/Roundcube/rcube.php b/lib/ext/Roundcube/rcube.php
index cc4905a..a914ae6 100644
--- a/lib/ext/Roundcube/rcube.php
+++ b/lib/ext/Roundcube/rcube.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -1260,13 +1258,30 @@ class rcube
return $this->decrypt($_SESSION['password']);
}
}
+
+
+ /**
+ * Getter for logged user language code.
+ *
+ * @return string User language code
+ */
+ public function get_user_language()
+ {
+ if (is_object($this->user)) {
+ return $this->user->language;
+ }
+ else if (isset($_SESSION['language'])) {
+ return $_SESSION['language'];
+ }
+ }
}
/**
* Lightweight plugin API class serving as a dummy if plugins are not enabled
*
- * @package Core
+ * @package Framework
+ * @subpackage Core
*/
class rcube_dummy_plugin_api
{
diff --git a/lib/ext/Roundcube/rcube_addressbook.php b/lib/ext/Roundcube/rcube_addressbook.php
index ea8df70..4210627 100644
--- a/lib/ext/Roundcube/rcube_addressbook.php
+++ b/lib/ext/Roundcube/rcube_addressbook.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_addressbook.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2012, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| Interface to the local address book database |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
@@ -48,6 +45,7 @@ abstract class rcube_addressbook
public $sort_col = 'name';
public $sort_order = 'ASC';
public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1));
+ public $date_cols = array();
protected $error;
@@ -141,7 +139,7 @@ abstract class rcube_addressbook
*/
function get_error()
{
- return $this->error;
+ return $this->error;
}
/**
@@ -152,7 +150,7 @@ abstract class rcube_addressbook
*/
protected function set_error($type, $message)
{
- $this->error = array('type' => $type, 'message' => $message);
+ $this->error = array('type' => $type, 'message' => $message);
}
/**
@@ -225,7 +223,6 @@ abstract class rcube_addressbook
return true;
}
-
/**
* Create a new contact record
*
@@ -410,7 +407,6 @@ abstract class rcube_addressbook
return array();
}
-
/**
* Utility function to return all values of a certain data column
* either as flat list or grouped by subtype
@@ -443,7 +439,6 @@ abstract class rcube_addressbook
return $out;
}
-
/**
* Normalize the given string for fulltext search.
* Currently only optimized for Latin-1 characters; to be extended
@@ -491,7 +486,6 @@ abstract class rcube_addressbook
return $fn;
}
-
/**
* Compose the name to display in the contacts list for the given contact record.
* This respects the settings parameter how to list conacts.
@@ -529,5 +523,50 @@ abstract class rcube_addressbook
return $fn;
}
+ /**
+ * Compare search value with contact data
+ *
+ * @param string $colname Data name
+ * @param string|array $value Data value
+ * @param string $search Search value
+ * @param int $mode Search mode
+ *
+ * @return bool Comparision result
+ */
+ protected function compare_search_value($colname, $value, $search, $mode)
+ {
+ // The value is a date string, for date we'll
+ // 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::strtotime($value))
+ && ($search = rcube_utils::strtotime($search))
+ && date('Ymd', $value) == date('Ymd', $search));
+ }
+
+ // composite field, e.g. address
+ foreach ((array)$value as $val) {
+ $val = mb_strtolower($val);
+ switch ($mode) {
+ case 1:
+ $got = ($val == $search);
+ break;
+
+ case 2:
+ $got = ($search == substr($val, 0, strlen($search)));
+ break;
+
+ default:
+ $got = (strpos($val, $search) !== false);
+ }
+
+ if ($got) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
}
diff --git a/lib/ext/Roundcube/rcube_base_replacer.php b/lib/ext/Roundcube/rcube_base_replacer.php
index b2a0fc1..fcd85c2 100644
--- a/lib/ext/Roundcube/rcube_base_replacer.php
+++ b/lib/ext/Roundcube/rcube_base_replacer.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_base_replacer.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| Provide basic functions for base URL replacement |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_browser.php b/lib/ext/Roundcube/rcube_browser.php
index 154e7ef..d10fe2a 100644
--- a/lib/ext/Roundcube/rcube_browser.php
+++ b/lib/ext/Roundcube/rcube_browser.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_browser.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2007-2009, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| Class representing the client browser's properties |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_cache.php b/lib/ext/Roundcube/rcube_cache.php
index 3e1ce4f..92f12a8 100644
--- a/lib/ext/Roundcube/rcube_cache.php
+++ b/lib/ext/Roundcube/rcube_cache.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_cache.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
@@ -14,7 +12,6 @@
| |
| PURPOSE: |
| Caching engine |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
diff --git a/lib/ext/Roundcube/rcube_charset.php b/lib/ext/Roundcube/rcube_charset.php
index 6135a57..a7f26a3 100644
--- a/lib/ext/Roundcube/rcube_charset.php
+++ b/lib/ext/Roundcube/rcube_charset.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_charset.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -15,7 +13,6 @@
| |
| PURPOSE: |
| Provide charset conversion functionality |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
@@ -649,12 +646,13 @@ class rcube_charset
/**
* A method to guess character set of a string.
*
- * @param string $string String.
- * @param string $failover Default result for failover.
+ * @param string $string String
+ * @param string $failover Default result for failover
+ * @param string $language User language
*
* @return string Charset name
*/
- public static function detect($string, $failover='')
+ public static function detect($string, $failover = null, $language = null)
{
if (substr($string, 0, 4) == "\0\0\xFE\xFF") return 'UTF-32BE'; // Big Endian
if (substr($string, 0, 4) == "\xFF\xFE\0\0") return 'UTF-32LE'; // Little Endian
@@ -669,38 +667,62 @@ class rcube_charset
if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE';
if (function_exists('mb_detect_encoding')) {
- // FIXME: the order is important, because sometimes
- // iso string is detected as euc-jp and etc.
- $enc = array(
- 'UTF-8', 'SJIS', 'GB2312',
- 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
- 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
- 'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
- 'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 'BIG5',
- 'ISO-2022-KR', 'ISO-2022-JP',
- );
+ if (empty($language)) {
+ $rcube = rcube::get_instance();
+ $language = $rcube->get_user_language();
+ }
+
+ // Prioritize charsets according to current language (#1485669)
+ switch ($language) {
+ case 'ja_JP': // for Japanese
+ $prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win');
+ break;
+
+ case 'zh_CN': // for Chinese (Simplified)
+ case 'zh_TW': // for Chinese (Traditional)
+ $prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW');
+ break;
+
+ case 'ko_KR': // for Korean
+ $prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR');
+ break;
- $result = mb_detect_encoding($string, join(',', $enc));
+ case 'ru_RU': // for Russian
+ $prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R');
+ break;
+
+ default:
+ $prio = array('UTF-8', 'SJIS', 'GB2312',
+ 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
+ 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
+ 'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
+ 'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 'BIG-5',
+ 'ISO-2022-KR', 'ISO-2022-JP',
+ );
+ }
+
+ $encodings = array_unique(array_merge($prio, mb_list_encodings()));
+
+ return mb_detect_encoding($string, $encodings);
}
- else {
- // No match, check for UTF-8
- // from http://w3.org/International/questions/qa-forms-utf-8.html
- if (preg_match('/\A(
- [\x09\x0A\x0D\x20-\x7E]
- | [\xC2-\xDF][\x80-\xBF]
- | \xE0[\xA0-\xBF][\x80-\xBF]
- | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
- | \xED[\x80-\x9F][\x80-\xBF]
- | \xF0[\x90-\xBF][\x80-\xBF]{2}
- | [\xF1-\xF3][\x80-\xBF]{3}
- | \xF4[\x80-\x8F][\x80-\xBF]{2}
- )*\z/xs', substr($string, 0, 2048))
- ) {
+
+ // No match, check for UTF-8
+ // from http://w3.org/International/questions/qa-forms-utf-8.html
+ if (preg_match('/\A(
+ [\x09\x0A\x0D\x20-\x7E]
+ | [\xC2-\xDF][\x80-\xBF]
+ | \xE0[\xA0-\xBF][\x80-\xBF]
+ | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
+ | \xED[\x80-\x9F][\x80-\xBF]
+ | \xF0[\x90-\xBF][\x80-\xBF]{2}
+ | [\xF1-\xF3][\x80-\xBF]{3}
+ | \xF4[\x80-\x8F][\x80-\xBF]{2}
+ )*\z/xs', substr($string, 0, 2048))
+ ) {
return 'UTF-8';
- }
}
- return $result ? $result : $failover;
+ return $failover;
}
diff --git a/lib/ext/Roundcube/rcube_config.php b/lib/ext/Roundcube/rcube_config.php
index 615faf3..2190dc4 100644
--- a/lib/ext/Roundcube/rcube_config.php
+++ b/lib/ext/Roundcube/rcube_config.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_config.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| Class to read configuration settings |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_contacts.php b/lib/ext/Roundcube/rcube_contacts.php
index 5b4292a..c66e986 100644
--- a/lib/ext/Roundcube/rcube_contacts.php
+++ b/lib/ext/Roundcube/rcube_contacts.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_contacts.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2012, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| Interface to the local address book database |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
@@ -63,6 +60,7 @@ class rcube_contacts extends rcube_addressbook
'jobtitle', 'organization', 'department', 'assistant', 'manager',
'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
+ public $date_cols = array('birthday', 'anniversary');
const SEPARATOR = ',';
@@ -404,32 +402,16 @@ class rcube_contacts extends rcube_addressbook
for ($i=0; $i<$pages; $i++) {
$this->list_records(null, $i, true);
while ($row = $this->result->next()) {
- $id = $row[$this->primary_key];
+ $id = $row[$this->primary_key];
$found = array();
foreach (preg_grep($regexp, array_keys($row)) as $col) {
$pos = strpos($col, ':');
$colname = $pos ? substr($col, 0, $pos) : $col;
$search = $post_search[$colname];
foreach ((array)$row[$col] as $value) {
- // composite field, e.g. address
- foreach ((array)$value as $val) {
- $val = mb_strtolower($val);
- switch ($mode) {
- case 1:
- $got = ($val == $search);
- break;
- case 2:
- $got = ($search == substr($val, 0, strlen($search)));
- break;
- default:
- $got = (strpos($val, $search) !== false);
- break;
- }
-
- if ($got) {
- $found[$colname] = true;
- break 2;
- }
+ if ($this->compare_search_value($colname, $value, $search, $mode)) {
+ $found[$colname] = true;
+ break 2;
}
}
}
diff --git a/lib/ext/Roundcube/rcube_content_filter.php b/lib/ext/Roundcube/rcube_content_filter.php
index 99916a3..b814bb7 100644
--- a/lib/ext/Roundcube/rcube_content_filter.php
+++ b/lib/ext/Roundcube/rcube_content_filter.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_content_filter.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2011, The Roundcube Dev Team |
| |
@@ -13,7 +11,6 @@
| |
| PURPOSE: |
| PHP stream filter to detect evil content in mail attachments |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_csv2vcard.php b/lib/ext/Roundcube/rcube_csv2vcard.php
index 850c0c4..0d3276b 100644
--- a/lib/ext/Roundcube/rcube_csv2vcard.php
+++ b/lib/ext/Roundcube/rcube_csv2vcard.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_csv2vcard.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| |
@@ -126,6 +124,12 @@ class rcube_csv2vcard
//'work_address_2' => '',
'work_country' => 'country:work',
'work_zipcode' => 'zipcode:work',
+ 'last' => 'surname',
+ 'first' => 'firstname',
+ 'work_city' => 'locality:work',
+ 'work_state' => 'region:work',
+ 'home_city_short' => 'locality:home',
+ 'home_state_short' => 'region:home',
);
/**
@@ -273,13 +277,7 @@ class rcube_csv2vcard
// Parse file
foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) {
- $line = trim($line);
- if (empty($line)) {
- continue;
- }
-
- $elements = rcube_utils::explode_quoted_string(',', $line);
-
+ $elements = $this->parse_line($line);
if (empty($elements)) {
continue;
}
@@ -307,6 +305,35 @@ class rcube_csv2vcard
}
/**
+ * Parse CSV file line
+ */
+ protected function parse_line($line)
+ {
+ $line = trim($line);
+ if (empty($line)) {
+ return null;
+ }
+
+ $fields = rcube_utils::explode_quoted_string(',', $line);
+
+ // remove quotes if needed
+ if (!empty($fields)) {
+ foreach ($fields as $idx => $value) {
+ if (($len = strlen($value)) > 1 && $value[0] == '"' && $value[$len-1] == '"') {
+ // remove surrounding quotes
+ $value = substr($value, 1, -1);
+ // replace doubled quotes inside the string with single quote
+ $value = str_replace('""', '"', $value);
+
+ $fields[$idx] = $value;
+ }
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
* Parse CSV header line, detect fields mapping
*/
protected function parse_header($elements)
@@ -369,6 +396,15 @@ class rcube_csv2vcard
}
}
+ // Convert address(es) to rcube_vcard data
+ foreach ($contact as $idx => $value) {
+ $name = explode(':', $idx);
+ if (in_array($name[0], array('street', 'locality', 'region', 'zipcode', 'country'))) {
+ $contact['address:'.$name[1]][$name[0]] = $value;
+ unset($contact[$idx]);
+ }
+ }
+
// Create vcard object
$vcard = new rcube_vcard();
foreach ($contact as $name => $value) {
diff --git a/lib/ext/Roundcube/rcube_db.php b/lib/ext/Roundcube/rcube_db.php
index 2c471e7..086a38a 100644
--- a/lib/ext/Roundcube/rcube_db.php
+++ b/lib/ext/Roundcube/rcube_db.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_db.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -13,19 +11,17 @@
| |
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Database independent query interface.
* This is a wrapper for the PHP PDO.
*
* @package Framework
- * @sbpackage Database
+ * @subpackage Database
*/
class rcube_db
{
@@ -404,6 +400,11 @@ class rcube_db
$this->debug($query);
+ // destroy reference to previous result, required for SQLite driver (#1488874)
+ $this->last_result = null;
+ $this->db_error_msg = null;
+
+ // send query
$query = $this->dbh->query($query);
if ($query === false) {
@@ -426,7 +427,7 @@ class rcube_db
*
* @param mixed $result Optional query handle
*
- * @return int Number of rows or false on failure
+ * @return int Number of (matching) rows
*/
public function affected_rows($result = null)
{
diff --git a/lib/ext/Roundcube/rcube_db_mssql.php b/lib/ext/Roundcube/rcube_db_mssql.php
index c95663c..84fe22b 100644
--- a/lib/ext/Roundcube/rcube_db_mssql.php
+++ b/lib/ext/Roundcube/rcube_db_mssql.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_db_mssql.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -14,13 +12,11 @@
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for MS SQL Server database |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
diff --git a/lib/ext/Roundcube/rcube_db_mysql.php b/lib/ext/Roundcube/rcube_db_mysql.php
index 1c5ba1d..8ab6403 100644
--- a/lib/ext/Roundcube/rcube_db_mysql.php
+++ b/lib/ext/Roundcube/rcube_db_mysql.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_db_mysql.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -14,13 +12,11 @@
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for MySQL database |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Database independent query interface
*
@@ -130,6 +126,9 @@ class rcube_db_mysql extends rcube_db
// Always return matching (not affected only) rows count
$result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
+ // Enable AUTOCOMMIT mode (#1488902)
+ $dsn_options[PDO::ATTR_AUTOCOMMIT] = true;
+
return $result;
}
diff --git a/lib/ext/Roundcube/rcube_db_pgsql.php b/lib/ext/Roundcube/rcube_db_pgsql.php
index 797860a..cf23c5e 100644
--- a/lib/ext/Roundcube/rcube_db_pgsql.php
+++ b/lib/ext/Roundcube/rcube_db_pgsql.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_db_pgsql.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -14,13 +12,11 @@
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for PostgreSQL database |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
diff --git a/lib/ext/Roundcube/rcube_db_sqlite.php b/lib/ext/Roundcube/rcube_db_sqlite.php
index 65dcb6d..145b8a3 100644
--- a/lib/ext/Roundcube/rcube_db_sqlite.php
+++ b/lib/ext/Roundcube/rcube_db_sqlite.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlite.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -14,13 +12,11 @@
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for SQLite database |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
@@ -124,12 +120,7 @@ class rcube_db_sqlite extends rcube_db
$q = $this->query('SELECT name FROM sqlite_master'
.' WHERE type = \'table\' ORDER BY name');
- if ($res = $this->_get_result($q)) {
- $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
- }
- else {
- $this->tables = array();
- }
+ $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
}
return $this->tables;
diff --git a/lib/ext/Roundcube/rcube_db_sqlsrv.php b/lib/ext/Roundcube/rcube_db_sqlsrv.php
index 8b6ffe8..e696780 100644
--- a/lib/ext/Roundcube/rcube_db_sqlsrv.php
+++ b/lib/ext/Roundcube/rcube_db_sqlsrv.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlsrv.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -14,13 +12,11 @@
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for MS SQL Server database |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
diff --git a/lib/ext/Roundcube/rcube_enriched.php b/lib/ext/Roundcube/rcube_enriched.php
index 8b64fe0..8c628c9 100644
--- a/lib/ext/Roundcube/rcube_enriched.php
+++ b/lib/ext/Roundcube/rcube_enriched.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_enriched.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -13,14 +11,12 @@
| |
| PURPOSE: |
| Helper class to convert Enriched to HTML format (RFC 1523, 1896) |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
| Author: Ryo Chijiiwa (IlohaMail) |
+-----------------------------------------------------------------------+
*/
-
/**
* Class for Enriched to HTML conversion
*
diff --git a/lib/ext/Roundcube/rcube_html2text.php b/lib/ext/Roundcube/rcube_html2text.php
new file mode 100644
index 0000000..0b172eb
--- /dev/null
+++ b/lib/ext/Roundcube/rcube_html2text.php
@@ -0,0 +1,691 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team |
+ | Copyright (c) 2005-2007, Jon Abernathy <jon at chuggnutt.com> |
+ | |
+ | 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: |
+ | Converts HTML to formatted plain text (based on html2text class) |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube at gmail.com> |
+ | Author: Aleksander Machniak <alec at alec.pl> |
+ | Author: Jon Abernathy <jon at chuggnutt.com> |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ * Takes HTML and converts it to formatted, plain text.
+ *
+ * Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
+ * correcting an error in the regexp search array. Fixed 7/30/03.
+ *
+ * Updated set_html() function's file reading mechanism, 9/25/03.
+ *
+ * Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
+ * several more HTML entity codes to the $search and $replace arrays.
+ * Updated 11/7/03.
+ *
+ * Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
+ * suggesting the addition of $allowed_tags and its supporting function
+ * (which I slightly modified). Updated 3/12/04.
+ *
+ * Thanks to Justin Dearing for pointing out that a replacement for the
+ * <TH> tag was missing, and suggesting an appropriate fix.
+ * Updated 8/25/04.
+ *
+ * Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
+ * display/formatting bug in the _build_link_list() function: email
+ * readers would show the left bracket and number ("[1") as part of the
+ * rendered email address.
+ * Updated 12/16/04.
+ *
+ * Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
+ * to handle relative links, which I hadn't considered. I modified his
+ * code a bit to handle normal HTTP links and MAILTO links. Also for
+ * suggesting three additional HTML entity codes to search for.
+ * Updated 03/02/05.
+ *
+ * Thanks to Jacob Chandler for pointing out another link condition
+ * for the _build_link_list() function: "https".
+ * Updated 04/06/05.
+ *
+ * Thanks to Marc Bertrand (http://www.dresdensky.com/) for
+ * suggesting a revision to the word wrapping functionality; if you
+ * specify a $width of 0 or less, word wrapping will be ignored.
+ * Updated 11/02/06.
+ *
+ * *** Big housecleaning updates below:
+ *
+ * Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
+ * suggesting the fix to handle </li> and blank lines (whitespace).
+ * Christian Basedau (http://www.movetheweb.de/) also suggested the
+ * blank lines fix.
+ *
+ * Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
+ * Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
+ * Bas van de Weijer, and Marijn van Butselaar
+ * for pointing out my glaring error in the <th> handling. Marcus also
+ * supplied a host of fixes.
+ *
+ * Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
+ * out that extra spaces should be compressed--a problem addressed with
+ * Marcus Bointon's fixes but that I had not yet incorporated.
+ *
+ * Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
+ * suggesting a valuable fix with <a> tag handling.
+ *
+ * Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
+ * including the <a> tag handling that Daniel Schledermann pointed
+ * out but that I had not yet incorporated. I haven't (yet)
+ * incorporated all of Wojciech's changes, though I may at some
+ * future time.
+ *
+ * *** End of the housecleaning updates. Updated 08/08/07.
+ */
+
+/**
+ * Converts HTML to formatted plain text
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_html2text
+{
+ /**
+ * Contains the HTML content to convert.
+ *
+ * @var string $html
+ */
+ protected $html;
+
+ /**
+ * Contains the converted, formatted text.
+ *
+ * @var string $text
+ */
+ protected $text;
+
+ /**
+ * Maximum width of the formatted text, in columns.
+ *
+ * Set this value to 0 (or less) to ignore word wrapping
+ * and not constrain text to a fixed-width column.
+ *
+ * @var integer $width
+ */
+ protected $width = 70;
+
+ /**
+ * Target character encoding for output text
+ *
+ * @var string $charset
+ */
+ protected $charset = 'UTF-8';
+
+ /**
+ * List of preg* regular expression patterns to search for,
+ * used in conjunction with $replace.
+ *
+ * @var array $search
+ * @see $replace
+ */
+ protected $search = array(
+ "/\r/", // Non-legal carriage return
+ "/[\n\t]+/", // Newlines and tabs
+ '/<head[^>]*>.*?<\/head>/i', // <head>
+ '/<script[^>]*>.*?<\/script>/i', // <script>s -- which strip_tags supposedly has problems with
+ '/<style[^>]*>.*?<\/style>/i', // <style>s -- which strip_tags supposedly has problems with
+ '/<p[^>]*>/i', // <P>
+ '/<br[^>]*>/i', // <br>
+ '/<i[^>]*>(.*?)<\/i>/i', // <i>
+ '/<em[^>]*>(.*?)<\/em>/i', // <em>
+ '/(<ul[^>]*>|<\/ul>)/i', // <ul> and </ul>
+ '/(<ol[^>]*>|<\/ol>)/i', // <ol> and </ol>
+ '/<li[^>]*>(.*?)<\/li>/i', // <li> and </li>
+ '/<li[^>]*>/i', // <li>
+ '/<hr[^>]*>/i', // <hr>
+ '/<div[^>]*>/i', // <div>
+ '/(<table[^>]*>|<\/table>)/i', // <table> and </table>
+ '/(<tr[^>]*>|<\/tr>)/i', // <tr> and </tr>
+ '/<td[^>]*>(.*?)<\/td>/i', // <td> and </td>
+ );
+
+ /**
+ * List of pattern replacements corresponding to patterns searched.
+ *
+ * @var array $replace
+ * @see $search
+ */
+ protected $replace = array(
+ '', // Non-legal carriage return
+ ' ', // Newlines and tabs
+ '', // <head>
+ '', // <script>s -- which strip_tags supposedly has problems with
+ '', // <style>s -- which strip_tags supposedly has problems with
+ "\n\n", // <P>
+ "\n", // <br>
+ '_\\1_', // <i>
+ '_\\1_', // <em>
+ "\n\n", // <ul> and </ul>
+ "\n\n", // <ol> and </ol>
+ "\t* \\1\n", // <li> and </li>
+ "\n\t* ", // <li>
+ "\n-------------------------\n", // <hr>
+ "<div>\n", // <div>
+ "\n\n", // <table> and </table>
+ "\n", // <tr> and </tr>
+ "\t\t\\1\n", // <td> and </td>
+ );
+
+ /**
+ * List of preg* regular expression patterns to search for,
+ * used in conjunction with $ent_replace.
+ *
+ * @var array $ent_search
+ * @see $ent_replace
+ */
+ protected $ent_search = array(
+ '/&(nbsp|#160);/i', // Non-breaking space
+ '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
+ // Double quotes
+ '/&(apos|rsquo|lsquo|#8216|#8217);/i', // Single quotes
+ '/>/i', // Greater-than
+ '/</i', // Less-than
+ '/&(copy|#169);/i', // Copyright
+ '/&(trade|#8482|#153);/i', // Trademark
+ '/&(reg|#174);/i', // Registered
+ '/&(mdash|#151|#8212);/i', // mdash
+ '/&(ndash|minus|#8211|#8722);/i', // ndash
+ '/&(bull|#149|#8226);/i', // Bullet
+ '/&(pound|#163);/i', // Pound sign
+ '/&(euro|#8364);/i', // Euro sign
+ '/&(amp|#38);/i', // Ampersand: see _converter()
+ '/[ ]{2,}/', // Runs of spaces, post-handling
+ );
+
+ /**
+ * List of pattern replacements corresponding to patterns searched.
+ *
+ * @var array $ent_replace
+ * @see $ent_search
+ */
+ protected $ent_replace = array(
+ ' ', // Non-breaking space
+ '"', // Double quotes
+ "'", // Single quotes
+ '>',
+ '<',
+ '(c)',
+ '(tm)',
+ '(R)',
+ '--',
+ '-',
+ '*',
+ '£',
+ 'EUR', // Euro sign. ?
+ '|+|amp|+|', // Ampersand: see _converter()
+ ' ', // Runs of spaces, post-handling
+ );
+
+ /**
+ * List of preg* regular expression patterns to search for
+ * and replace using callback function.
+ *
+ * @var array $callback_search
+ */
+ protected $callback_search = array(
+ '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
+ '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i', // h1 - h6
+ '/<(b)( [^>]*)?>(.*?)<\/b>/i', // <b>
+ '/<(strong)( [^>]*)?>(.*?)<\/strong>/i', // <strong>
+ '/<(th)( [^>]*)?>(.*?)<\/th>/i', // <th> and </th>
+ );
+
+ /**
+ * List of preg* regular expression patterns to search for in PRE body,
+ * used in conjunction with $pre_replace.
+ *
+ * @var array $pre_search
+ * @see $pre_replace
+ */
+ protected $pre_search = array(
+ "/\n/",
+ "/\t/",
+ '/ /',
+ '/<pre[^>]*>/',
+ '/<\/pre>/'
+ );
+
+ /**
+ * List of pattern replacements corresponding to patterns searched for PRE body.
+ *
+ * @var array $pre_replace
+ * @see $pre_search
+ */
+ protected $pre_replace = array(
+ '<br>',
+ ' ',
+ ' ',
+ '',
+ ''
+ );
+
+ /**
+ * Contains a list of HTML tags to allow in the resulting text.
+ *
+ * @var string $allowed_tags
+ * @see set_allowed_tags()
+ */
+ protected $allowed_tags = '';
+
+ /**
+ * Contains the base URL that relative links should resolve to.
+ *
+ * @var string $url
+ */
+ protected $url;
+
+ /**
+ * Indicates whether content in the $html variable has been converted yet.
+ *
+ * @var boolean $_converted
+ * @see $html, $text
+ */
+ protected $_converted = false;
+
+ /**
+ * Contains URL addresses from links to be rendered in plain text.
+ *
+ * @var array $_link_list
+ * @see _build_link_list()
+ */
+ protected $_link_list = array();
+
+ /**
+ * Boolean flag, true if a table of link URLs should be listed after the text.
+ *
+ * @var boolean $_do_links
+ * @see __construct()
+ */
+ protected $_do_links = true;
+
+ /**
+ * Constructor.
+ *
+ * If the HTML source string (or file) is supplied, the class
+ * will instantiate with that source propagated, all that has
+ * to be done it to call get_text().
+ *
+ * @param string $source HTML content
+ * @param boolean $from_file Indicates $source is a file to pull content from
+ * @param boolean $do_links Indicate whether a table of link URLs is desired
+ * @param integer $width Maximum width of the formatted text, 0 for no limit
+ */
+ function __construct($source = '', $from_file = false, $do_links = true, $width = 75, $charset = 'UTF-8')
+ {
+ if (!empty($source)) {
+ $this->set_html($source, $from_file);
+ }
+
+ $this->set_base_url();
+
+ $this->_do_links = $do_links;
+ $this->width = $width;
+ $this->charset = $charset;
+ }
+
+ /**
+ * Loads source HTML into memory, either from $source string or a file.
+ *
+ * @param string $source HTML content
+ * @param boolean $from_file Indicates $source is a file to pull content from
+ */
+ function set_html($source, $from_file = false)
+ {
+ if ($from_file && file_exists($source)) {
+ $this->html = file_get_contents($source);
+ }
+ else {
+ $this->html = $source;
+ }
+
+ $this->_converted = false;
+ }
+
+ /**
+ * Returns the text, converted from HTML.
+ *
+ * @return string Plain text
+ */
+ function get_text()
+ {
+ if (!$this->_converted) {
+ $this->_convert();
+ }
+
+ return $this->text;
+ }
+
+ /**
+ * Prints the text, converted from HTML.
+ */
+ function print_text()
+ {
+ print $this->get_text();
+ }
+
+ /**
+ * Sets the allowed HTML tags to pass through to the resulting text.
+ *
+ * Tags should be in the form "<p>", with no corresponding closing tag.
+ */
+ function set_allowed_tags($allowed_tags = '')
+ {
+ if (!empty($allowed_tags)) {
+ $this->allowed_tags = $allowed_tags;
+ }
+ }
+
+ /**
+ * Sets a base URL to handle relative links.
+ */
+ function set_base_url($url = '')
+ {
+ if (empty($url)) {
+ if (!empty($_SERVER['HTTP_HOST'])) {
+ $this->url = 'http://' . $_SERVER['HTTP_HOST'];
+ }
+ else {
+ $this->url = '';
+ }
+ }
+ else {
+ // Strip any trailing slashes for consistency (relative
+ // URLs may already start with a slash like "/file.html")
+ if (substr($url, -1) == '/') {
+ $url = substr($url, 0, -1);
+ }
+ $this->url = $url;
+ }
+ }
+
+ /**
+ * Workhorse function that does actual conversion (calls _converter() method).
+ */
+ protected function _convert()
+ {
+ // Variables used for building the link list
+ $this->_link_list = array();
+
+ $text = trim(stripslashes($this->html));
+
+ // Convert HTML to TXT
+ $this->_converter($text);
+
+ // Add link list
+ if (!empty($this->_link_list)) {
+ $text .= "\n\nLinks:\n------\n";
+ foreach ($this->_link_list as $idx => $url) {
+ $text .= '[' . ($idx+1) . '] ' . $url . "\n";
+ }
+ }
+
+ $this->text = $text;
+ $this->_converted = true;
+ }
+
+ /**
+ * Workhorse function that does actual conversion.
+ *
+ * First performs custom tag replacement specified by $search and
+ * $replace arrays. Then strips any remaining HTML tags, reduces whitespace
+ * and newlines to a readable format, and word wraps the text to
+ * $width characters.
+ *
+ * @param string Reference to HTML content string
+ */
+ protected function _converter(&$text)
+ {
+ // Convert <BLOCKQUOTE> (before PRE!)
+ $this->_convert_blockquotes($text);
+
+ // Convert <PRE>
+ $this->_convert_pre($text);
+
+ // Run our defined tags search-and-replace
+ $text = preg_replace($this->search, $this->replace, $text);
+
+ // Run our defined tags search-and-replace with callback
+ $text = preg_replace_callback($this->callback_search, array($this, 'tags_preg_callback'), $text);
+
+ // Strip any other HTML tags
+ $text = strip_tags($text, $this->allowed_tags);
+
+ // Run our defined entities/characters search-and-replace
+ $text = preg_replace($this->ent_search, $this->ent_replace, $text);
+
+ // Replace known html entities
+ $text = html_entity_decode($text, ENT_QUOTES, $this->charset);
+
+ // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
+ $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
+
+ // Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
+ // This properly handles situation of """ in input string
+ $text = str_replace('|+|amp|+|', '&', $text);
+
+ // Bring down number of empty lines to 2 max
+ $text = preg_replace("/\n\s+\n/", "\n\n", $text);
+ $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
+
+ // remove leading empty lines (can be produced by eg. P tag on the beginning)
+ $text = ltrim($text, "\n");
+
+ // Wrap the text to a readable format
+ // for PHP versions >= 4.0.2. Default width is 75
+ // If width is 0 or less, don't wrap the text.
+ if ( $this->width > 0 ) {
+ $text = wordwrap($text, $this->width);
+ }
+ }
+
+ /**
+ * Helper function called by preg_replace() on link replacement.
+ *
+ * Maintains an internal list of links to be displayed at the end of the
+ * text, with numeric indices to the original point in the text they
+ * appeared. Also makes an effort at identifying and handling absolute
+ * and relative links.
+ *
+ * @param string $link URL of the link
+ * @param string $display Part of the text to associate number with
+ */
+ protected function _build_link_list( $link, $display )
+ {
+ if (!$this->_do_links || empty($link)) {
+ return $display;
+ }
+
+ // Ignored link types
+ if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
+ return $display;
+ }
+
+ if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
+ $url = $link;
+ }
+ else {
+ $url = $this->url;
+ if (substr($link, 0, 1) != '/') {
+ $url .= '/';
+ }
+ $url .= "$link";
+ }
+
+ if (($index = array_search($url, $this->_link_list)) === false) {
+ $index = count($this->_link_list);
+ $this->_link_list[] = $url;
+ }
+
+ return $display . ' [' . ($index+1) . ']';
+ }
+
+ /**
+ * Helper function for PRE body conversion.
+ *
+ * @param string HTML content
+ */
+ protected function _convert_pre(&$text)
+ {
+ // get the content of PRE element
+ while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
+ $this->pre_content = $matches[1];
+
+ // Run our defined tags search-and-replace with callback
+ $this->pre_content = preg_replace_callback($this->callback_search,
+ array($this, 'tags_preg_callback'), $this->pre_content);
+
+ // convert the content
+ $this->pre_content = sprintf('<div><br>%s<br></div>',
+ preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
+
+ // replace the content (use callback because content can contain $0 variable)
+ $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
+ array($this, 'pre_preg_callback'), $text, 1);
+
+ // free memory
+ $this->pre_content = '';
+ }
+ }
+
+ /**
+ * Helper function for BLOCKQUOTE body conversion.
+ *
+ * @param string HTML content
+ */
+ protected function _convert_blockquotes(&$text)
+ {
+ if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
+ $level = 0;
+ $diff = 0;
+ foreach ($matches[0] as $m) {
+ if ($m[0][0] == '<' && $m[0][1] == '/') {
+ $level--;
+ if ($level < 0) {
+ $level = 0; // malformed HTML: go to next blockquote
+ }
+ else if ($level > 0) {
+ // skip inner blockquote
+ }
+ else {
+ $end = $m[1];
+ $len = $end - $taglen - $start;
+ // Get blockquote content
+ $body = substr($text, $start + $taglen - $diff, $len);
+
+ // Set text width
+ $p_width = $this->width;
+ if ($this->width > 0) $this->width -= 2;
+ // Convert blockquote content
+ $body = trim($body);
+ $this->_converter($body);
+ // Add citation markers and create PRE block
+ $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
+ $body = '<pre>' . htmlspecialchars($body) . '</pre>';
+ // Re-set text width
+ $this->width = $p_width;
+ // Replace content
+ $text = substr($text, 0, $start - $diff)
+ . $body . substr($text, $end + strlen($m[0]) - $diff);
+
+ $diff = $len + $taglen + strlen($m[0]) - strlen($body);
+ unset($body);
+ }
+ }
+ else {
+ if ($level == 0) {
+ $start = $m[1];
+ $taglen = strlen($m[0]);
+ }
+ $level ++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback function for preg_replace_callback use.
+ *
+ * @param array PREG matches
+ * @return string
+ */
+ public function tags_preg_callback($matches)
+ {
+ switch (strtolower($matches[1])) {
+ case 'b':
+ case 'strong':
+ return $this->_toupper($matches[3]);
+ case 'th':
+ return $this->_toupper("\t\t". $matches[3] ."\n");
+ case 'h':
+ return $this->_toupper("\n\n". $matches[3] ."\n\n");
+ case 'a':
+ // Remove spaces in URL (#1487805)
+ $url = str_replace(' ', '', $matches[3]);
+ return $this->_build_link_list($url, $matches[4]);
+ }
+ }
+
+ /**
+ * Callback function for preg_replace_callback use in PRE content handler.
+ *
+ * @param array PREG matches
+ * @return string
+ */
+ public function pre_preg_callback($matches)
+ {
+ return $this->pre_content;
+ }
+
+ /**
+ * Strtoupper function with HTML tags and entities handling.
+ *
+ * @param string $str Text to convert
+ * @return string Converted text
+ */
+ private function _toupper($str)
+ {
+ // string can containg HTML tags
+ $chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+
+ // convert toupper only the text between HTML tags
+ foreach ($chunks as $idx => $chunk) {
+ if ($chunk[0] != '<') {
+ $chunks[$idx] = $this->_strtoupper($chunk);
+ }
+ }
+
+ return implode($chunks);
+ }
+
+ /**
+ * Strtoupper multibyte wrapper function with HTML entities handling.
+ *
+ * @param string $str Text to convert
+ * @return string Converted text
+ */
+ private function _strtoupper($str)
+ {
+ $str = html_entity_decode($str, ENT_COMPAT, $this->charset);
+ $str = mb_strtoupper($str);
+ $str = htmlspecialchars($str, ENT_COMPAT, $this->charset);
+
+ return $str;
+ }
+}
diff --git a/lib/ext/Roundcube/rcube_image.php b/lib/ext/Roundcube/rcube_image.php
index b72a24c..9695022 100644
--- a/lib/ext/Roundcube/rcube_image.php
+++ b/lib/ext/Roundcube/rcube_image.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_image.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -14,7 +12,6 @@
| |
| PURPOSE: |
| Image resizer and converter |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
@@ -131,17 +128,20 @@ class rcube_image
}
// use GD extension
- $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
- if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
- if ($props['gd_type'] == IMAGETYPE_JPEG) {
+ if ($props['gd_type']) {
+ if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
$image = imagecreatefromjpeg($this->image_file);
}
- elseif($props['gd_type'] == IMAGETYPE_GIF) {
+ else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
$image = imagecreatefromgif($this->image_file);
}
- elseif($props['gd_type'] == IMAGETYPE_PNG) {
+ else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
$image = imagecreatefrompng($this->image_file);
}
+ else {
+ // @TODO: print error to the log?
+ return false;
+ }
$scale = $size / max($props['width'], $props['height']);
$width = $props['width'] * $scale;
@@ -219,19 +219,22 @@ class rcube_image
}
// use GD extension (TIFF isn't supported)
- $props = $this->props();
- $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
+ $props = $this->props();
- if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
- if ($props['gd_type'] == IMAGETYPE_JPEG) {
+ if ($props['gd_type']) {
+ if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
$image = imagecreatefromjpeg($this->image_file);
}
- else if ($props['gd_type'] == IMAGETYPE_GIF) {
+ else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
$image = imagecreatefromgif($this->image_file);
}
- else if ($props['gd_type'] == IMAGETYPE_PNG) {
+ else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
$image = imagecreatefrompng($this->image_file);
}
+ else {
+ // @TODO: print error to the log?
+ return false;
+ }
if ($type == self::TYPE_JPG) {
$result = imagejpeg($image, $filename, 75);
diff --git a/lib/ext/Roundcube/rcube_imap.php b/lib/ext/Roundcube/rcube_imap.php
index 8ca24de..74c1f53 100644
--- a/lib/ext/Roundcube/rcube_imap.php
+++ b/lib/ext/Roundcube/rcube_imap.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_imap.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| IMAP Storage Engine |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Interface class for accessing an IMAP server
*
@@ -151,7 +147,7 @@ class rcube_imap extends rcube_storage
$attempt = 0;
do {
- $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
+ $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
array_merge($this->options, array('host' => $host, 'user' => $user,
'attempt' => ++$attempt)));
@@ -571,7 +567,7 @@ class rcube_imap extends rcube_storage
* Get message count for a specific folder
*
* @param string $folder Folder name
- * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
+ * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
* @param boolean $force Force reading from server and update cache
* @param boolean $status Enables storing folder status info (max UID/count),
* required for folder_status()
@@ -592,7 +588,7 @@ class rcube_imap extends rcube_storage
* protected method for getting nr of messages
*
* @param string $folder Folder name
- * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
+ * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
* @param boolean $force Force reading from server and update cache
* @param boolean $status Enables storing folder status info (max UID/count),
* required for folder_status()
@@ -614,6 +610,10 @@ class rcube_imap extends rcube_storage
}
}
+ // EXISTS is a special alias for ALL, it allows to get the number
+ // of all messages in a folder also when search is active and with
+ // any skip_deleted setting
+
$a_folder_cache = $this->get_cache('messagecount');
// return cached value
@@ -644,7 +644,7 @@ class rcube_imap extends rcube_storage
$count = $this->conn->countRecent($folder);
}
// use SEARCH for message counting
- else if (!empty($this->options['skip_deleted'])) {
+ else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) {
$search_str = "ALL UNDELETED";
$keys = array('COUNT');
@@ -683,8 +683,8 @@ class rcube_imap extends rcube_storage
}
else {
$count = $this->conn->countMessages($folder);
- if ($status) {
- $this->set_folder_stats($folder,'cnt', $count);
+ if ($status && $mode == 'ALL') {
+ $this->set_folder_stats($folder, 'cnt', $count);
$this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
}
}
@@ -2226,10 +2226,11 @@ class rcube_imap extends rcube_storage
* @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
*/
- public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
+ public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false)
{
if (!strlen($folder)) {
$folder = $this->folder;
@@ -2247,10 +2248,10 @@ class rcube_imap extends rcube_storage
$date = $this->date_format($date);
if ($is_file) {
- $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
+ $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary);
}
else {
- $saved = $this->conn->append($folder, $message, $flags, $date);
+ $saved = $this->conn->append($folder, $message, $flags, $date, $binary);
}
if ($saved) {
diff --git a/lib/ext/Roundcube/rcube_imap_cache.php b/lib/ext/Roundcube/rcube_imap_cache.php
index 31214cf..f33ac07 100644
--- a/lib/ext/Roundcube/rcube_imap_cache.php
+++ b/lib/ext/Roundcube/rcube_imap_cache.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_imap_cache.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -13,14 +11,12 @@
| |
| PURPOSE: |
| Caching of IMAP folder contents (messages and index) |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Interface class for accessing Roundcube messages cache
*
diff --git a/lib/ext/Roundcube/rcube_imap_generic.php b/lib/ext/Roundcube/rcube_imap_generic.php
index 0f32d83..8d84bf7 100644
--- a/lib/ext/Roundcube/rcube_imap_generic.php
+++ b/lib/ext/Roundcube/rcube_imap_generic.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_imap_generic.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -19,14 +17,12 @@
| functionality built-in. |
| |
| Based on Iloha IMAP Library. See http://ilohamail.org/ for details |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
| Author: Ryo Chijiiwa <Ryo at IlohaMail.org> |
+-----------------------------------------------------------------------+
*/
-
/**
* PHP based wrapper class to connect to an IMAP server
*
@@ -757,12 +753,16 @@ class rcube_imap_generic
$this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
if (!$this->fp) {
+ if (!$errstr) {
+ $errstr = "Unknown reason (fsockopen() function disabled?)";
+ }
$this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
return false;
}
- if ($this->prefs['timeout'] > 0)
+ if ($this->prefs['timeout'] > 0) {
stream_set_timeout($this->fp, $this->prefs['timeout']);
+ }
$line = trim(fgets($this->fp, 8192));
@@ -1311,6 +1311,11 @@ class rcube_imap_generic
if ($cmd == 'LIST' || $cmd == 'LSUB') {
list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
+ // Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879)
+ if ($delim) {
+ $mailbox = rtrim($mailbox, $delim);
+ }
+
// Add to result array
if (!$lstatus) {
$folders[] = $mailbox;
@@ -2548,10 +2553,11 @@ class rcube_imap_generic
* @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
*/
- function append($mailbox, &$message, $flags = array(), $date = null)
+ function append($mailbox, &$message, $flags = array(), $date = null, $binary = false)
{
unset($this->data['APPENDUID']);
@@ -2559,8 +2565,13 @@ class rcube_imap_generic
return false;
}
- $message = str_replace("\r", '', $message);
- $message = str_replace("\n", "\r\n", $message);
+ $binary = $binary && $this->getCapability('BINARY');
+ $literal_plus = !$binary && $this->prefs['literal+'];
+
+ if (!$binary) {
+ $message = str_replace("\r", '', $message);
+ $message = str_replace("\n", "\r\n", $message);
+ }
$len = strlen($message);
if (!$len) {
@@ -2573,12 +2584,12 @@ class rcube_imap_generic
if (!empty($date)) {
$request .= ' ' . $this->escape($date);
}
- $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+ $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
// send APPEND command
if ($this->putLine($request)) {
// Do not wait when LITERAL+ is supported
- if (!$this->prefs['literal+']) {
+ if (!$literal_plus) {
$line = $this->readReply();
if ($line[0] != '+') {
@@ -2620,10 +2631,11 @@ class rcube_imap_generic
* @param string $headers Message headers
* @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
*/
- function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null)
+ function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
{
unset($this->data['APPENDUID']);
@@ -2654,18 +2666,21 @@ class rcube_imap_generic
$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);
}
- $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+ $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
// send APPEND command
if ($this->putLine($request)) {
// Don't wait when LITERAL+ is supported
- if (!$this->prefs['literal+']) {
+ if (!$literal_plus) {
$line = $this->readReply();
if ($line[0] != '+') {
diff --git a/lib/ext/Roundcube/rcube_ldap.php b/lib/ext/Roundcube/rcube_ldap.php
index c9a14d8..700c6f6 100644
--- a/lib/ext/Roundcube/rcube_ldap.php
+++ b/lib/ext/Roundcube/rcube_ldap.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_ldap.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -14,7 +12,6 @@
| |
| PURPOSE: |
| Interface to an LDAP address directory |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Andreas Dick <andudi (at) gmx (dot) ch> |
@@ -22,7 +19,6 @@
+-----------------------------------------------------------------------+
*/
-
/**
* Model class to access an LDAP address directory
*
@@ -798,27 +794,14 @@ class rcube_ldap extends rcube_addressbook
$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);
+ $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) {
- $val = mb_strtolower($val);
- switch ($mode) {
- case 1:
- $got = ($val == $search);
- break;
- case 2:
- $got = ($search == substr($val, 0, strlen($search)));
- break;
- default:
- $got = (strpos($val, $search) !== false);
- break;
- }
-
- if ($got) {
+ if ($this->compare_search_value($f, $val, $search, $mode)) {
$this->result->add($rec);
$this->result->count++;
break 2;
@@ -1455,6 +1438,7 @@ class rcube_ldap extends rcube_addressbook
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);
diff --git a/lib/ext/Roundcube/rcube_message.php b/lib/ext/Roundcube/rcube_message.php
index 4ef534a..b52b79b 100644
--- a/lib/ext/Roundcube/rcube_message.php
+++ b/lib/ext/Roundcube/rcube_message.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_message.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2010, The Roundcube Dev Team |
| |
@@ -19,7 +17,6 @@
+-----------------------------------------------------------------------+
*/
-
/**
* Logical representation of a mail message with all its data
* and related functions
@@ -274,7 +271,7 @@ class rcube_message
$out = $this->get_part_content($mime_id);
// create instance of html2text class
- $txt = new html2text($out);
+ $txt = new rcube_html2text($out);
return $txt->get_text();
}
}
@@ -320,8 +317,15 @@ class rcube_message
private function parse_structure($structure, $recursive = false)
{
// real content-type of message/rfc822 part
- if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype)
+ if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype) {
$mimetype = $structure->real_mimetype;
+
+ // parse headers from message/rfc822 part
+ if (!isset($structure->headers['subject'])) {
+ list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192));
+ $structure->headers = rcube_mime::parse_headers($headers);
+ }
+ }
else
$mimetype = $structure->mimetype;
@@ -329,7 +333,7 @@ class rcube_message
if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
$c = new stdClass;
$c->type = 'headers';
- $c->headers = &$structure->headers;
+ $c->headers = $structure->headers;
$this->parts[] = $c;
}
@@ -346,45 +350,59 @@ class rcube_message
// print body if message doesn't have multiple parts
if ($message_ctype_primary == 'text' && !$recursive) {
+ // parts with unsupported type add to attachments list
+ if (!in_array($message_ctype_secondary, array('plain', 'html', 'enriched'))) {
+ $this->attachments[] = $structure;
+ return;
+ }
+
$structure->type = 'content';
- $this->parts[] = &$structure;
+ $this->parts[] = $structure;
// Parse simple (plain text) message body
- if ($message_ctype_secondary == 'plain')
+ if ($message_ctype_secondary == 'plain') {
foreach ((array)$this->uu_decode($structure) as $uupart) {
$this->mime_parts[$uupart->mime_id] = $uupart;
$this->attachments[] = $uupart;
}
+ }
}
// the same for pgp signed messages
else if ($mimetype == 'application/pgp' && !$recursive) {
$structure->type = 'content';
- $this->parts[] = &$structure;
+ $this->parts[] = $structure;
}
// message contains (more than one!) alternative parts
else if ($mimetype == 'multipart/alternative'
&& is_array($structure->parts) && count($structure->parts) > 1
) {
- // get html/plaintext parts
- $plain_part = $html_part = $print_part = $related_part = null;
+ $plain_part = null;
+ $html_part = null;
+ $print_part = null;
+ $related_part = null;
+ $attach_part = null;
+ // get html/plaintext parts, other add to attachments list
foreach ($structure->parts as $p => $sub_part) {
$sub_mimetype = $sub_part->mimetype;
+ $is_multipart = preg_match('/^multipart\/(related|relative|mixed|alternative)/', $sub_mimetype);
// skip empty text parts
- if (!$sub_part->size && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) {
+ if (!$sub_part->size && !$is_multipart) {
continue;
}
// check if sub part is
- if ($sub_mimetype == 'text/plain')
+ if ($is_multipart)
+ $related_part = $p;
+ else if ($sub_mimetype == 'text/plain')
$plain_part = $p;
else if ($sub_mimetype == 'text/html')
$html_part = $p;
else if ($sub_mimetype == 'text/enriched')
$enriched_part = $p;
- else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')))
- $related_part = $p;
+ else
+ $attach_part = $p;
}
// parse related part (alternative part could be in here)
@@ -400,13 +418,13 @@ class rcube_message
// choose html/plain part to print
if ($html_part !== null && $this->opt['prefer_html']) {
- $print_part = &$structure->parts[$html_part];
+ $print_part = $structure->parts[$html_part];
}
else if ($enriched_part !== null) {
- $print_part = &$structure->parts[$enriched_part];
+ $print_part = $structure->parts[$enriched_part];
}
else if ($plain_part !== null) {
- $print_part = &$structure->parts[$plain_part];
+ $print_part = $structure->parts[$plain_part];
}
// add the right message body
@@ -428,11 +446,16 @@ class rcube_message
// add html part as attachment
if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
- $html_part = &$structure->parts[$html_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];
+ }
}
// this is an ecrypted message -> create a plaintext body with the according message
else if ($mimetype == 'multipart/encrypted') {
@@ -537,7 +560,7 @@ class rcube_message
continue;
// part belongs to a related message and is linked
- if ($mimetype == 'multipart/related'
+ if (preg_match('/^multipart\/(related|relative)/', $mimetype)
&& ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) {
if ($mail_part->headers['content-id'])
$mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
@@ -557,9 +580,6 @@ class rcube_message
// regular attachment with valid content type
// (content-type name regexp according to RFC4288.4.2)
else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
- if (!$mail_part->filename)
- $mail_part->filename = 'Part '.$mail_part->mime_id;
-
$this->attachments[] = $mail_part;
}
// attachment with invalid content type
@@ -579,7 +599,7 @@ class rcube_message
}
// if this was a related part try to resolve references
- if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
+ if (preg_match('/^multipart\/(related|relative)/', $mimetype) && sizeof($this->inline_parts)) {
$a_replaces = array();
$img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/';
@@ -624,7 +644,6 @@ class rcube_message
}
// message is a single part non-text (without filename)
else if (preg_match('/application\//i', $mimetype)) {
- $structure->filename = 'Part '.$structure->mime_id;
$this->attachments[] = $structure;
}
}
diff --git a/lib/ext/Roundcube/rcube_message_header.php b/lib/ext/Roundcube/rcube_message_header.php
index 445d0bd..274ae7f 100644
--- a/lib/ext/Roundcube/rcube_message_header.php
+++ b/lib/ext/Roundcube/rcube_message_header.php
@@ -2,8 +2,6 @@
/**
+-----------------------------------------------------------------------+
- | program/include/rcube_message_header.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -14,7 +12,6 @@
| |
| PURPOSE: |
| E-mail message headers representation |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
@@ -235,13 +232,30 @@ class rcube_message_header
$this->others[$name] = $value;
}
}
+
+
+ /**
+ * Factory method to instantiate headers from a data array
+ *
+ * @param array Hash array with header values
+ * @return object rcube_message_header instance filled with headers values
+ */
+ public static function from_array($arr)
+ {
+ $obj = new rcube_message_header;
+ foreach ($arr as $k => $v)
+ $obj->set($k, $v);
+
+ return $obj;
+ }
}
/**
* Class for sorting an array of rcube_message_header objects in a predetermined order.
*
- * @package Mail
+ * @package Framework
+ * @subpackage Storage
* @author Aleksander Machniak <alec at alec.pl>
*/
class rcube_message_header_sorter
diff --git a/lib/ext/Roundcube/rcube_message_part.php b/lib/ext/Roundcube/rcube_message_part.php
index c9c9257..4222ba3 100644
--- a/lib/ext/Roundcube/rcube_message_part.php
+++ b/lib/ext/Roundcube/rcube_message_part.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_message_part.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| Class representing a message part |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Class representing a message part
*
diff --git a/lib/ext/Roundcube/rcube_mime.php b/lib/ext/Roundcube/rcube_mime.php
index 17cb3f0..eef8ca1 100644
--- a/lib/ext/Roundcube/rcube_mime.php
+++ b/lib/ext/Roundcube/rcube_mime.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_mime.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| MIME message parsing utilities |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Class for parsing MIME messages
*
@@ -717,6 +713,7 @@ class rcube_mime
$file_paths[] = $mime_types;
// try common locations
+ $file_paths[] = '/etc/mime.types';
$file_paths[] = '/etc/httpd/mime.types';
$file_paths[] = '/etc/httpd2/mime.types';
$file_paths[] = '/etc/apache/mime.types';
@@ -749,7 +746,7 @@ class rcube_mime
// fallback to some well-known types most important for daily emails
if (empty($mime_types)) {
$mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
- $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
+ $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
foreach ($mime_extensions as $ext => $mime)
$mime_types[$mime][] = $ext;
diff --git a/lib/ext/Roundcube/rcube_output.php b/lib/ext/Roundcube/rcube_output.php
index 4ef42f5..b8ae86c 100644
--- a/lib/ext/Roundcube/rcube_output.php
+++ b/lib/ext/Roundcube/rcube_output.php
@@ -2,17 +2,15 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_output.php |
- | |
| This file is part of the Roundcube PHP suite |
| Copyright (C) 2005-2012 The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
+ | |
| CONTENTS: |
| Abstract class for output generation |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
diff --git a/lib/ext/Roundcube/rcube_plugin.php b/lib/ext/Roundcube/rcube_plugin.php
index 5db8502..66e77cc 100644
--- a/lib/ext/Roundcube/rcube_plugin.php
+++ b/lib/ext/Roundcube/rcube_plugin.php
@@ -2,10 +2,8 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_plugin.php |
- | |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2009, 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. |
@@ -27,334 +25,353 @@
*/
abstract class rcube_plugin
{
- /**
- * Class name of the plugin instance
- *
- * @var string
- */
- public $ID;
-
- /**
- * Instance of Plugin API
- *
- * @var rcube_plugin_api
- */
- public $api;
-
- /**
- * Regular expression defining task(s) to bind with
- *
- * @var string
- */
- public $task;
-
- /**
- * Disables plugin in AJAX requests
- *
- * @var boolean
- */
- public $noajax = false;
-
- /**
- * Disables plugin in framed mode
- *
- * @var boolean
- */
- public $noframe = false;
-
- protected $home;
- protected $urlbase;
- private $mytask;
-
-
- /**
- * Default constructor.
- *
- * @param rcube_plugin_api $api Plugin API
- */
- public function __construct($api)
- {
- $this->ID = get_class($this);
- $this->api = $api;
- $this->home = $api->dir . $this->ID;
- $this->urlbase = $api->url . $this->ID . '/';
- }
-
- /**
- * Initialization method, needs to be implemented by the plugin itself
- */
- abstract function init();
-
-
- /**
- * Attempt to load the given plugin which is required for the current plugin
- *
- * @param string Plugin name
- * @return boolean True on success, false on failure
- */
- public function require_plugin($plugin_name)
- {
- return $this->api->load_plugin($plugin_name);
- }
-
-
- /**
- * Load local config file from plugins directory.
- * The loaded values are patched over the global configuration.
- *
- * @param string $fname Config file name relative to the plugin's folder
- * @return boolean True on success, false on failure
- */
- public function load_config($fname = 'config.inc.php')
- {
- $fpath = $this->home.'/'.$fname;
- $rcube = rcube::get_instance();
- if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
- rcube::raise_error(array(
- 'code' => 527, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Failed to load config from $fpath"), true, false);
- return false;
+ /**
+ * Class name of the plugin instance
+ *
+ * @var string
+ */
+ public $ID;
+
+ /**
+ * Instance of Plugin API
+ *
+ * @var rcube_plugin_api
+ */
+ public $api;
+
+ /**
+ * Regular expression defining task(s) to bind with
+ *
+ * @var string
+ */
+ public $task;
+
+ /**
+ * Disables plugin in AJAX requests
+ *
+ * @var boolean
+ */
+ public $noajax = false;
+
+ /**
+ * Disables plugin in framed mode
+ *
+ * @var boolean
+ */
+ public $noframe = false;
+
+ protected $home;
+ protected $urlbase;
+ private $mytask;
+
+
+ /**
+ * Default constructor.
+ *
+ * @param rcube_plugin_api $api Plugin API
+ */
+ public function __construct($api)
+ {
+ $this->ID = get_class($this);
+ $this->api = $api;
+ $this->home = $api->dir . $this->ID;
+ $this->urlbase = $api->url . $this->ID . '/';
+ }
+
+ /**
+ * Initialization method, needs to be implemented by the plugin itself
+ */
+ abstract function init();
+
+ /**
+ * Attempt to load the given plugin which is required for the current plugin
+ *
+ * @param string Plugin name
+ * @return boolean True on success, false on failure
+ */
+ public function require_plugin($plugin_name)
+ {
+ return $this->api->load_plugin($plugin_name);
+ }
+
+ /**
+ * Load local config file from plugins directory.
+ * The loaded values are patched over the global configuration.
+ *
+ * @param string $fname Config file name relative to the plugin's folder
+ *
+ * @return boolean True on success, false on failure
+ */
+ public function load_config($fname = 'config.inc.php')
+ {
+ $fpath = $this->home.'/'.$fname;
+ $rcube = rcube::get_instance();
+
+ if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
+ rcube::raise_error(array(
+ 'code' => 527, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Failed to load config from $fpath"), true, false);
+ return false;
+ }
+
+ return true;
}
- return true;
- }
-
- /**
- * Register a callback function for a specific (server-side) hook
- *
- * @param string $hook Hook name
- * @param mixed $callback Callback function as string or array with object reference and method name
- */
- public function add_hook($hook, $callback)
- {
- $this->api->register_hook($hook, $callback);
- }
-
- /**
- * Unregister a callback function for a specific (server-side) hook.
- *
- * @param string $hook Hook name
- * @param mixed $callback Callback function as string or array with object reference and method name
- */
- public function remove_hook($hook, $callback)
- {
- $this->api->unregister_hook($hook, $callback);
- }
-
- /**
- * Load localized texts from the plugins dir
- *
- * @param string $dir Directory to search in
- * @param mixed $add2client Make texts also available on the client (array with list or true for all)
- */
- public function add_texts($dir, $add2client = false)
- {
- $domain = $this->ID;
- $lang = $_SESSION['language'];
- $langs = array_unique(array('en_US', $lang));
- $locdir = slashify(realpath(slashify($this->home) . $dir));
- $texts = array();
-
- // Language aliases used to find localization in similar lang, see below
- $aliases = array(
- 'de_CH' => 'de_DE',
- 'es_AR' => 'es_ES',
- 'fa_AF' => 'fa_IR',
- 'nl_BE' => 'nl_NL',
- 'pt_BR' => 'pt_PT',
- 'zh_CN' => 'zh_TW',
- );
-
- // use buffering to handle empty lines/spaces after closing PHP tag
- ob_start();
-
- foreach ($langs as $lng) {
- $fpath = $locdir . $lng . '.inc';
- if (is_file($fpath) && is_readable($fpath)) {
- include $fpath;
- $texts = (array)$labels + (array)$messages + (array)$texts;
- }
- else if ($lng != 'en_US') {
- // Find localization in similar language (#1488401)
- $alias = null;
- if (!empty($aliases[$lng])) {
- $alias = $aliases[$lng];
+ /**
+ * Register a callback function for a specific (server-side) hook
+ *
+ * @param string $hook Hook name
+ * @param mixed $callback Callback function as string or array
+ * with object reference and method name
+ */
+ public function add_hook($hook, $callback)
+ {
+ $this->api->register_hook($hook, $callback);
+ }
+
+ /**
+ * Unregister a callback function for a specific (server-side) hook.
+ *
+ * @param string $hook Hook name
+ * @param mixed $callback Callback function as string or array
+ * with object reference and method name
+ */
+ public function remove_hook($hook, $callback)
+ {
+ $this->api->unregister_hook($hook, $callback);
+ }
+
+ /**
+ * Load localized texts from the plugins dir
+ *
+ * @param string $dir Directory to search in
+ * @param mixed $add2client Make texts also available on the client
+ * (array with list or true for all)
+ */
+ public function add_texts($dir, $add2client = false)
+ {
+ $domain = $this->ID;
+ $lang = $_SESSION['language'];
+ $langs = array_unique(array('en_US', $lang));
+ $locdir = slashify(realpath(slashify($this->home) . $dir));
+ $texts = array();
+
+ // Language aliases used to find localization in similar lang, see below
+ $aliases = array(
+ 'de_CH' => 'de_DE',
+ 'es_AR' => 'es_ES',
+ 'fa_AF' => 'fa_IR',
+ 'nl_BE' => 'nl_NL',
+ 'pt_BR' => 'pt_PT',
+ 'zh_CN' => 'zh_TW',
+ );
+
+ // use buffering to handle empty lines/spaces after closing PHP tag
+ ob_start();
+
+ foreach ($langs as $lng) {
+ $fpath = $locdir . $lng . '.inc';
+ if (is_file($fpath) && is_readable($fpath)) {
+ include $fpath;
+ $texts = (array)$labels + (array)$messages + (array)$texts;
+ }
+ else if ($lng != 'en_US') {
+ // Find localization in similar language (#1488401)
+ $alias = null;
+ if (!empty($aliases[$lng])) {
+ $alias = $aliases[$lng];
+ }
+ else if ($key = array_search($lng, $aliases)) {
+ $alias = $key;
+ }
+
+ if (!empty($alias)) {
+ $fpath = $locdir . $alias . '.inc';
+ if (is_file($fpath) && is_readable($fpath)) {
+ include $fpath;
+ $texts = (array)$labels + (array)$messages + (array)$texts;
+ }
+ }
+ }
}
- else if ($key = array_search($lng, $aliases)) {
- $alias = $key;
+
+ ob_end_clean();
+
+ // prepend domain to text keys and add to the application texts repository
+ if (!empty($texts)) {
+ $add = array();
+ foreach ($texts as $key => $value) {
+ $add[$domain.'.'.$key] = $value;
+ }
+
+ $rcube = rcube::get_instance();
+ $rcube->load_language($lang, $add);
+
+ // add labels to client
+ if ($add2client) {
+ if (is_array($add2client)) {
+ $js_labels = array_map(array($this, 'label_map_callback'), $add2client);
+ }
+ else {
+ $js_labels = array_keys($add);
+ }
+ $rcube->output->add_label($js_labels);
+ }
}
+ }
+
+ /**
+ * Wrapper for rcube::gettext() adding the plugin ID as domain
+ *
+ * @param string $p Message identifier
+ *
+ * @return string Localized text
+ * @see rcube::gettext()
+ */
+ public function gettext($p)
+ {
+ return rcube::get_instance()->gettext($p, $this->ID);
+ }
- if (!empty($alias)) {
- $fpath = $locdir . $alias . '.inc';
- if (is_file($fpath) && is_readable($fpath)) {
- include $fpath;
- $texts = (array)$labels + (array)$messages + (array)$texts;
- }
+ /**
+ * Register this plugin to be responsible for a specific task
+ *
+ * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+ */
+ public function register_task($task)
+ {
+ if ($this->api->register_task($task, $this->ID)) {
+ $this->mytask = $task;
}
- }
}
- ob_end_clean();
+ /**
+ * Register a handler for a specific client-request action
+ *
+ * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
+ *
+ * @param string $action Action name (should be unique)
+ * @param mixed $callback Callback function as string
+ * or array with object reference and method name
+ */
+ public function register_action($action, $callback)
+ {
+ $this->api->register_action($action, $this->ID, $callback, $this->mytask);
+ }
- // prepend domain to text keys and add to the application texts repository
- if (!empty($texts)) {
- $add = array();
- foreach ($texts as $key => $value)
- $add[$domain.'.'.$key] = $value;
+ /**
+ * Register a handler function for a template object
+ *
+ * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
+ * will be replaced by the return value if the registered callback function.
+ *
+ * @param string $name Object name (should be unique and start with 'plugin.')
+ * @param mixed $callback Callback function as string or array with object reference
+ * and method name
+ */
+ public function register_handler($name, $callback)
+ {
+ $this->api->register_handler($name, $this->ID, $callback);
+ }
- $rcube = rcube::get_instance();
- $rcube->load_language($lang, $add);
+ /**
+ * Make this javascipt file available on the client
+ *
+ * @param string $fn File path; absolute or relative to the plugin directory
+ */
+ public function include_script($fn)
+ {
+ $this->api->include_script($this->resource_url($fn));
+ }
- // add labels to client
- if ($add2client) {
- $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
- $rcube->output->add_label($js_labels);
- }
+ /**
+ * Make this stylesheet available on the client
+ *
+ * @param string $fn File path; absolute or relative to the plugin directory
+ */
+ public function include_stylesheet($fn)
+ {
+ $this->api->include_stylesheet($this->resource_url($fn));
+ }
+
+ /**
+ * Append a button to a certain container
+ *
+ * @param array $p Hash array with named parameters (as used in skin templates)
+ * @param string $container Container name where the buttons should be added to
+ *
+ * @see rcube_remplate::button()
+ */
+ public function add_button($p, $container)
+ {
+ if ($this->api->output->type == 'html') {
+ // fix relative paths
+ foreach (array('imagepas', 'imageact', 'imagesel') as $key) {
+ if ($p[$key]) {
+ $p[$key] = $this->api->url . $this->resource_url($p[$key]);
+ }
+ }
+
+ $this->api->add_content($this->api->output->button($p), $container);
+ }
}
- }
-
- /**
- * Wrapper for rcube::gettext() adding the plugin ID as domain
- *
- * @param string $p Message identifier
- * @return string Localized text
- * @see rcube::gettext()
- */
- public function gettext($p)
- {
- return rcube::get_instance()->gettext($p, $this->ID);
- }
-
- /**
- * Register this plugin to be responsible for a specific task
- *
- * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
- */
- public function register_task($task)
- {
- if ($this->api->register_task($task, $this->ID))
- $this->mytask = $task;
- }
-
- /**
- * Register a handler for a specific client-request action
- *
- * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
- *
- * @param string $action Action name (should be unique)
- * @param mixed $callback Callback function as string or array with object reference and method name
- */
- public function register_action($action, $callback)
- {
- $this->api->register_action($action, $this->ID, $callback, $this->mytask);
- }
-
- /**
- * Register a handler function for a template object
- *
- * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
- * will be replaced by the return value if the registered callback function.
- *
- * @param string $name Object name (should be unique and start with 'plugin.')
- * @param mixed $callback Callback function as string or array with object reference and method name
- */
- public function register_handler($name, $callback)
- {
- $this->api->register_handler($name, $this->ID, $callback);
- }
-
- /**
- * Make this javascipt file available on the client
- *
- * @param string $fn File path; absolute or relative to the plugin directory
- */
- public function include_script($fn)
- {
- $this->api->include_script($this->resource_url($fn));
- }
-
- /**
- * Make this stylesheet available on the client
- *
- * @param string $fn File path; absolute or relative to the plugin directory
- */
- public function include_stylesheet($fn)
- {
- $this->api->include_stylesheet($this->resource_url($fn));
- }
-
- /**
- * Append a button to a certain container
- *
- * @param array $p Hash array with named parameters (as used in skin templates)
- * @param string $container Container name where the buttons should be added to
- * @see rcube_remplate::button()
- */
- public function add_button($p, $container)
- {
- if ($this->api->output->type == 'html') {
- // fix relative paths
- foreach (array('imagepas', 'imageact', 'imagesel') as $key)
- if ($p[$key])
- $p[$key] = $this->api->url . $this->resource_url($p[$key]);
-
- $this->api->add_content($this->api->output->button($p), $container);
+
+ /**
+ * Generate an absolute URL to the given resource within the current
+ * plugin directory
+ *
+ * @param string $fn The file name
+ *
+ * @return string Absolute URL to the given resource
+ */
+ public function url($fn)
+ {
+ return $this->api->url . $this->resource_url($fn);
}
- }
-
- /**
- * Generate an absolute URL to the given resource within the current
- * plugin directory
- *
- * @param string $fn The file name
- * @return string Absolute URL to the given resource
- */
- public function url($fn)
- {
- return $this->api->url . $this->resource_url($fn);
- }
-
- /**
- * Make the given file name link into the plugin directory
- *
- * @param string $fn Filename
- */
- private function resource_url($fn)
- {
- if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
- return $this->ID . '/' . $fn;
- else
- return $fn;
- }
-
- /**
- * Provide path to the currently selected skin folder within the plugin directory
- * with a fallback to the default skin folder.
- *
- * @return string Skin path relative to plugins directory
- */
- public function local_skin_path()
- {
- $rcube = rcube::get_instance();
- foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
- $skin_path = 'skins/' . $skin;
- if (is_dir(realpath(slashify($this->home) . $skin_path)))
- break;
+
+ /**
+ * Make the given file name link into the plugin directory
+ *
+ * @param string $fn Filename
+ */
+ private function resource_url($fn)
+ {
+ if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) {
+ return $this->ID . '/' . $fn;
+ }
+ else {
+ return $fn;
+ }
}
- return $skin_path;
- }
+ /**
+ * Provide path to the currently selected skin folder within the plugin directory
+ * with a fallback to the default skin folder.
+ *
+ * @return string Skin path relative to plugins directory
+ */
+ public function local_skin_path()
+ {
+ $rcube = rcube::get_instance();
+ foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
+ $skin_path = 'skins/' . $skin;
+ if (is_dir(realpath(slashify($this->home) . $skin_path))) {
+ break;
+ }
+ }
- /**
- * Callback function for array_map
- *
- * @param string $key Array key.
- * @return string
- */
- private function label_map_callback($key)
- {
- return $this->ID.'.'.$key;
- }
+ return $skin_path;
+ }
+ /**
+ * Callback function for array_map
+ *
+ * @param string $key Array key.
+ * @return string
+ */
+ private function label_map_callback($key)
+ {
+ return $this->ID.'.'.$key;
+ }
}
diff --git a/lib/ext/Roundcube/rcube_plugin_api.php b/lib/ext/Roundcube/rcube_plugin_api.php
index 51cf5d2..8a4cce2 100644
--- a/lib/ext/Roundcube/rcube_plugin_api.php
+++ b/lib/ext/Roundcube/rcube_plugin_api.php
@@ -2,10 +2,8 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_plugin_api.php |
- | |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2011, 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. |
@@ -13,16 +11,15 @@
| |
| PURPOSE: |
| Plugins repository |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
*/
// location where plugins are loade from
-if (!defined('RCUBE_PLUGINS_DIR'))
- define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
-
+if (!defined('RCUBE_PLUGINS_DIR')) {
+ define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
+}
/**
* The plugin loader and global API
@@ -32,468 +29,476 @@ if (!defined('RCUBE_PLUGINS_DIR'))
*/
class rcube_plugin_api
{
- static protected $instance;
-
- public $dir;
- public $url = 'plugins/';
- public $task = '';
- public $output;
-
- public $handlers = array();
- protected $plugins = array();
- protected $tasks = array();
- protected $actions = array();
- protected $actionmap = array();
- protected $objectsmap = array();
- protected $template_contents = array();
- protected $active_hook = false;
-
- // Deprecated names of hooks, will be removed after 0.5-stable release
- protected $deprecated_hooks = array(
- 'create_user' => 'user_create',
- 'kill_session' => 'session_destroy',
- 'upload_attachment' => 'attachment_upload',
- 'save_attachment' => 'attachment_save',
- 'get_attachment' => 'attachment_get',
- 'cleanup_attachments' => 'attachments_cleanup',
- 'display_attachment' => 'attachment_display',
- 'remove_attachment' => 'attachment_delete',
- 'outgoing_message_headers' => 'message_outgoing_headers',
- 'outgoing_message_body' => 'message_outgoing_body',
- 'address_sources' => 'addressbooks_list',
- 'get_address_book' => 'addressbook_get',
- 'create_contact' => 'contact_create',
- 'save_contact' => 'contact_update',
- 'contact_save' => 'contact_update',
- 'delete_contact' => 'contact_delete',
- 'manage_folders' => 'folders_list',
- 'list_mailboxes' => 'mailboxes_list',
- 'save_preferences' => 'preferences_save',
- 'user_preferences' => 'preferences_list',
- 'list_prefs_sections' => 'preferences_sections_list',
- 'list_identities' => 'identities_list',
- 'create_identity' => 'identity_create',
- 'delete_identity' => 'identity_delete',
- 'save_identity' => 'identity_update',
- 'identity_save' => 'identity_update',
- // to be removed after 0.8
- 'imap_init' => 'storage_init',
- 'mailboxes_list' => 'storage_folders',
- );
-
- /**
- * This implements the 'singleton' design pattern
- *
- * @return rcube_plugin_api The one and only instance if this class
- */
- static function get_instance()
- {
- if (!self::$instance) {
- self::$instance = new rcube_plugin_api();
- }
+ static protected $instance;
+
+ public $dir;
+ public $url = 'plugins/';
+ public $task = '';
+ public $output;
+ public $handlers = array();
+
+ protected $plugins = array();
+ protected $tasks = array();
+ protected $actions = array();
+ protected $actionmap = array();
+ protected $objectsmap = array();
+ protected $template_contents = array();
+ protected $active_hook = false;
+
+ // Deprecated names of hooks, will be removed after 0.5-stable release
+ protected $deprecated_hooks = array(
+ 'create_user' => 'user_create',
+ 'kill_session' => 'session_destroy',
+ 'upload_attachment' => 'attachment_upload',
+ 'save_attachment' => 'attachment_save',
+ 'get_attachment' => 'attachment_get',
+ 'cleanup_attachments' => 'attachments_cleanup',
+ 'display_attachment' => 'attachment_display',
+ 'remove_attachment' => 'attachment_delete',
+ 'outgoing_message_headers' => 'message_outgoing_headers',
+ 'outgoing_message_body' => 'message_outgoing_body',
+ 'address_sources' => 'addressbooks_list',
+ 'get_address_book' => 'addressbook_get',
+ 'create_contact' => 'contact_create',
+ 'save_contact' => 'contact_update',
+ 'contact_save' => 'contact_update',
+ 'delete_contact' => 'contact_delete',
+ 'manage_folders' => 'folders_list',
+ 'list_mailboxes' => 'mailboxes_list',
+ 'save_preferences' => 'preferences_save',
+ 'user_preferences' => 'preferences_list',
+ 'list_prefs_sections' => 'preferences_sections_list',
+ 'list_identities' => 'identities_list',
+ 'create_identity' => 'identity_create',
+ 'delete_identity' => 'identity_delete',
+ 'save_identity' => 'identity_update',
+ 'identity_save' => 'identity_update',
+ // to be removed after 0.8
+ 'imap_init' => 'storage_init',
+ 'mailboxes_list' => 'storage_folders',
+ 'imap_connect' => 'storage_connect',
+ );
+
+ /**
+ * This implements the 'singleton' design pattern
+ *
+ * @return rcube_plugin_api The one and only instance if this class
+ */
+ static function get_instance()
+ {
+ if (!self::$instance) {
+ self::$instance = new rcube_plugin_api();
+ }
- return self::$instance;
- }
-
-
- /**
- * Private constructor
- */
- protected function __construct()
- {
- $this->dir = slashify(RCUBE_PLUGINS_DIR);
- }
-
-
- /**
- * Initialize plugin engine
- *
- * This has to be done after rcmail::load_gui() or rcmail::json_init()
- * was called because plugins need to have access to rcmail->output
- *
- * @param object rcube Instance of the rcube base class
- * @param string Current application task (used for conditional plugin loading)
- */
- public function init($app, $task = '')
- {
- $this->task = $task;
- $this->output = $app->output;
-
- // register an internal hook
- $this->register_hook('template_container', array($this, 'template_container_hook'));
-
- // maybe also register a shudown function which triggers shutdown functions of all plugin objects
- }
-
-
- /**
- * Load and init all enabled plugins
- *
- * This has to be done after rcmail::load_gui() or rcmail::json_init()
- * was called because plugins need to have access to rcmail->output
- *
- * @param array List of configured plugins to load
- * @param array List of plugins required by the application
- */
- public function load_plugins($plugins_enabled, $required_plugins = array())
- {
- foreach ($plugins_enabled as $plugin_name) {
- $this->load_plugin($plugin_name);
+ return self::$instance;
}
- // check existance of all required core plugins
- foreach ($required_plugins as $plugin_name) {
- $loaded = false;
- foreach ($this->plugins as $plugin) {
- if ($plugin instanceof $plugin_name) {
- $loaded = true;
- break;
- }
- }
-
- // load required core plugin if no derivate was found
- if (!$loaded)
- $loaded = $this->load_plugin($plugin_name);
-
- // trigger fatal error if still not loaded
- if (!$loaded) {
- rcube::raise_error(array('code' => 520, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
- }
+ /**
+ * Private constructor
+ */
+ protected function __construct()
+ {
+ $this->dir = slashify(RCUBE_PLUGINS_DIR);
}
- }
-
- /**
- * Load the specified plugin
- *
- * @param string Plugin name
- * @return boolean True on success, false if not loaded or failure
- */
- public function load_plugin($plugin_name)
- {
- static $plugins_dir;
-
- if (!$plugins_dir) {
- $dir = dir($this->dir);
- $plugins_dir = unslashify($dir->path);
+
+ /**
+ * Initialize plugin engine
+ *
+ * This has to be done after rcmail::load_gui() or rcmail::json_init()
+ * was called because plugins need to have access to rcmail->output
+ *
+ * @param object rcube Instance of the rcube base class
+ * @param string Current application task (used for conditional plugin loading)
+ */
+ public function init($app, $task = '')
+ {
+ $this->task = $task;
+ $this->output = $app->output;
+
+ // register an internal hook
+ $this->register_hook('template_container', array($this, 'template_container_hook'));
+
+ // maybe also register a shudown function which triggers
+ // shutdown functions of all plugin objects
}
- // plugin already loaded
- if ($this->plugins[$plugin_name] || class_exists($plugin_name, false))
- return true;
-
- $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
-
- if (file_exists($fn)) {
- include($fn);
-
- // instantiate class if exists
- if (class_exists($plugin_name, false)) {
- $plugin = new $plugin_name($this);
- // check inheritance...
- if (is_subclass_of($plugin, 'rcube_plugin')) {
- // ... task, request type and framed mode
- if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
- && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
- && (!$plugin->noframe || empty($_REQUEST['_framed']))
- ) {
- $plugin->init();
- $this->plugins[$plugin_name] = $plugin;
- }
- return true;
+ /**
+ * Load and init all enabled plugins
+ *
+ * This has to be done after rcmail::load_gui() or rcmail::json_init()
+ * was called because plugins need to have access to rcmail->output
+ *
+ * @param array List of configured plugins to load
+ * @param array List of plugins required by the application
+ */
+ public function load_plugins($plugins_enabled, $required_plugins = array())
+ {
+ foreach ($plugins_enabled as $plugin_name) {
+ $this->load_plugin($plugin_name);
+ }
+
+ // check existance of all required core plugins
+ foreach ($required_plugins as $plugin_name) {
+ $loaded = false;
+ foreach ($this->plugins as $plugin) {
+ if ($plugin instanceof $plugin_name) {
+ $loaded = true;
+ break;
+ }
+ }
+
+ // load required core plugin if no derivate was found
+ if (!$loaded) {
+ $loaded = $this->load_plugin($plugin_name);
+ }
+
+ // trigger fatal error if still not loaded
+ if (!$loaded) {
+ rcube::raise_error(array(
+ 'code' => 520, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
+ }
}
- }
- else {
- rcube::raise_error(array('code' => 520, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "No plugin class $plugin_name found in $fn"), true, false);
- }
}
- else {
- rcube::raise_error(array('code' => 520, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Failed to load plugin file $fn"), true, false);
+
+ /**
+ * Load the specified plugin
+ *
+ * @param string Plugin name
+ *
+ * @return boolean True on success, false if not loaded or failure
+ */
+ public function load_plugin($plugin_name)
+ {
+ static $plugins_dir;
+
+ if (!$plugins_dir) {
+ $dir = dir($this->dir);
+ $plugins_dir = unslashify($dir->path);
+ }
+
+ // plugin already loaded
+ if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) {
+ return true;
+ }
+
+ $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name
+ . DIRECTORY_SEPARATOR . $plugin_name . '.php';
+
+ if (file_exists($fn)) {
+ include $fn;
+
+ // instantiate class if exists
+ if (class_exists($plugin_name, false)) {
+ $plugin = new $plugin_name($this);
+ // check inheritance...
+ if (is_subclass_of($plugin, 'rcube_plugin')) {
+ // ... task, request type and framed mode
+ if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
+ && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
+ && (!$plugin->noframe || empty($_REQUEST['_framed']))
+ ) {
+ $plugin->init();
+ $this->plugins[$plugin_name] = $plugin;
+ }
+ return true;
+ }
+ }
+ else {
+ rcube::raise_error(array('code' => 520, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "No plugin class $plugin_name found in $fn"),
+ true, false);
+ }
+ }
+ else {
+ rcube::raise_error(array('code' => 520, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Failed to load plugin file $fn"), true, false);
+ }
+
+ return false;
}
- return false;
- }
-
-
- /**
- * Allows a plugin object to register a callback for a certain hook
- *
- * @param string $hook Hook name
- * @param mixed $callback String with global function name or array($obj, 'methodname')
- */
- public function register_hook($hook, $callback)
- {
- if (is_callable($callback)) {
- if (isset($this->deprecated_hooks[$hook])) {
- rcube::raise_error(array('code' => 522, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false);
- $hook = $this->deprecated_hooks[$hook];
- }
- $this->handlers[$hook][] = $callback;
+ /**
+ * Allows a plugin object to register a callback for a certain hook
+ *
+ * @param string $hook Hook name
+ * @param mixed $callback String with global function name or array($obj, 'methodname')
+ */
+ public function register_hook($hook, $callback)
+ {
+ if (is_callable($callback)) {
+ if (isset($this->deprecated_hooks[$hook])) {
+ rcube::raise_error(array('code' => 522, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Deprecated hook name. "
+ . $hook . ' -> ' . $this->deprecated_hooks[$hook]), true, false);
+ $hook = $this->deprecated_hooks[$hook];
+ }
+ $this->handlers[$hook][] = $callback;
+ }
+ else {
+ rcube::raise_error(array('code' => 521, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Invalid callback function for $hook"), true, false);
+ }
}
- else
- rcube::raise_error(array('code' => 521, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Invalid callback function for $hook"), true, false);
- }
-
- /**
- * Allow a plugin object to unregister a callback.
- *
- * @param string $hook Hook name
- * @param mixed $callback String with global function name or array($obj, 'methodname')
- */
- public function unregister_hook($hook, $callback)
- {
- $callback_id = array_search($callback, $this->handlers[$hook]);
- if ($callback_id !== false) {
- unset($this->handlers[$hook][$callback_id]);
+
+ /**
+ * Allow a plugin object to unregister a callback.
+ *
+ * @param string $hook Hook name
+ * @param mixed $callback String with global function name or array($obj, 'methodname')
+ */
+ public function unregister_hook($hook, $callback)
+ {
+ $callback_id = array_search($callback, $this->handlers[$hook]);
+ if ($callback_id !== false) {
+ unset($this->handlers[$hook][$callback_id]);
+ }
}
- }
-
-
- /**
- * Triggers a plugin hook.
- * This is called from the application and executes all registered handlers
- *
- * @param string $hook Hook name
- * @param array $args Named arguments (key->value pairs)
- * @return array The (probably) altered hook arguments
- */
- public function exec_hook($hook, $args = array())
- {
- if (!is_array($args))
- $args = array('arg' => $args);
-
- $args += array('abort' => false);
- $this->active_hook = $hook;
-
- foreach ((array)$this->handlers[$hook] as $callback) {
- $ret = call_user_func($callback, $args);
- if ($ret && is_array($ret))
- $args = $ret + $args;
-
- if ($args['abort'])
- break;
+
+ /**
+ * Triggers a plugin hook.
+ * This is called from the application and executes all registered handlers
+ *
+ * @param string $hook Hook name
+ * @param array $args Named arguments (key->value pairs)
+ *
+ * @return array The (probably) altered hook arguments
+ */
+ public function exec_hook($hook, $args = array())
+ {
+ if (!is_array($args)) {
+ $args = array('arg' => $args);
+ }
+
+ $args += array('abort' => false);
+ $this->active_hook = $hook;
+
+ foreach ((array)$this->handlers[$hook] as $callback) {
+ $ret = call_user_func($callback, $args);
+ if ($ret && is_array($ret)) {
+ $args = $ret + $args;
+ }
+
+ if ($args['abort']) {
+ break;
+ }
+ }
+
+ $this->active_hook = false;
+ return $args;
}
- $this->active_hook = false;
- return $args;
- }
-
-
- /**
- * Let a plugin register a handler for a specific request
- *
- * @param string $action Action name (_task=mail&_action=plugin.foo)
- * @param string $owner Plugin name that registers this action
- * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
- * @param string $task Task name registered by this plugin
- */
- public function register_action($action, $owner, $callback, $task = null)
- {
- // check action name
- if ($task)
- $action = $task.'.'.$action;
- else if (strpos($action, 'plugin.') !== 0)
- $action = 'plugin.'.$action;
-
- // can register action only if it's not taken or registered by myself
- if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
- $this->actions[$action] = $callback;
- $this->actionmap[$action] = $owner;
+ /**
+ * Let a plugin register a handler for a specific request
+ *
+ * @param string $action Action name (_task=mail&_action=plugin.foo)
+ * @param string $owner Plugin name that registers this action
+ * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
+ * @param string $task Task name registered by this plugin
+ */
+ public function register_action($action, $owner, $callback, $task = null)
+ {
+ // check action name
+ if ($task)
+ $action = $task.'.'.$action;
+ else if (strpos($action, 'plugin.') !== 0)
+ $action = 'plugin.'.$action;
+
+ // can register action only if it's not taken or registered by myself
+ if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
+ $this->actions[$action] = $callback;
+ $this->actionmap[$action] = $owner;
+ }
+ else {
+ rcube::raise_error(array('code' => 523, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Cannot register action $action;"
+ ." already taken by another plugin"), true, false);
+ }
}
- else {
- rcube::raise_error(array('code' => 523, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Cannot register action $action; already taken by another plugin"), true, false);
+
+ /**
+ * This method handles requests like _task=mail&_action=plugin.foo
+ * It executes the callback function that was registered with the given action.
+ *
+ * @param string $action Action name
+ */
+ public function exec_action($action)
+ {
+ if (isset($this->actions[$action])) {
+ call_user_func($this->actions[$action]);
+ }
+ else if (rcube::get_instance()->action != 'refresh') {
+ rcube::raise_error(array('code' => 524, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "No handler found for action $action"), true, true);
+ }
}
- }
-
-
- /**
- * This method handles requests like _task=mail&_action=plugin.foo
- * It executes the callback function that was registered with the given action.
- *
- * @param string $action Action name
- */
- public function exec_action($action)
- {
- if (isset($this->actions[$action])) {
- call_user_func($this->actions[$action]);
+
+ /**
+ * Register a handler function for template objects
+ *
+ * @param string $name Object name
+ * @param string $owner Plugin name that registers this action
+ * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
+ */
+ public function register_handler($name, $owner, $callback)
+ {
+ // check name
+ if (strpos($name, 'plugin.') !== 0) {
+ $name = 'plugin.' . $name;
+ }
+
+ // can register handler only if it's not taken or registered by myself
+ if (is_object($this->output)
+ && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)
+ ) {
+ $this->output->add_handler($name, $callback);
+ $this->objectsmap[$name] = $owner;
+ }
+ else {
+ rcube::raise_error(array('code' => 525, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Cannot register template handler $name;"
+ ." already taken by another plugin or no output object available"), true, false);
+ }
}
- else if (rcube::get_instance()->action != 'refresh') {
- rcube::raise_error(array('code' => 524, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "No handler found for action $action"), true, true);
+
+ /**
+ * Register this plugin to be responsible for a specific task
+ *
+ * @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)
+ {
+ // tasks are irrelevant in framework mode
+ if (!class_exists('rcmail', false)) {
+ return true;
+ }
+
+ if ($task != asciiwords($task)) {
+ rcube::raise_error(array('code' => 526, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Invalid task name: $task."
+ ." Only characters [a-z0-9_.-] are allowed"), true, false);
+ }
+ else if (in_array($task, rcmail::$main_tasks)) {
+ rcube::raise_error(array('code' => 526, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Cannot register taks $task;"
+ ." already taken by another plugin or the application itself"), true, false);
+ }
+ else {
+ $this->tasks[$task] = $owner;
+ rcmail::$main_tasks[] = $task;
+ return true;
+ }
+
+ return false;
}
- }
-
-
- /**
- * Register a handler function for template objects
- *
- * @param string $name Object name
- * @param string $owner Plugin name that registers this action
- * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
- */
- public function register_handler($name, $owner, $callback)
- {
- // check name
- if (strpos($name, 'plugin.') !== 0)
- $name = 'plugin.'.$name;
-
- // can register handler only if it's not taken or registered by myself
- if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) {
- $this->output->add_handler($name, $callback);
- $this->objectsmap[$name] = $owner;
+
+ /**
+ * Checks whether the given task is registered by a plugin
+ *
+ * @param string $task Task name
+ *
+ * @return boolean True if registered, otherwise false
+ */
+ public function is_plugin_task($task)
+ {
+ return $this->tasks[$task] ? true : false;
}
- else {
- rcube::raise_error(array('code' => 525, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false);
+
+ /**
+ * Check if a plugin hook is currently processing.
+ * Mainly used to prevent loops and recursion.
+ *
+ * @param string $hook Hook to check (optional)
+ *
+ * @return boolean True if any/the given hook is currently processed, otherwise false
+ */
+ public function is_processing($hook = null)
+ {
+ return $this->active_hook && (!$hook || $this->active_hook == $hook);
}
- }
-
-
- /**
- * Register this plugin to be responsible for a specific task
- *
- * @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)
- {
- // tasks are irrelevant in framework mode
- if (!class_exists('rcmail', false))
- return true;
-
- if ($task != asciiwords($task)) {
- rcube::raise_error(array('code' => 526, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
+
+ /**
+ * Include a plugin script file in the current HTML page
+ *
+ * @param string $fn Path to script
+ */
+ public function include_script($fn)
+ {
+ if (is_object($this->output) && $this->output->type == 'html') {
+ $src = $this->resource_url($fn);
+ $this->output->add_header(html::tag('script',
+ array('type' => "text/javascript", 'src' => $src)));
+ }
}
- else if (in_array($task, rcmail::$main_tasks)) {
- rcube::raise_error(array('code' => 526, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
+
+ /**
+ * Include a plugin stylesheet in the current HTML page
+ *
+ * @param string $fn Path to stylesheet
+ */
+ public function include_stylesheet($fn)
+ {
+ if (is_object($this->output) && $this->output->type == 'html') {
+ $src = $this->resource_url($fn);
+ $this->output->include_css($src);
+ }
}
- else {
- $this->tasks[$task] = $owner;
- rcmail::$main_tasks[] = $task;
- return true;
+
+ /**
+ * Save the given HTML content to be added to a template container
+ *
+ * @param string $html HTML content
+ * @param string $container Template container identifier
+ */
+ public function add_content($html, $container)
+ {
+ $this->template_contents[$container] .= $html . "\n";
}
- return false;
- }
-
-
- /**
- * Checks whether the given task is registered by a plugin
- *
- * @param string $task Task name
- * @return boolean True if registered, otherwise false
- */
- public function is_plugin_task($task)
- {
- return $this->tasks[$task] ? true : false;
- }
-
-
- /**
- * Check if a plugin hook is currently processing.
- * Mainly used to prevent loops and recursion.
- *
- * @param string $hook Hook to check (optional)
- * @return boolean True if any/the given hook is currently processed, otherwise false
- */
- public function is_processing($hook = null)
- {
- return $this->active_hook && (!$hook || $this->active_hook == $hook);
- }
-
- /**
- * Include a plugin script file in the current HTML page
- *
- * @param string $fn Path to script
- */
- public function include_script($fn)
- {
- if (is_object($this->output) && $this->output->type == 'html') {
- $src = $this->resource_url($fn);
- $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
+ /**
+ * Returns list of loaded plugins names
+ *
+ * @return array List of plugin names
+ */
+ public function loaded_plugins()
+ {
+ return array_keys($this->plugins);
}
- }
-
-
- /**
- * Include a plugin stylesheet in the current HTML page
- *
- * @param string $fn Path to stylesheet
- */
- public function include_stylesheet($fn)
- {
- if (is_object($this->output) && $this->output->type == 'html') {
- $src = $this->resource_url($fn);
- $this->output->include_css($src);
+
+ /**
+ * Callback for template_container hooks
+ *
+ * @param array $attrib
+ * @return array
+ */
+ protected function template_container_hook($attrib)
+ {
+ $container = $attrib['name'];
+ return array('content' => $attrib['content'] . $this->template_contents[$container]);
}
- }
-
-
- /**
- * Save the given HTML content to be added to a template container
- *
- * @param string $html HTML content
- * @param string $container Template container identifier
- */
- public function add_content($html, $container)
- {
- $this->template_contents[$container] .= $html . "\n";
- }
-
-
- /**
- * Returns list of loaded plugins names
- *
- * @return array List of plugin names
- */
- public function loaded_plugins()
- {
- return array_keys($this->plugins);
- }
-
-
- /**
- * Callback for template_container hooks
- *
- * @param array $attrib
- * @return array
- */
- protected function template_container_hook($attrib)
- {
- $container = $attrib['name'];
- return array('content' => $attrib['content'] . $this->template_contents[$container]);
- }
-
-
- /**
- * Make the given file name link into the plugins directory
- *
- * @param string $fn Filename
- * @return string
- */
- protected function resource_url($fn)
- {
- if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
- return $this->url . $fn;
- else
- return $fn;
- }
+ /**
+ * Make the given file name link into the plugins directory
+ *
+ * @param string $fn Filename
+ * @return string
+ */
+ protected function resource_url($fn)
+ {
+ if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
+ return $this->url . $fn;
+ else
+ return $fn;
+ }
}
diff --git a/lib/ext/Roundcube/rcube_result_index.php b/lib/ext/Roundcube/rcube_result_index.php
index 4d1ae13..5f592c5 100644
--- a/lib/ext/Roundcube/rcube_result_index.php
+++ b/lib/ext/Roundcube/rcube_result_index.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_result_index.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| SORT/SEARCH/ESEARCH response handler |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Class for accessing IMAP's SORT/SEARCH/ESEARCH result
*
diff --git a/lib/ext/Roundcube/rcube_result_set.php b/lib/ext/Roundcube/rcube_result_set.php
index 456d1c9..1391e5e 100644
--- a/lib/ext/Roundcube/rcube_result_set.php
+++ b/lib/ext/Roundcube/rcube_result_set.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_result_set.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2011, The Roundcube Dev Team |
| |
@@ -13,13 +11,11 @@
| |
| PURPOSE: |
| Class representing an address directory result set |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
*/
-
/**
* Roundcube result set class.
* Representing an address directory result set.
diff --git a/lib/ext/Roundcube/rcube_result_thread.php b/lib/ext/Roundcube/rcube_result_thread.php
index c609bdc..7657550 100644
--- a/lib/ext/Roundcube/rcube_result_thread.php
+++ b/lib/ext/Roundcube/rcube_result_thread.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_result_thread.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| THREAD response handler |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Class for accessing IMAP's THREAD result
*
diff --git a/lib/ext/Roundcube/rcube_session.php b/lib/ext/Roundcube/rcube_session.php
index fdbf668..1aa5d58 100644
--- a/lib/ext/Roundcube/rcube_session.php
+++ b/lib/ext/Roundcube/rcube_session.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_session.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
@@ -14,7 +12,6 @@
| |
| PURPOSE: |
| Provide database supported session management |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
@@ -31,602 +28,629 @@
*/
class rcube_session
{
- private $db;
- private $ip;
- private $start;
- private $changed;
- private $unsets = array();
- private $gc_handlers = array();
- private $cookiename = 'roundcube_sessauth';
- private $vars;
- private $key;
- private $now;
- private $secret = '';
- private $ip_check = false;
- private $logging = false;
- private $memcache;
-
- /**
- * Default constructor
- */
- public function __construct($db, $config)
- {
- $this->db = $db;
- $this->start = microtime(true);
- $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
- 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) {
- session_set_save_handler(
- array($this, 'open'),
- array($this, 'close'),
- array($this, 'mc_read'),
- array($this, 'mc_write'),
- array($this, 'mc_destroy'),
- array($this, 'gc'));
- }
- else {
- rcube::raise_error(array('code' => 604, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "Failed to connect to memcached. Please check configuration"),
- true, true);
- }
+ private $db;
+ private $ip;
+ private $start;
+ private $changed;
+ private $unsets = array();
+ private $gc_handlers = array();
+ private $cookiename = 'roundcube_sessauth';
+ private $vars;
+ private $key;
+ private $now;
+ private $secret = '';
+ private $ip_check = false;
+ private $logging = false;
+ private $memcache;
+
+
+ /**
+ * Default constructor
+ */
+ public function __construct($db, $config)
+ {
+ $this->db = $db;
+ $this->start = microtime(true);
+ $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
+ 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) {
+ session_set_save_handler(
+ array($this, 'open'),
+ array($this, 'close'),
+ array($this, 'mc_read'),
+ array($this, 'mc_write'),
+ array($this, 'mc_destroy'),
+ array($this, 'gc'));
+ }
+ else {
+ rcube::raise_error(array('code' => 604, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => "Failed to connect to memcached. Please check configuration"),
+ true, true);
+ }
+ }
+ else {
+ // set custom functions for PHP session management
+ session_set_save_handler(
+ array($this, 'open'),
+ array($this, 'close'),
+ array($this, 'db_read'),
+ array($this, 'db_write'),
+ array($this, 'db_destroy'),
+ array($this, 'db_gc'));
+ }
}
- else {
- // set custom functions for PHP session management
- session_set_save_handler(
- array($this, 'open'),
- array($this, 'close'),
- array($this, 'db_read'),
- array($this, 'db_write'),
- array($this, 'db_destroy'),
- array($this, 'db_gc'));
- }
- }
-
-
- public function open($save_path, $session_name)
- {
- return true;
- }
-
-
- public function close()
- {
- return true;
- }
-
-
- /**
- * Delete session data for the given key
- *
- * @param string Session ID
- */
- public function destroy($key)
- {
- return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
- }
-
-
- /**
- * Read session data from database
- *
- * @param string Session ID
- * @return string Session vars
- */
- public function db_read($key)
- {
- $sql_result = $this->db->query(
- "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->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 : '';
+
+
+ public function open($save_path, $session_name)
+ {
+ return true;
}
- return null;
- }
-
-
- /**
- * Save session data.
- * handler for session_read()
- *
- * @param string Session ID
- * @param string Serialized session vars
- * @return boolean True on success
- */
- public function db_write($key, $vars)
- {
- $ts = microtime(true);
- $now = $this->db->fromunixtime((int)$ts);
-
- // no session row in DB (db_read() returns false)
- if (!$this->key) {
- $oldvars = null;
+
+ public function close()
+ {
+ return true;
}
- // use internal data from read() for fast requests (up to 0.5 sec.)
- else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
- $oldvars = $this->vars;
+
+
+ /**
+ * Delete session data for the given key
+ *
+ * @param string Session ID
+ */
+ public function destroy($key)
+ {
+ return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
+ }
+
+
+ /**
+ * Read session data from database
+ *
+ * @param string Session ID
+ *
+ * @return string Session vars
+ */
+ public function db_read($key)
+ {
+ $sql_result = $this->db->query(
+ "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->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 : '';
+ }
+
+ return null;
}
- else { // else read data again from DB
- $oldvars = $this->db_read($key);
+
+
+ /**
+ * Save session data.
+ * handler for session_read()
+ *
+ * @param string Session ID
+ * @param string Serialized session vars
+ *
+ * @return boolean True on success
+ */
+ public function db_write($key, $vars)
+ {
+ $ts = microtime(true);
+ $now = $this->db->fromunixtime((int)$ts);
+
+ // no session row in DB (db_read() returns false)
+ if (!$this->key) {
+ $oldvars = null;
+ }
+ // use internal data from read() for fast requests (up to 0.5 sec.)
+ else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
+ $oldvars = $this->vars;
+ }
+ else { // else read data again from DB
+ $oldvars = $this->db_read($key);
+ }
+
+ if ($oldvars !== null) {
+ $newvars = $this->_fixvars($vars, $oldvars);
+
+ if ($newvars !== $oldvars) {
+ $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->lifetime / 2) {
+ $this->db->query("UPDATE ".$this->db->table_name('session')
+ ." SET changed=$now WHERE sess_id=?", $key);
+ }
+ }
+ else {
+ $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;
}
- if ($oldvars !== null) {
- $newvars = $this->_fixvars($vars, $oldvars);
- if ($newvars !== $oldvars) {
+ /**
+ * Merge vars with old vars and apply unsets
+ */
+ private function _fixvars($vars, $oldvars)
+ {
+ if ($oldvars !== null) {
+ $a_oldvars = $this->unserialize($oldvars);
+ if (is_array($a_oldvars)) {
+ foreach ((array)$this->unsets as $k)
+ unset($a_oldvars[$k]);
+
+ $newvars = $this->serialize(array_merge(
+ (array)$a_oldvars, (array)$this->unserialize($vars)));
+ }
+ else {
+ $newvars = $vars;
+ }
+ }
+
+ $this->unsets = array();
+ return $newvars;
+ }
+
+
+ /**
+ * Handler for session_destroy()
+ *
+ * @param string Session ID
+ *
+ * @return boolean True on success
+ */
+ public function db_destroy($key)
+ {
+ if ($key) {
+ $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?",
+ $this->db->table_name('session')), $key);
+ }
+
+ return true;
+ }
+
+
+ /**
+ * 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("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->lifetime / 2) {
- $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key);
- }
+ 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
+ * @return string Session vars
+ */
+ public function mc_read($key)
+ {
+ if ($value = $this->memcache->get($key)) {
+ $arr = unserialize($value);
+ $this->changed = $arr['changed'];
+ $this->ip = $arr['ip'];
+ $this->vars = $arr['vars'];
+ $this->key = $key;
+
+ return !empty($this->vars) ? (string) $this->vars : '';
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Save session data.
+ * handler for session_read()
+ *
+ * @param string Session ID
+ * @param string Serialized session vars
+ *
+ * @return boolean True on success
+ */
+ public function mc_write($key, $vars)
+ {
+ $ts = microtime(true);
+
+ // no session data in cache (mc_read() returns false)
+ if (!$this->key)
+ $oldvars = null;
+ // use internal data for fast requests (up to 0.5 sec.)
+ else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
+ $oldvars = $this->vars;
+ else // else read data again
+ $oldvars = $this->mc_read($key);
+
+ $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
+
+ if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) {
+ return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)),
+ MEMCACHE_COMPRESSED, $this->lifetime);
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Handler for session_destroy() with memcache backend
+ *
+ * @param string Session ID
+ *
+ * @return boolean True on success
+ */
+ public function mc_destroy($key)
+ {
+ if ($key) {
+ // #1488592: use 2nd argument
+ $this->memcache->delete($key, 0);
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Execute registered garbage collector routines
+ */
+ public function gc()
+ {
+ foreach ($this->gc_handlers as $fct) {
+ call_user_func($fct);
+ }
}
- else {
- $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);
+
+
+ /**
+ * Register additional garbage collector functions
+ *
+ * @param mixed Callback function
+ */
+ public function register_gc_handler($func)
+ {
+ foreach ($this->gc_handlers as $handler) {
+ if ($handler == $func) {
+ return;
+ }
+ }
+
+ $this->gc_handlers[] = $func;
}
- return true;
- }
-
-
- /**
- * Merge vars with old vars and apply unsets
- */
- private function _fixvars($vars, $oldvars)
- {
- if ($oldvars !== null) {
- $a_oldvars = $this->unserialize($oldvars);
- if (is_array($a_oldvars)) {
- foreach ((array)$this->unsets as $k)
- unset($a_oldvars[$k]);
-
- $newvars = $this->serialize(array_merge(
- (array)$a_oldvars, (array)$this->unserialize($vars)));
- }
- else
- $newvars = $vars;
+
+ /**
+ * Generate and set new session id
+ *
+ * @param boolean $destroy If enabled the current session will be destroyed
+ */
+ public function regenerate_id($destroy=true)
+ {
+ session_regenerate_id($destroy);
+
+ $this->vars = null;
+ $this->key = session_id();
+
+ return true;
}
- $this->unsets = array();
- return $newvars;
- }
-
-
- /**
- * Handler for session_destroy()
- *
- * @param string Session ID
- *
- * @return boolean True on success
- */
- public function db_destroy($key)
- {
- if ($key) {
- $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')), $key);
+
+ /**
+ * Unset a session variable
+ *
+ * @param string Varibale name
+ * @return boolean True on success
+ */
+ public function remove($var=null)
+ {
+ if (empty($var)) {
+ return $this->destroy(session_id());
+ }
+
+ $this->unsets[] = $var;
+ unset($_SESSION[$var]);
+
+ return true;
}
- return true;
- }
-
-
- /**
- * 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
- * @return string Session vars
- */
- public function mc_read($key)
- {
- if ($value = $this->memcache->get($key)) {
- $arr = unserialize($value);
- $this->changed = $arr['changed'];
- $this->ip = $arr['ip'];
- $this->vars = $arr['vars'];
- $this->key = $key;
-
- return !empty($this->vars) ? (string) $this->vars : '';
+
+ /**
+ * Kill this session
+ */
+ public function kill()
+ {
+ $this->vars = null;
+ $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
+ $this->destroy(session_id());
+ rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
}
- return null;
- }
-
-
- /**
- * Save session data.
- * handler for session_read()
- *
- * @param string Session ID
- * @param string Serialized session vars
- * @return boolean True on success
- */
- public function mc_write($key, $vars)
- {
- $ts = microtime(true);
-
- // no session data in cache (mc_read() returns false)
- if (!$this->key)
- $oldvars = null;
- // use internal data for fast requests (up to 0.5 sec.)
- else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
- $oldvars = $this->vars;
- else // else read data again
- $oldvars = $this->mc_read($key);
-
- $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
-
- if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2)
- return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), MEMCACHE_COMPRESSED, $this->lifetime);
-
- return true;
- }
-
-
- /**
- * Handler for session_destroy() with memcache backend
- *
- * @param string Session ID
- *
- * @return boolean True on success
- */
- public function mc_destroy($key)
- {
- if ($key) {
- // #1488592: use 2nd argument
- $this->memcache->delete($key, 0);
+
+ /**
+ * Re-read session data from storage backend
+ */
+ public function reload()
+ {
+ if ($this->key && $this->memcache)
+ $data = $this->mc_read($this->key);
+ else if ($this->key)
+ $data = $this->db_read($this->key);
+
+ if ($data)
+ session_decode($data);
}
- return true;
- }
+ /**
+ * Serialize session data
+ */
+ private function serialize($vars)
+ {
+ $data = '';
+ if (is_array($vars)) {
+ foreach ($vars as $var=>$value)
+ $data .= $var.'|'.serialize($value);
+ }
+ else {
+ $data = 'b:0;';
+ }
- /**
- * Execute registered garbage collector routines
- */
- public function gc()
- {
- foreach ($this->gc_handlers as $fct) {
- call_user_func($fct);
+ return $data;
}
- }
-
-
- /**
- * Register additional garbage collector functions
- *
- * @param mixed Callback function
- */
- public function register_gc_handler($func)
- {
- foreach ($this->gc_handlers as $handler) {
- if ($handler == $func) {
- return;
- }
+
+
+ /**
+ * Unserialize session data
+ * http://www.php.net/manual/en/function.session-decode.php#56106
+ */
+ private function unserialize($str)
+ {
+ $str = (string)$str;
+ $endptr = strlen($str);
+ $p = 0;
+
+ $serialized = '';
+ $items = 0;
+ $level = 0;
+
+ while ($p < $endptr) {
+ $q = $p;
+ while ($str[$q] != '|')
+ if (++$q >= $endptr)
+ break 2;
+
+ if ($str[$p] == '!') {
+ $p++;
+ $has_value = false;
+ }
+ else {
+ $has_value = true;
+ }
+
+ $name = substr($str, $p, $q - $p);
+ $q++;
+
+ $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
+
+ if ($has_value) {
+ for (;;) {
+ $p = $q;
+ switch (strtolower($str[$q])) {
+ case 'n': // null
+ case 'b': // boolean
+ case 'i': // integer
+ case 'd': // decimal
+ do $q++;
+ while ( ($q < $endptr) && ($str[$q] != ';') );
+ $q++;
+ $serialized .= substr($str, $p, $q - $p);
+ if ($level == 0)
+ break 2;
+ break;
+ case 'r': // reference
+ $q+= 2;
+ for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++)
+ $id .= $str[$q];
+ $q++;
+ // increment pointer because of outer array
+ $serialized .= 'R:' . ($id + 1) . ';';
+ if ($level == 0)
+ break 2;
+ break;
+ case 's': // string
+ $q+=2;
+ for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++)
+ $length .= $str[$q];
+ $q+=2;
+ $q+= (int)$length + 2;
+ $serialized .= substr($str, $p, $q - $p);
+ if ($level == 0)
+ break 2;
+ break;
+ case 'a': // array
+ case 'o': // object
+ do $q++;
+ while ($q < $endptr && $str[$q] != '{');
+ $q++;
+ $level++;
+ $serialized .= substr($str, $p, $q - $p);
+ break;
+ case '}': // end of array|object
+ $q++;
+ $serialized .= substr($str, $p, $q - $p);
+ if (--$level == 0)
+ break 2;
+ break;
+ default:
+ return false;
+ }
+ }
+ }
+ else {
+ $serialized .= 'N;';
+ $q += 2;
+ }
+ $items++;
+ $p = $q;
+ }
+
+ return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
}
- $this->gc_handlers[] = $func;
- }
-
-
- /**
- * Generate and set new session id
- *
- * @param boolean $destroy If enabled the current session will be destroyed
- */
- public function regenerate_id($destroy=true)
- {
- session_regenerate_id($destroy);
-
- $this->vars = null;
- $this->key = session_id();
-
- return true;
- }
-
-
- /**
- * Unset a session variable
- *
- * @param string Varibale name
- * @return boolean True on success
- */
- public function remove($var=null)
- {
- if (empty($var))
- return $this->destroy(session_id());
-
- $this->unsets[] = $var;
- unset($_SESSION[$var]);
-
- return true;
- }
-
-
- /**
- * Kill this session
- */
- public function kill()
- {
- $this->vars = null;
- $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
- $this->destroy(session_id());
- rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
- }
-
-
- /**
- * Re-read session data from storage backend
- */
- public function reload()
- {
- if ($this->key && $this->memcache)
- $data = $this->mc_read($this->key);
- else if ($this->key)
- $data = $this->db_read($this->key);
-
- if ($data)
- session_decode($data);
- }
-
-
- /**
- * Serialize session data
- */
- private function serialize($vars)
- {
- $data = '';
- if (is_array($vars))
- foreach ($vars as $var=>$value)
- $data .= $var.'|'.serialize($value);
- else
- $data = 'b:0;';
- return $data;
- }
-
-
- /**
- * Unserialize session data
- * http://www.php.net/manual/en/function.session-decode.php#56106
- */
- private function unserialize($str)
- {
- $str = (string)$str;
- $endptr = strlen($str);
- $p = 0;
-
- $serialized = '';
- $items = 0;
- $level = 0;
-
- while ($p < $endptr) {
- $q = $p;
- while ($str[$q] != '|')
- if (++$q >= $endptr) break 2;
-
- if ($str[$p] == '!') {
- $p++;
- $has_value = false;
- } else {
- $has_value = true;
- }
-
- $name = substr($str, $p, $q - $p);
- $q++;
-
- $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
-
- if ($has_value) {
- for (;;) {
- $p = $q;
- switch (strtolower($str[$q])) {
- case 'n': /* null */
- case 'b': /* boolean */
- case 'i': /* integer */
- case 'd': /* decimal */
- do $q++;
- while ( ($q < $endptr) && ($str[$q] != ';') );
- $q++;
- $serialized .= substr($str, $p, $q - $p);
- if ($level == 0) break 2;
- break;
- case 'r': /* reference */
- $q+= 2;
- for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q];
- $q++;
- $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */
- if ($level == 0) break 2;
- break;
- case 's': /* string */
- $q+=2;
- for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q];
- $q+=2;
- $q+= (int)$length + 2;
- $serialized .= substr($str, $p, $q - $p);
- if ($level == 0) break 2;
- break;
- case 'a': /* array */
- case 'o': /* object */
- do $q++;
- while ( ($q < $endptr) && ($str[$q] != '{') );
- $q++;
- $level++;
- $serialized .= substr($str, $p, $q - $p);
- break;
- case '}': /* end of array|object */
- $q++;
- $serialized .= substr($str, $p, $q - $p);
- if (--$level == 0) break 2;
- break;
- default:
- return false;
- }
+
+ /**
+ * Setter for session lifetime
+ */
+ public function set_lifetime($lifetime)
+ {
+ $this->lifetime = max(120, $lifetime);
+
+ // valid time range is now - 1/2 lifetime to now + 1/2 lifetime
+ $now = time();
+ $this->now = $now - ($now % ($this->lifetime / 2));
+ }
+
+
+ /**
+ * Getter for remote IP saved with this session
+ */
+ public function get_ip()
+ {
+ return $this->ip;
+ }
+
+
+ /**
+ * Setter for cookie encryption secret
+ */
+ function set_secret($secret)
+ {
+ $this->secret = $secret;
+ }
+
+
+ /**
+ * Enable/disable IP check
+ */
+ function set_ip_check($check)
+ {
+ $this->ip_check = $check;
+ }
+
+
+ /**
+ * Setter for the cookie name used for session cookie
+ */
+ function set_cookiename($cookiename)
+ {
+ if ($cookiename) {
+ $this->cookiename = $cookiename;
}
- } else {
- $serialized .= 'N;';
- $q += 2;
- }
- $items++;
- $p = $q;
}
- return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
- }
-
-
- /**
- * Setter for session lifetime
- */
- public function set_lifetime($lifetime)
- {
- $this->lifetime = max(120, $lifetime);
-
- // valid time range is now - 1/2 lifetime to now + 1/2 lifetime
- $now = time();
- $this->now = $now - ($now % ($this->lifetime / 2));
- }
-
-
- /**
- * Getter for remote IP saved with this session
- */
- public function get_ip()
- {
- return $this->ip;
- }
-
-
- /**
- * Setter for cookie encryption secret
- */
- function set_secret($secret)
- {
- $this->secret = $secret;
- }
-
-
- /**
- * Enable/disable IP check
- */
- function set_ip_check($check)
- {
- $this->ip_check = $check;
- }
-
-
- /**
- * Setter for the cookie name used for session cookie
- */
- function set_cookiename($cookiename)
- {
- if ($cookiename)
- $this->cookiename = $cookiename;
- }
-
-
- /**
- * Check session authentication cookie
- *
- * @return boolean True if valid, False if not
- */
- function check_auth()
- {
- $this->cookie = $_COOKIE[$this->cookiename];
- $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
-
- if (!$result)
- $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
-
- if ($result && $this->_mkcookie($this->now) != $this->cookie) {
- $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
- $result = false;
-
- // Check if using id from a previous time slot
- for ($i = 1; $i <= 2; $i++) {
- $prev = $this->now - ($this->lifetime / 2) * $i;
- if ($this->_mkcookie($prev) == $this->cookie) {
- $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
- $this->set_auth_cookie();
- $result = true;
+
+ /**
+ * Check session authentication cookie
+ *
+ * @return boolean True if valid, False if not
+ */
+ function check_auth()
+ {
+ $this->cookie = $_COOKIE[$this->cookiename];
+ $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
+
+ if (!$result) {
+ $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
}
- }
+
+ if ($result && $this->_mkcookie($this->now) != $this->cookie) {
+ $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
+ $result = false;
+
+ // Check if using id from a previous time slot
+ for ($i = 1; $i <= 2; $i++) {
+ $prev = $this->now - ($this->lifetime / 2) * $i;
+ if ($this->_mkcookie($prev) == $this->cookie) {
+ $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
+ $this->set_auth_cookie();
+ $result = true;
+ }
+ }
+ }
+
+ if (!$result) {
+ $this->log("Session authentication failed for " . $this->key
+ . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Set session authentication cookie
+ */
+ function set_auth_cookie()
+ {
+ $this->cookie = $this->_mkcookie($this->now);
+ rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
+ $_COOKIE[$this->cookiename] = $this->cookie;
}
- if (!$result)
- $this->log("Session authentication failed for " . $this->key . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
-
- return $result;
- }
-
-
- /**
- * Set session authentication cookie
- */
- function set_auth_cookie()
- {
- $this->cookie = $this->_mkcookie($this->now);
- rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
- $_COOKIE[$this->cookiename] = $this->cookie;
- }
-
-
- /**
- * Create session cookie from session data
- *
- * @param int Time slot to use
- */
- function _mkcookie($timeslot)
- {
- $auth_string = "$this->key,$this->secret,$timeslot";
- return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
- }
-
- /**
- * Writes debug information to the log
- */
- function log($line)
- {
- if ($this->logging)
- rcube::write_log('session', $line);
- }
+ /**
+ * Create session cookie from session data
+ *
+ * @param int Time slot to use
+ */
+ function _mkcookie($timeslot)
+ {
+ $auth_string = "$this->key,$this->secret,$timeslot";
+ return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
+ }
+
+ /**
+ * Writes debug information to the log
+ */
+ function log($line)
+ {
+ if ($this->logging) {
+ rcube::write_log('session', $line);
+ }
+ }
}
diff --git a/lib/ext/Roundcube/rcube_smtp.php b/lib/ext/Roundcube/rcube_smtp.php
index 96534c0..5c7d220 100644
--- a/lib/ext/Roundcube/rcube_smtp.php
+++ b/lib/ext/Roundcube/rcube_smtp.php
@@ -2,10 +2,8 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_smtp.php |
- | |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2010, The Roundcube Dev Team |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -13,15 +11,11 @@
| |
| PURPOSE: |
| Provide SMTP functionality using socket connections |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
*/
-// define headers delimiter
-define('SMTP_MIME_CRLF', "\r\n");
-
/**
* Class to provide SMTP functionality using PEAR Net_SMTP
*
@@ -32,439 +26,425 @@ define('SMTP_MIME_CRLF', "\r\n");
*/
class rcube_smtp
{
-
- private $conn = null;
- private $response;
- private $error;
-
-
- /**
- * SMTP Connection and authentication
- *
- * @param string Server host
- * @param string Server port
- * @param string User name
- * @param string Password
- *
- * @return bool Returns true on success, or false on error
- */
- public function connect($host=null, $port=null, $user=null, $pass=null)
- {
- $rcube = rcube::get_instance();
-
- // disconnect/destroy $this->conn
- $this->disconnect();
-
- // reset error/response var
- $this->error = $this->response = null;
-
- // let plugins alter smtp connection config
- $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
- 'smtp_server' => $host ? $host : $rcube->config->get('smtp_server'),
- 'smtp_port' => $port ? $port : $rcube->config->get('smtp_port', 25),
- 'smtp_user' => $user ? $user : $rcube->config->get('smtp_user'),
- 'smtp_pass' => $pass ? $pass : $rcube->config->get('smtp_pass'),
- 'smtp_auth_cid' => $rcube->config->get('smtp_auth_cid'),
- 'smtp_auth_pw' => $rcube->config->get('smtp_auth_pw'),
- 'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
- 'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
- 'smtp_timeout' => $rcube->config->get('smtp_timeout'),
- 'smtp_auth_callbacks' => array(),
- ));
-
- $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
- // when called from Installer it's possible to have empty $smtp_host here
- if (!$smtp_host) $smtp_host = 'localhost';
- $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
- $smtp_host_url = parse_url($smtp_host);
-
- // overwrite port
- if (isset($smtp_host_url['host']) && isset($smtp_host_url['port']))
+ private $conn = null;
+ private $response;
+ private $error;
+
+ // define headers delimiter
+ const SMTP_MIME_CRLF = "\r\n";
+
+
+ /**
+ * SMTP Connection and authentication
+ *
+ * @param string Server host
+ * @param string Server port
+ * @param string User name
+ * @param string Password
+ *
+ * @return bool Returns true on success, or false on error
+ */
+ public function connect($host=null, $port=null, $user=null, $pass=null)
{
- $smtp_host = $smtp_host_url['host'];
- $smtp_port = $smtp_host_url['port'];
- }
+ $rcube = rcube::get_instance();
- // re-write smtp host
- if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme']))
- $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+ // disconnect/destroy $this->conn
+ $this->disconnect();
- // remove TLS prefix and set flag for use in Net_SMTP::auth()
- if (preg_match('#^tls://#i', $smtp_host)) {
- $smtp_host = preg_replace('#^tls://#i', '', $smtp_host);
- $use_tls = true;
- }
+ // reset error/response var
+ $this->error = $this->response = null;
+
+ // let plugins alter smtp connection config
+ $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
+ 'smtp_server' => $host ? $host : $rcube->config->get('smtp_server'),
+ 'smtp_port' => $port ? $port : $rcube->config->get('smtp_port', 25),
+ 'smtp_user' => $user ? $user : $rcube->config->get('smtp_user'),
+ 'smtp_pass' => $pass ? $pass : $rcube->config->get('smtp_pass'),
+ 'smtp_auth_cid' => $rcube->config->get('smtp_auth_cid'),
+ 'smtp_auth_pw' => $rcube->config->get('smtp_auth_pw'),
+ 'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
+ 'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
+ 'smtp_timeout' => $rcube->config->get('smtp_timeout'),
+ 'smtp_auth_callbacks' => array(),
+ ));
+
+ $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
+ // when called from Installer it's possible to have empty $smtp_host here
+ if (!$smtp_host) $smtp_host = 'localhost';
+ $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
+ $smtp_host_url = parse_url($smtp_host);
+
+ // overwrite port
+ if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) {
+ $smtp_host = $smtp_host_url['host'];
+ $smtp_port = $smtp_host_url['port'];
+ }
- if (!empty($CONFIG['smtp_helo_host']))
- $helo_host = $CONFIG['smtp_helo_host'];
- else if (!empty($_SERVER['SERVER_NAME']))
- $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
- else
- $helo_host = 'localhost';
+ // re-write smtp host
+ if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) {
+ $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+ }
- // IDNA Support
- $smtp_host = rcube_utils::idn_to_ascii($smtp_host);
+ // remove TLS prefix and set flag for use in Net_SMTP::auth()
+ if (preg_match('#^tls://#i', $smtp_host)) {
+ $smtp_host = preg_replace('#^tls://#i', '', $smtp_host);
+ $use_tls = true;
+ }
- $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
+ if (!empty($CONFIG['smtp_helo_host'])) {
+ $helo_host = $CONFIG['smtp_helo_host'];
+ }
+ else if (!empty($_SERVER['SERVER_NAME'])) {
+ $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
+ }
+ else {
+ $helo_host = 'localhost';
+ }
- if ($rcube->config->get('smtp_debug'))
- $this->conn->setDebug(true, array($this, 'debug_handler'));
+ // IDNA Support
+ $smtp_host = rcube_utils::idn_to_ascii($smtp_host);
- // register authentication methods
- if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
- foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
- $this->conn->setAuthMethod($callback['name'], $callback['function'],
- isset($callback['prepend']) ? $callback['prepend'] : true);
- }
- }
+ $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
- // try to connect to server and exit on failure
- $result = $this->conn->connect($smtp_timeout);
+ if ($rcube->config->get('smtp_debug')) {
+ $this->conn->setDebug(true, array($this, 'debug_handler'));
+ }
- if (PEAR::isError($result)) {
- $this->response[] = "Connection failed: ".$result->getMessage();
- $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
- $this->conn = null;
- return false;
- }
+ // register authentication methods
+ if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
+ foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
+ $this->conn->setAuthMethod($callback['name'], $callback['function'],
+ isset($callback['prepend']) ? $callback['prepend'] : true);
+ }
+ }
- // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843)
- if (method_exists($this->conn, 'setTimeout')
- && ($timeout = ini_get('default_socket_timeout'))
- ) {
- $this->conn->setTimeout($timeout);
- }
+ // try to connect to server and exit on failure
+ $result = $this->conn->connect($smtp_timeout);
- $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']);
- $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']);
- $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
+ if (PEAR::isError($result)) {
+ $this->response[] = "Connection failed: ".$result->getMessage();
+ $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
+ $this->conn = null;
+ return false;
+ }
- if (!empty($CONFIG['smtp_auth_cid'])) {
- $smtp_authz = $smtp_user;
- $smtp_user = $CONFIG['smtp_auth_cid'];
- $smtp_pass = $CONFIG['smtp_auth_pw'];
- }
+ // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843)
+ if (method_exists($this->conn, 'setTimeout')
+ && ($timeout = ini_get('default_socket_timeout'))
+ ) {
+ $this->conn->setTimeout($timeout);
+ }
- // attempt to authenticate to the SMTP server
- if ($smtp_user && $smtp_pass)
- {
- // IDNA Support
- if (strpos($smtp_user, '@')) {
- $smtp_user = rcube_utils::idn_to_ascii($smtp_user);
- }
-
- $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz);
-
- if (PEAR::isError($result))
- {
- $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
- $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
- $this->reset();
- $this->disconnect();
- return false;
- }
- }
+ $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']);
+ $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']);
+ $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
- return true;
- }
-
-
- /**
- * Function for sending mail
- *
- * @param string Sender e-Mail address
- *
- * @param mixed Either a comma-seperated list of recipients
- * (RFC822 compliant), or an array of recipients,
- * each RFC822 valid. This may contain recipients not
- * specified in the headers, for Bcc:, resending
- * messages, etc.
- * @param mixed The message headers to send with the mail
- * Either as an associative array or a finally
- * formatted string
- * @param mixed The full text of the message body, including any Mime parts
- * or file handle
- * @param array Delivery options (e.g. DSN request)
- *
- * @return bool Returns true on success, or false on error
- */
- public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
- {
- if (!is_object($this->conn))
- return false;
-
- // prepare message headers as string
- if (is_array($headers))
- {
- if (!($headerElements = $this->_prepare_headers($headers))) {
- $this->reset();
- return false;
- }
+ if (!empty($CONFIG['smtp_auth_cid'])) {
+ $smtp_authz = $smtp_user;
+ $smtp_user = $CONFIG['smtp_auth_cid'];
+ $smtp_pass = $CONFIG['smtp_auth_pw'];
+ }
- list($from, $text_headers) = $headerElements;
- }
- else if (is_string($headers))
- $text_headers = $headers;
- else
- {
- $this->reset();
- $this->response[] = "Invalid message headers";
- return false;
+ // attempt to authenticate to the SMTP server
+ if ($smtp_user && $smtp_pass) {
+ // IDNA Support
+ if (strpos($smtp_user, '@')) {
+ $smtp_user = rcube_utils::idn_to_ascii($smtp_user);
+ }
+
+ $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz);
+
+ if (PEAR::isError($result)) {
+ $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
+ $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
+ $this->reset();
+ $this->disconnect();
+ return false;
+ }
+ }
+
+ return true;
}
- // exit if no from address is given
- if (!isset($from))
+ /**
+ * Function for sending mail
+ *
+ * @param string Sender e-Mail address
+ *
+ * @param mixed Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ * @param mixed The message headers to send with the mail
+ * Either as an associative array or a finally
+ * formatted string
+ * @param mixed The full text of the message body, including any Mime parts
+ * or file handle
+ * @param array Delivery options (e.g. DSN request)
+ *
+ * @return bool Returns true on success, or false on error
+ */
+ public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
{
- $this->reset();
- $this->response[] = "No From address has been provided";
- return false;
- }
+ if (!is_object($this->conn)) {
+ return false;
+ }
- // RFC3461: Delivery Status Notification
- if ($opts['dsn']) {
- $exts = $this->conn->getServiceExtensions();
+ // prepare message headers as string
+ if (is_array($headers)) {
+ if (!($headerElements = $this->_prepare_headers($headers))) {
+ $this->reset();
+ return false;
+ }
- if (isset($exts['DSN'])) {
- $from_params = 'RET=HDRS';
- $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
- }
- }
+ list($from, $text_headers) = $headerElements;
+ }
+ else if (is_string($headers)) {
+ $text_headers = $headers;
+ }
+ else {
+ $this->reset();
+ $this->response[] = "Invalid message headers";
+ return false;
+ }
+
+ // exit if no from address is given
+ if (!isset($from)) {
+ $this->reset();
+ $this->response[] = "No From address has been provided";
+ return false;
+ }
+
+ // RFC3461: Delivery Status Notification
+ if ($opts['dsn']) {
+ $exts = $this->conn->getServiceExtensions();
+
+ if (isset($exts['DSN'])) {
+ $from_params = 'RET=HDRS';
+ $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
+ }
+ }
+
+ // RFC2298.3: remove envelope sender address
+ if (empty($opts['mdn_use_from'])
+ && preg_match('/Content-Type: multipart\/report/', $text_headers)
+ && preg_match('/report-type=disposition-notification/', $text_headers)
+ ) {
+ $from = '';
+ }
+
+ // set From: address
+ if (PEAR::isError($this->conn->mailFrom($from, $from_params))) {
+ $err = $this->conn->getResponse();
+ $this->error = array('label' => 'smtpfromerror', 'vars' => array(
+ 'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
+ $this->response[] = "Failed to set sender '$from'";
+ $this->reset();
+ return false;
+ }
+
+ // prepare list of recipients
+ $recipients = $this->_parse_rfc822($recipients);
+ if (PEAR::isError($recipients)) {
+ $this->error = array('label' => 'smtprecipientserror');
+ $this->reset();
+ return false;
+ }
- // RFC2298.3: remove envelope sender address
- if (preg_match('/Content-Type: multipart\/report/', $text_headers)
- && preg_match('/report-type=disposition-notification/', $text_headers)
- ) {
- $from = '';
+ // set mail recipients
+ foreach ($recipients as $recipient) {
+ if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
+ $err = $this->conn->getResponse();
+ $this->error = array('label' => 'smtptoerror', 'vars' => array(
+ 'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
+ $this->response[] = "Failed to add recipient '$recipient'";
+ $this->reset();
+ return false;
+ }
+ }
+
+ if (is_resource($body)) {
+ // file handle
+ $data = $body;
+ $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
+ }
+ else {
+ // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
+ // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy.
+ // We are still forced to make another copy here for a couple ticks so we don't really
+ // get to save a copy in the method call.
+ $data = $text_headers . "\r\n" . $body;
+
+ // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
+ unset($text_headers, $body);
+ }
+
+ // Send the message's headers and the body as SMTP data.
+ if (PEAR::isError($result = $this->conn->data($data, $text_headers))) {
+ $err = $this->conn->getResponse();
+ if (!in_array($err[0], array(354, 250, 221))) {
+ $msg = sprintf('[%d] %s', $err[0], $err[1]);
+ }
+ else {
+ $msg = $result->getMessage();
+ }
+
+ $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
+ $this->response[] = "Failed to send data";
+ $this->reset();
+ return false;
+ }
+
+ $this->response[] = join(': ', $this->conn->getResponse());
+ return true;
}
- // set From: address
- if (PEAR::isError($this->conn->mailFrom($from, $from_params)))
+ /**
+ * Reset the global SMTP connection
+ */
+ public function reset()
{
- $err = $this->conn->getResponse();
- $this->error = array('label' => 'smtpfromerror', 'vars' => array(
- 'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
- $this->response[] = "Failed to set sender '$from'";
- $this->reset();
- return false;
+ if (is_object($this->conn)) {
+ $this->conn->rset();
+ }
}
- // prepare list of recipients
- $recipients = $this->_parse_rfc822($recipients);
- if (PEAR::isError($recipients))
+ /**
+ * Disconnect the global SMTP connection
+ */
+ public function disconnect()
{
- $this->error = array('label' => 'smtprecipientserror');
- $this->reset();
- return false;
+ if (is_object($this->conn)) {
+ $this->conn->disconnect();
+ $this->conn = null;
+ }
}
- // set mail recipients
- foreach ($recipients as $recipient)
+
+ /**
+ * This is our own debug handler for the SMTP connection
+ */
+ public function debug_handler(&$smtp, $message)
{
- if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
- $err = $this->conn->getResponse();
- $this->error = array('label' => 'smtptoerror', 'vars' => array(
- 'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
- $this->response[] = "Failed to add recipient '$recipient'";
- $this->reset();
- return false;
- }
+ rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
}
- if (is_resource($body))
+ /**
+ * Get error message
+ */
+ public function get_error()
{
- // file handle
- $data = $body;
- $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
- } else {
- // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
- // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy.
- // We are still forced to make another copy here for a couple ticks so we don't really
- // get to save a copy in the method call.
- $data = $text_headers . "\r\n" . $body;
-
- // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
- unset($text_headers, $body);
+ return $this->error;
}
- // Send the message's headers and the body as SMTP data.
- if (PEAR::isError($result = $this->conn->data($data, $text_headers)))
+ /**
+ * Get server response messages array
+ */
+ public function get_response()
{
- $err = $this->conn->getResponse();
- if (!in_array($err[0], array(354, 250, 221)))
- $msg = sprintf('[%d] %s', $err[0], $err[1]);
- else
- $msg = $result->getMessage();
-
- $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
- $this->response[] = "Failed to send data";
- $this->reset();
- return false;
+ return $this->response;
}
- $this->response[] = join(': ', $this->conn->getResponse());
- return true;
- }
-
-
- /**
- * Reset the global SMTP connection
- * @access public
- */
- public function reset()
- {
- if (is_object($this->conn))
- $this->conn->rset();
- }
-
-
- /**
- * Disconnect the global SMTP connection
- * @access public
- */
- public function disconnect()
- {
- if (is_object($this->conn)) {
- $this->conn->disconnect();
- $this->conn = null;
- }
- }
-
-
- /**
- * This is our own debug handler for the SMTP connection
- * @access public
- */
- public function debug_handler(&$smtp, $message)
- {
- rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
- }
-
-
- /**
- * Get error message
- * @access public
- */
- public function get_error()
- {
- return $this->error;
- }
-
-
- /**
- * Get server response messages array
- * @access public
- */
- public function get_response()
- {
- return $this->response;
- }
-
-
- /**
- * Take an array of mail headers and return a string containing
- * text usable in sending a message.
- *
- * @param array $headers The array of headers to prepare, in an associative
- * array, where the array key is the header name (ie,
- * 'Subject'), and the array value is the header
- * value (ie, 'test'). The header produced from those
- * values would be 'Subject: test'.
- *
- * @return mixed Returns false if it encounters a bad address,
- * otherwise returns an array containing two
- * elements: Any From: address found in the headers,
- * and the plain text version of the headers.
- * @access private
- */
- private function _prepare_headers($headers)
- {
- $lines = array();
- $from = null;
-
- foreach ($headers as $key => $value)
+ /**
+ * Take an array of mail headers and return a string containing
+ * text usable in sending a message.
+ *
+ * @param array $headers The array of headers to prepare, in an associative
+ * array, where the array key is the header name (ie,
+ * 'Subject'), and the array value is the header
+ * value (ie, 'test'). The header produced from those
+ * values would be 'Subject: test'.
+ *
+ * @return mixed Returns false if it encounters a bad address,
+ * otherwise returns an array containing two
+ * elements: Any From: address found in the headers,
+ * and the plain text version of the headers.
+ */
+ private function _prepare_headers($headers)
{
- if (strcasecmp($key, 'From') === 0)
- {
- $addresses = $this->_parse_rfc822($value);
-
- if (is_array($addresses))
- $from = $addresses[0];
-
- // Reject envelope From: addresses with spaces.
- if (strpos($from, ' ') !== false)
- return false;
-
- $lines[] = $key . ': ' . $value;
- }
- else if (strcasecmp($key, 'Received') === 0)
- {
- $received = array();
- if (is_array($value))
- {
- foreach ($value as $line)
- $received[] = $key . ': ' . $line;
- }
- else
- {
- $received[] = $key . ': ' . $value;
+ $lines = array();
+ $from = null;
+
+ foreach ($headers as $key => $value) {
+ if (strcasecmp($key, 'From') === 0) {
+ $addresses = $this->_parse_rfc822($value);
+
+ if (is_array($addresses)) {
+ $from = $addresses[0];
+ }
+
+ // Reject envelope From: addresses with spaces.
+ if (strpos($from, ' ') !== false) {
+ return false;
+ }
+
+ $lines[] = $key . ': ' . $value;
+ }
+ else if (strcasecmp($key, 'Received') === 0) {
+ $received = array();
+ if (is_array($value)) {
+ foreach ($value as $line) {
+ $received[] = $key . ': ' . $line;
+ }
+ }
+ else {
+ $received[] = $key . ': ' . $value;
+ }
+
+ // Put Received: headers at the top. Spam detectors often
+ // flag messages with Received: headers after the Subject:
+ // as spam.
+ $lines = array_merge($received, $lines);
+ }
+ else {
+ // If $value is an array (i.e., a list of addresses), convert
+ // it to a comma-delimited string of its elements (addresses).
+ if (is_array($value)) {
+ $value = implode(', ', $value);
+ }
+
+ $lines[] = $key . ': ' . $value;
+ }
}
- // Put Received: headers at the top. Spam detectors often
- // flag messages with Received: headers after the Subject:
- // as spam.
- $lines = array_merge($received, $lines);
- }
- else
- {
- // If $value is an array (i.e., a list of addresses), convert
- // it to a comma-delimited string of its elements (addresses).
- if (is_array($value))
- $value = implode(', ', $value);
-
- $lines[] = $key . ': ' . $value;
- }
+ return array($from, join(self::SMTP_MIME_CRLF, $lines) . self::SMTP_MIME_CRLF);
}
- return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
- }
-
- /**
- * Take a set of recipients and parse them, returning an array of
- * bare addresses (forward paths) that can be passed to sendmail
- * or an smtp server with the rcpt to: command.
- *
- * @param mixed Either a comma-seperated list of recipients
- * (RFC822 compliant), or an array of recipients,
- * each RFC822 valid.
- *
- * @return array An array of forward paths (bare addresses).
- * @access private
- */
- private function _parse_rfc822($recipients)
- {
- // if we're passed an array, assume addresses are valid and implode them before parsing.
- if (is_array($recipients))
- $recipients = implode(', ', $recipients);
-
- $addresses = array();
- $recipients = rcube_utils::explode_quoted_string(',', $recipients);
-
- reset($recipients);
- while (list($k, $recipient) = each($recipients))
+ /**
+ * Take a set of recipients and parse them, returning an array of
+ * bare addresses (forward paths) that can be passed to sendmail
+ * or an smtp server with the rcpt to: command.
+ *
+ * @param mixed Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid.
+ *
+ * @return array An array of forward paths (bare addresses).
+ */
+ private function _parse_rfc822($recipients)
{
- $a = rcube_utils::explode_quoted_string(' ', $recipient);
- 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)
- array_push($addresses, $word);
+ // if we're passed an array, assume addresses are valid and implode them before parsing.
+ if (is_array($recipients)) {
+ $recipients = implode(', ', $recipients);
}
- }
- }
- return $addresses;
- }
+ $addresses = array();
+ $recipients = rcube_utils::explode_quoted_string(',', $recipients);
+
+ reset($recipients);
+ while (list($k, $recipient) = each($recipients)) {
+ $a = rcube_utils::explode_quoted_string(' ', $recipient);
+ 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) {
+ array_push($addresses, $word);
+ }
+ }
+ }
+ }
+ return $addresses;
+ }
}
diff --git a/lib/ext/Roundcube/rcube_spellchecker.php b/lib/ext/Roundcube/rcube_spellchecker.php
index fce2cac..3d4d3a3 100644
--- a/lib/ext/Roundcube/rcube_spellchecker.php
+++ b/lib/ext/Roundcube/rcube_spellchecker.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_spellchecker.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2011, Kolab Systems AG |
| Copyright (C) 2008-2011, The Roundcube Dev Team |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| Spellchecking using different backends |
- | |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak at kolabsys.com> |
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
*/
-
/**
* Helper class for spellchecking with Googielspell and PSpell support.
*
@@ -447,7 +443,7 @@ class rcube_spellchecker
private function html2text($text)
{
- $h2t = new html2text($text, false, true, 0);
+ $h2t = new rcube_html2text($text, false, true, 0);
return $h2t->get_text();
}
diff --git a/lib/ext/Roundcube/rcube_storage.php b/lib/ext/Roundcube/rcube_storage.php
index 245d911..8a36f1f 100644
--- a/lib/ext/Roundcube/rcube_storage.php
+++ b/lib/ext/Roundcube/rcube_storage.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_storage.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2012, Kolab Systems AG |
@@ -14,14 +12,12 @@
| |
| PURPOSE: |
| Mail Storage Engine |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Abstract class for accessing mail messages storage server
*
@@ -57,6 +53,7 @@ abstract class rcube_storage
protected $all_headers = array(
'IN-REPLY-TO',
'BCC',
+ 'SENDER',
'MESSAGE-ID',
'CONTENT-TRANSFER-ENCODING',
'REFERENCES',
@@ -65,6 +62,7 @@ abstract class rcube_storage
'MAIL-REPLY-TO',
'RETURN-PATH',
'DELIVERED-TO',
+ 'ENVELOPE-TO',
);
const UNKNOWN = 0;
@@ -353,7 +351,7 @@ abstract class rcube_storage
* Get messages count for a specific folder.
*
* @param string $folder Folder name
- * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
+ * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
* @param boolean $force Force reading from server and update cache
* @param boolean $status Enables storing folder status info (max UID/count),
* required for folder_status()
diff --git a/lib/ext/Roundcube/rcube_string_replacer.php b/lib/ext/Roundcube/rcube_string_replacer.php
index 584b9f6..49a3781 100644
--- a/lib/ext/Roundcube/rcube_string_replacer.php
+++ b/lib/ext/Roundcube/rcube_string_replacer.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_string_replacer.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2009-2012, The Roundcube Dev Team |
| |
@@ -13,13 +11,11 @@
| |
| PURPOSE: |
| Handle string replacements based on preg_replace_callback |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
+-----------------------------------------------------------------------+
*/
-
/**
* Helper class for string replacements based on preg_replace_callback
*
@@ -28,164 +24,189 @@
*/
class rcube_string_replacer
{
- public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
- public $mailto_pattern;
- public $link_pattern;
- private $values = array();
-
-
- function __construct()
- {
- // Simplified domain expression for UTF8 characters handling
- // Support unicode/punycode in top-level domain part
- $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
- $url1 = '.:;,';
- $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
-
- $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
- $this->mailto_pattern = "/("
- ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part
- ."@$utf_domain" // domain-part
- ."(\?[$url1$url2]+)?" // e.g. ?subject=test...
- .")/";
- }
-
- /**
- * Add a string to the internal list
- *
- * @param string String value
- * @return int Index of value for retrieval
- */
- public function add($str)
- {
- $i = count($this->values);
- $this->values[$i] = $str;
- return $i;
- }
-
- /**
- * Build replacement string
- */
- public function get_replacement($i)
- {
- return '##str_replacement['.$i.']##';
- }
-
- /**
- * Callback function used to build HTML links around URL strings
- *
- * @param array Matches result from preg_replace_callback
- * @return int Index of saved string value
- */
- public function link_callback($matches)
- {
- $i = -1;
- $scheme = strtolower($matches[1]);
-
- if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
- $url = $matches[1] . $matches[2];
+ public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
+ public $mailto_pattern;
+ public $link_pattern;
+ private $values = array();
+
+
+ function __construct()
+ {
+ // Simplified domain expression for UTF8 characters handling
+ // Support unicode/punycode in top-level domain part
+ $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
+ $url1 = '.:;,';
+ $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-';
+
+ $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
+ $this->mailto_pattern = "/("
+ ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part
+ ."@$utf_domain" // domain-part
+ ."(\?[$url1$url2]+)?" // e.g. ?subject=test...
+ .")/";
}
- else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
- $url = $m[2] . $matches[2];
- $url_prefix = 'http://';
- $prefix = $m[1];
+
+ /**
+ * Add a string to the internal list
+ *
+ * @param string String value
+ * @return int Index of value for retrieval
+ */
+ public function add($str)
+ {
+ $i = count($this->values);
+ $this->values[$i] = $str;
+ return $i;
}
- if ($url) {
- $suffix = $this->parse_url_brackets($url);
- $i = $this->add($prefix . html::a(array(
- 'href' => $url_prefix . $url,
- 'target' => '_blank'
- ), rcube::Q($url)) . $suffix);
+ /**
+ * Build replacement string
+ */
+ public function get_replacement($i)
+ {
+ return '##str_replacement['.$i.']##';
}
- // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
- return $i >= 0 ? $this->get_replacement($i) : $matches[0];
- }
-
- /**
- * Callback function used to build mailto: links around e-mail strings
- *
- * @param array Matches result from preg_replace_callback
- * @return int Index of saved string value
- */
- public function mailto_callback($matches)
- {
- $href = $matches[1];
- $suffix = $this->parse_url_brackets($href);
- $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
-
- return $i >= 0 ? $this->get_replacement($i) : '';
- }
-
- /**
- * Look up the index from the preg_replace matches array
- * and return the substitution value.
- *
- * @param array Matches result from preg_replace_callback
- * @return string Value at index $matches[1]
- */
- public function replace_callback($matches)
- {
- return $this->values[$matches[1]];
- }
-
- /**
- * Replace all defined (link|mailto) patterns with replacement string
- *
- * @param string $str Text
- *
- * @return string Text
- */
- public function replace($str)
- {
- // search for patterns like links and e-mail addresses
- $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
- $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
-
- return $str;
- }
-
- /**
- * Replace substituted strings with original values
- */
- public function resolve($str)
- {
- return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
- }
-
- /**
- * Fixes bracket characters in URL handling
- */
- public static function parse_url_brackets(&$url)
- {
- // #1487672: special handling of square brackets,
- // URL regexp allows [] characters in URL, for example:
- // "http://example.com/?a[b]=c". However we need to handle
- // properly situation when a bracket is placed at the end
- // of the link e.g. "[http://example.com]"
- if (preg_match('/(\\[|\\])/', $url)) {
- $in = false;
- for ($i=0, $len=strlen($url); $i<$len; $i++) {
- if ($url[$i] == '[') {
- if ($in)
- break;
- $in = true;
+ /**
+ * Callback function used to build HTML links around URL strings
+ *
+ * @param array Matches result from preg_replace_callback
+ * @return int Index of saved string value
+ */
+ public function link_callback($matches)
+ {
+ $i = -1;
+ $scheme = strtolower($matches[1]);
+
+ if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
+ $url = $matches[1] . $matches[2];
}
- else if ($url[$i] == ']') {
- if (!$in)
- break;
- $in = false;
+ else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
+ $url = $m[2] . $matches[2];
+ $url_prefix = 'http://';
+ $prefix = $m[1];
}
- }
- if ($i<$len) {
- $suffix = substr($url, $i);
- $url = substr($url, 0, $i);
- }
+ if ($url) {
+ $suffix = $this->parse_url_brackets($url);
+ $i = $this->add($prefix . html::a(array(
+ 'href' => $url_prefix . $url,
+ 'target' => '_blank'
+ ), rcube::Q($url)) . $suffix);
+ }
+
+ // Return valid link for recognized schemes, otherwise
+ // return the unmodified string for unrecognized schemes.
+ return $i >= 0 ? $this->get_replacement($i) : $matches[0];
+ }
+
+ /**
+ * Callback function used to build mailto: links around e-mail strings
+ *
+ * @param array Matches result from preg_replace_callback
+ * @return int Index of saved string value
+ */
+ public function mailto_callback($matches)
+ {
+ $href = $matches[1];
+ $suffix = $this->parse_url_brackets($href);
+ $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
+
+ return $i >= 0 ? $this->get_replacement($i) : '';
+ }
+
+ /**
+ * Look up the index from the preg_replace matches array
+ * and return the substitution value.
+ *
+ * @param array Matches result from preg_replace_callback
+ * @return string Value at index $matches[1]
+ */
+ public function replace_callback($matches)
+ {
+ return $this->values[$matches[1]];
}
- return $suffix;
- }
+ /**
+ * Replace all defined (link|mailto) patterns with replacement string
+ *
+ * @param string $str Text
+ *
+ * @return string Text
+ */
+ public function replace($str)
+ {
+ // search for patterns like links and e-mail addresses
+ $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
+ $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
+
+ return $str;
+ }
+ /**
+ * Replace substituted strings with original values
+ */
+ public function resolve($str)
+ {
+ return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
+ }
+
+ /**
+ * Fixes bracket characters in URL handling
+ */
+ public static function parse_url_brackets(&$url)
+ {
+ // #1487672: special handling of square brackets,
+ // URL regexp allows [] characters in URL, for example:
+ // "http://example.com/?a[b]=c". However we need to handle
+ // properly situation when a bracket is placed at the end
+ // of the link e.g. "[http://example.com]"
+ // Yes, this is not perfect handles correctly only paired characters
+ // but it should work for common cases
+
+ if (preg_match('/(\\[|\\])/', $url)) {
+ $in = false;
+ for ($i=0, $len=strlen($url); $i<$len; $i++) {
+ if ($url[$i] == '[') {
+ if ($in)
+ break;
+ $in = true;
+ }
+ else if ($url[$i] == ']') {
+ if (!$in)
+ break;
+ $in = false;
+ }
+ }
+
+ if ($i < $len) {
+ $suffix = substr($url, $i);
+ $url = substr($url, 0, $i);
+ }
+ }
+
+ // Do the same for parentheses
+ if (preg_match('/(\\(|\\))/', $url)) {
+ $in = false;
+ for ($i=0, $len=strlen($url); $i<$len; $i++) {
+ if ($url[$i] == '(') {
+ if ($in)
+ break;
+ $in = true;
+ }
+ else if ($url[$i] == ')') {
+ if (!$in)
+ break;
+ $in = false;
+ }
+ }
+
+ if ($i < $len) {
+ $suffix = substr($url, $i);
+ $url = substr($url, 0, $i);
+ }
+ }
+
+ return $suffix;
+ }
}
diff --git a/lib/ext/Roundcube/rcube_user.php b/lib/ext/Roundcube/rcube_user.php
index f6b77f5..505b190 100644
--- a/lib/ext/Roundcube/rcube_user.php
+++ b/lib/ext/Roundcube/rcube_user.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_user.inc |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
@@ -14,14 +12,12 @@
| PURPOSE: |
| This class represents a system user linked and provides access |
| to the related database records. |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube at gmail.com> |
| Author: Aleksander Machniak <alec at alec.pl> |
+-----------------------------------------------------------------------+
*/
-
/**
* Class representing a system user
*
diff --git a/lib/ext/Roundcube/rcube_utils.php b/lib/ext/Roundcube/rcube_utils.php
index 500f2c3..4b68711 100644
--- a/lib/ext/Roundcube/rcube_utils.php
+++ b/lib/ext/Roundcube/rcube_utils.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_utils.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
@@ -20,7 +18,6 @@
+-----------------------------------------------------------------------+
*/
-
/**
* Utility class providing common functions
*
diff --git a/lib/ext/Roundcube/rcube_vcard.php b/lib/ext/Roundcube/rcube_vcard.php
index 45ee601..c2b30af 100644
--- a/lib/ext/Roundcube/rcube_vcard.php
+++ b/lib/ext/Roundcube/rcube_vcard.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_vcard.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| |
@@ -19,7 +17,6 @@
+-----------------------------------------------------------------------+
*/
-
/**
* Logical representation of a vcard-based address record
* Provides functions to parse and export vCard data format
@@ -29,765 +26,824 @@
*/
class rcube_vcard
{
- private static $values_decoded = false;
- private $raw = array(
- 'FN' => array(),
- 'N' => array(array('','','','','')),
- );
- private static $fieldmap = array(
- 'phone' => 'TEL',
- 'birthday' => 'BDAY',
- 'website' => 'URL',
- 'notes' => 'NOTE',
- 'email' => 'EMAIL',
- 'address' => 'ADR',
- 'jobtitle' => 'TITLE',
- 'department' => 'X-DEPARTMENT',
- 'gender' => 'X-GENDER',
- 'maidenname' => 'X-MAIDENNAME',
- 'anniversary' => 'X-ANNIVERSARY',
- 'assistant' => 'X-ASSISTANT',
- 'manager' => 'X-MANAGER',
- 'spouse' => 'X-SPOUSE',
- 'edit' => 'X-AB-EDIT',
- );
- private $typemap = array('IPHONE' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax');
- private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'BUSINESSFAX' => 'WORK,FAX', 'MOBILE' => 'CELL');
- private $addresstypemap = array('BUSINESS' => 'WORK');
- private $immap = array('X-JABBER' => 'jabber', 'X-ICQ' => 'icq', 'X-MSN' => 'msn', 'X-AIM' => 'aim', 'X-YAHOO' => 'yahoo', 'X-SKYPE' => 'skype', 'X-SKYPE-USERNAME' => 'skype');
-
- public $business = false;
- public $displayname;
- public $surname;
- public $firstname;
- public $middlename;
- public $nickname;
- public $organization;
- public $email = array();
-
- public static $eol = "\r\n";
-
- /**
- * Constructor
- */
- public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
- {
- if (!empty($fielmap))
- $this->extend_fieldmap($fieldmap);
-
- if (!empty($vcard))
- $this->load($vcard, $charset, $detect);
- }
-
-
- /**
- * Load record from (internal, unfolded) vcard 3.0 format
- *
- * @param string vCard string to parse
- * @param string Charset of string values
- * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
- */
- public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
- {
- self::$values_decoded = false;
- $this->raw = self::vcard_decode($vcard);
-
- // resolve charset parameters
- if ($charset == null) {
- $this->raw = self::charset_convert($this->raw);
+ private static $values_decoded = false;
+ private $raw = array(
+ 'FN' => array(),
+ 'N' => array(array('','','','','')),
+ );
+ private static $fieldmap = array(
+ 'phone' => 'TEL',
+ 'birthday' => 'BDAY',
+ 'website' => 'URL',
+ 'notes' => 'NOTE',
+ 'email' => 'EMAIL',
+ 'address' => 'ADR',
+ 'jobtitle' => 'TITLE',
+ 'department' => 'X-DEPARTMENT',
+ 'gender' => 'X-GENDER',
+ 'maidenname' => 'X-MAIDENNAME',
+ 'anniversary' => 'X-ANNIVERSARY',
+ 'assistant' => 'X-ASSISTANT',
+ 'manager' => 'X-MANAGER',
+ 'spouse' => 'X-SPOUSE',
+ 'edit' => 'X-AB-EDIT',
+ );
+ private $typemap = array(
+ 'IPHONE' => 'mobile',
+ 'CELL' => 'mobile',
+ 'WORK,FAX' => 'workfax',
+ );
+ private $phonetypemap = array(
+ 'HOME1' => 'HOME',
+ 'BUSINESS1' => 'WORK',
+ 'BUSINESS2' => 'WORK2',
+ 'BUSINESSFAX' => 'WORK,FAX',
+ 'MOBILE' => 'CELL',
+ );
+ private $addresstypemap = array(
+ 'BUSINESS' => 'WORK',
+ );
+ private $immap = array(
+ 'X-JABBER' => 'jabber',
+ 'X-ICQ' => 'icq',
+ 'X-MSN' => 'msn',
+ 'X-AIM' => 'aim',
+ 'X-YAHOO' => 'yahoo',
+ 'X-SKYPE' => 'skype',
+ 'X-SKYPE-USERNAME' => 'skype',
+ );
+
+ public $business = false;
+ public $displayname;
+ public $surname;
+ public $firstname;
+ public $middlename;
+ public $nickname;
+ public $organization;
+ public $email = array();
+
+ public static $eol = "\r\n";
+
+
+ /**
+ * Constructor
+ */
+ public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
+ {
+ if (!empty($fielmap)) {
+ $this->extend_fieldmap($fieldmap);
+ }
+
+ if (!empty($vcard)) {
+ $this->load($vcard, $charset, $detect);
+ }
}
- // vcard has encoded values and charset should be detected
- else if ($detect && self::$values_decoded &&
- ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw))) && $detected_charset != RCUBE_CHARSET) {
- $this->raw = self::charset_convert($this->raw, $detected_charset);
+
+ /**
+ * Load record from (internal, unfolded) vcard 3.0 format
+ *
+ * @param string vCard string to parse
+ * @param string Charset of string values
+ * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
+ */
+ public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
+ {
+ self::$values_decoded = false;
+ $this->raw = self::vcard_decode($vcard);
+
+ // resolve charset parameters
+ if ($charset == null) {
+ $this->raw = self::charset_convert($this->raw);
+ }
+ // vcard has encoded values and charset should be detected
+ else if ($detect && self::$values_decoded
+ && ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw)))
+ && $detected_charset != RCUBE_CHARSET
+ ) {
+ $this->raw = self::charset_convert($this->raw, $detected_charset);
+ }
+
+ // consider FN empty if the same as the primary e-mail address
+ if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0]) {
+ $this->raw['FN'][0][0] = '';
+ }
+
+ // find well-known address fields
+ $this->displayname = $this->raw['FN'][0][0];
+ $this->surname = $this->raw['N'][0][0];
+ $this->firstname = $this->raw['N'][0][1];
+ $this->middlename = $this->raw['N'][0][2];
+ $this->nickname = $this->raw['NICKNAME'][0][0];
+ $this->organization = $this->raw['ORG'][0][0];
+ $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
+
+ foreach ((array)$this->raw['EMAIL'] as $i => $raw_email) {
+ $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
+ }
+
+ // make the pref e-mail address the first entry in $this->email
+ $pref_index = $this->get_type_index('EMAIL', 'pref');
+ if ($pref_index > 0) {
+ $tmp = $this->email[0];
+ $this->email[0] = $this->email[$pref_index];
+ $this->email[$pref_index] = $tmp;
+ }
}
- // consider FN empty if the same as the primary e-mail address
- if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0])
- $this->raw['FN'][0][0] = '';
-
- // find well-known address fields
- $this->displayname = $this->raw['FN'][0][0];
- $this->surname = $this->raw['N'][0][0];
- $this->firstname = $this->raw['N'][0][1];
- $this->middlename = $this->raw['N'][0][2];
- $this->nickname = $this->raw['NICKNAME'][0][0];
- $this->organization = $this->raw['ORG'][0][0];
- $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
-
- foreach ((array)$this->raw['EMAIL'] as $i => $raw_email)
- $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
-
- // make the pref e-mail address the first entry in $this->email
- $pref_index = $this->get_type_index('EMAIL', 'pref');
- if ($pref_index > 0) {
- $tmp = $this->email[0];
- $this->email[0] = $this->email[$pref_index];
- $this->email[$pref_index] = $tmp;
+ /**
+ * Return vCard data as associative array to be unsed in Roundcube address books
+ *
+ * @return array Hash array with key-value pairs
+ */
+ public function get_assoc()
+ {
+ $out = array('name' => $this->displayname);
+ $typemap = $this->typemap;
+
+ // copy name fields to output array
+ foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
+ if (strlen($this->$col)) {
+ $out[$col] = $this->$col;
+ }
+ }
+
+ if ($this->raw['N'][0][3])
+ $out['prefix'] = $this->raw['N'][0][3];
+ if ($this->raw['N'][0][4])
+ $out['suffix'] = $this->raw['N'][0][4];
+
+ // convert from raw vcard data into associative data for Roundcube
+ foreach (array_flip(self::$fieldmap) as $tag => $col) {
+ foreach ((array)$this->raw[$tag] as $i => $raw) {
+ if (is_array($raw)) {
+ $k = -1;
+ $key = $col;
+ $subtype = '';
+
+ if (!empty($raw['type'])) {
+ $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
+ $combined = strtoupper($combined);
+
+ if ($typemap[$combined]) {
+ $subtype = $typemap[$combined];
+ }
+ else if ($typemap[$raw['type'][++$k]]) {
+ $subtype = $typemap[$raw['type'][$k]];
+ }
+ else {
+ $subtype = strtolower($raw['type'][$k]);
+ }
+
+ while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) {
+ $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
+ }
+ }
+
+ // read vcard 2.1 subtype
+ if (!$subtype) {
+ foreach ($raw as $k => $v) {
+ if (!is_numeric($k) && $v === true && ($k = strtolower($k))
+ && !in_array($k, array('pref','internet','voice','base64'))
+ ) {
+ $k_uc = strtoupper($k);
+ $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
+ break;
+ }
+ }
+ }
+
+ // force subtype if none set
+ if (!$subtype && preg_match('/^(email|phone|address|website)/', $key)) {
+ $subtype = 'other';
+ }
+
+ if ($subtype) {
+ $key .= ':' . $subtype;
+ }
+
+ // split ADR values into assoc array
+ if ($tag == 'ADR') {
+ list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
+ $out[$key][] = $value;
+ }
+ else {
+ $out[$key][] = $raw[0];
+ }
+ }
+ else {
+ $out[$col][] = $raw;
+ }
+ }
+ }
+
+ // handle special IM fields as used by Apple
+ foreach ($this->immap as $tag => $type) {
+ foreach ((array)$this->raw[$tag] as $i => $raw) {
+ $out['im:'.$type][] = $raw[0];
+ }
+ }
+
+ // copy photo data
+ if ($this->raw['PHOTO']) {
+ $out['photo'] = $this->raw['PHOTO'][0][0];
+ }
+
+ return $out;
}
- }
-
-
- /**
- * Return vCard data as associative array to be unsed in Roundcube address books
- *
- * @return array Hash array with key-value pairs
- */
- public function get_assoc()
- {
- $out = array('name' => $this->displayname);
- $typemap = $this->typemap;
-
- // copy name fields to output array
- foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
- if (strlen($this->$col))
- $out[$col] = $this->$col;
+
+ /**
+ * Convert the data structure into a vcard 3.0 string
+ */
+ public function export($folded = true)
+ {
+ $vcard = self::vcard_encode($this->raw);
+ return $folded ? self::rfc2425_fold($vcard) : $vcard;
}
- if ($this->raw['N'][0][3])
- $out['prefix'] = $this->raw['N'][0][3];
- if ($this->raw['N'][0][4])
- $out['suffix'] = $this->raw['N'][0][4];
-
- // convert from raw vcard data into associative data for Roundcube
- foreach (array_flip(self::$fieldmap) as $tag => $col) {
- foreach ((array)$this->raw[$tag] as $i => $raw) {
- if (is_array($raw)) {
- $k = -1;
- $key = $col;
- $subtype = '';
-
- if (!empty($raw['type'])) {
- $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
- $combined = strtoupper($combined);
-
- if ($typemap[$combined]) {
- $subtype = $typemap[$combined];
- }
- else if ($typemap[$raw['type'][++$k]]) {
- $subtype = $typemap[$raw['type'][$k]];
+ /**
+ * Clear the given fields in the loaded vcard data
+ *
+ * @param array List of field names to be reset
+ */
+ public function reset($fields = null)
+ {
+ if (!$fields) {
+ $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap),
+ array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
+ }
+
+ foreach ($fields as $f) {
+ unset($this->raw[$f]);
+ }
+
+ if (!$this->raw['N']) {
+ $this->raw['N'] = array(array('','','','',''));
+ }
+ if (!$this->raw['FN']) {
+ $this->raw['FN'] = array();
+ }
+
+ $this->email = array();
+ }
+
+ /**
+ * Setter for address record fields
+ *
+ * @param string Field name
+ * @param string Field value
+ * @param string Type/section name
+ */
+ public function set($field, $value, $type = 'HOME')
+ {
+ $field = strtolower($field);
+ $type_uc = strtoupper($type);
+
+ switch ($field) {
+ case 'name':
+ case 'displayname':
+ $this->raw['FN'][0][0] = $this->displayname = $value;
+ break;
+
+ case 'surname':
+ $this->raw['N'][0][0] = $this->surname = $value;
+ break;
+
+ case 'firstname':
+ $this->raw['N'][0][1] = $this->firstname = $value;
+ break;
+
+ case 'middlename':
+ $this->raw['N'][0][2] = $this->middlename = $value;
+ break;
+
+ case 'prefix':
+ $this->raw['N'][0][3] = $value;
+ break;
+
+ case 'suffix':
+ $this->raw['N'][0][4] = $value;
+ break;
+
+ case 'nickname':
+ $this->raw['NICKNAME'][0][0] = $this->nickname = $value;
+ break;
+
+ case 'organization':
+ $this->raw['ORG'][0][0] = $this->organization = $value;
+ break;
+
+ case 'photo':
+ if (strpos($value, 'http:') === 0) {
+ // TODO: fetch file from URL and save it locally?
+ $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
}
else {
- $subtype = strtolower($raw['type'][$k]);
+ $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
}
+ break;
+
+ case 'email':
+ $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
+ $this->email[] = $value;
+ break;
+
+ case 'im':
+ // save IM subtypes into extension fields
+ $typemap = array_flip($this->immap);
+ if ($field = $typemap[strtolower($type)]) {
+ $this->raw[$field][] = array(0 => $value);
+ }
+ break;
- while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref'))
- $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
- }
-
- // read vcard 2.1 subtype
- if (!$subtype) {
- foreach ($raw as $k => $v) {
- if (!is_numeric($k) && $v === true && ($k = strtolower($k))
- && !in_array($k, array('pref','internet','voice','base64'))
- ) {
- $k_uc = strtoupper($k);
- $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
- break;
- }
+ case 'birthday':
+ case 'anniversary':
+ if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) {
+ $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
}
- }
+ break;
- // force subtype if none set
- if (!$subtype && preg_match('/^(email|phone|address|website)/', $key))
- $subtype = 'other';
+ case 'address':
+ if ($this->addresstypemap[$type_uc]) {
+ $type = $this->addresstypemap[$type_uc];
+ }
- if ($subtype)
- $key .= ':' . $subtype;
+ $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
- // split ADR values into assoc array
- if ($tag == 'ADR') {
- list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
- $out[$key][] = $value;
- }
- else
- $out[$key][] = $raw[0];
- }
- else {
- $out[$col][] = $raw;
+ // fall through if not empty
+ if (!strlen(join('', $value))) {
+ break;
+ }
+
+ default:
+ if ($field == 'phone' && $this->phonetypemap[$type_uc]) {
+ $type = $this->phonetypemap[$type_uc];
+ }
+
+ if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
+ $index = count($this->raw[$tag]);
+ $this->raw[$tag][$index] = (array)$value;
+ if ($type) {
+ $typemap = array_flip($this->typemap);
+ $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
+ }
+ }
+ break;
}
- }
}
- // handle special IM fields as used by Apple
- foreach ($this->immap as $tag => $type) {
- foreach ((array)$this->raw[$tag] as $i => $raw) {
- $out['im:'.$type][] = $raw[0];
- }
+ /**
+ * Setter for individual vcard properties
+ *
+ * @param string VCard tag name
+ * @param array Value-set of this vcard property
+ * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
+ */
+ public function set_raw($tag, $value, $append = false)
+ {
+ $index = $append ? count($this->raw[$tag]) : 0;
+ $this->raw[$tag][$index] = (array)$value;
}
- // copy photo data
- if ($this->raw['PHOTO'])
- $out['photo'] = $this->raw['PHOTO'][0][0];
-
- return $out;
- }
-
-
- /**
- * Convert the data structure into a vcard 3.0 string
- */
- public function export($folded = true)
- {
- $vcard = self::vcard_encode($this->raw);
- return $folded ? self::rfc2425_fold($vcard) : $vcard;
- }
-
-
- /**
- * Clear the given fields in the loaded vcard data
- *
- * @param array List of field names to be reset
- */
- public function reset($fields = null)
- {
- if (!$fields)
- $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap), array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
-
- foreach ($fields as $f)
- unset($this->raw[$f]);
-
- if (!$this->raw['N'])
- $this->raw['N'] = array(array('','','','',''));
- if (!$this->raw['FN'])
- $this->raw['FN'] = array();
-
- $this->email = array();
- }
-
-
- /**
- * Setter for address record fields
- *
- * @param string Field name
- * @param string Field value
- * @param string Type/section name
- */
- public function set($field, $value, $type = 'HOME')
- {
- $field = strtolower($field);
- $type_uc = strtoupper($type);
-
- switch ($field) {
- case 'name':
- case 'displayname':
- $this->raw['FN'][0][0] = $this->displayname = $value;
- break;
-
- case 'surname':
- $this->raw['N'][0][0] = $this->surname = $value;
- break;
-
- case 'firstname':
- $this->raw['N'][0][1] = $this->firstname = $value;
- break;
-
- case 'middlename':
- $this->raw['N'][0][2] = $this->middlename = $value;
- break;
-
- case 'prefix':
- $this->raw['N'][0][3] = $value;
- break;
-
- case 'suffix':
- $this->raw['N'][0][4] = $value;
- break;
-
- case 'nickname':
- $this->raw['NICKNAME'][0][0] = $this->nickname = $value;
- break;
-
- case 'organization':
- $this->raw['ORG'][0][0] = $this->organization = $value;
- break;
-
- case 'photo':
- if (strpos($value, 'http:') === 0) {
- // TODO: fetch file from URL and save it locally?
- $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
- }
- else {
- $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
- }
- break;
-
- case 'email':
- $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
- $this->email[] = $value;
- break;
-
- case 'im':
- // save IM subtypes into extension fields
- $typemap = array_flip($this->immap);
- if ($field = $typemap[strtolower($type)])
- $this->raw[$field][] = array(0 => $value);
- break;
-
- case 'birthday':
- case 'anniversary':
- if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
- $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
- break;
-
- case 'address':
- if ($this->addresstypemap[$type_uc])
- $type = $this->addresstypemap[$type_uc];
-
- $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
-
- // fall through if not empty
- if (!strlen(join('', $value)))
- break;
-
- default:
- if ($field == 'phone' && $this->phonetypemap[$type_uc])
- $type = $this->phonetypemap[$type_uc];
-
- if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
- $index = count($this->raw[$tag]);
- $this->raw[$tag][$index] = (array)$value;
- if ($type) {
- $typemap = array_flip($this->typemap);
- $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
- }
- }
- break;
- }
- }
-
- /**
- * Setter for individual vcard properties
- *
- * @param string VCard tag name
- * @param array Value-set of this vcard property
- * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
- */
- public function set_raw($tag, $value, $append = false)
- {
- $index = $append ? count($this->raw[$tag]) : 0;
- $this->raw[$tag][$index] = (array)$value;
- }
-
-
- /**
- * Find index with the '$type' attribute
- *
- * @param string Field name
- * @return int Field index having $type set
- */
- private function get_type_index($field, $type = 'pref')
- {
- $result = 0;
- if ($this->raw[$field]) {
- foreach ($this->raw[$field] as $i => $data) {
- if (is_array($data['type']) && in_array_nocase('pref', $data['type']))
- $result = $i;
- }
+ /**
+ * Find index with the '$type' attribute
+ *
+ * @param string Field name
+ * @return int Field index having $type set
+ */
+ private function get_type_index($field, $type = 'pref')
+ {
+ $result = 0;
+ if ($this->raw[$field]) {
+ foreach ($this->raw[$field] as $i => $data) {
+ if (is_array($data['type']) && in_array_nocase('pref', $data['type'])) {
+ $result = $i;
+ }
+ }
+ }
+
+ return $result;
}
- return $result;
- }
-
-
- /**
- * Convert a whole vcard (array) to UTF-8.
- * If $force_charset is null, each member value that has a charset parameter will be converted
- */
- private static function charset_convert($card, $force_charset = null)
- {
- foreach ($card as $key => $node) {
- foreach ($node as $i => $subnode) {
- if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
- foreach ($subnode as $j => $value) {
- if (is_numeric($j) && is_string($value))
- $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
- }
- unset($card[$key][$i]['charset']);
- }
- }
+ /**
+ * Convert a whole vcard (array) to UTF-8.
+ * If $force_charset is null, each member value that has a charset parameter will be converted
+ */
+ private static function charset_convert($card, $force_charset = null)
+ {
+ foreach ($card as $key => $node) {
+ foreach ($node as $i => $subnode) {
+ if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
+ foreach ($subnode as $j => $value) {
+ if (is_numeric($j) && is_string($value)) {
+ $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
+ }
+ }
+ unset($card[$key][$i]['charset']);
+ }
+ }
+ }
+
+ return $card;
}
- return $card;
- }
-
-
- /**
- * Extends fieldmap definition
- */
- public function extend_fieldmap($map)
- {
- if (is_array($map))
- self::$fieldmap = array_merge($map, self::$fieldmap);
- }
-
-
- /**
- * Factory method to import a vcard file
- *
- * @param string vCard file content
- * @return array List of rcube_vcard objects
- */
- public static function import($data)
- {
- $out = array();
-
- // check if charsets are specified (usually vcard version < 3.0 but this is not reliable)
- if (preg_match('/charset=/i', substr($data, 0, 2048)))
- $charset = null;
- // detect charset and convert to utf-8
- else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) {
- $data = rcube_charset::convert($data, $charset);
- $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
- $charset = RCUBE_CHARSET;
+ /**
+ * Extends fieldmap definition
+ */
+ public function extend_fieldmap($map)
+ {
+ if (is_array($map)) {
+ self::$fieldmap = array_merge($map, self::$fieldmap);
+ }
}
- $vcard_block = '';
- $in_vcard_block = false;
+ /**
+ * Factory method to import a vcard file
+ *
+ * @param string vCard file content
+ *
+ * @return array List of rcube_vcard objects
+ */
+ public static function import($data)
+ {
+ $out = array();
+
+ // check if charsets are specified (usually vcard version < 3.0 but this is not reliable)
+ if (preg_match('/charset=/i', substr($data, 0, 2048))) {
+ $charset = null;
+ }
+ // detect charset and convert to utf-8
+ else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) {
+ $data = rcube_charset::convert($data, $charset);
+ $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
+ $charset = RCUBE_CHARSET;
+ }
- foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
- if ($in_vcard_block && !empty($line))
- $vcard_block .= $line . "\n";
+ $vcard_block = '';
+ $in_vcard_block = false;
- $line = trim($line);
+ foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
+ if ($in_vcard_block && !empty($line)) {
+ $vcard_block .= $line . "\n";
+ }
- if (preg_match('/^END:VCARD$/i', $line)) {
- // parse vcard
- $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
- if (!empty($obj->displayname) || !empty($obj->email))
- $out[] = $obj;
+ $line = trim($line);
- $in_vcard_block = false;
- }
- else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
- $vcard_block = $line . "\n";
- $in_vcard_block = true;
- }
+ if (preg_match('/^END:VCARD$/i', $line)) {
+ // parse vcard
+ $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
+ if (!empty($obj->displayname) || !empty($obj->email)) {
+ $out[] = $obj;
+ }
+
+ $in_vcard_block = false;
+ }
+ else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
+ $vcard_block = $line . "\n";
+ $in_vcard_block = true;
+ }
+ }
+
+ return $out;
}
- return $out;
- }
-
-
- /**
- * Normalize vcard data for better parsing
- *
- * @param string vCard block
- * @return string Cleaned vcard block
- */
- private 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);
-
- // convert X-WAB-GENDER to X-GENDER
- if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
- $value = $matches[1] == '2' ? 'male' : 'female';
- $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
+ /**
+ * Normalize vcard data for better parsing
+ *
+ * @param string vCard block
+ *
+ * @return string Cleaned vcard block
+ */
+ private 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);
+
+ // convert X-WAB-GENDER to X-GENDER
+ if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
+ $value = $matches[1] == '2' ? 'male' : 'female';
+ $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;
}
- // if N doesn't have any semicolons, add some
- $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
-
- return $vcard;
- }
-
- private static function x_abrelatednames_callback($matches)
- {
- return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
- }
-
- private static function rfc2425_fold_callback($matches)
- {
- // chunk_split string and avoid lines breaking multibyte characters
- $c = 71;
- $out .= substr($matches[1], 0, $c);
- for ($n = $c; $c < strlen($matches[1]); $c++) {
- // break if length > 75 or mutlibyte character starts after position 71
- if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
- $out .= "\r\n ";
- $n = 0;
- }
- $out .= $matches[1][$c];
- $n++;
+ private static function x_abrelatednames_callback($matches)
+ {
+ return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
}
- return $out;
- }
-
- public static function rfc2425_fold($val)
- {
- return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
- }
-
-
- /**
- * Decodes a vcard block (vcard 3.0 format, unfolded)
- * into an array structure
- *
- * @param string vCard block to parse
- * @return array Raw data structure
- */
- private static function vcard_decode($vcard)
- {
- // Perform RFC2425 line unfolding and split lines
- $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
- $lines = explode("\n", $vcard);
- $data = array();
-
- for ($i=0; $i < count($lines); $i++) {
- if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
- continue;
-
- if (preg_match('/^(BEGIN|END)$/i', $line[1]))
- continue;
-
- // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
- if (($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) {
- $line[1] = $regs2[1];
- foreach (explode(';', $regs2[2]) as $prop)
- $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
- }
-
- if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
- $entry = array();
- $field = strtoupper($regs2[1][0]);
- $enc = null;
-
- foreach($regs2[1] as $attrid => $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
- if ($value == 'QUOTED-PRINTABLE') {
- while (preg_match('/=$/', $lines[$i]))
- $line[2] .= "\n" . $lines[++$i];
- }
- $enc = $value;
- }
- else {
- $lc_key = strtolower($key);
- $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
+ private static function rfc2425_fold_callback($matches)
+ {
+ // chunk_split string and avoid lines breaking multibyte characters
+ $c = 71;
+ $out .= substr($matches[1], 0, $c);
+ for ($n = $c; $c < strlen($matches[1]); $c++) {
+ // break if length > 75 or mutlibyte character starts after position 71
+ if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
+ $out .= "\r\n ";
+ $n = 0;
}
- }
- else if ($attrid > 0) {
- $entry[strtolower($key)] = true; // true means attr without =value
- }
+ $out .= $matches[1][$c];
+ $n++;
}
- // decode value
- if ($enc || !empty($entry['base64'])) {
- // save encoding type (#1488432)
- if ($enc == 'B') {
- $entry['encoding'] = 'B';
- // should we use vCard 3.0 instead?
- // $entry['base64'] = true;
- }
- $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
- }
+ return $out;
+ }
+
+ public static function rfc2425_fold($val)
+ {
+ return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
+ }
- if ($enc != 'B' && empty($entry['base64'])) {
- $line[2] = self::vcard_unquote($line[2]);
+ /**
+ * Decodes a vcard block (vcard 3.0 format, unfolded)
+ * into an array structure
+ *
+ * @param string vCard block to parse
+ *
+ * @return array Raw data structure
+ */
+ private static function vcard_decode($vcard)
+ {
+ // Perform RFC2425 line unfolding and split lines
+ $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
+ $lines = explode("\n", $vcard);
+ $data = array();
+
+ for ($i=0; $i < count($lines); $i++) {
+ if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
+ continue;
+
+ if (preg_match('/^(BEGIN|END)$/i', $line[1]))
+ continue;
+
+ // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
+ if ($data['VERSION'][0] == "2.1"
+ && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2)
+ && !preg_match('/^TYPE=/i', $regs2[2])
+ ) {
+ $line[1] = $regs2[1];
+ foreach (explode(';', $regs2[2]) as $prop) {
+ $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
+ }
+ }
+
+ if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
+ $entry = array();
+ $field = strtoupper($regs2[1][0]);
+ $enc = null;
+
+ foreach($regs2[1] as $attrid => $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
+ if ($value == 'QUOTED-PRINTABLE') {
+ while (preg_match('/=$/', $lines[$i])) {
+ $line[2] .= "\n" . $lines[++$i];
+ }
+ }
+ $enc = $value;
+ }
+ else {
+ $lc_key = strtolower($key);
+ $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
+ }
+ }
+ else if ($attrid > 0) {
+ $entry[strtolower($key)] = true; // true means attr without =value
+ }
+ }
+
+ // decode value
+ if ($enc || !empty($entry['base64'])) {
+ // save encoding type (#1488432)
+ if ($enc == 'B') {
+ $entry['encoding'] = 'B';
+ // should we use vCard 3.0 instead?
+ // $entry['base64'] = true;
+ }
+ $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
+ }
+
+ if ($enc != 'B' && empty($entry['base64'])) {
+ $line[2] = self::vcard_unquote($line[2]);
+ }
+
+ $entry = array_merge($entry, (array) $line[2]);
+ $data[$field][] = $entry;
+ }
}
- $entry = array_merge($entry, (array) $line[2]);
- $data[$field][] = $entry;
- }
+ unset($data['VERSION']);
+ return $data;
}
- unset($data['VERSION']);
- return $data;
- }
-
-
- /**
- * Decode a given string with the encoding rule from ENCODING attributes
- *
- * @param string String to decode
- * @param string Encoding type (quoted-printable and base64 supported)
- * @return string Decoded 8bit value
- */
- private static function decode_value($value, $encoding)
- {
- switch (strtolower($encoding)) {
- case 'quoted-printable':
- self::$values_decoded = true;
- return quoted_printable_decode($value);
-
- case 'base64':
- case 'b':
- self::$values_decoded = true;
- return base64_decode($value);
-
- default:
- return $value;
+ /**
+ * Decode a given string with the encoding rule from ENCODING attributes
+ *
+ * @param string String to decode
+ * @param string Encoding type (quoted-printable and base64 supported)
+ *
+ * @return string Decoded 8bit value
+ */
+ private static function decode_value($value, $encoding)
+ {
+ switch (strtolower($encoding)) {
+ case 'quoted-printable':
+ self::$values_decoded = true;
+ return quoted_printable_decode($value);
+
+ case 'base64':
+ case 'b':
+ self::$values_decoded = true;
+ return base64_decode($value);
+
+ default:
+ return $value;
+ }
}
- }
-
-
- /**
- * Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
- *
- * @param array Raw data structure to encode
- * @return string vCard encoded string
- */
- static function vcard_encode($data)
- {
- foreach((array)$data as $type => $entries) {
- /* valid N has 5 properties */
- while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5)
- $entries[0][] = "";
-
- // make sure FN is not empty (required by RFC2426)
- if ($type == "FN" && empty($entries))
- $entries[0] = $data['EMAIL'][0][0];
-
- foreach((array)$entries as $entry) {
- $attr = '';
- if (is_array($entry)) {
- $value = array();
- foreach($entry as $attrname => $attrvalues) {
- if (is_int($attrname)) {
- if (!empty($entry['base64']) || $entry['encoding'] == 'B') {
- $attrvalues = base64_encode($attrvalues);
- }
- $value[] = $attrvalues;
+
+ /**
+ * Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
+ *
+ * @param array Raw data structure to encode
+ *
+ * @return string vCard encoded string
+ */
+ static function vcard_encode($data)
+ {
+ foreach ((array)$data as $type => $entries) {
+ // valid N has 5 properties
+ while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5) {
+ $entries[0][] = "";
}
- else if (is_bool($attrvalues)) {
- if ($attrvalues) {
- $attr .= strtoupper(";$attrname"); // true means just tag, not tag=value, as in PHOTO;BASE64:...
- }
+
+ // make sure FN is not empty (required by RFC2426)
+ if ($type == "FN" && empty($entries)) {
+ $entries[0] = $data['EMAIL'][0][0];
}
- else {
- foreach((array)$attrvalues as $attrvalue)
- $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ',');
+
+ foreach ((array)$entries as $entry) {
+ $attr = '';
+ if (is_array($entry)) {
+ $value = array();
+ foreach ($entry as $attrname => $attrvalues) {
+ if (is_int($attrname)) {
+ if (!empty($entry['base64']) || $entry['encoding'] == 'B') {
+ $attrvalues = base64_encode($attrvalues);
+ }
+ $value[] = $attrvalues;
+ }
+ else if (is_bool($attrvalues)) {
+ // true means just tag, not tag=value, as in PHOTO;BASE64:...
+ if ($attrvalues) {
+ $attr .= strtoupper(";$attrname");
+ }
+ }
+ else {
+ foreach((array)$attrvalues as $attrvalue) {
+ $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ',');
+ }
+ }
+ }
+ }
+ else {
+ $value = $entry;
+ }
+
+ // skip empty entries
+ if (self::is_empty($value)) {
+ continue;
+ }
+
+ $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol;
}
- }
}
- else {
- $value = $entry;
- }
-
- // skip empty entries
- if (self::is_empty($value))
- continue;
- $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol;
- }
+ return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD';
}
- return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD';
- }
-
-
- /**
- * Join indexed data array to a vcard quoted string
- *
- * @param array Field data
- * @param string Separator
- * @return string Joined and quoted string
- */
- private static function vcard_quote($s, $sep = ';')
- {
- if (is_array($s)) {
- foreach($s as $part) {
- $r[] = self::vcard_quote($part, $sep);
- }
- return(implode($sep, (array)$r));
- }
- else {
- return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
- }
- }
-
-
- /**
- * Split quoted string
- *
- * @param string vCard string to split
- * @param string Separator char/string
- * @return array List with splited values
- */
- private static function vcard_unquote($s, $sep = ';')
- {
- // break string into parts separated by $sep, but leave escaped $sep alone
- if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
- foreach($parts as $s) {
- $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
- }
- return $result;
+ /**
+ * Join indexed data array to a vcard quoted string
+ *
+ * @param array Field data
+ * @param string Separator
+ *
+ * @return string Joined and quoted string
+ */
+ private static function vcard_quote($s, $sep = ';')
+ {
+ if (is_array($s)) {
+ foreach($s as $part) {
+ $r[] = self::vcard_quote($part, $sep);
+ }
+ return(implode($sep, (array)$r));
+ }
+
+ return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
}
- else {
- return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
+
+ /**
+ * Split quoted string
+ *
+ * @param string vCard string to split
+ * @param string Separator char/string
+ *
+ * @return array List with splited values
+ */
+ private static function vcard_unquote($s, $sep = ';')
+ {
+ // break string into parts separated by $sep
+ if (!empty($sep)) {
+ // Handle properly backslash escaping (#1488896)
+ $rep1 = array("\\\\" => "\010", "\\$sep" => "\007");
+ $rep2 = array("\007" => "\\$sep", "\010" => "\\\\");
+
+ if (count($parts = explode($sep, strtr($s, $rep1))) > 1) {
+ foreach ($parts as $s) {
+ $result[] = self::vcard_unquote(strtr($s, $rep2));
+ }
+ return $result;
+ }
+ }
+
+ return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
}
- }
-
-
- /**
- * Check if vCard entry is empty: empty string or an array with
- * all entries empty.
- *
- * @param mixed $value Attribute value (string or array)
- *
- * @return bool True if the value is empty, False otherwise
- */
- private static function is_empty($value)
- {
- foreach ((array)$value as $v) {
- if (((string)$v) !== '') {
- return false;
- }
+
+ /**
+ * Check if vCard entry is empty: empty string or an array with
+ * all entries empty.
+ *
+ * @param mixed $value Attribute value (string or array)
+ *
+ * @return bool True if the value is empty, False otherwise
+ */
+ private static function is_empty($value)
+ {
+ foreach ((array)$value as $v) {
+ if (((string)$v) !== '') {
+ return false;
+ }
+ }
+
+ return true;
}
- return true;
- }
-
- /**
- * Extract array values by a filter
- *
- * @param array Array to filter
- * @param keys Array or comma separated list of values to keep
- * @param boolean Invert key selection: remove the listed values
- * @return array The filtered array
- */
- private static function array_filter($arr, $values, $inverse = false)
- {
- if (!is_array($values))
- $values = explode(',', $values);
-
- $result = array();
- $keep = array_flip((array)$values);
- foreach ($arr as $key => $val)
- if ($inverse != isset($keep[strtolower($val)]))
- $result[$key] = $val;
-
- return $result;
- }
-
- /**
- * Returns UNICODE type based on BOM (Byte Order Mark)
- *
- * @param string Input string to test
- * @return string Detected encoding
- */
- private static function detect_encoding($string)
- {
- $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1
-
- return rcube_charset::detect($string, $fallback);
- }
+ /**
+ * Extract array values by a filter
+ *
+ * @param array Array to filter
+ * @param keys Array or comma separated list of values to keep
+ * @param boolean Invert key selection: remove the listed values
+ *
+ * @return array The filtered array
+ */
+ private static function array_filter($arr, $values, $inverse = false)
+ {
+ if (!is_array($values)) {
+ $values = explode(',', $values);
+ }
+
+ $result = array();
+ $keep = array_flip((array)$values);
+ foreach ($arr as $key => $val) {
+ if ($inverse != isset($keep[strtolower($val)])) {
+ $result[$key] = $val;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns UNICODE type based on BOM (Byte Order Mark)
+ *
+ * @param string Input string to test
+ *
+ * @return string Detected encoding
+ */
+ private static function detect_encoding($string)
+ {
+ $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1
+
+ return rcube_charset::detect($string, $fallback);
+ }
}
diff --git a/lib/ext/Roundcube/rcube_washtml.php b/lib/ext/Roundcube/rcube_washtml.php
new file mode 100644
index 0000000..715c460
--- /dev/null
+++ b/lib/ext/Roundcube/rcube_washtml.php
@@ -0,0 +1,451 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | 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. |
+ | See the README file for a full license statement. |
+ | |
+ | PURPOSE: |
+ | Utility class providing HTML sanityzer (based on Washtml class) |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube at gmail.com> |
+ | Author: Aleksander Machniak <alec at alec.pl> |
+ | Author: Frederic Motte <fmotte at ubixis.com> |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ * Washtml, a HTML sanityzer.
+ *
+ * Copyright (c) 2007 Frederic Motte <fmotte at ubixis.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * OVERVIEW:
+ *
+ * Wahstml take an untrusted HTML and return a safe html string.
+ *
+ * SYNOPSIS:
+ *
+ * $washer = new washtml($config);
+ * $washer->wash($html);
+ * It return a sanityzed string of the $html parameter without html and head tags.
+ * $html is a string containing the html code to wash.
+ * $config is an array containing options:
+ * $config['allow_remote'] is a boolean to allow link to remote images.
+ * $config['blocked_src'] string with image-src to be used for blocked remote images
+ * $config['show_washed'] is a boolean to include washed out attributes as x-washed
+ * $config['cid_map'] is an array where cid urls index urls to replace them.
+ * $config['charset'] is a string containing the charset of the HTML document if it is not defined in it.
+ * $washer->extlinks is a reference to a boolean that is set to true if remote images were removed. (FE: show remote images link)
+ *
+ * INTERNALS:
+ *
+ * Only tags and attributes in the static lists $html_elements and $html_attributes
+ * are kept, inline styles are also filtered: all style identifiers matching
+ * /[a-z\-]/i are allowed. Values matching colors, sizes, /[a-z\-]/i and safe
+ * urls if allowed and cid urls if mapped are kept.
+ *
+ * Roundcube Changes:
+ * - added $block_elements
+ * - changed $ignore_elements behaviour
+ * - added RFC2397 support
+ * - base URL support
+ * - invalid HTML comments removal before parsing
+ * - "fixing" unitless CSS values for XHTML output
+ * - base url resolving
+ */
+
+/**
+ * Utility class providing HTML sanityzer
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_washtml
+{
+ /* Allowed HTML elements (default) */
+ static $html_elements = array('a', 'abbr', 'acronym', 'address', 'area', 'b',
+ 'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center',
+ 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl',
+ 'dt', 'em', 'fieldset', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i',
+ 'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q',
+ 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
+ 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img',
+ // form elements
+ 'button', 'input', 'textarea', 'select', 'option', 'optgroup'
+ );
+
+ /* Ignore these HTML tags and their content */
+ static $ignore_elements = array('script', 'applet', 'embed', 'object', 'style');
+
+ /* Allowed HTML attributes */
+ static $html_attribs = array('name', 'class', 'title', 'alt', 'width', 'height',
+ 'align', 'nowrap', 'col', 'row', 'id', 'rowspan', 'colspan', 'cellspacing',
+ 'cellpadding', 'valign', 'bgcolor', 'color', 'border', 'bordercolorlight',
+ 'bordercolordark', 'face', 'marginwidth', 'marginheight', 'axis', 'border',
+ 'abbr', 'char', 'charoff', 'clear', 'compact', 'coords', 'vspace', 'hspace',
+ 'cellborder', 'size', 'lang', 'dir', 'usemap', 'shape', 'media',
+ // attributes of form elements
+ 'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value'
+ );
+
+ /* Block elements which could be empty but cannot be returned in short form (<tag />) */
+ static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center',
+ 'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong',
+ 'i', 'b', 'u', 'span',
+ );
+
+ /* State for linked objects in HTML */
+ public $extlinks = false;
+
+ /* Current settings */
+ private $config = array();
+
+ /* Registered callback functions for tags */
+ private $handlers = array();
+
+ /* Allowed HTML elements */
+ private $_html_elements = array();
+
+ /* Ignore these HTML tags but process their content */
+ private $_ignore_elements = array();
+
+ /* Block elements which could be empty but cannot be returned in short form (<tag />) */
+ private $_block_elements = array();
+
+ /* Allowed HTML attributes */
+ private $_html_attribs = array();
+
+
+ /**
+ * Class constructor
+ */
+ public function __construct($p = array())
+ {
+ $this->_html_elements = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ;
+ $this->_html_attribs = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs);
+ $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements);
+ $this->_block_elements = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements);
+
+ unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']);
+
+ $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array());
+ }
+
+ /**
+ * Register a callback function for a certain tag
+ */
+ public function add_callback($tagName, $callback)
+ {
+ $this->handlers[$tagName] = $callback;
+ }
+
+ /**
+ * Check CSS style
+ */
+ private function wash_style($style)
+ {
+ $s = '';
+
+ foreach (explode(';', $style) as $declaration) {
+ if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
+ $cssid = $match[1];
+ $str = $match[2];
+ $value = '';
+
+ while (sizeof($str) > 0 &&
+ preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/
+ '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'.
+ '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'.
+ '|#[0-9a-f]{3,6}'.
+ '|[a-z0-9", -]+'.
+ ')\s*/i', $str, $match)
+ ) {
+ if ($match[2]) {
+ if (($src = $this->config['cid_map'][$match[2]])
+ || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])
+ ) {
+ $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
+ }
+ else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) {
+ if ($this->config['allow_remote']) {
+ $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')';
+ }
+ else {
+ $this->extlinks = true;
+ }
+ }
+ else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397
+ $value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')';
+ }
+ }
+ else {
+ // whitelist ?
+ $value .= ' ' . $match[0];
+
+ // #1488535: Fix size units, so width:800 would be changed to width:800px
+ if (preg_match('/(left|right|top|bottom|width|height)/i', $cssid)
+ && preg_match('/^[0-9]+$/', $match[0])
+ ) {
+ $value .= 'px';
+ }
+ }
+
+ $str = substr($str, strlen($match[0]));
+ }
+
+ if (isset($value[0])) {
+ $s .= ($s?' ':'') . $cssid . ':' . $value . ';';
+ }
+ }
+ }
+
+ return $s;
+ }
+
+ /**
+ * Take a node and return allowed attributes and check values
+ */
+ private function wash_attribs($node)
+ {
+ $t = '';
+ $washed = '';
+
+ foreach ($node->attributes as $key => $plop) {
+ $key = strtolower($key);
+ $value = $node->getAttribute($key);
+
+ if (isset($this->_html_attribs[$key]) ||
+ ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value)
+ && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value))
+ ) {
+ $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+ }
+ else if ($key == 'style' && ($style = $this->wash_style($value))) {
+ $quot = strpos($style, '"') !== false ? "'" : '"';
+ $t .= ' style=' . $quot . $style . $quot;
+ }
+ else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway
+ if (($src = $this->config['cid_map'][$value])
+ || ($src = $this->config['cid_map'][$this->config['base_url'].$value])
+ ) {
+ $t .= ' ' . $key . '="' . htmlspecialchars($src, ENT_QUOTES) . '"';
+ }
+ else if (preg_match('/^(http|https|ftp):.+/i', $value)) {
+ if ($this->config['allow_remote']) {
+ $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+ }
+ else {
+ $this->extlinks = true;
+ if ($this->config['blocked_src']) {
+ $t .= ' ' . $key . '="' . htmlspecialchars($this->config['blocked_src'], ENT_QUOTES) . '"';
+ }
+ }
+ }
+ else if (preg_match('/^data:.+/i', $value)) { // RFC2397
+ $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+ }
+ }
+ else {
+ $washed .= ($washed ? ' ' : '') . $key;
+ }
+ }
+
+ return $t . ($washed && $this->config['show_washed'] ? ' x-washed="'.$washed.'"' : '');
+ }
+
+ /**
+ * The main loop that recurse on a node tree.
+ * It output only allowed tags with allowed attributes
+ * and allowed inline styles
+ */
+ private function dumpHtml($node)
+ {
+ if (!$node->hasChildNodes()) {
+ return '';
+ }
+
+ $node = $node->firstChild;
+ $dump = '';
+
+ do {
+ switch($node->nodeType) {
+ case XML_ELEMENT_NODE: //Check element
+ $tagName = strtolower($node->tagName);
+ if ($callback = $this->handlers[$tagName]) {
+ $dump .= call_user_func($callback, $tagName,
+ $this->wash_attribs($node), $this->dumpHtml($node), $this);
+ }
+ else if (isset($this->_html_elements[$tagName])) {
+ $content = $this->dumpHtml($node);
+ $dump .= '<' . $tagName . $this->wash_attribs($node) .
+ ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />');
+ }
+ else if (isset($this->_ignore_elements[$tagName])) {
+ $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->';
+ }
+ else {
+ $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' ignored -->';
+ $dump .= $this->dumpHtml($node); // ignore tags not its content
+ }
+ break;
+
+ case XML_CDATA_SECTION_NODE:
+ $dump .= $node->nodeValue;
+ break;
+
+ case XML_TEXT_NODE:
+ $dump .= htmlspecialchars($node->nodeValue);
+ break;
+
+ case XML_HTML_DOCUMENT_NODE:
+ $dump .= $this->dumpHtml($node);
+ break;
+
+ case XML_DOCUMENT_TYPE_NODE:
+ break;
+
+ default:
+ $dump . '<!-- node type ' . $node->nodeType . ' -->';
+ }
+ } while($node = $node->nextSibling);
+
+ return $dump;
+ }
+
+ /**
+ * Main function, give it untrusted HTML, tell it if you allow loading
+ * remote images and give it a map to convert "cid:" urls.
+ */
+ public function wash($html)
+ {
+ // Charset seems to be ignored (probably if defined in the HTML document)
+ $node = new DOMDocument('1.0', $this->config['charset']);
+ $this->extlinks = false;
+
+ $html = $this->cleanup($html);
+
+ // Find base URL for images
+ if (preg_match('/<base\s+href=[\'"]*([^\'"]+)/is', $html, $matches)) {
+ $this->config['base_url'] = $matches[1];
+ }
+ else {
+ $this->config['base_url'] = '';
+ }
+
+ @$node->loadHTML($html);
+ return $this->dumpHtml($node);
+ }
+
+ /**
+ * Getter for config parameters
+ */
+ public function get_config($prop)
+ {
+ return $this->config[$prop];
+ }
+
+ /**
+ * Clean HTML input
+ */
+ private function cleanup($html)
+ {
+ // special replacements (not properly handled by washtml class)
+ $html_search = array(
+ '/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR>
+ '/<title[^>]*>[^<]*<\/title>/i', // PHP bug #32547 workaround: remove title tag
+ '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?)
+ '/<html\s[^>]+>/i', // washtml/DOMDocument cannot handle xml namespaces
+ );
+
+ $html_replace = array(
+ '\\1'.' '.'\\3',
+ '',
+ '',
+ '<html>',
+ );
+ $html = preg_replace($html_search, $html_replace, trim($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.";
+
+ if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
+ $errstr .= " Consider raising pcre.backtrack_limit!";
+ }
+ if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
+ $errstr .= " Consider raising pcre.recursion_limit!";
+ }
+
+ rcube::raise_error(array('code' => 620, 'type' => 'php',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $errstr), true, false);
+ return '';
+ }
+
+ // fix (unknown/malformed) HTML tags before "wash"
+ $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html);
+
+ // Remove invalid HTML comments (#1487759)
+ // Don't remove valid conditional comments
+ $html = preg_replace('/<!--[^->[\n]*>/', '', $html);
+
+ // turn relative into absolute urls
+ $html = self::resolve_base($html);
+
+ return $html;
+ }
+
+ /**
+ * Callback function for HTML tags fixing
+ */
+ public static function html_tag_callback($matches)
+ {
+ $tagname = $matches[2];
+ $tagname = preg_replace(array(
+ '/:.*$/', // Microsoft's Smart Tags <st1:xxxx>
+ '/[^a-z0-9_\[\]\!-]/i', // forbidden characters
+ ), '', $tagname);
+
+ return $matches[1] . $tagname;
+ }
+
+ /**
+ * Convert all relative URLs according to a <base> in HTML
+ */
+ public static function resolve_base($body)
+ {
+ // check for <base href=...>
+ if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
+ $replacer = new rcube_base_replacer($regs[2]);
+ $body = $replacer->replace($body);
+ }
+
+ return $body;
+ }
+}
+
diff --git a/lib/ext/html2text.php b/lib/ext/html2text.php
deleted file mode 100644
index 761d61f..0000000
--- a/lib/ext/html2text.php
+++ /dev/null
@@ -1,746 +0,0 @@
-<?php
-
-/*************************************************************************
- * *
- * class.html2text.inc *
- * *
- *************************************************************************
- * *
- * Converts HTML to formatted plain text *
- * *
- * Copyright (c) 2005-2007 Jon Abernathy <jon at chuggnutt.com> *
- * All rights reserved. *
- * *
- * This script is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 of the License, or *
- * (at your option) any later version. *
- * *
- * The GNU General Public License can be found at *
- * http://www.gnu.org/copyleft/gpl.html. *
- * *
- * This script is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * Author(s): Jon Abernathy <jon at chuggnutt.com> *
- * *
- * Last modified: 08/08/07 *
- * *
- *************************************************************************/
-
-
-/**
- * Takes HTML and converts it to formatted, plain text.
- *
- * Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
- * correcting an error in the regexp search array. Fixed 7/30/03.
- *
- * Updated set_html() function's file reading mechanism, 9/25/03.
- *
- * Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
- * several more HTML entity codes to the $search and $replace arrays.
- * Updated 11/7/03.
- *
- * Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
- * suggesting the addition of $allowed_tags and its supporting function
- * (which I slightly modified). Updated 3/12/04.
- *
- * Thanks to Justin Dearing for pointing out that a replacement for the
- * <TH> tag was missing, and suggesting an appropriate fix.
- * Updated 8/25/04.
- *
- * Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
- * display/formatting bug in the _build_link_list() function: email
- * readers would show the left bracket and number ("[1") as part of the
- * rendered email address.
- * Updated 12/16/04.
- *
- * Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
- * to handle relative links, which I hadn't considered. I modified his
- * code a bit to handle normal HTTP links and MAILTO links. Also for
- * suggesting three additional HTML entity codes to search for.
- * Updated 03/02/05.
- *
- * Thanks to Jacob Chandler for pointing out another link condition
- * for the _build_link_list() function: "https".
- * Updated 04/06/05.
- *
- * Thanks to Marc Bertrand (http://www.dresdensky.com/) for
- * suggesting a revision to the word wrapping functionality; if you
- * specify a $width of 0 or less, word wrapping will be ignored.
- * Updated 11/02/06.
- *
- * *** Big housecleaning updates below:
- *
- * Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
- * suggesting the fix to handle </li> and blank lines (whitespace).
- * Christian Basedau (http://www.movetheweb.de/) also suggested the
- * blank lines fix.
- *
- * Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
- * Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
- * Bas van de Weijer, and Marijn van Butselaar
- * for pointing out my glaring error in the <th> handling. Marcus also
- * supplied a host of fixes.
- *
- * Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
- * out that extra spaces should be compressed--a problem addressed with
- * Marcus Bointon's fixes but that I had not yet incorporated.
- *
- * Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
- * suggesting a valuable fix with <a> tag handling.
- *
- * Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
- * including the <a> tag handling that Daniel Schledermann pointed
- * out but that I had not yet incorporated. I haven't (yet)
- * incorporated all of Wojciech's changes, though I may at some
- * future time.
- *
- * *** End of the housecleaning updates. Updated 08/08/07.
- *
- * @author Jon Abernathy <jon at chuggnutt.com>
- * @version 1.0.0
- * @since PHP 4.0.2
- */
-class html2text
-{
-
- /**
- * Contains the HTML content to convert.
- *
- * @var string $html
- * @access public
- */
- var $html;
-
- /**
- * Contains the converted, formatted text.
- *
- * @var string $text
- * @access public
- */
- var $text;
-
- /**
- * Maximum width of the formatted text, in columns.
- *
- * Set this value to 0 (or less) to ignore word wrapping
- * and not constrain text to a fixed-width column.
- *
- * @var integer $width
- * @access public
- */
- var $width = 70;
-
- /**
- * List of preg* regular expression patterns to search for,
- * used in conjunction with $replace.
- *
- * @var array $search
- * @access public
- * @see $replace
- */
- var $search = array(
- "/\r/", // Non-legal carriage return
- "/[\n\t]+/", // Newlines and tabs
- '/<head[^>]*>.*?<\/head>/i', // <head>
- '/<script[^>]*>.*?<\/script>/i', // <script>s -- which strip_tags supposedly has problems with
- '/<style[^>]*>.*?<\/style>/i', // <style>s -- which strip_tags supposedly has problems with
- '/<p[^>]*>/i', // <P>
- '/<br[^>]*>/i', // <br>
- '/<i[^>]*>(.*?)<\/i>/i', // <i>
- '/<em[^>]*>(.*?)<\/em>/i', // <em>
- '/(<ul[^>]*>|<\/ul>)/i', // <ul> and </ul>
- '/(<ol[^>]*>|<\/ol>)/i', // <ol> and </ol>
- '/<li[^>]*>(.*?)<\/li>/i', // <li> and </li>
- '/<li[^>]*>/i', // <li>
- '/<hr[^>]*>/i', // <hr>
- '/<div[^>]*>/i', // <div>
- '/(<table[^>]*>|<\/table>)/i', // <table> and </table>
- '/(<tr[^>]*>|<\/tr>)/i', // <tr> and </tr>
- '/<td[^>]*>(.*?)<\/td>/i', // <td> and </td>
- );
-
- /**
- * List of pattern replacements corresponding to patterns searched.
- *
- * @var array $replace
- * @access public
- * @see $search
- */
- var $replace = array(
- '', // Non-legal carriage return
- ' ', // Newlines and tabs
- '', // <head>
- '', // <script>s -- which strip_tags supposedly has problems with
- '', // <style>s -- which strip_tags supposedly has problems with
- "\n\n", // <P>
- "\n", // <br>
- '_\\1_', // <i>
- '_\\1_', // <em>
- "\n\n", // <ul> and </ul>
- "\n\n", // <ol> and </ol>
- "\t* \\1\n", // <li> and </li>
- "\n\t* ", // <li>
- "\n-------------------------\n", // <hr>
- "<div>\n", // <div>
- "\n\n", // <table> and </table>
- "\n", // <tr> and </tr>
- "\t\t\\1\n", // <td> and </td>
- );
-
- /**
- * List of preg* regular expression patterns to search for,
- * used in conjunction with $ent_replace.
- *
- * @var array $ent_search
- * @access public
- * @see $ent_replace
- */
- var $ent_search = array(
- '/&(nbsp|#160);/i', // Non-breaking space
- '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
- // Double quotes
- '/&(apos|rsquo|lsquo|#8216|#8217);/i', // Single quotes
- '/>/i', // Greater-than
- '/</i', // Less-than
- '/&(copy|#169);/i', // Copyright
- '/&(trade|#8482|#153);/i', // Trademark
- '/&(reg|#174);/i', // Registered
- '/&(mdash|#151|#8212);/i', // mdash
- '/&(ndash|minus|#8211|#8722);/i', // ndash
- '/&(bull|#149|#8226);/i', // Bullet
- '/&(pound|#163);/i', // Pound sign
- '/&(euro|#8364);/i', // Euro sign
- '/&(amp|#38);/i', // Ampersand: see _converter()
- '/[ ]{2,}/', // Runs of spaces, post-handling
- );
-
- /**
- * List of pattern replacements corresponding to patterns searched.
- *
- * @var array $ent_replace
- * @access public
- * @see $ent_search
- */
- var $ent_replace = array(
- ' ', // Non-breaking space
- '"', // Double quotes
- "'", // Single quotes
- '>',
- '<',
- '(c)',
- '(tm)',
- '(R)',
- '--',
- '-',
- '*',
- '£',
- 'EUR', // Euro sign. ?
- '|+|amp|+|', // Ampersand: see _converter()
- ' ', // Runs of spaces, post-handling
- );
-
- /**
- * List of preg* regular expression patterns to search for
- * and replace using callback function.
- *
- * @var array $callback_search
- * @access public
- */
- var $callback_search = array(
- '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
- '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i', // h1 - h6
- '/<(b)( [^>]*)?>(.*?)<\/b>/i', // <b>
- '/<(strong)( [^>]*)?>(.*?)<\/strong>/i', // <strong>
- '/<(th)( [^>]*)?>(.*?)<\/th>/i', // <th> and </th>
- );
-
- /**
- * List of preg* regular expression patterns to search for in PRE body,
- * used in conjunction with $pre_replace.
- *
- * @var array $pre_search
- * @access public
- * @see $pre_replace
- */
- var $pre_search = array(
- "/\n/",
- "/\t/",
- '/ /',
- '/<pre[^>]*>/',
- '/<\/pre>/'
- );
-
- /**
- * List of pattern replacements corresponding to patterns searched for PRE body.
- *
- * @var array $pre_replace
- * @access public
- * @see $pre_search
- */
- var $pre_replace = array(
- '<br>',
- ' ',
- ' ',
- '',
- ''
- );
-
- /**
- * Contains a list of HTML tags to allow in the resulting text.
- *
- * @var string $allowed_tags
- * @access public
- * @see set_allowed_tags()
- */
- var $allowed_tags = '';
-
- /**
- * Contains the base URL that relative links should resolve to.
- *
- * @var string $url
- * @access public
- */
- var $url;
-
- /**
- * Indicates whether content in the $html variable has been converted yet.
- *
- * @var boolean $_converted
- * @access private
- * @see $html, $text
- */
- var $_converted = false;
-
- /**
- * Contains URL addresses from links to be rendered in plain text.
- *
- * @var array $_link_list
- * @access private
- * @see _build_link_list()
- */
- var $_link_list = array();
-
- /**
- * Boolean flag, true if a table of link URLs should be listed after the text.
- *
- * @var boolean $_do_links
- * @access private
- * @see html2text()
- */
- var $_do_links = true;
-
- /**
- * Constructor.
- *
- * If the HTML source string (or file) is supplied, the class
- * will instantiate with that source propagated, all that has
- * to be done it to call get_text().
- *
- * @param string $source HTML content
- * @param boolean $from_file Indicates $source is a file to pull content from
- * @param boolean $do_links Indicate whether a table of link URLs is desired
- * @param integer $width Maximum width of the formatted text, 0 for no limit
- * @access public
- * @return void
- */
- function html2text( $source = '', $from_file = false, $do_links = true, $width = 75 )
- {
- if ( !empty($source) ) {
- $this->set_html($source, $from_file);
- }
-
- $this->set_base_url();
- $this->_do_links = $do_links;
- $this->width = $width;
- }
-
- /**
- * Loads source HTML into memory, either from $source string or a file.
- *
- * @param string $source HTML content
- * @param boolean $from_file Indicates $source is a file to pull content from
- * @access public
- * @return void
- */
- function set_html( $source, $from_file = false )
- {
- if ( $from_file && file_exists($source) ) {
- $this->html = file_get_contents($source);
- }
- else
- $this->html = $source;
-
- $this->_converted = false;
- }
-
- /**
- * Returns the text, converted from HTML.
- *
- * @access public
- * @return string
- */
- function get_text()
- {
- if ( !$this->_converted ) {
- $this->_convert();
- }
-
- return $this->text;
- }
-
- /**
- * Prints the text, converted from HTML.
- *
- * @access public
- * @return void
- */
- function print_text()
- {
- print $this->get_text();
- }
-
- /**
- * Alias to print_text(), operates identically.
- *
- * @access public
- * @return void
- * @see print_text()
- */
- function p()
- {
- print $this->get_text();
- }
-
- /**
- * Sets the allowed HTML tags to pass through to the resulting text.
- *
- * Tags should be in the form "<p>", with no corresponding closing tag.
- *
- * @access public
- * @return void
- */
- function set_allowed_tags( $allowed_tags = '' )
- {
- if ( !empty($allowed_tags) ) {
- $this->allowed_tags = $allowed_tags;
- }
- }
-
- /**
- * Sets a base URL to handle relative links.
- *
- * @access public
- * @return void
- */
- function set_base_url( $url = '' )
- {
- if ( empty($url) ) {
- if ( !empty($_SERVER['HTTP_HOST']) ) {
- $this->url = 'http://' . $_SERVER['HTTP_HOST'];
- } else {
- $this->url = '';
- }
- } else {
- // Strip any trailing slashes for consistency (relative
- // URLs may already start with a slash like "/file.html")
- if ( substr($url, -1) == '/' ) {
- $url = substr($url, 0, -1);
- }
- $this->url = $url;
- }
- }
-
- /**
- * Workhorse function that does actual conversion (calls _converter() method).
- *
- * @access private
- * @return void
- */
- function _convert()
- {
- // Variables used for building the link list
- $this->_link_list = array();
-
- $text = trim(stripslashes($this->html));
-
- // Convert HTML to TXT
- $this->_converter($text);
-
- // Add link list
- if (!empty($this->_link_list)) {
- $text .= "\n\nLinks:\n------\n";
- foreach ($this->_link_list as $idx => $url) {
- $text .= '[' . ($idx+1) . '] ' . $url . "\n";
- }
- }
-
- $this->text = $text;
-
- $this->_converted = true;
- }
-
- /**
- * Workhorse function that does actual conversion.
- *
- * First performs custom tag replacement specified by $search and
- * $replace arrays. Then strips any remaining HTML tags, reduces whitespace
- * and newlines to a readable format, and word wraps the text to
- * $width characters.
- *
- * @param string Reference to HTML content string
- *
- * @access private
- * @return void
- */
- function _converter(&$text)
- {
- // Convert <BLOCKQUOTE> (before PRE!)
- $this->_convert_blockquotes($text);
-
- // Convert <PRE>
- $this->_convert_pre($text);
-
- // Run our defined tags search-and-replace
- $text = preg_replace($this->search, $this->replace, $text);
-
- // Run our defined tags search-and-replace with callback
- $text = preg_replace_callback($this->callback_search, array('html2text', '_preg_callback'), $text);
-
- // Strip any other HTML tags
- $text = strip_tags($text, $this->allowed_tags);
-
- // Run our defined entities/characters search-and-replace
- $text = preg_replace($this->ent_search, $this->ent_replace, $text);
-
- // Replace known html entities
- $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
-
- // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
- $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
-
- // Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
- // This properly handles situation of """ in input string
- $text = str_replace('|+|amp|+|', '&', $text);
-
- // Bring down number of empty lines to 2 max
- $text = preg_replace("/\n\s+\n/", "\n\n", $text);
- $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
-
- // remove leading empty lines (can be produced by eg. P tag on the beginning)
- $text = ltrim($text, "\n");
-
- // Wrap the text to a readable format
- // for PHP versions >= 4.0.2. Default width is 75
- // If width is 0 or less, don't wrap the text.
- if ( $this->width > 0 ) {
- $text = wordwrap($text, $this->width);
- }
- }
-
- /**
- * Helper function called by preg_replace() on link replacement.
- *
- * Maintains an internal list of links to be displayed at the end of the
- * text, with numeric indices to the original point in the text they
- * appeared. Also makes an effort at identifying and handling absolute
- * and relative links.
- *
- * @param string $link URL of the link
- * @param string $display Part of the text to associate number with
- * @access private
- * @return string
- */
- function _build_link_list( $link, $display )
- {
- if (!$this->_do_links || empty($link)) {
- return $display;
- }
-
- // Ignored link types
- if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
- return $display;
- }
-
- if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
- $url = $link;
- }
- else {
- $url = $this->url;
- if (substr($link, 0, 1) != '/') {
- $url .= '/';
- }
- $url .= "$link";
- }
-
- if (($index = array_search($url, $this->_link_list)) === false) {
- $index = count($this->_link_list);
- $this->_link_list[] = $url;
- }
-
- return $display . ' [' . ($index+1) . ']';
- }
-
- /**
- * Helper function for PRE body conversion.
- *
- * @param string HTML content
- * @access private
- */
- function _convert_pre(&$text)
- {
- // get the content of PRE element
- while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
- $this->pre_content = $matches[1];
-
- // Run our defined tags search-and-replace with callback
- $this->pre_content = preg_replace_callback($this->callback_search,
- array('html2text', '_preg_callback'), $this->pre_content);
-
- // convert the content
- $this->pre_content = sprintf('<div><br>%s<br></div>',
- preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
-
- // replace the content (use callback because content can contain $0 variable)
- $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
- array('html2text', '_preg_pre_callback'), $text, 1);
-
- // free memory
- $this->pre_content = '';
- }
- }
-
- /**
- * Helper function for BLOCKQUOTE body conversion.
- *
- * @param string HTML content
- * @access private
- */
- function _convert_blockquotes(&$text)
- {
- if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
- $level = 0;
- $diff = 0;
- foreach ($matches[0] as $m) {
- if ($m[0][0] == '<' && $m[0][1] == '/') {
- $level--;
- if ($level < 0) {
- $level = 0; // malformed HTML: go to next blockquote
- }
- else if ($level > 0) {
- // skip inner blockquote
- }
- else {
- $end = $m[1];
- $len = $end - $taglen - $start;
- // Get blockquote content
- $body = substr($text, $start + $taglen - $diff, $len);
-
- // Set text width
- $p_width = $this->width;
- if ($this->width > 0) $this->width -= 2;
- // Convert blockquote content
- $body = trim($body);
- $this->_converter($body);
- // Add citation markers and create PRE block
- $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
- $body = '<pre>' . htmlspecialchars($body) . '</pre>';
- // Re-set text width
- $this->width = $p_width;
- // Replace content
- $text = substr($text, 0, $start - $diff)
- . $body . substr($text, $end + strlen($m[0]) - $diff);
-
- $diff = $len + $taglen + strlen($m[0]) - strlen($body);
- unset($body);
- }
- }
- else {
- if ($level == 0) {
- $start = $m[1];
- $taglen = strlen($m[0]);
- }
- $level ++;
- }
- }
- }
- }
-
- /**
- * Callback function for preg_replace_callback use.
- *
- * @param array PREG matches
- * @return string
- */
- private function _preg_callback($matches)
- {
- switch (strtolower($matches[1])) {
- case 'b':
- case 'strong':
- return $this->_toupper($matches[3]);
- case 'th':
- return $this->_toupper("\t\t". $matches[3] ."\n");
- case 'h':
- return $this->_toupper("\n\n". $matches[3] ."\n\n");
- case 'a':
- // Remove spaces in URL (#1487805)
- $url = str_replace(' ', '', $matches[3]);
- return $this->_build_link_list($url, $matches[4]);
- }
- }
-
- /**
- * Callback function for preg_replace_callback use in PRE content handler.
- *
- * @param array PREG matches
- * @return string
- */
- private function _preg_pre_callback($matches)
- {
- return $this->pre_content;
- }
-
- /**
- * Strtoupper function with HTML tags and entities handling.
- *
- * @param string $str Text to convert
- * @return string Converted text
- */
- private function _toupper($str)
- {
- // string can containg HTML tags
- $chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-
- // convert toupper only the text between HTML tags
- foreach ($chunks as $idx => $chunk) {
- if ($chunk[0] != '<') {
- $chunks[$idx] = $this->_strtoupper($chunk);
- }
- }
-
- return implode($chunks);
- }
-
- /**
- * Strtoupper multibyte wrapper function with HTML entities handling.
- *
- * @param string $str Text to convert
- * @return string Converted text
- */
- private function _strtoupper($str)
- {
- $str = html_entity_decode($str, ENT_COMPAT, RCUBE_CHARSET);
-
- if (function_exists('mb_strtoupper'))
- $str = mb_strtoupper($str);
- else
- $str = strtoupper($str);
-
- $str = htmlspecialchars($str, ENT_COMPAT, RCUBE_CHARSET);
-
- return $str;
- }
-}
diff --git a/lib/kolab_sync_body_converter.php b/lib/kolab_sync_body_converter.php
index 69b24db..11e544c 100644
--- a/lib/kolab_sync_body_converter.php
+++ b/lib/kolab_sync_body_converter.php
@@ -106,7 +106,7 @@ class kolab_sync_body_converter
{
switch ($type) {
case Syncroton_Model_EmailBody::TYPE_PLAINTEXT:
- $txt = new html2text($this->text, false, true);
+ $txt = new rcube_html2text($this->text, false, true);
return $txt->get_text();
case Syncroton_Model_EmailBody::TYPE_RTF:
// @TODO
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 9a3c336..0bc3be8 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1143,7 +1143,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
}
if ($part->ctype_secondary == 'html') {
- $txt = new html2text($body, false, true);
+ $txt = new rcube_html2text($body, false, true);
$body = $txt->get_text();
}
else {
More information about the commits
mailing list