mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-01-07 22:19:19 +00:00
Show spam aliases #
This commit is contained in:
111
data/web/rc/program/lib/Roundcube/README.md
Normal file
111
data/web/rc/program/lib/Roundcube/README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
Roundcube Framework
|
||||
===================
|
||||
|
||||
INTRODUCTION
|
||||
------------
|
||||
The Roundcube Framework is the basic library used for the Roundcube Webmail
|
||||
application. It is an extract of classes providing the core functionality for
|
||||
an email system. They can be used individually or as package for the following
|
||||
tasks:
|
||||
|
||||
- IMAP mailbox access with optional caching
|
||||
- MIME message handling
|
||||
- Email message creation and sending through SMTP
|
||||
- General caching utilities using the local database
|
||||
- Database abstraction using PDO
|
||||
- VCard parsing and writing
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
PHP Version 5.4 or greater including:
|
||||
- PCRE, DOM, JSON, Session, Sockets, OpenSSL, Mbstring (required)
|
||||
- PHP PDO with driver for either MySQL, PostgreSQL, SQL Server, Oracle or SQLite (required)
|
||||
- Libiconv, Zip, Fileinfo, Intl, Exif (recommended)
|
||||
- LDAP for LDAP addressbook support (optional)
|
||||
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
Copy all files of this directory to your project or install it in the default
|
||||
include_path directory of your webserver. Some classes of the framework require
|
||||
one or multiple of the following [PEAR][pear] libraries:
|
||||
|
||||
- Mail_Mime 1.8.1 or newer
|
||||
- Net_SMTP 1.7.1 or newer
|
||||
- Net_Socket 1.0.12 or newer
|
||||
- Net_IDNA2 0.1.1 or newer
|
||||
- Auth_SASL 1.0.6 or newer
|
||||
|
||||
|
||||
USAGE
|
||||
-----
|
||||
The Roundcube Framework provides a bootstrapping file which registers an
|
||||
autoloader and sets up the environment necessary for the Roundcube classes.
|
||||
In order to make use of the framework, simply include the bootstrap.php file
|
||||
from this directory in your application and start using the classes by simply
|
||||
instantiating them.
|
||||
|
||||
If you wanna use more complex functionality like IMAP access with database
|
||||
caching or plugins, the rcube singleton helps you loading the necessary files:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
define('RCUBE_CONFIG_DIR', '<path-to-config-directory>');
|
||||
define('RCUBE_PLUGINS_DIR', '<path-to-roundcube-plugins-directory');
|
||||
|
||||
require_once '<path-to-roundcube-framework/bootstrap.php';
|
||||
|
||||
$rcube = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS);
|
||||
$imap = $rcube->get_storage();
|
||||
|
||||
// do cool stuff here...
|
||||
|
||||
?>
|
||||
```
|
||||
|
||||
LICENSE
|
||||
-------
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License (**with exceptions
|
||||
for plugins**) as published by the Free Software Foundation, either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program 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.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see [www.gnu.org/licenses/][gpl].
|
||||
|
||||
This file forms part of the Roundcube Webmail Framework for which the
|
||||
following exception is added: Plugins which merely make function calls to the
|
||||
Roundcube Webmail Framework, and for that purpose include it by reference
|
||||
shall not be considered modifications of the software.
|
||||
|
||||
If you wish to use this file in another project or create a modified
|
||||
version that will not be part of the Roundcube Webmail Framework, you
|
||||
may remove the exception above and use this source code under the
|
||||
original version of the license.
|
||||
|
||||
For more details about licensing and the exceptions for skins and plugins
|
||||
see [roundcube.net/license][license]
|
||||
|
||||
|
||||
CONTACT
|
||||
-------
|
||||
For bug reports or feature requests please refer to the tracking system
|
||||
at [Github][githubissues] or subscribe to our mailing list.
|
||||
See [roundcube.net/support][support] for details.
|
||||
|
||||
You're always welcome to send a message to the project admins:
|
||||
hello(at)roundcube(dot)net
|
||||
|
||||
|
||||
[pear]: http://pear.php.net
|
||||
[gpl]: http://www.gnu.org/licenses/
|
||||
[license]: http://roundcube.net/license
|
||||
[support]: http://roundcube.net/support
|
||||
[githubissues]: https://github.com/roundcube/roundcubemail/issues
|
||||
469
data/web/rc/program/lib/Roundcube/bootstrap.php
Normal file
469
data/web/rc/program/lib/Roundcube/bootstrap.php
Normal file
@@ -0,0 +1,469 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube PHP suite |
|
||||
| Copyright (C) 2005-2017, 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: |
|
||||
| Roundcube Framework Initialization |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Roundcube Framework Initialization
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
*/
|
||||
|
||||
$config = array(
|
||||
'error_reporting' => E_ALL & ~E_NOTICE & ~E_STRICT,
|
||||
// Some users are not using Installer, so we'll check some
|
||||
// critical PHP settings here. Only these, which doesn't provide
|
||||
// an error/warning in the logs later. See (#1486307).
|
||||
'mbstring.func_overload' => 0,
|
||||
);
|
||||
|
||||
// check these additional ini settings if not called via CLI
|
||||
if (php_sapi_name() != 'cli') {
|
||||
$config += array(
|
||||
'suhosin.session.encrypt' => false,
|
||||
'file_uploads' => true,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($config as $optname => $optval) {
|
||||
$ini_optval = filter_var(ini_get($optname), is_bool($optval) ? FILTER_VALIDATE_BOOLEAN : FILTER_VALIDATE_INT);
|
||||
if ($optval != $ini_optval && @ini_set($optname, $optval) === false) {
|
||||
$optval = !is_bool($optval) ? $optval : ($optval ? 'On' : 'Off');
|
||||
$error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
|
||||
. "Check your PHP configuration (including php_admin_flag).";
|
||||
|
||||
if (defined('STDERR')) fwrite(STDERR, $error); else echo $error;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// framework constants
|
||||
define('RCUBE_VERSION', '1.3-beta');
|
||||
define('RCUBE_CHARSET', 'UTF-8');
|
||||
|
||||
if (!defined('RCUBE_LIB_DIR')) {
|
||||
define('RCUBE_LIB_DIR', __DIR__ . '/');
|
||||
}
|
||||
|
||||
if (!defined('RCUBE_INSTALL_PATH')) {
|
||||
define('RCUBE_INSTALL_PATH', RCUBE_LIB_DIR);
|
||||
}
|
||||
|
||||
if (!defined('RCUBE_CONFIG_DIR')) {
|
||||
define('RCUBE_CONFIG_DIR', RCUBE_INSTALL_PATH . 'config/');
|
||||
}
|
||||
|
||||
if (!defined('RCUBE_PLUGINS_DIR')) {
|
||||
define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
|
||||
}
|
||||
|
||||
if (!defined('RCUBE_LOCALIZATION_DIR')) {
|
||||
define('RCUBE_LOCALIZATION_DIR', RCUBE_INSTALL_PATH . 'localization/');
|
||||
}
|
||||
|
||||
// set internal encoding for mbstring extension
|
||||
if (function_exists('mb_internal_encoding')) {
|
||||
mb_internal_encoding(RCUBE_CHARSET);
|
||||
}
|
||||
if (function_exists('mb_regex_encoding')) {
|
||||
mb_regex_encoding(RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
// make sure the Roundcube lib directory is in the include_path
|
||||
$rcube_path = realpath(RCUBE_LIB_DIR . '..');
|
||||
$sep = PATH_SEPARATOR;
|
||||
$regexp = "!(^|$sep)" . preg_quote($rcube_path, '!') . "($sep|\$)!";
|
||||
$path = ini_get('include_path');
|
||||
|
||||
if (!preg_match($regexp, $path)) {
|
||||
set_include_path($path . PATH_SEPARATOR . $rcube_path);
|
||||
}
|
||||
|
||||
// Register autoloader
|
||||
spl_autoload_register('rcube_autoload');
|
||||
|
||||
// set PEAR error handling (will also load the PEAR main class)
|
||||
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
|
||||
|
||||
|
||||
/**
|
||||
* Similar function as in_array() but case-insensitive with multibyte support.
|
||||
*
|
||||
* @param string $needle Needle value
|
||||
* @param array $heystack Array to search in
|
||||
*
|
||||
* @return boolean True if found, False if not
|
||||
*/
|
||||
function in_array_nocase($needle, $haystack)
|
||||
{
|
||||
// use much faster method for ascii
|
||||
if (is_ascii($needle)) {
|
||||
foreach ((array) $haystack as $value) {
|
||||
if (strcasecmp($value, $needle) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$needle = mb_strtolower($needle);
|
||||
foreach ((array) $haystack as $value) {
|
||||
if ($needle === mb_strtolower($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a human readable string for a number of bytes.
|
||||
*
|
||||
* @param string $str Input string
|
||||
*
|
||||
* @return float Number of bytes
|
||||
*/
|
||||
function parse_bytes($str)
|
||||
{
|
||||
if (is_numeric($str)) {
|
||||
return floatval($str);
|
||||
}
|
||||
|
||||
if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) {
|
||||
$bytes = floatval($regs[1]);
|
||||
switch (strtolower($regs[2])) {
|
||||
case 'g':
|
||||
case 'gb':
|
||||
$bytes *= 1073741824;
|
||||
break;
|
||||
case 'm':
|
||||
case 'mb':
|
||||
$bytes *= 1048576;
|
||||
break;
|
||||
case 'k':
|
||||
case 'kb':
|
||||
$bytes *= 1024;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return floatval($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the string ends with a slash
|
||||
*/
|
||||
function slashify($str)
|
||||
{
|
||||
return unslashify($str).'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove slashes at the end of the string
|
||||
*/
|
||||
function unslashify($str)
|
||||
{
|
||||
return preg_replace('/\/+$/', '', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of seconds for a specified offset string.
|
||||
*
|
||||
* @param string $str String representation of the offset (e.g. 20min, 5h, 2days, 1week)
|
||||
*
|
||||
* @return int Number of seconds
|
||||
*/
|
||||
function get_offset_sec($str)
|
||||
{
|
||||
if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) {
|
||||
$amount = (int) $regs[1];
|
||||
$unit = strtolower($regs[2]);
|
||||
}
|
||||
else {
|
||||
$amount = (int) $str;
|
||||
$unit = 's';
|
||||
}
|
||||
|
||||
switch ($unit) {
|
||||
case 'w':
|
||||
$amount *= 7;
|
||||
case 'd':
|
||||
$amount *= 24;
|
||||
case 'h':
|
||||
$amount *= 60;
|
||||
case 'm':
|
||||
$amount *= 60;
|
||||
}
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unix timestamp with a specified offset from now.
|
||||
*
|
||||
* @param string $offset_str String representation of the offset (e.g. 20min, 5h, 2days)
|
||||
* @param int $factor Factor to multiply with the offset
|
||||
*
|
||||
* @return int Unix timestamp
|
||||
*/
|
||||
function get_offset_time($offset_str, $factor = 1)
|
||||
{
|
||||
return time() + get_offset_sec($offset_str) * $factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate string if it is longer than the allowed length.
|
||||
* Replace the middle or the ending part of a string with a placeholder.
|
||||
*
|
||||
* @param string $str Input string
|
||||
* @param int $maxlength Max. length
|
||||
* @param string $placeholder Replace removed chars with this
|
||||
* @param bool $ending Set to True if string should be truncated from the end
|
||||
*
|
||||
* @return string Abbreviated string
|
||||
*/
|
||||
function abbreviate_string($str, $maxlength, $placeholder = '...', $ending = false)
|
||||
{
|
||||
$length = mb_strlen($str);
|
||||
|
||||
if ($length > $maxlength) {
|
||||
if ($ending) {
|
||||
return mb_substr($str, 0, $maxlength) . $placeholder;
|
||||
}
|
||||
|
||||
$placeholder_length = mb_strlen($placeholder);
|
||||
$first_part_length = floor(($maxlength - $placeholder_length)/2);
|
||||
$second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length;
|
||||
|
||||
$prefix = mb_substr($str, 0, $first_part_length);
|
||||
$suffix = mb_substr($str, $second_starting_location);
|
||||
$str = $prefix . $placeholder . $suffix;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all keys from array (recursive).
|
||||
*
|
||||
* @param array $array Input array
|
||||
*
|
||||
* @return array List of array keys
|
||||
*/
|
||||
function array_keys_recursive($array)
|
||||
{
|
||||
$keys = array();
|
||||
|
||||
if (!empty($array) && is_array($array)) {
|
||||
foreach ($array as $key => $child) {
|
||||
$keys[] = $key;
|
||||
foreach (array_keys_recursive($child) as $val) {
|
||||
$keys[] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all non-ascii and non-word chars except ., -, _
|
||||
*/
|
||||
function asciiwords($str, $css_id = false, $replace_with = '')
|
||||
{
|
||||
$allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
|
||||
return preg_replace("/[^$allowed]/i", $replace_with, $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string contains only ascii characters
|
||||
*
|
||||
* @param string $str String to check
|
||||
* @param bool $control_chars Includes control characters
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_ascii($str, $control_chars = true)
|
||||
{
|
||||
$regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/';
|
||||
return preg_match($regexp, $str) ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a valid representation of name and e-mail address
|
||||
*
|
||||
* @param string $email E-mail address
|
||||
* @param string $name Person name
|
||||
*
|
||||
* @return string Formatted string
|
||||
*/
|
||||
function format_email_recipient($email, $name = '')
|
||||
{
|
||||
$email = trim($email);
|
||||
|
||||
if ($name && $name != $email) {
|
||||
// Special chars as defined by RFC 822 need to in quoted string (or escaped).
|
||||
if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) {
|
||||
$name = '"'.addcslashes($name, '"').'"';
|
||||
}
|
||||
|
||||
return "$name <$email>";
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format e-mail address
|
||||
*
|
||||
* @param string $email E-mail address
|
||||
*
|
||||
* @return string Formatted e-mail address
|
||||
*/
|
||||
function format_email($email)
|
||||
{
|
||||
$email = trim($email);
|
||||
$parts = explode('@', $email);
|
||||
$count = count($parts);
|
||||
|
||||
if ($count > 1) {
|
||||
$parts[$count-1] = mb_strtolower($parts[$count-1]);
|
||||
|
||||
$email = implode('@', $parts);
|
||||
}
|
||||
|
||||
return $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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* intl replacement functions
|
||||
*/
|
||||
|
||||
if (!function_exists('idn_to_utf8'))
|
||||
{
|
||||
function idn_to_utf8($domain)
|
||||
{
|
||||
static $idn, $loaded;
|
||||
|
||||
if (!$loaded) {
|
||||
$idn = new Net_IDNA2();
|
||||
$loaded = true;
|
||||
}
|
||||
|
||||
if ($idn && $domain && preg_match('/(^|\.)xn--/i', $domain)) {
|
||||
try {
|
||||
$domain = $idn->decode($domain);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('idn_to_ascii'))
|
||||
{
|
||||
function idn_to_ascii($domain)
|
||||
{
|
||||
static $idn, $loaded;
|
||||
|
||||
if (!$loaded) {
|
||||
$idn = new Net_IDNA2();
|
||||
$loaded = true;
|
||||
}
|
||||
|
||||
if ($idn && $domain && preg_match('/[^\x20-\x7E]/', $domain)) {
|
||||
try {
|
||||
$domain = $idn->encode($domain);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use PHP5 autoload for dynamic class loading
|
||||
*
|
||||
* @todo Make Zend, PEAR etc play with this
|
||||
* @todo Make our classes conform to a more straight forward CS.
|
||||
*/
|
||||
function rcube_autoload($classname)
|
||||
{
|
||||
if (strpos($classname, 'rcube') === 0) {
|
||||
$classname = 'Roundcube/' . $classname;
|
||||
}
|
||||
else if (strpos($classname, 'html_') === 0 || $classname === 'html') {
|
||||
$classname = 'Roundcube/html';
|
||||
}
|
||||
else if (strpos($classname, 'Mail_') === 0) {
|
||||
$classname = 'Mail/' . substr($classname, 5);
|
||||
}
|
||||
else if (strpos($classname, 'Net_') === 0) {
|
||||
$classname = 'Net/' . substr($classname, 4);
|
||||
}
|
||||
else if (strpos($classname, 'Auth_') === 0) {
|
||||
$classname = 'Auth/' . substr($classname, 5);
|
||||
}
|
||||
|
||||
// Translate PHP namespaces into directories,
|
||||
// i.e. use \Sabre\VObject; $vcf = VObject\Reader::read(...)
|
||||
// -> Sabre/VObject/Reader.php
|
||||
$classname = str_replace('\\', '/', $classname);
|
||||
|
||||
if ($fp = @fopen("$classname.php", 'r', true)) {
|
||||
fclose($fp);
|
||||
include_once "$classname.php";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local callback function for PEAR errors
|
||||
*/
|
||||
function rcube_pear_error($err)
|
||||
{
|
||||
$msg = sprintf("ERROR: %s (%s)", $err->getMessage(), $err->getCode());
|
||||
|
||||
if ($info = $err->getUserinfo()) {
|
||||
$msg .= ': ' . $info;
|
||||
}
|
||||
|
||||
error_log($msg, 0);
|
||||
}
|
||||
950
data/web/rc/program/lib/Roundcube/html.php
Normal file
950
data/web/rc/program/lib/Roundcube/html.php
Normal file
@@ -0,0 +1,950 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| 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. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Helper class to create valid XHTML code |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for HTML code creation
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html
|
||||
{
|
||||
protected $tagname;
|
||||
protected $content;
|
||||
protected $attrib = array();
|
||||
protected $allowed = array();
|
||||
|
||||
public static $doctype = 'xhtml';
|
||||
public static $lc_tags = true;
|
||||
public static $common_attrib = array('id','class','style','title','align','unselectable','tabindex','role');
|
||||
public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script');
|
||||
public static $bool_attrib = array('checked','multiple','disabled','selected','autofocus','readonly');
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $attrib Hash array with tag attributes
|
||||
*/
|
||||
public function __construct($attrib = array())
|
||||
{
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = $attrib;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tag code
|
||||
*
|
||||
* @return string The finally composed HTML tag
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
return self::tag($this->tagname, $this->attrib, $this->content, array_merge(self::$common_attrib, $this->allowed));
|
||||
}
|
||||
|
||||
/****** STATIC METHODS *******/
|
||||
|
||||
/**
|
||||
* Generic method to create a HTML tag
|
||||
*
|
||||
* @param string $tagname Tag name
|
||||
* @param array $attrib Tag attributes as key/value pairs
|
||||
* @param string $content Optinal Tag content (creates a container tag)
|
||||
* @param array $allowed List with allowed attributes, omit to allow all
|
||||
*
|
||||
* @return string The XHTML tag
|
||||
*/
|
||||
public static function tag($tagname, $attrib = array(), $content = null, $allowed = null)
|
||||
{
|
||||
if (is_string($attrib)) {
|
||||
$attrib = array('class' => $attrib);
|
||||
}
|
||||
|
||||
$inline_tags = array('a','span','img');
|
||||
$suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
|
||||
|
||||
$tagname = self::$lc_tags ? strtolower($tagname) : $tagname;
|
||||
if (isset($content) || in_array($tagname, self::$containers)) {
|
||||
$suffix = $attrib['noclose'] ? $suffix : '</' . $tagname . '>' . $suffix;
|
||||
unset($attrib['noclose'], $attrib['nl']);
|
||||
return '<' . $tagname . self::attrib_string($attrib, $allowed) . '>' . $content . $suffix;
|
||||
}
|
||||
else {
|
||||
return '<' . $tagname . self::attrib_string($attrib, $allowed) . '>' . $suffix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return DOCTYPE tag of specified type
|
||||
*
|
||||
* @param string $type Document type (html5, xhtml, 'xhtml-trans, xhtml-strict)
|
||||
*/
|
||||
public static function doctype($type)
|
||||
{
|
||||
$doctypes = array(
|
||||
'html5' => '<!DOCTYPE html>',
|
||||
'xhtml' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
||||
'xhtml-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
||||
'xhtml-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||
);
|
||||
|
||||
if ($doctypes[$type]) {
|
||||
self::$doctype = preg_replace('/-\w+$/', '', $type);
|
||||
return $doctypes[$type];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method for <div> containers
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with class name
|
||||
* @param string $cont Div content
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function div($attr = null, $cont = null)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('class' => $attr);
|
||||
}
|
||||
|
||||
return self::tag('div', $attr, $cont, array_merge(self::$common_attrib, array('onclick')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method for <p> blocks
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with class name
|
||||
* @param string $cont Paragraph content
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function p($attr = null, $cont = null)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('class' => $attr);
|
||||
}
|
||||
|
||||
return self::tag('p', $attr, $cont, self::$common_attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method to create <img />
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with image source (src)
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function img($attr = null)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('src' => $attr);
|
||||
}
|
||||
|
||||
return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
|
||||
array('src','alt','width','height','border','usemap','onclick','onerror','onload')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method for link tags
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with link location (href)
|
||||
* @param string $cont Link content
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function a($attr, $cont)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$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')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method for inline span tags
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with class name
|
||||
* @param string $cont Tag content
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function span($attr, $cont)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('class' => $attr);
|
||||
}
|
||||
|
||||
return self::tag('span', $attr, $cont, self::$common_attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method for form element labels
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with 'for' attrib
|
||||
* @param string $cont Tag content
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function label($attr, $cont)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('for' => $attr);
|
||||
}
|
||||
|
||||
return self::tag('label', $attr, $cont, array_merge(self::$common_attrib,
|
||||
array('for','onkeypress')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method to create <iframe></iframe>
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with frame source (src)
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function iframe($attr = null, $cont = null)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('src' => $attr);
|
||||
}
|
||||
|
||||
return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
|
||||
array('src','name','width','height','border','frameborder','onload','allowfullscreen')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method to create <script> tags
|
||||
*
|
||||
* @param mixed $attr Hash array with tag attributes or string with script source (src)
|
||||
* @param string $cont Javascript code to be placed as tag content
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function script($attr, $cont = null)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('src' => $attr);
|
||||
}
|
||||
if ($cont) {
|
||||
if (self::$doctype == 'xhtml')
|
||||
$cont = "\n/* <![CDATA[ */\n" . $cont . "\n/* ]]> */\n";
|
||||
else
|
||||
$cont = "\n" . $cont . "\n";
|
||||
}
|
||||
|
||||
return self::tag('script', $attr + array('type' => 'text/javascript', 'nl' => true),
|
||||
$cont, array_merge(self::$common_attrib, array('src','type','charset')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derrived method for line breaks
|
||||
*
|
||||
* @param array $attrib Associative arry with tag attributes
|
||||
*
|
||||
* @return string HTML code
|
||||
* @see html::tag()
|
||||
*/
|
||||
public static function br($attrib = array())
|
||||
{
|
||||
return self::tag('br', $attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create string with attributes
|
||||
*
|
||||
* @param array $attrib Associative array with tag attributes
|
||||
* @param array $allowed List of allowed attributes
|
||||
*
|
||||
* @return string Valid attribute string
|
||||
*/
|
||||
public static function attrib_string($attrib = array(), $allowed = null)
|
||||
{
|
||||
if (empty($attrib)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$allowed_f = array_flip((array)$allowed);
|
||||
$attrib_arr = array();
|
||||
|
||||
foreach ($attrib as $key => $value) {
|
||||
// skip size if not numeric
|
||||
if ($key == 'size' && !is_numeric($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore "internal" or empty attributes
|
||||
if ($key == 'nl' || $value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore not allowed attributes, except aria-* and data-*
|
||||
if (!empty($allowed)) {
|
||||
$is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0;
|
||||
$is_aria_attr = @substr_compare($key, 'aria-', 0, 5) === 0;
|
||||
if (!$is_aria_attr && !$is_data_attr && !isset($allowed_f[$key])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// skip empty eventhandlers
|
||||
if (preg_match('/^on[a-z]+/', $key) && !$value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// attributes with no value
|
||||
if (in_array($key, self::$bool_attrib)) {
|
||||
if ($value) {
|
||||
$value = $key;
|
||||
if (self::$doctype == 'xhtml') {
|
||||
$value .= '="' . $value . '"';
|
||||
}
|
||||
|
||||
$attrib_arr[] = $value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$attrib_arr[] = $key . '="' . self::quote($value) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a HTML attribute string attributes to an associative array (name => value)
|
||||
*
|
||||
* @param string $str Input string
|
||||
*
|
||||
* @return array Key-value pairs of parsed attributes
|
||||
*/
|
||||
public static function parse_attrib_string($str)
|
||||
{
|
||||
$attrib = array();
|
||||
$html = '<html><body><div ' . rtrim($str, '/ ') . ' /></body></html>';
|
||||
|
||||
$document = new DOMDocument('1.0', RCUBE_CHARSET);
|
||||
@$document->loadHTML($html);
|
||||
|
||||
if ($node = $document->getElementsByTagName('div')->item(0)) {
|
||||
foreach ($node->attributes as $name => $attr) {
|
||||
$attrib[strtolower($name)] = $attr->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $attrib;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacing specials characters in html attribute value
|
||||
*
|
||||
* @param string $str Input string
|
||||
*
|
||||
* @return string The quoted string
|
||||
*/
|
||||
public static function quote($str)
|
||||
{
|
||||
static $flags;
|
||||
|
||||
if (!$flags) {
|
||||
$flags = ENT_COMPAT;
|
||||
if (defined('ENT_SUBSTITUTE')) {
|
||||
$flags |= ENT_SUBSTITUTE;
|
||||
}
|
||||
}
|
||||
|
||||
return @htmlspecialchars($str, $flags, RCUBE_CHARSET);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class to create an HTML input field
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_inputfield extends html
|
||||
{
|
||||
protected $tagname = 'input';
|
||||
protected $type = 'text';
|
||||
protected $allowed = array(
|
||||
'type','name','value','size','tabindex','autocapitalize','required',
|
||||
'autocomplete','checked','onchange','onclick','disabled','readonly',
|
||||
'spellcheck','results','maxlength','src','multiple','accept',
|
||||
'placeholder','autofocus','pattern',
|
||||
);
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param array $attrib Associative array with tag attributes
|
||||
*/
|
||||
public function __construct($attrib = array())
|
||||
{
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = $attrib;
|
||||
}
|
||||
|
||||
if ($attrib['type']) {
|
||||
$this->type = $attrib['type'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose input tag
|
||||
*
|
||||
* @param string $value Field value
|
||||
* @param array $attrib Additional attributes to override
|
||||
*
|
||||
* @return string HTML output
|
||||
*/
|
||||
public function show($value = null, $attrib = null)
|
||||
{
|
||||
// overwrite object attributes
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = array_merge($this->attrib, $attrib);
|
||||
}
|
||||
|
||||
// set value attribute
|
||||
if ($value !== null) {
|
||||
$this->attrib['value'] = $value;
|
||||
}
|
||||
// set type
|
||||
$this->attrib['type'] = $this->type;
|
||||
|
||||
return parent::show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to create an HTML password field
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_passwordfield extends html_inputfield
|
||||
{
|
||||
protected $type = 'password';
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to create an hidden HTML input field
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_hiddenfield extends html
|
||||
{
|
||||
protected $tagname = 'input';
|
||||
protected $type = 'hidden';
|
||||
protected $allowed = array('type','name','value','onchange','disabled','readonly');
|
||||
protected $fields = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $attrib Named tag attributes
|
||||
*/
|
||||
public function __construct($attrib = null)
|
||||
{
|
||||
if (is_array($attrib)) {
|
||||
$this->add($attrib);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a hidden field to this instance
|
||||
*
|
||||
* @param array $attrib Named tag attributes
|
||||
*/
|
||||
public function add($attrib)
|
||||
{
|
||||
$this->fields[] = $attrib;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTML code for the hidden fields
|
||||
*
|
||||
* @return string Final HTML code
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$out = '';
|
||||
foreach ($this->fields as $attrib) {
|
||||
$out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to create HTML radio buttons
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_radiobutton extends html_inputfield
|
||||
{
|
||||
protected $type = 'radio';
|
||||
|
||||
/**
|
||||
* Get HTML code for this object
|
||||
*
|
||||
* @param string $value Value of the checked field
|
||||
* @param array $attrib Additional attributes to override
|
||||
*
|
||||
* @return string HTML output
|
||||
*/
|
||||
public function show($value = '', $attrib = null)
|
||||
{
|
||||
// overwrite object attributes
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = array_merge($this->attrib, $attrib);
|
||||
}
|
||||
|
||||
// set value attribute
|
||||
$this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
|
||||
|
||||
return parent::show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to create HTML checkboxes
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_checkbox extends html_inputfield
|
||||
{
|
||||
protected $type = 'checkbox';
|
||||
|
||||
/**
|
||||
* Get HTML code for this object
|
||||
*
|
||||
* @param string $value Value of the checked field
|
||||
* @param array $attrib Additional attributes to override
|
||||
*
|
||||
* @return string HTML output
|
||||
*/
|
||||
public function show($value = '', $attrib = null)
|
||||
{
|
||||
// overwrite object attributes
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = array_merge($this->attrib, $attrib);
|
||||
}
|
||||
|
||||
// set value attribute
|
||||
$this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
|
||||
|
||||
return parent::show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to create an HTML textarea
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_textarea extends html
|
||||
{
|
||||
protected $tagname = 'textarea';
|
||||
protected $allowed = array('name','rows','cols','wrap','tabindex',
|
||||
'onchange','disabled','readonly','spellcheck');
|
||||
|
||||
/**
|
||||
* Get HTML code for this object
|
||||
*
|
||||
* @param string $value Textbox value
|
||||
* @param array $attrib Additional attributes to override
|
||||
*
|
||||
* @return string HTML output
|
||||
*/
|
||||
public function show($value = '', $attrib = null)
|
||||
{
|
||||
// overwrite object attributes
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = array_merge($this->attrib, $attrib);
|
||||
}
|
||||
|
||||
// take value attribute as content
|
||||
if (empty($value) && !empty($this->attrib['value'])) {
|
||||
$value = $this->attrib['value'];
|
||||
}
|
||||
|
||||
// make shure we don't print the value attribute
|
||||
if (isset($this->attrib['value'])) {
|
||||
unset($this->attrib['value']);
|
||||
}
|
||||
|
||||
if (!empty($value) && empty($this->attrib['is_escaped'])) {
|
||||
$value = self::quote($value);
|
||||
}
|
||||
|
||||
return self::tag($this->tagname, $this->attrib, $value,
|
||||
array_merge(self::$common_attrib, $this->allowed));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for HTML drop-down menus
|
||||
* Syntax:<pre>
|
||||
* // create instance. arguments are used to set attributes of select-tag
|
||||
* $select = new html_select(array('name' => 'fieldname'));
|
||||
*
|
||||
* // add one option
|
||||
* $select->add('Switzerland', 'CH');
|
||||
*
|
||||
* // add multiple options
|
||||
* $select->add(array('Switzerland','Germany'), array('CH','DE'));
|
||||
*
|
||||
* // generate pulldown with selection 'Switzerland' and return html-code
|
||||
* // as second argument the same attributes available to instanciate can be used
|
||||
* print $select->show('CH');
|
||||
* </pre>
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_select extends html
|
||||
{
|
||||
protected $tagname = 'select';
|
||||
protected $options = array();
|
||||
protected $allowed = array('name','size','tabindex','autocomplete',
|
||||
'multiple','onchange','disabled','rel');
|
||||
|
||||
/**
|
||||
* Add a new option to this drop-down
|
||||
*
|
||||
* @param mixed $names Option name or array with option names
|
||||
* @param mixed $values Option value or array with option values
|
||||
* @param array $attrib Additional attributes for the option entry
|
||||
*/
|
||||
public function add($names, $values = null, $attrib = array())
|
||||
{
|
||||
if (is_array($names)) {
|
||||
foreach ($names as $i => $text) {
|
||||
$this->options[] = array('text' => $text, 'value' => $values[$i]) + $attrib;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->options[] = array('text' => $names, 'value' => $values) + $attrib;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML code for this object
|
||||
*
|
||||
* @param string $select Value of the selection option
|
||||
* @param array $attrib Additional attributes to override
|
||||
*
|
||||
* @return string HTML output
|
||||
*/
|
||||
public function show($select = array(), $attrib = null)
|
||||
{
|
||||
// overwrite object attributes
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = array_merge($this->attrib, $attrib);
|
||||
}
|
||||
|
||||
$this->content = "\n";
|
||||
$select = (array)$select;
|
||||
foreach ($this->options as $option) {
|
||||
$attr = array(
|
||||
'value' => $option['value'],
|
||||
'selected' => (in_array($option['value'], $select, true) ||
|
||||
in_array($option['text'], $select, true)) ? 1 : null);
|
||||
|
||||
$option_content = $option['text'];
|
||||
if (empty($this->attrib['is_escaped'])) {
|
||||
$option_content = self::quote($option_content);
|
||||
}
|
||||
|
||||
$this->content .= self::tag('option', $attr + $option, $option_content, array('value','label','class','style','title','disabled','selected'));
|
||||
}
|
||||
|
||||
return parent::show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class to build an HTML table
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
class html_table extends html
|
||||
{
|
||||
protected $tagname = 'table';
|
||||
protected $allowed = array('id','class','style','width','summary',
|
||||
'cellpadding','cellspacing','border');
|
||||
|
||||
private $header = array();
|
||||
private $rows = array();
|
||||
private $rowindex = 0;
|
||||
private $colindex = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $attrib Named tag attributes
|
||||
*/
|
||||
public function __construct($attrib = array())
|
||||
{
|
||||
$default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => '0') : array();
|
||||
$this->attrib = array_merge($attrib, $default_attrib);
|
||||
|
||||
if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
|
||||
$this->tagname = $attrib['tagname'];
|
||||
$this->allowed = self::$common_attrib;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a table cell
|
||||
*
|
||||
* @param array $attr Cell attributes
|
||||
* @param string $cont Cell content
|
||||
*/
|
||||
public function add($attr, $cont)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('class' => $attr);
|
||||
}
|
||||
|
||||
$cell = new stdClass;
|
||||
$cell->attrib = $attr;
|
||||
$cell->content = $cont;
|
||||
|
||||
$this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
|
||||
$this->colindex += max(1, intval($attr['colspan']));
|
||||
|
||||
if ($this->attrib['cols'] && $this->colindex >= $this->attrib['cols']) {
|
||||
$this->add_row();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a table header cell
|
||||
*
|
||||
* @param array $attr Cell attributes
|
||||
* @param string $cont Cell content
|
||||
*/
|
||||
public function add_header($attr, $cont)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('class' => $attr);
|
||||
}
|
||||
|
||||
$cell = new stdClass;
|
||||
$cell->attrib = $attr;
|
||||
$cell->content = $cont;
|
||||
$this->header[] = $cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a column from a table
|
||||
* Useful for plugins making alterations
|
||||
*
|
||||
* @param string $class Class name
|
||||
*/
|
||||
public function remove_column($class)
|
||||
{
|
||||
// Remove the header
|
||||
foreach ($this->header as $index => $header){
|
||||
if ($header->attrib['class'] == $class){
|
||||
unset($this->header[$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove cells from rows
|
||||
foreach ($this->rows as $i => $row){
|
||||
foreach ($row->cells as $j => $cell){
|
||||
if ($cell->attrib['class'] == $class){
|
||||
unset($this->rows[$i]->cells[$j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump to next row
|
||||
*
|
||||
* @param array $attr Row attributes
|
||||
*/
|
||||
public function add_row($attr = array())
|
||||
{
|
||||
$this->rowindex++;
|
||||
$this->colindex = 0;
|
||||
$this->rows[$this->rowindex] = new stdClass;
|
||||
$this->rows[$this->rowindex]->attrib = $attr;
|
||||
$this->rows[$this->rowindex]->cells = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set row attributes
|
||||
*
|
||||
* @param array $attr Row attributes
|
||||
* @param int $index Optional row index (default current row index)
|
||||
*/
|
||||
public function set_row_attribs($attr = array(), $index = null)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = array('class' => $attr);
|
||||
}
|
||||
|
||||
if ($index === null) {
|
||||
$index = $this->rowindex;
|
||||
}
|
||||
|
||||
// make sure row object exists (#1489094)
|
||||
if (!$this->rows[$index]) {
|
||||
$this->rows[$index] = new stdClass;
|
||||
}
|
||||
|
||||
$this->rows[$index]->attrib = $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row attributes
|
||||
*
|
||||
* @param int $index Row index
|
||||
*
|
||||
* @return array Row attributes
|
||||
*/
|
||||
public function get_row_attribs($index = null)
|
||||
{
|
||||
if ($index === null) {
|
||||
$index = $this->rowindex;
|
||||
}
|
||||
|
||||
return $this->rows[$index] ? $this->rows[$index]->attrib : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML output of the table data
|
||||
*
|
||||
* @param array $attrib Table attributes
|
||||
*
|
||||
* @return string The final table HTML code
|
||||
*/
|
||||
public function show($attrib = null)
|
||||
{
|
||||
if (is_array($attrib)) {
|
||||
$this->attrib = array_merge($this->attrib, $attrib);
|
||||
}
|
||||
|
||||
$thead = $tbody = "";
|
||||
|
||||
// include <thead>
|
||||
if (!empty($this->header)) {
|
||||
$rowcontent = '';
|
||||
foreach ($this->header as $c => $col) {
|
||||
$rowcontent .= self::tag($this->_head_tagname(), $col->attrib, $col->content);
|
||||
}
|
||||
$thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
|
||||
self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
|
||||
}
|
||||
|
||||
foreach ($this->rows as $r => $row) {
|
||||
$rowcontent = '';
|
||||
foreach ($row->cells as $c => $col) {
|
||||
$rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
|
||||
}
|
||||
|
||||
if ($r < $this->rowindex || count($row->cells)) {
|
||||
$tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->attrib['rowsonly']) {
|
||||
return $tbody;
|
||||
}
|
||||
|
||||
// add <tbody>
|
||||
$this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
|
||||
|
||||
unset($this->attrib['cols'], $this->attrib['rowsonly']);
|
||||
return parent::show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count number of rows
|
||||
*
|
||||
* @return The number of rows
|
||||
*/
|
||||
public function size()
|
||||
{
|
||||
return count($this->rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove table body (all rows)
|
||||
*/
|
||||
public function remove_body()
|
||||
{
|
||||
$this->rows = array();
|
||||
$this->rowindex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the corresponding tag name for table row elements
|
||||
*/
|
||||
private function _row_tagname()
|
||||
{
|
||||
static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
|
||||
return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the corresponding tag name for table row elements
|
||||
*/
|
||||
private function _head_tagname()
|
||||
{
|
||||
static $head_tagnames = array('table' => 'th', '*' => 'span');
|
||||
return $head_tagnames[$this->tagname] ?: $head_tagnames['*'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the corresponding tag name for table cell elements
|
||||
*/
|
||||
private function _col_tagname()
|
||||
{
|
||||
static $col_tagnames = array('table' => 'td', '*' => 'span');
|
||||
return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
|
||||
}
|
||||
}
|
||||
1713
data/web/rc/program/lib/Roundcube/rcube.php
Normal file
1713
data/web/rc/program/lib/Roundcube/rcube.php
Normal file
File diff suppressed because it is too large
Load Diff
705
data/web/rc/program/lib/Roundcube/rcube_addressbook.php
Normal file
705
data/web/rc/program/lib/Roundcube/rcube_addressbook.php
Normal file
@@ -0,0 +1,705 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2006-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Interface to the local address book database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract skeleton of an address book/repository
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Addressbook
|
||||
*/
|
||||
abstract class rcube_addressbook
|
||||
{
|
||||
// constants for error reporting
|
||||
const ERROR_READ_ONLY = 1;
|
||||
const ERROR_NO_CONNECTION = 2;
|
||||
const ERROR_VALIDATE = 3;
|
||||
const ERROR_SAVING = 4;
|
||||
const ERROR_SEARCH = 5;
|
||||
|
||||
// search modes
|
||||
const SEARCH_ALL = 0;
|
||||
const SEARCH_STRICT = 1;
|
||||
const SEARCH_PREFIX = 2;
|
||||
const SEARCH_GROUPS = 4;
|
||||
|
||||
// public properties (mandatory)
|
||||
public $primary_key;
|
||||
public $groups = false;
|
||||
public $export_groups = true;
|
||||
public $readonly = true;
|
||||
public $searchonly = false;
|
||||
public $undelete = false;
|
||||
public $ready = false;
|
||||
public $group_id = null;
|
||||
public $list_page = 1;
|
||||
public $page_size = 10;
|
||||
public $sort_col = 'name';
|
||||
public $sort_order = 'ASC';
|
||||
public $date_cols = array();
|
||||
public $coltypes = array(
|
||||
'name' => array('limit'=>1),
|
||||
'firstname' => array('limit'=>1),
|
||||
'surname' => array('limit'=>1),
|
||||
'email' => array('limit'=>1)
|
||||
);
|
||||
|
||||
protected $error;
|
||||
|
||||
/**
|
||||
* Returns addressbook name (e.g. for addressbooks listing)
|
||||
*/
|
||||
abstract function get_name();
|
||||
|
||||
/**
|
||||
* Save a search string for future listings
|
||||
*
|
||||
* @param mixed $filter Search params to use in listing method, obtained by get_search_set()
|
||||
*/
|
||||
abstract function set_search_set($filter);
|
||||
|
||||
/**
|
||||
* Getter for saved search properties
|
||||
*
|
||||
* @return mixed Search properties used by this class
|
||||
*/
|
||||
abstract function get_search_set();
|
||||
|
||||
/**
|
||||
* Reset saved results and search parameters
|
||||
*/
|
||||
abstract function reset();
|
||||
|
||||
/**
|
||||
* Refresh saved search set after data has changed
|
||||
*
|
||||
* @return mixed New search set
|
||||
*/
|
||||
function refresh_search()
|
||||
{
|
||||
return $this->get_search_set();
|
||||
}
|
||||
|
||||
/**
|
||||
* List the current set of contact records
|
||||
*
|
||||
* @param array $cols List of cols to show
|
||||
* @param int $subset Only return this number of records, use negative values for tail
|
||||
*
|
||||
* @return array Indexed list of contact records, each a hash array
|
||||
*/
|
||||
abstract function list_records($cols=null, $subset=0);
|
||||
|
||||
/**
|
||||
* Search records
|
||||
*
|
||||
* @param array $fields List of fields to search in
|
||||
* @param string $value Search value
|
||||
* @param int $mode Search mode. Sum of self::SEARCH_*.
|
||||
* @param boolean $select True if results are requested, False if count only
|
||||
* @param boolean $nocount True to skip the count query (select only)
|
||||
* @param array $required List of fields that cannot be empty
|
||||
*
|
||||
* @return object rcube_result_set List of contact records and 'count' value
|
||||
*/
|
||||
abstract function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array());
|
||||
|
||||
/**
|
||||
* Count number of available contacts in database
|
||||
*
|
||||
* @return rcube_result_set Result set with values for 'count' and 'first'
|
||||
*/
|
||||
abstract function count();
|
||||
|
||||
/**
|
||||
* Return the last result set
|
||||
*
|
||||
* @return rcube_result_set Current result set or NULL if nothing selected yet
|
||||
*/
|
||||
abstract function get_result();
|
||||
|
||||
/**
|
||||
* Get a specific contact record
|
||||
*
|
||||
* @param mixed $id Record identifier(s)
|
||||
* @param boolean $assoc True to return record as associative array, otherwise a result set is returned
|
||||
*
|
||||
* @return rcube_result_set|array Result object with all record fields
|
||||
*/
|
||||
abstract function get_record($id, $assoc=false);
|
||||
|
||||
/**
|
||||
* Returns the last error occurred (e.g. when updating/inserting failed)
|
||||
*
|
||||
* @return array Hash array with the following fields: type, message
|
||||
*/
|
||||
function get_error()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for errors for internal use
|
||||
*
|
||||
* @param int $type Error type (one of this class' error constants)
|
||||
* @param string $message Error message (name of a text label)
|
||||
*/
|
||||
protected function set_error($type, $message)
|
||||
{
|
||||
$this->error = array('type' => $type, 'message' => $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connection to source
|
||||
* Called on script shutdown
|
||||
*/
|
||||
function close() { }
|
||||
|
||||
/**
|
||||
* Set internal list page
|
||||
*
|
||||
* @param number $page Page number to list
|
||||
*/
|
||||
function set_page($page)
|
||||
{
|
||||
$this->list_page = (int)$page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set internal page size
|
||||
*
|
||||
* @param number $size Number of messages to display on one page
|
||||
*/
|
||||
function set_pagesize($size)
|
||||
{
|
||||
$this->page_size = (int)$size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set internal sort settings
|
||||
*
|
||||
* @param string $sort_col Sort column
|
||||
* @param string $sort_order Sort order
|
||||
*/
|
||||
function set_sort_order($sort_col, $sort_order = null)
|
||||
{
|
||||
if ($sort_col != null && ($this->coltypes[$sort_col] || in_array($sort_col, $this->coltypes))) {
|
||||
$this->sort_col = $sort_col;
|
||||
}
|
||||
if ($sort_order != null) {
|
||||
$this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given data before saving.
|
||||
* If input isn't valid, the message to display can be fetched using get_error()
|
||||
*
|
||||
* @param array &$save_data Associative array with data to save
|
||||
* @param boolean $autofix Attempt to fix/complete record automatically
|
||||
*
|
||||
* @return boolean True if input is valid, False if not.
|
||||
*/
|
||||
public function validate(&$save_data, $autofix = false)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$valid = true;
|
||||
|
||||
// check validity of email addresses
|
||||
foreach ($this->get_col_values('email', $save_data, true) as $email) {
|
||||
if (strlen($email)) {
|
||||
if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
|
||||
$error = $rcube->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
|
||||
$this->set_error(self::ERROR_VALIDATE, $error);
|
||||
$valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allow plugins to do contact validation and auto-fixing
|
||||
$plugin = $rcube->plugins->exec_hook('contact_validate', array(
|
||||
'record' => $save_data,
|
||||
'autofix' => $autofix,
|
||||
'valid' => $valid,
|
||||
));
|
||||
|
||||
if ($valid && !$plugin['valid']) {
|
||||
$this->set_error(self::ERROR_VALIDATE, $plugin['error']);
|
||||
}
|
||||
|
||||
if (is_array($plugin['record'])) {
|
||||
$save_data = $plugin['record'];
|
||||
}
|
||||
|
||||
return $plugin['valid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new contact record
|
||||
*
|
||||
* @param array $save_data Associative array with save data
|
||||
* Keys: Field name with optional section in the form FIELD:SECTION
|
||||
* Values: Field value. Can be either a string or an array of strings for multiple values
|
||||
* @param boolean $check True to check for duplicates first
|
||||
*
|
||||
* @return mixed The created record ID on success, False on error
|
||||
*/
|
||||
function insert($save_data, $check=false)
|
||||
{
|
||||
/* empty for read-only address books */
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new contact records for every item in the record set
|
||||
*
|
||||
* @param rcube_result_set $recset Recordset to insert
|
||||
* @param boolean $check True to check for duplicates first
|
||||
*
|
||||
* @return array List of created record IDs
|
||||
*/
|
||||
function insertMultiple($recset, $check=false)
|
||||
{
|
||||
$ids = array();
|
||||
if (is_object($recset) && is_a($recset, rcube_result_set)) {
|
||||
while ($row = $recset->next()) {
|
||||
if ($insert = $this->insert($row, $check))
|
||||
$ids[] = $insert;
|
||||
}
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific contact record
|
||||
*
|
||||
* @param mixed $id Record identifier
|
||||
* @param array $save_cols Associative array with save data
|
||||
* Keys: Field name with optional section in the form FIELD:SECTION
|
||||
* Values: Field value. Can be either a string or an array of strings for multiple values
|
||||
*
|
||||
* @return mixed On success if ID has been changed returns ID, otherwise True, False on error
|
||||
*/
|
||||
function update($id, $save_cols)
|
||||
{
|
||||
/* empty for read-only address books */
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark one or more contact records as deleted
|
||||
*
|
||||
* @param array $ids Record identifiers
|
||||
* @param bool $force Remove records irreversible (see self::undelete)
|
||||
*/
|
||||
function delete($ids, $force = true)
|
||||
{
|
||||
/* empty for read-only address books */
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmark delete flag on contact record(s)
|
||||
*
|
||||
* @param array $ids Record identifiers
|
||||
*/
|
||||
function undelete($ids)
|
||||
{
|
||||
/* empty for read-only address books */
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all records in database as deleted
|
||||
*
|
||||
* @param bool $with_groups Remove also groups
|
||||
*/
|
||||
function delete_all($with_groups = false)
|
||||
{
|
||||
/* empty for read-only address books */
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the current group
|
||||
* (empty, has to be re-implemented by extending class)
|
||||
*/
|
||||
function set_group($group_id) { }
|
||||
|
||||
/**
|
||||
* List all active contact groups of this source
|
||||
*
|
||||
* @param string $search Optional search string to match group name
|
||||
* @param int $mode Search mode. Sum of self::SEARCH_*
|
||||
*
|
||||
* @return array Indexed list of contact groups, each a hash array
|
||||
*/
|
||||
function list_groups($search = null, $mode = 0)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group properties such as name and email address(es)
|
||||
*
|
||||
* @param string $group_id Group identifier
|
||||
*
|
||||
* @return array Group properties as hash array
|
||||
*/
|
||||
function get_group($group_id)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a contact group with the given name
|
||||
*
|
||||
* @param string $name The group name
|
||||
*
|
||||
* @return mixed False on error, array with record props in success
|
||||
*/
|
||||
function create_group($name)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given group and all linked group members
|
||||
*
|
||||
* @param string $group_id Group identifier
|
||||
*
|
||||
* @return boolean True on success, false if no data was changed
|
||||
*/
|
||||
function delete_group($group_id)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a specific contact group
|
||||
*
|
||||
* @param string $group_id Group identifier
|
||||
* @param string $newname New name to set for this group
|
||||
* @param string &$newid New group identifier (if changed, otherwise don't set)
|
||||
*
|
||||
* @return boolean New name on success, false if no data was changed
|
||||
*/
|
||||
function rename_group($group_id, $newname, &$newid)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given contact records the a certain group
|
||||
*
|
||||
* @param string $group_id Group identifier
|
||||
* @param array|string $ids List of contact identifiers to be added
|
||||
*
|
||||
* @return int Number of contacts added
|
||||
*/
|
||||
function add_to_group($group_id, $ids)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given contact records from a certain group
|
||||
*
|
||||
* @param string $group_id Group identifier
|
||||
* @param array|string $ids List of contact identifiers to be removed
|
||||
*
|
||||
* @return int Number of deleted group members
|
||||
*/
|
||||
function remove_from_group($group_id, $ids)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group assignments of a specific contact record
|
||||
*
|
||||
* @param mixed Record identifier
|
||||
*
|
||||
* @return array $id List of assigned groups as ID=>Name pairs
|
||||
* @since 0.5-beta
|
||||
*/
|
||||
function get_record_groups($id)
|
||||
{
|
||||
/* empty for address books don't supporting groups */
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to return all values of a certain data column
|
||||
* either as flat list or grouped by subtype
|
||||
*
|
||||
* @param string $col Col name
|
||||
* @param array $data Record data array as used for saving
|
||||
* @param bool $flat True to return one array with all values,
|
||||
* False for hash array with values grouped by type
|
||||
*
|
||||
* @return array List of column values
|
||||
*/
|
||||
public static function get_col_values($col, $data, $flat = false)
|
||||
{
|
||||
$out = array();
|
||||
foreach ((array)$data as $c => $values) {
|
||||
if ($c === $col || strpos($c, $col.':') === 0) {
|
||||
if ($flat) {
|
||||
$out = array_merge($out, (array)$values);
|
||||
}
|
||||
else {
|
||||
list(, $type) = explode(':', $c);
|
||||
$out[$type] = array_merge((array)$out[$type], (array)$values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
if ($flat && !empty($out)) {
|
||||
$out = array_unique($out);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given string for fulltext search.
|
||||
* Currently only optimized for Latin-1 characters; to be extended
|
||||
*
|
||||
* @param string $str Input string (UTF-8)
|
||||
* @return string Normalized string
|
||||
* @deprecated since 0.9-beta
|
||||
*/
|
||||
protected static function normalize_string($str)
|
||||
{
|
||||
return rcube_utils::normalize_string($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a valid display name from the given structured contact data
|
||||
*
|
||||
* @param array $contact Hash array with contact data as key-value pairs
|
||||
* @param bool $full_email Don't attempt to extract components from the email address
|
||||
*
|
||||
* @return string Display name
|
||||
*/
|
||||
public static function compose_display_name($contact, $full_email = false)
|
||||
{
|
||||
$contact = rcube::get_instance()->plugins->exec_hook('contact_displayname', $contact);
|
||||
$fn = $contact['name'];
|
||||
|
||||
if (!$fn) // default display name composition according to vcard standard
|
||||
$fn = trim(join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']))));
|
||||
|
||||
// use email address part for name
|
||||
$email = self::get_col_values('email', $contact, true);
|
||||
$email = $email[0];
|
||||
|
||||
if ($email && (empty($fn) || $fn == $email)) {
|
||||
// return full email
|
||||
if ($full_email)
|
||||
return $email;
|
||||
|
||||
list($emailname) = explode('@', $email);
|
||||
if (preg_match('/(.*)[\.\-\_](.*)/', $emailname, $match))
|
||||
$fn = trim(ucfirst($match[1]).' '.ucfirst($match[2]));
|
||||
else
|
||||
$fn = ucfirst($emailname);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param array $contact Hash array with contact data as key-value pairs
|
||||
*
|
||||
* @return string List name
|
||||
*/
|
||||
public static function compose_list_name($contact)
|
||||
{
|
||||
static $compose_mode;
|
||||
|
||||
if (!isset($compose_mode)) // cache this
|
||||
$compose_mode = rcube::get_instance()->config->get('addressbook_name_listing', 0);
|
||||
|
||||
if ($compose_mode == 3)
|
||||
$fn = join(' ', array($contact['surname'] . ',', $contact['firstname'], $contact['middlename']));
|
||||
else if ($compose_mode == 2)
|
||||
$fn = join(' ', array($contact['surname'], $contact['firstname'], $contact['middlename']));
|
||||
else if ($compose_mode == 1)
|
||||
$fn = join(' ', array($contact['firstname'], $contact['middlename'], $contact['surname']));
|
||||
else if ($compose_mode == 0)
|
||||
$fn = $contact['name'] ?: join(' ', array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']));
|
||||
else {
|
||||
$plugin = rcube::get_instance()->plugins->exec_hook('contact_listname', array('contact' => $contact));
|
||||
$fn = $plugin['fn'];
|
||||
}
|
||||
|
||||
$fn = trim($fn, ', ');
|
||||
|
||||
// fallbacks...
|
||||
if ($fn === '') {
|
||||
// ... display name
|
||||
if ($name = trim($contact['name'])) {
|
||||
$fn = $name;
|
||||
}
|
||||
// ... organization
|
||||
else if ($org = trim($contact['organization'])) {
|
||||
$fn = $org;
|
||||
}
|
||||
// ... email address
|
||||
else if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
|
||||
$fn = $email[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build contact display name for autocomplete listing
|
||||
*
|
||||
* @param array $contact Hash array with contact data as key-value pairs
|
||||
* @param string $email Optional email address
|
||||
* @param string $name Optional name (self::compose_list_name() result)
|
||||
* @param string $templ Optional template to use (defaults to the 'contact_search_name' config option)
|
||||
*
|
||||
* @return string Display name
|
||||
*/
|
||||
public static function compose_search_name($contact, $email = null, $name = null, $templ = null)
|
||||
{
|
||||
static $template;
|
||||
|
||||
if (empty($templ) && !isset($template)) { // cache this
|
||||
$template = rcube::get_instance()->config->get('contact_search_name');
|
||||
if (empty($template)) {
|
||||
$template = '{name} <{email}>';
|
||||
}
|
||||
}
|
||||
|
||||
$result = $templ ?: $template;
|
||||
|
||||
if (preg_match_all('/\{[a-z]+\}/', $result, $matches)) {
|
||||
foreach ($matches[0] as $key) {
|
||||
$key = trim($key, '{}');
|
||||
$value = '';
|
||||
|
||||
switch ($key) {
|
||||
case 'name':
|
||||
$value = $name ?: self::compose_list_name($contact);
|
||||
|
||||
// If name(s) are undefined compose_list_name() may return an email address
|
||||
// here we prevent from returning the same name and email
|
||||
if ($name === $email && strpos($result, '{email}') !== false) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'email':
|
||||
$value = $email;
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
$value = strpos($key, ':') ? $contact[$key] : self::get_col_values($key, $contact, true);
|
||||
if (is_array($value)) {
|
||||
$value = $value[0];
|
||||
}
|
||||
}
|
||||
|
||||
$result = str_replace('{' . $key . '}', $value, $result);
|
||||
}
|
||||
}
|
||||
|
||||
$result = preg_replace('/\s+/', ' ', $result);
|
||||
$result = preg_replace('/\s*(<>|\(\)|\[\])/', '', $result);
|
||||
$result = trim($result, '/ ');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique key for sorting contacts
|
||||
*
|
||||
* @param array $contact Contact record
|
||||
* @param string $sort_col Sorting column name
|
||||
*
|
||||
* @return string Unique key
|
||||
*/
|
||||
public static function compose_contact_key($contact, $sort_col)
|
||||
{
|
||||
$key = $contact[$sort_col] . ':' . $contact['sourceid'];
|
||||
|
||||
// add email to a key to not skip contacts with the same name (#1488375)
|
||||
if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
|
||||
$key .= ':' . implode(':', (array)$email);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::anytodatetime($value))
|
||||
&& ($search = rcube_utils::anytodatetime($search))
|
||||
&& $value->format('Ymd') == $search->format('Ymd'));
|
||||
}
|
||||
|
||||
// composite field, e.g. address
|
||||
foreach ((array)$value as $val) {
|
||||
$val = mb_strtolower($val);
|
||||
|
||||
if ($mode & self::SEARCH_STRICT) {
|
||||
$got = ($val == $search);
|
||||
}
|
||||
else if ($mode & self::SEARCH_PREFIX) {
|
||||
$got = ($search == substr($val, 0, strlen($search)));
|
||||
}
|
||||
else {
|
||||
$got = (strpos($val, $search) !== false);
|
||||
}
|
||||
|
||||
if ($got) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
123
data/web/rc/program/lib/Roundcube/rcube_base_replacer.php
Normal file
123
data/web/rc/program/lib/Roundcube/rcube_base_replacer.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Provide basic functions for base URL replacement |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper class to turn relative urls into absolute ones
|
||||
* using a predefined base
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
*/
|
||||
class rcube_base_replacer
|
||||
{
|
||||
private $base_url;
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $base Base URL
|
||||
*/
|
||||
public function __construct($base)
|
||||
{
|
||||
$this->base_url = $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace callback
|
||||
*
|
||||
* @param array $matches Matching entries
|
||||
*
|
||||
* @return string Replaced text with absolute URL
|
||||
*/
|
||||
public function callback($matches)
|
||||
{
|
||||
return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert base URLs to absolute ones
|
||||
*
|
||||
* @param string $body Text body
|
||||
*
|
||||
* @return string Replaced text
|
||||
*/
|
||||
public function replace($body)
|
||||
{
|
||||
$regexp = array(
|
||||
'/(src|background|href)=(["\']?)([^"\'\s>]+)(\2|\s|>)/i',
|
||||
'/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/i',
|
||||
);
|
||||
|
||||
return preg_replace_callback($regexp, array($this, 'callback'), $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert paths like ../xxx to an absolute path using a base url
|
||||
*
|
||||
* @param string $path Relative path
|
||||
* @param string $base_url Base URL
|
||||
*
|
||||
* @return string Absolute URL
|
||||
*/
|
||||
public static function absolute_url($path, $base_url)
|
||||
{
|
||||
// check if path is an absolute URL
|
||||
if (preg_match('/^[fhtps]+:\/\//', $path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// check if path is a content-id scheme
|
||||
if (strpos($path, 'cid:') === 0) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$host_url = $base_url;
|
||||
$abs_path = $path;
|
||||
|
||||
// cut base_url to the last directory
|
||||
if (strrpos($base_url, '/') > 7) {
|
||||
$host_url = substr($base_url, 0, strpos($base_url, '/', 7));
|
||||
$base_url = substr($base_url, 0, strrpos($base_url, '/'));
|
||||
}
|
||||
|
||||
// $path is absolute
|
||||
if ($path[0] == '/') {
|
||||
$abs_path = $host_url.$path;
|
||||
}
|
||||
else {
|
||||
// strip './' because its the same as ''
|
||||
$path = preg_replace('/^\.\//', '', $path);
|
||||
|
||||
if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
|
||||
$cnt = count($matches);
|
||||
while ($cnt--) {
|
||||
if ($pos = strrpos($base_url, '/')) {
|
||||
$base_url = substr($base_url, 0, $pos);
|
||||
}
|
||||
$path = substr($path, 3);
|
||||
}
|
||||
}
|
||||
|
||||
$abs_path = $base_url.'/'.$path;
|
||||
}
|
||||
|
||||
return $abs_path;
|
||||
}
|
||||
}
|
||||
69
data/web/rc/program/lib/Roundcube/rcube_browser.php
Normal file
69
data/web/rc/program/lib/Roundcube/rcube_browser.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2007-2015, 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: |
|
||||
| Class representing the client browser's properties |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provide details about the client's browser based on the User-Agent header
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_browser
|
||||
{
|
||||
function __construct()
|
||||
{
|
||||
$HTTP_USER_AGENT = strtolower($_SERVER['HTTP_USER_AGENT']);
|
||||
|
||||
$this->ver = 0;
|
||||
$this->win = strpos($HTTP_USER_AGENT, 'win') != false;
|
||||
$this->mac = strpos($HTTP_USER_AGENT, 'mac') != false;
|
||||
$this->linux = strpos($HTTP_USER_AGENT, 'linux') != false;
|
||||
$this->unix = strpos($HTTP_USER_AGENT, 'unix') != false;
|
||||
|
||||
$this->webkit = strpos($HTTP_USER_AGENT, 'applewebkit') !== false;
|
||||
$this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false || ($this->webkit && strpos($HTTP_USER_AGENT, 'opr/') !== false);
|
||||
$this->ns = strpos($HTTP_USER_AGENT, 'netscape') !== false;
|
||||
$this->chrome = !$this->opera && strpos($HTTP_USER_AGENT, 'chrome') !== false;
|
||||
$this->ie = !$this->opera && (strpos($HTTP_USER_AGENT, 'compatible; msie') !== false || strpos($HTTP_USER_AGENT, 'trident/') !== false);
|
||||
$this->safari = !$this->opera && !$this->chrome && ($this->webkit || strpos($HTTP_USER_AGENT, 'safari') !== false);
|
||||
$this->mz = !$this->ie && !$this->safari && !$this->chrome && !$this->ns && !$this->opera && strpos($HTTP_USER_AGENT, 'mozilla') !== false;
|
||||
|
||||
if ($this->opera) {
|
||||
if (preg_match('/(opera|opr)\/([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
|
||||
$this->ver = (float) $regs[2];
|
||||
}
|
||||
}
|
||||
else if (preg_match('/(chrome|msie|version|khtml)(\s*|\/)([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
|
||||
$this->ver = (float) $regs[3];
|
||||
}
|
||||
else if (preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
|
||||
$this->ver = (float) $regs[1];
|
||||
}
|
||||
|
||||
if (preg_match('/ ([a-z]{2})-([a-z]{2})/', $HTTP_USER_AGENT, $regs)) {
|
||||
$this->lang = $regs[1];
|
||||
}
|
||||
else {
|
||||
$this->lang = 'en';
|
||||
}
|
||||
|
||||
$this->dom = $this->mz || $this->safari || ($this->ie && $this->ver>=5) || ($this->opera && $this->ver>=7);
|
||||
$this->pngalpha = $this->mz || $this->safari || ($this->ie && $this->ver>=5.5) ||
|
||||
($this->ie && $this->ver>=5 && $this->mac) || ($this->opera && $this->ver>=7);
|
||||
$this->imgdata = !$this->ie;
|
||||
}
|
||||
}
|
||||
667
data/web/rc/program/lib/Roundcube/rcube_cache.php
Normal file
667
data/web/rc/program/lib/Roundcube/rcube_cache.php
Normal file
@@ -0,0 +1,667 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2011, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Caching engine |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface class for accessing Roundcube cache
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Cache
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_cache
|
||||
{
|
||||
/**
|
||||
* Instance of database handler
|
||||
*
|
||||
* @var rcube_db|Memcache|bool
|
||||
*/
|
||||
private $db;
|
||||
private $type;
|
||||
private $userid;
|
||||
private $prefix;
|
||||
private $table;
|
||||
private $ttl;
|
||||
private $packed;
|
||||
private $index;
|
||||
private $debug;
|
||||
private $index_changed = false;
|
||||
private $cache = array();
|
||||
private $cache_changes = array();
|
||||
private $cache_sums = array();
|
||||
private $max_packet = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param string $type Engine type ('db' or 'memcache' or 'apc')
|
||||
* @param int $userid User identifier
|
||||
* @param string $prefix Key name prefix
|
||||
* @param string $ttl Expiration time of memcache/apc items
|
||||
* @param bool $packed Enables/disabled data serialization.
|
||||
* It's possible to disable data serialization if you're sure
|
||||
* stored data will be always a safe string
|
||||
*/
|
||||
function __construct($type, $userid, $prefix='', $ttl=0, $packed=true)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$type = strtolower($type);
|
||||
|
||||
if ($type == 'memcache') {
|
||||
$this->type = 'memcache';
|
||||
$this->db = $rcube->get_memcache();
|
||||
$this->debug = $rcube->config->get('memcache_debug');
|
||||
}
|
||||
else if ($type == 'apc') {
|
||||
$this->type = 'apc';
|
||||
$this->db = function_exists('apc_exists'); // APC 3.1.4 required
|
||||
$this->debug = $rcube->config->get('apc_debug');
|
||||
}
|
||||
else {
|
||||
$this->type = 'db';
|
||||
$this->db = $rcube->get_dbh();
|
||||
$this->table = $this->db->table_name('cache', true);
|
||||
}
|
||||
|
||||
// convert ttl string to seconds
|
||||
$ttl = get_offset_sec($ttl);
|
||||
if ($ttl > 2592000) $ttl = 2592000;
|
||||
|
||||
$this->userid = (int) $userid;
|
||||
$this->ttl = $ttl;
|
||||
$this->packed = $packed;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cached value.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
function get($key)
|
||||
{
|
||||
if (!array_key_exists($key, $this->cache)) {
|
||||
return $this->read_record($key);
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (add/update) value in cache.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Cache data
|
||||
*/
|
||||
function set($key, $data)
|
||||
{
|
||||
$this->cache[$key] = $data;
|
||||
$this->cache_changes[$key] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cached value without storing it in internal memory.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
function read($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->cache)) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
return $this->read_record($key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (add/update) value in cache and immediately saves
|
||||
* it in the backend, no internal memory will be used.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Cache data
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
function write($key, $data)
|
||||
{
|
||||
return $this->write_record($key, $this->serialize($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*
|
||||
* @param string $key Cache key name or pattern
|
||||
* @param boolean $prefix_mode Enable it to clear all keys starting
|
||||
* with prefix specified in $key
|
||||
*/
|
||||
function remove($key=null, $prefix_mode=false)
|
||||
{
|
||||
// Remove all keys
|
||||
if ($key === null) {
|
||||
$this->cache = array();
|
||||
$this->cache_changes = array();
|
||||
$this->cache_sums = array();
|
||||
}
|
||||
// Remove keys by name prefix
|
||||
else if ($prefix_mode) {
|
||||
foreach (array_keys($this->cache) as $k) {
|
||||
if (strpos($k, $key) === 0) {
|
||||
$this->cache[$k] = null;
|
||||
$this->cache_changes[$k] = false;
|
||||
unset($this->cache_sums[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove one key by name
|
||||
else {
|
||||
$this->cache[$key] = null;
|
||||
$this->cache_changes[$key] = false;
|
||||
unset($this->cache_sums[$key]);
|
||||
}
|
||||
|
||||
// Remove record(s) from the backend
|
||||
$this->remove_record($key, $prefix_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cache records older than ttl
|
||||
*/
|
||||
function expunge()
|
||||
{
|
||||
if ($this->type == 'db' && $this->db && $this->ttl) {
|
||||
$this->db->query(
|
||||
"DELETE FROM {$this->table}".
|
||||
" WHERE `user_id` = ?".
|
||||
" AND `cache_key` LIKE ?".
|
||||
" AND `expires` < " . $this->db->now(),
|
||||
$this->userid,
|
||||
$this->prefix.'.%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired records of all caches
|
||||
*/
|
||||
static function gc()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$db = $rcube->get_dbh();
|
||||
|
||||
$db->query("DELETE FROM " . $db->table_name('cache', true) . " WHERE `expires` < " . $db->now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the cache back to the DB.
|
||||
*/
|
||||
function close()
|
||||
{
|
||||
foreach ($this->cache as $key => $data) {
|
||||
// The key has been used
|
||||
if ($this->cache_changes[$key]) {
|
||||
// Make sure we're not going to write unchanged data
|
||||
// by comparing current md5 sum with the sum calculated on DB read
|
||||
$data = $this->serialize($data);
|
||||
|
||||
if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
|
||||
$this->write_record($key, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->index_changed) {
|
||||
$this->write_index();
|
||||
}
|
||||
|
||||
// reset internal cache index, thanks to this we can force index reload
|
||||
$this->index = null;
|
||||
$this->index_changed = false;
|
||||
$this->cache = array();
|
||||
$this->cache_sums = array();
|
||||
$this->cache_changes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads cache entry.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param boolean $nostore Enable to skip in-memory store
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
private function read_record($key, $nostore=false)
|
||||
{
|
||||
if (!$this->db) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->type != 'db') {
|
||||
$this->load_index();
|
||||
|
||||
// Consistency check (#1490390)
|
||||
if (!in_array($key, $this->index)) {
|
||||
// we always check if the key exist in the index
|
||||
// to have data in consistent state. Keeping the index consistent
|
||||
// is needed for keys delete operation when we delete all keys or by prefix.
|
||||
}
|
||||
else {
|
||||
$ckey = $this->ckey($key);
|
||||
|
||||
if ($this->type == 'memcache') {
|
||||
$data = $this->db->get($ckey);
|
||||
}
|
||||
else if ($this->type == 'apc') {
|
||||
$data = apc_fetch($ckey);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('get', $ckey, $data);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data !== false) {
|
||||
$md5sum = md5($data);
|
||||
$data = $this->unserialize($data);
|
||||
|
||||
if ($nostore) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->cache_sums[$key] = $md5sum;
|
||||
$this->cache[$key] = $data;
|
||||
}
|
||||
else {
|
||||
$this->cache[$key] = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT `data`, `cache_key` FROM {$this->table}"
|
||||
. " WHERE `user_id` = ? AND `cache_key` = ?",
|
||||
$this->userid, $this->prefix.'.'.$key);
|
||||
|
||||
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
if (strlen($sql_arr['data']) > 0) {
|
||||
$md5sum = md5($sql_arr['data']);
|
||||
$data = $this->unserialize($sql_arr['data']);
|
||||
}
|
||||
|
||||
$this->db->reset();
|
||||
|
||||
if ($nostore) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->cache[$key] = $data;
|
||||
$this->cache_sums[$key] = $md5sum;
|
||||
}
|
||||
else {
|
||||
$this->cache[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes single cache record into DB.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Serialized cache data
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
private function write_record($key, $data)
|
||||
{
|
||||
if (!$this->db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't attempt to write too big data sets
|
||||
if (strlen($data) > $this->max_packet_size()) {
|
||||
trigger_error("rcube_cache: max_packet_size ($this->max_packet) exceeded for key $key. Tried to write " . strlen($data) . " bytes", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->type == 'memcache' || $this->type == 'apc') {
|
||||
$result = $this->add_record($this->ckey($key), $data);
|
||||
|
||||
// make sure index will be updated
|
||||
if ($result) {
|
||||
if (!array_key_exists($key, $this->cache_sums)) {
|
||||
$this->cache_sums[$key] = true;
|
||||
}
|
||||
|
||||
$this->load_index();
|
||||
|
||||
if (!$this->index_changed && !in_array($key, $this->index)) {
|
||||
$this->index_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$db_key = $this->prefix . '.' . $key;
|
||||
|
||||
// Remove NULL rows (here we don't need to check if the record exist)
|
||||
if ($data == 'N;') {
|
||||
$result = $this->db->query(
|
||||
"DELETE FROM {$this->table}".
|
||||
" WHERE `user_id` = ? AND `cache_key` = ?",
|
||||
$this->userid, $db_key);
|
||||
|
||||
return !$this->db->is_error($result);
|
||||
}
|
||||
|
||||
$key_exists = array_key_exists($key, $this->cache_sums);
|
||||
$expires = $this->ttl ? $this->db->now($this->ttl) : 'NULL';
|
||||
|
||||
if (!$key_exists) {
|
||||
// Try INSERT temporarily ignoring "duplicate key" errors
|
||||
$this->db->set_option('ignore_key_errors', true);
|
||||
|
||||
$result = $this->db->query(
|
||||
"INSERT INTO {$this->table} (`expires`, `user_id`, `cache_key`, `data`)"
|
||||
. " VALUES ($expires, ?, ?, ?)",
|
||||
$this->userid, $db_key, $data);
|
||||
|
||||
$this->db->set_option('ignore_key_errors', false);
|
||||
}
|
||||
|
||||
// otherwise try UPDATE
|
||||
if (!isset($result) || !($count = $this->db->affected_rows($result))) {
|
||||
$result = $this->db->query(
|
||||
"UPDATE {$this->table} SET `expires` = $expires, `data` = ?"
|
||||
. " WHERE `user_id` = ? AND `cache_key` = ?",
|
||||
$data, $this->userid, $db_key);
|
||||
|
||||
$count = $this->db->affected_rows($result);
|
||||
}
|
||||
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cache record(s).
|
||||
*
|
||||
* @param string $key Cache key name or pattern
|
||||
* @param boolean $prefix_mode Enable it to clear all keys starting
|
||||
* with prefix specified in $key
|
||||
*/
|
||||
private function remove_record($key=null, $prefix_mode=false)
|
||||
{
|
||||
if (!$this->db) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->type != 'db') {
|
||||
$this->load_index();
|
||||
|
||||
// Remove all keys
|
||||
if ($key === null) {
|
||||
foreach ($this->index as $key) {
|
||||
$this->delete_record($this->ckey($key));
|
||||
}
|
||||
|
||||
$this->index = array();
|
||||
}
|
||||
// Remove keys by name prefix
|
||||
else if ($prefix_mode) {
|
||||
foreach ($this->index as $idx => $k) {
|
||||
if (strpos($k, $key) === 0) {
|
||||
$this->delete_record($this->ckey($k));
|
||||
unset($this->index[$idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove one key by name
|
||||
else {
|
||||
$this->delete_record($this->ckey($key));
|
||||
if (($idx = array_search($key, $this->index)) !== false) {
|
||||
unset($this->index[$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->index_changed = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all keys (in specified cache)
|
||||
if ($key === null) {
|
||||
$where = " AND `cache_key` LIKE " . $this->db->quote($this->prefix.'.%');
|
||||
}
|
||||
// Remove keys by name prefix
|
||||
else if ($prefix_mode) {
|
||||
$where = " AND `cache_key` LIKE " . $this->db->quote($this->prefix.'.'.$key.'%');
|
||||
}
|
||||
// Remove one key by name
|
||||
else {
|
||||
$where = " AND `cache_key` = " . $this->db->quote($this->prefix.'.'.$key);
|
||||
}
|
||||
|
||||
$this->db->query(
|
||||
"DELETE FROM {$this->table} WHERE `user_id` = ?" . $where,
|
||||
$this->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entry into memcache/apc DB.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Serialized cache data
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
private function add_record($key, $data)
|
||||
{
|
||||
if ($this->type == 'memcache') {
|
||||
$result = $this->db->replace($key, $data, MEMCACHE_COMPRESSED, $this->ttl);
|
||||
|
||||
if (!$result) {
|
||||
$result = $this->db->set($key, $data, MEMCACHE_COMPRESSED, $this->ttl);
|
||||
}
|
||||
}
|
||||
else if ($this->type == 'apc') {
|
||||
if (apc_exists($key)) {
|
||||
apc_delete($key);
|
||||
}
|
||||
|
||||
$result = apc_store($key, $data, $this->ttl);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('set', $key, $data, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entry from memcache/apc DB.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
private function delete_record($key)
|
||||
{
|
||||
if ($this->type == 'memcache') {
|
||||
// #1488592: use 2nd argument
|
||||
$result = $this->db->delete($key, 0);
|
||||
}
|
||||
else {
|
||||
$result = apc_delete($key);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('delete', $key, null, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the index entry into memcache/apc DB.
|
||||
*/
|
||||
private function write_index()
|
||||
{
|
||||
if (!$this->db || $this->type == 'db') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->load_index();
|
||||
|
||||
// Make sure index contains new keys
|
||||
foreach ($this->cache as $key => $value) {
|
||||
if ($value !== null && !in_array($key, $this->index)) {
|
||||
$this->index[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
// new keys added using self::write()
|
||||
foreach ($this->cache_sums as $key => $value) {
|
||||
if ($value === true && !in_array($key, $this->index)) {
|
||||
$this->index[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$data = serialize($this->index);
|
||||
$this->add_record($this->ikey(), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index entry from memcache/apc DB.
|
||||
*/
|
||||
private function load_index()
|
||||
{
|
||||
if (!$this->db || $this->type == 'db') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->index !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$index_key = $this->ikey();
|
||||
|
||||
if ($this->type == 'memcache') {
|
||||
$data = $this->db->get($index_key);
|
||||
}
|
||||
else if ($this->type == 'apc') {
|
||||
$data = apc_fetch($index_key);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('get', $index_key, $data);
|
||||
}
|
||||
|
||||
$this->index = $data ? unserialize($data) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates per-user cache key name (for memcache and apc)
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @return string Cache key
|
||||
*/
|
||||
private function ckey($key)
|
||||
{
|
||||
return sprintf('%d:%s:%s', $this->userid, $this->prefix, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates per-user index cache key name (for memcache and apc)
|
||||
*
|
||||
* @return string Cache key
|
||||
*/
|
||||
private function ikey()
|
||||
{
|
||||
// This way each cache will have its own index
|
||||
return sprintf('%d:%s%s', $this->userid, $this->prefix, 'INDEX');
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes data for storing
|
||||
*/
|
||||
private function serialize($data)
|
||||
{
|
||||
if ($this->type == 'db') {
|
||||
return $this->db->encode($data, $this->packed);
|
||||
}
|
||||
|
||||
return $this->packed ? serialize($data) : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes serialized data
|
||||
*/
|
||||
private function unserialize($data)
|
||||
{
|
||||
if ($this->type == 'db') {
|
||||
return $this->db->decode($data, $this->packed);
|
||||
}
|
||||
|
||||
return $this->packed ? @unserialize($data) : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the maximum size for cache data to be written
|
||||
*/
|
||||
private function max_packet_size()
|
||||
{
|
||||
if ($this->max_packet < 0) {
|
||||
$this->max_packet = 2097152; // default/max is 2 MB
|
||||
|
||||
if ($this->type == 'db') {
|
||||
if ($value = $this->db->get_variable('max_allowed_packet', $this->max_packet)) {
|
||||
$this->max_packet = $value;
|
||||
}
|
||||
$this->max_packet -= 2000;
|
||||
}
|
||||
else {
|
||||
$max_packet = rcube::get_instance()->config->get($this->type . '_max_allowed_packet');
|
||||
$this->max_packet = parse_bytes($max_packet) ?: $this->max_packet;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->max_packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write memcache/apc debug info to the log
|
||||
*/
|
||||
private function debug($type, $key, $data = null, $result = null)
|
||||
{
|
||||
$line = strtoupper($type) . ' ' . $key;
|
||||
|
||||
if ($data !== null) {
|
||||
$line .= ' ' . ($this->packed ? $data : serialize($data));
|
||||
}
|
||||
|
||||
rcube::debug($this->type, $line, $result);
|
||||
}
|
||||
}
|
||||
657
data/web/rc/program/lib/Roundcube/rcube_cache_shared.php
Normal file
657
data/web/rc/program/lib/Roundcube/rcube_cache_shared.php
Normal file
@@ -0,0 +1,657 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2011-2013, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011-2013, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Shared (cross-user) caching engine |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface class for accessing Roundcube shared cache
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Cache
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_cache_shared
|
||||
{
|
||||
/**
|
||||
* Instance of database handler
|
||||
*
|
||||
* @var rcube_db|Memcache|bool
|
||||
*/
|
||||
private $db;
|
||||
private $type;
|
||||
private $prefix;
|
||||
private $ttl;
|
||||
private $packed;
|
||||
private $index;
|
||||
private $table;
|
||||
private $debug;
|
||||
private $index_changed = false;
|
||||
private $cache = array();
|
||||
private $cache_changes = array();
|
||||
private $cache_sums = array();
|
||||
private $max_packet = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param string $type Engine type ('db' or 'memcache' or 'apc')
|
||||
* @param string $prefix Key name prefix
|
||||
* @param string $ttl Expiration time of memcache/apc items
|
||||
* @param bool $packed Enables/disabled data serialization.
|
||||
* It's possible to disable data serialization if you're sure
|
||||
* stored data will be always a safe string
|
||||
*/
|
||||
function __construct($type, $prefix='', $ttl=0, $packed=true)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$type = strtolower($type);
|
||||
|
||||
if ($type == 'memcache') {
|
||||
$this->type = 'memcache';
|
||||
$this->db = $rcube->get_memcache();
|
||||
$this->debug = $rcube->config->get('memcache_debug');
|
||||
}
|
||||
else if ($type == 'apc') {
|
||||
$this->type = 'apc';
|
||||
$this->db = function_exists('apc_exists'); // APC 3.1.4 required
|
||||
$this->debug = $rcube->config->get('apc_debug');
|
||||
}
|
||||
else {
|
||||
$this->type = 'db';
|
||||
$this->db = $rcube->get_dbh();
|
||||
$this->table = $this->db->table_name('cache_shared', true);
|
||||
}
|
||||
|
||||
// convert ttl string to seconds
|
||||
$ttl = get_offset_sec($ttl);
|
||||
if ($ttl > 2592000) $ttl = 2592000;
|
||||
|
||||
$this->ttl = $ttl;
|
||||
$this->packed = $packed;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cached value.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
function get($key)
|
||||
{
|
||||
if (!array_key_exists($key, $this->cache)) {
|
||||
return $this->read_record($key);
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (add/update) value in cache.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Cache data
|
||||
*/
|
||||
function set($key, $data)
|
||||
{
|
||||
$this->cache[$key] = $data;
|
||||
$this->cache_changes[$key] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cached value without storing it in internal memory.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
function read($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->cache)) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
return $this->read_record($key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (add/update) value in cache and immediately saves
|
||||
* it in the backend, no internal memory will be used.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Cache data
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
function write($key, $data)
|
||||
{
|
||||
return $this->write_record($key, $this->serialize($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*
|
||||
* @param string $key Cache key name or pattern
|
||||
* @param boolean $prefix_mode Enable it to clear all keys starting
|
||||
* with prefix specified in $key
|
||||
*/
|
||||
function remove($key=null, $prefix_mode=false)
|
||||
{
|
||||
// Remove all keys
|
||||
if ($key === null) {
|
||||
$this->cache = array();
|
||||
$this->cache_changes = array();
|
||||
$this->cache_sums = array();
|
||||
}
|
||||
// Remove keys by name prefix
|
||||
else if ($prefix_mode) {
|
||||
foreach (array_keys($this->cache) as $k) {
|
||||
if (strpos($k, $key) === 0) {
|
||||
$this->cache[$k] = null;
|
||||
$this->cache_changes[$k] = false;
|
||||
unset($this->cache_sums[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove one key by name
|
||||
else {
|
||||
$this->cache[$key] = null;
|
||||
$this->cache_changes[$key] = false;
|
||||
unset($this->cache_sums[$key]);
|
||||
}
|
||||
|
||||
// Remove record(s) from the backend
|
||||
$this->remove_record($key, $prefix_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cache records older than ttl
|
||||
*/
|
||||
function expunge()
|
||||
{
|
||||
if ($this->type == 'db' && $this->db && $this->ttl) {
|
||||
$this->db->query(
|
||||
"DELETE FROM {$this->table}"
|
||||
. " WHERE `cache_key` LIKE ?"
|
||||
. " AND `expires` < " . $this->db->now(),
|
||||
$this->prefix . '.%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired records of all caches
|
||||
*/
|
||||
static function gc()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$db = $rcube->get_dbh();
|
||||
|
||||
$db->query("DELETE FROM " . $db->table_name('cache_shared', true) . " WHERE `expires` < " . $db->now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the cache back to the DB.
|
||||
*/
|
||||
function close()
|
||||
{
|
||||
foreach ($this->cache as $key => $data) {
|
||||
// The key has been used
|
||||
if ($this->cache_changes[$key]) {
|
||||
// Make sure we're not going to write unchanged data
|
||||
// by comparing current md5 sum with the sum calculated on DB read
|
||||
$data = $this->serialize($data);
|
||||
|
||||
if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
|
||||
$this->write_record($key, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->index_changed) {
|
||||
$this->write_index();
|
||||
}
|
||||
|
||||
// reset internal cache index, thanks to this we can force index reload
|
||||
$this->index = null;
|
||||
$this->index_changed = false;
|
||||
$this->cache = array();
|
||||
$this->cache_sums = array();
|
||||
$this->cache_changes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads cache entry.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param boolean $nostore Enable to skip in-memory store
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
private function read_record($key, $nostore=false)
|
||||
{
|
||||
if (!$this->db) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->type != 'db') {
|
||||
$this->load_index();
|
||||
|
||||
// Consistency check (#1490390)
|
||||
if (!in_array($key, $this->index)) {
|
||||
// we always check if the key exist in the index
|
||||
// to have data in consistent state. Keeping the index consistent
|
||||
// is needed for keys delete operation when we delete all keys or by prefix.
|
||||
}
|
||||
else {
|
||||
$ckey = $this->ckey($key);
|
||||
|
||||
if ($this->type == 'memcache') {
|
||||
$data = $this->db->get($ckey);
|
||||
}
|
||||
else if ($this->type == 'apc') {
|
||||
$data = apc_fetch($ckey);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('get', $ckey, $data);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data !== false) {
|
||||
$md5sum = md5($data);
|
||||
$data = $this->unserialize($data);
|
||||
|
||||
if ($nostore) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->cache_sums[$key] = $md5sum;
|
||||
$this->cache[$key] = $data;
|
||||
}
|
||||
else {
|
||||
$this->cache[$key] = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT `data`, `cache_key` FROM {$this->table}"
|
||||
. " WHERE `cache_key` = ?",
|
||||
$this->prefix . '.' . $key);
|
||||
|
||||
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
if (strlen($sql_arr['data']) > 0) {
|
||||
$md5sum = md5($sql_arr['data']);
|
||||
$data = $this->unserialize($sql_arr['data']);
|
||||
}
|
||||
|
||||
$this->db->reset();
|
||||
|
||||
if ($nostore) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->cache[$key] = $data;
|
||||
$this->cache_sums[$key] = $md5sum;
|
||||
}
|
||||
else {
|
||||
$this->cache[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes single cache record into DB.
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
* @param mixed $data Serialized cache data
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
private function write_record($key, $data)
|
||||
{
|
||||
if (!$this->db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't attempt to write too big data sets
|
||||
if (strlen($data) > $this->max_packet_size()) {
|
||||
trigger_error("rcube_cache: max_packet_size ($this->max_packet) exceeded for key $key. Tried to write " . strlen($data) . " bytes", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->type == 'memcache' || $this->type == 'apc') {
|
||||
$result = $this->add_record($this->ckey($key), $data);
|
||||
|
||||
// make sure index will be updated
|
||||
if ($result) {
|
||||
if (!array_key_exists($key, $this->cache_sums)) {
|
||||
$this->cache_sums[$key] = true;
|
||||
}
|
||||
|
||||
$this->load_index();
|
||||
|
||||
if (!$this->index_changed && !in_array($key, $this->index)) {
|
||||
$this->index_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$db_key = $this->prefix . '.' . $key;
|
||||
|
||||
// Remove NULL rows (here we don't need to check if the record exist)
|
||||
if ($data == 'N;') {
|
||||
$result = $this->db->query("DELETE FROM {$this->table} WHERE `cache_key` = ?", $db_key);
|
||||
|
||||
return !$this->db->is_error($result);
|
||||
}
|
||||
|
||||
$key_exists = array_key_exists($key, $this->cache_sums);
|
||||
$expires = $this->ttl ? $this->db->now($this->ttl) : 'NULL';
|
||||
|
||||
if (!$key_exists) {
|
||||
// Try INSERT temporarily ignoring "duplicate key" errors
|
||||
$this->db->set_option('ignore_key_errors', true);
|
||||
|
||||
$result = $this->db->query(
|
||||
"INSERT INTO {$this->table} (`expires`, `cache_key`, `data`)"
|
||||
. " VALUES ($expires, ?, ?)",
|
||||
$db_key, $data);
|
||||
|
||||
$this->db->set_option('ignore_key_errors', false);
|
||||
}
|
||||
|
||||
// otherwise try UPDATE
|
||||
if (!isset($result) || !($count = $this->db->affected_rows($result))) {
|
||||
$result = $this->db->query(
|
||||
"UPDATE {$this->table} SET `expires` = $expires, `data` = ?"
|
||||
. " WHERE `cache_key` = ?",
|
||||
$data, $db_key);
|
||||
|
||||
$count = $this->db->affected_rows($result);
|
||||
}
|
||||
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cache record(s).
|
||||
*
|
||||
* @param string $key Cache key name or pattern
|
||||
* @param boolean $prefix_mode Enable it to clear all keys starting
|
||||
* with prefix specified in $key
|
||||
*/
|
||||
private function remove_record($key=null, $prefix_mode=false)
|
||||
{
|
||||
if (!$this->db) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->type != 'db') {
|
||||
$this->load_index();
|
||||
|
||||
// Remove all keys
|
||||
if ($key === null) {
|
||||
foreach ($this->index as $key) {
|
||||
$this->delete_record($this->ckey($key));
|
||||
}
|
||||
|
||||
$this->index = array();
|
||||
}
|
||||
// Remove keys by name prefix
|
||||
else if ($prefix_mode) {
|
||||
foreach ($this->index as $idx => $k) {
|
||||
if (strpos($k, $key) === 0) {
|
||||
$this->delete_record($this->ckey($k));
|
||||
unset($this->index[$idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove one key by name
|
||||
else {
|
||||
$this->delete_record($this->ckey($key));
|
||||
if (($idx = array_search($key, $this->index)) !== false) {
|
||||
unset($this->index[$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->index_changed = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all keys (in specified cache)
|
||||
if ($key === null) {
|
||||
$where = " WHERE `cache_key` LIKE " . $this->db->quote($this->prefix.'.%');
|
||||
}
|
||||
// Remove keys by name prefix
|
||||
else if ($prefix_mode) {
|
||||
$where = " WHERE `cache_key` LIKE " . $this->db->quote($this->prefix.'.'.$key.'%');
|
||||
}
|
||||
// Remove one key by name
|
||||
else {
|
||||
$where = " WHERE `cache_key` = " . $this->db->quote($this->prefix.'.'.$key);
|
||||
}
|
||||
|
||||
$this->db->query("DELETE FROM " . $this->table . $where);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entry into memcache/apc DB.
|
||||
*
|
||||
* @param string $key Cache internal key name
|
||||
* @param mixed $data Serialized cache data
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
private function add_record($key, $data)
|
||||
{
|
||||
if ($this->type == 'memcache') {
|
||||
$result = $this->db->replace($key, $data, MEMCACHE_COMPRESSED, $this->ttl);
|
||||
|
||||
if (!$result) {
|
||||
$result = $this->db->set($key, $data, MEMCACHE_COMPRESSED, $this->ttl);
|
||||
}
|
||||
}
|
||||
else if ($this->type == 'apc') {
|
||||
if (apc_exists($key)) {
|
||||
apc_delete($key);
|
||||
}
|
||||
|
||||
$result = apc_store($key, $data, $this->ttl);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('set', $key, $data, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entry from memcache/apc DB.
|
||||
*
|
||||
* @param string $key Cache internal key name
|
||||
*
|
||||
* @param boolean True on success, False on failure
|
||||
*/
|
||||
private function delete_record($key)
|
||||
{
|
||||
if ($this->type == 'memcache') {
|
||||
// #1488592: use 2nd argument
|
||||
$result = $this->db->delete($key, 0);
|
||||
}
|
||||
else {
|
||||
$result = apc_delete($key);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('delete', $key, null, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the index entry into memcache/apc DB.
|
||||
*/
|
||||
private function write_index()
|
||||
{
|
||||
if (!$this->db || $this->type == 'db') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->load_index();
|
||||
|
||||
// Make sure index contains new keys
|
||||
foreach ($this->cache as $key => $value) {
|
||||
if ($value !== null && !in_array($key, $this->index)) {
|
||||
$this->index[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
// new keys added using self::write()
|
||||
foreach ($this->cache_sums as $key => $value) {
|
||||
if ($value === true && !in_array($key, $this->index)) {
|
||||
$this->index[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$data = serialize($this->index);
|
||||
$this->add_record($this->ikey(), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index entry from memcache/apc DB.
|
||||
*/
|
||||
private function load_index()
|
||||
{
|
||||
if (!$this->db || $this->type == 'db') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->index !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$index_key = $this->ikey();
|
||||
|
||||
if ($this->type == 'memcache') {
|
||||
$data = $this->db->get($index_key);
|
||||
}
|
||||
else if ($this->type == 'apc') {
|
||||
$data = apc_fetch($index_key);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('get', $index_key, $data);
|
||||
}
|
||||
|
||||
$this->index = $data ? unserialize($data) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates cache key name (for memcache and apc)
|
||||
*
|
||||
* @param string $key Cache key name
|
||||
*
|
||||
* @return string Cache key
|
||||
*/
|
||||
private function ckey($key)
|
||||
{
|
||||
return $this->prefix . ':' . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates index cache key name (for memcache and apc)
|
||||
*
|
||||
* @return string Cache key
|
||||
*/
|
||||
private function ikey()
|
||||
{
|
||||
// This way each cache will have its own index
|
||||
return $this->prefix . 'INDEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes data for storing
|
||||
*/
|
||||
private function serialize($data)
|
||||
{
|
||||
if ($this->type == 'db') {
|
||||
return $this->db->encode($data, $this->packed);
|
||||
}
|
||||
|
||||
return $this->packed ? serialize($data) : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes serialized data
|
||||
*/
|
||||
private function unserialize($data)
|
||||
{
|
||||
if ($this->type == 'db') {
|
||||
return $this->db->decode($data, $this->packed);
|
||||
}
|
||||
|
||||
return $this->packed ? @unserialize($data) : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the maximum size for cache data to be written
|
||||
*/
|
||||
private function max_packet_size()
|
||||
{
|
||||
if ($this->max_packet < 0) {
|
||||
$this->max_packet = 2097152; // default/max is 2 MB
|
||||
|
||||
if ($this->type == 'db') {
|
||||
if ($value = $this->db->get_variable('max_allowed_packet', $this->max_packet)) {
|
||||
$this->max_packet = $value;
|
||||
}
|
||||
$this->max_packet -= 2000;
|
||||
}
|
||||
else {
|
||||
$max_packet = rcube::get_instance()->config->get($this->type . '_max_allowed_packet');
|
||||
$this->max_packet = parse_bytes($max_packet) ?: $this->max_packet;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->max_packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write memcache/apc debug info to the log
|
||||
*/
|
||||
private function debug($type, $key, $data = null, $result = null)
|
||||
{
|
||||
$line = strtoupper($type) . ' ' . $key;
|
||||
|
||||
if ($data !== null) {
|
||||
$line .= ' ' . ($this->packed ? $data : serialize($data));
|
||||
}
|
||||
|
||||
rcube::debug($this->type, $line, $result);
|
||||
}
|
||||
}
|
||||
802
data/web/rc/program/lib/Roundcube/rcube_charset.php
Normal file
802
data/web/rc/program/lib/Roundcube/rcube_charset.php
Normal file
@@ -0,0 +1,802 @@
|
||||
<?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 |
|
||||
| Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org> |
|
||||
| |
|
||||
| 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: |
|
||||
| Provide charset conversion functionality |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Character sets conversion functionality
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
* @author Edmund Grimley Evans <edmundo@rano.org>
|
||||
*/
|
||||
class rcube_charset
|
||||
{
|
||||
// Aliases: some of them from HTML5 spec.
|
||||
static public $aliases = array(
|
||||
'USASCII' => 'WINDOWS-1252',
|
||||
'ANSIX31101983' => 'WINDOWS-1252',
|
||||
'ANSIX341968' => 'WINDOWS-1252',
|
||||
'UNKNOWN8BIT' => 'ISO-8859-15',
|
||||
'UNKNOWN' => 'ISO-8859-15',
|
||||
'USERDEFINED' => 'ISO-8859-15',
|
||||
'KSC56011987' => 'EUC-KR',
|
||||
'GB2312' => 'GBK',
|
||||
'GB231280' => 'GBK',
|
||||
'UNICODE' => 'UTF-8',
|
||||
'UTF7IMAP' => 'UTF7-IMAP',
|
||||
'TIS620' => 'WINDOWS-874',
|
||||
'ISO88599' => 'WINDOWS-1254',
|
||||
'ISO885911' => 'WINDOWS-874',
|
||||
'MACROMAN' => 'MACINTOSH',
|
||||
'77' => 'MAC',
|
||||
'128' => 'SHIFT-JIS',
|
||||
'129' => 'CP949',
|
||||
'130' => 'CP1361',
|
||||
'134' => 'GBK',
|
||||
'136' => 'BIG5',
|
||||
'161' => 'WINDOWS-1253',
|
||||
'162' => 'WINDOWS-1254',
|
||||
'163' => 'WINDOWS-1258',
|
||||
'177' => 'WINDOWS-1255',
|
||||
'178' => 'WINDOWS-1256',
|
||||
'186' => 'WINDOWS-1257',
|
||||
'204' => 'WINDOWS-1251',
|
||||
'222' => 'WINDOWS-874',
|
||||
'238' => 'WINDOWS-1250',
|
||||
'MS950' => 'CP950',
|
||||
'WINDOWS949' => 'UHC',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Catch an error and throw an exception.
|
||||
*
|
||||
* @param int $errno Level of the error
|
||||
* @param string $errstr Error message
|
||||
*/
|
||||
public static function error_handler($errno, $errstr)
|
||||
{
|
||||
throw new ErrorException($errstr, 0, $errno);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate charset name string (see #1485758).
|
||||
* Sometimes charset string is malformed, there are also charset aliases
|
||||
* but we need strict names for charset conversion (specially utf8 class)
|
||||
*
|
||||
* @param string $input Input charset name
|
||||
*
|
||||
* @return string The validated charset name
|
||||
*/
|
||||
public static function parse_charset($input)
|
||||
{
|
||||
static $charsets = array();
|
||||
$charset = strtoupper($input);
|
||||
|
||||
if (isset($charsets[$input])) {
|
||||
return $charsets[$input];
|
||||
}
|
||||
|
||||
$charset = preg_replace(array(
|
||||
'/^[^0-9A-Z]+/', // e.g. _ISO-8859-JP$SIO
|
||||
'/\$.*$/', // e.g. _ISO-8859-JP$SIO
|
||||
'/UNICODE-1-1-*/', // RFC1641/1642
|
||||
'/^X-/', // X- prefix (e.g. X-ROMAN8 => ROMAN8)
|
||||
), '', $charset);
|
||||
|
||||
if ($charset == 'BINARY') {
|
||||
return $charsets[$input] = null;
|
||||
}
|
||||
|
||||
// allow A-Z and 0-9 only
|
||||
$str = preg_replace('/[^A-Z0-9]/', '', $charset);
|
||||
|
||||
if (isset(self::$aliases[$str])) {
|
||||
$result = self::$aliases[$str];
|
||||
}
|
||||
// UTF
|
||||
else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m)) {
|
||||
$result = 'UTF-' . $m[1] . $m[2];
|
||||
}
|
||||
// ISO-8859
|
||||
else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
|
||||
$iso = 'ISO-8859-' . ($m[1] ?: 1);
|
||||
// some clients sends windows-1252 text as latin1,
|
||||
// it is safe to use windows-1252 for all latin1
|
||||
$result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
|
||||
}
|
||||
// handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
|
||||
else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
|
||||
$result = 'WINDOWS-' . $m[2];
|
||||
}
|
||||
// LATIN
|
||||
else if (preg_match('/LATIN(.*)/', $str, $m)) {
|
||||
$aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
|
||||
'7' => 13, '8' => 14, '9' => 15, '10' => 16,
|
||||
'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8
|
||||
);
|
||||
|
||||
// some clients sends windows-1252 text as latin1,
|
||||
// it is safe to use windows-1252 for all latin1
|
||||
if ($m[1] == 1) {
|
||||
$result = 'WINDOWS-1252';
|
||||
}
|
||||
// if iconv is not supported we need ISO labels, it's also safe for iconv
|
||||
else if (!empty($aliases[$m[1]])) {
|
||||
$result = 'ISO-8859-'.$aliases[$m[1]];
|
||||
}
|
||||
// iconv requires convertion of e.g. LATIN-1 to LATIN1
|
||||
else {
|
||||
$result = $str;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result = $charset;
|
||||
}
|
||||
|
||||
$charsets[$input] = $result;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string from one charset to another.
|
||||
* Uses mbstring and iconv functions if possible
|
||||
*
|
||||
* @param string $str Input string
|
||||
* @param string $from Suspected charset of the input string
|
||||
* @param string $to Target charset to convert to; defaults to RCUBE_CHARSET
|
||||
*
|
||||
* @return string Converted string
|
||||
*/
|
||||
public static function convert($str, $from, $to = null)
|
||||
{
|
||||
static $iconv_options = null;
|
||||
static $mbstring_sc = null;
|
||||
|
||||
$to = empty($to) ? RCUBE_CHARSET : strtoupper($to);
|
||||
$from = self::parse_charset($from);
|
||||
|
||||
// It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
|
||||
// In that case we can just skip the conversion (use UTF-8)
|
||||
if ($from == 'UTF-16' && !preg_match('/[^\x00-\x7F]/', $str)) {
|
||||
$from = 'UTF-8';
|
||||
}
|
||||
|
||||
if ($from == $to || empty($str) || empty($from)) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
if ($iconv_options === null) {
|
||||
if (function_exists('iconv')) {
|
||||
// ignore characters not available in output charset
|
||||
$iconv_options = '//IGNORE';
|
||||
if (iconv('', $iconv_options, '') === false) {
|
||||
// iconv implementation does not support options
|
||||
$iconv_options = '';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$iconv_options = false;
|
||||
}
|
||||
}
|
||||
|
||||
// convert charset using iconv module
|
||||
if ($iconv_options !== false && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
|
||||
// throw an exception if iconv reports an illegal character in input
|
||||
// it means that input string has been truncated
|
||||
set_error_handler(array('rcube_charset', 'error_handler'), E_NOTICE);
|
||||
try {
|
||||
$out = iconv($from, $to . $iconv_options, $str);
|
||||
}
|
||||
catch (ErrorException $e) {
|
||||
$out = false;
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
if ($out !== false) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mbstring_sc === null) {
|
||||
$mbstring_sc = extension_loaded('mbstring') ? mb_substitute_character() : false;
|
||||
}
|
||||
|
||||
// convert charset using mbstring module
|
||||
if ($mbstring_sc !== false) {
|
||||
$aliases = array(
|
||||
'WINDOWS-1257' => 'ISO-8859-13',
|
||||
'US-ASCII' => 'ASCII',
|
||||
);
|
||||
|
||||
$mb_from = $aliases[$from] ?: $from;
|
||||
$mb_to = $aliases[$to] ?: $to;
|
||||
|
||||
// Do the same as //IGNORE with iconv
|
||||
mb_substitute_character('none');
|
||||
|
||||
// throw an exception if mbstring reports an illegal character in input
|
||||
// using mb_check_encoding() is much slower
|
||||
set_error_handler(array('rcube_charset', 'error_handler'), E_WARNING);
|
||||
try {
|
||||
$out = mb_convert_encoding($str, $mb_to, $mb_from);
|
||||
}
|
||||
catch (ErrorException $e) {
|
||||
$out = false;
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
mb_substitute_character($mbstring_sc);
|
||||
|
||||
if ($out !== false) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
// convert charset using bundled classes/functions
|
||||
if ($to == 'UTF-8') {
|
||||
if ($from == 'UTF7-IMAP') {
|
||||
if ($out = self::utf7imap_to_utf8($str)) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
else if ($from == 'UTF-7') {
|
||||
if ($out = self::utf7_to_utf8($str)) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encode string for output
|
||||
if ($from == 'UTF-8') {
|
||||
// @TODO: we need a function for UTF-7 (RFC2152) conversion
|
||||
if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
|
||||
if ($out = self::utf8_to_utf7imap($str)) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($out)) {
|
||||
trigger_error("No suitable function found for '$from' to '$to' conversion");
|
||||
}
|
||||
|
||||
// return original string
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string from standard UTF-7 (RFC 2152) to UTF-8.
|
||||
*
|
||||
* @param string $str Input string (UTF-7)
|
||||
*
|
||||
* @return string Converted string (UTF-8)
|
||||
*/
|
||||
public static function utf7_to_utf8($str)
|
||||
{
|
||||
$Index_64 = array(
|
||||
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
|
||||
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
|
||||
0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
|
||||
1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
|
||||
0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
|
||||
1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
|
||||
0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
|
||||
1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
|
||||
);
|
||||
|
||||
$u7len = strlen($str);
|
||||
$str = strval($str);
|
||||
$res = '';
|
||||
|
||||
for ($i=0; $u7len > 0; $i++, $u7len--) {
|
||||
$u7 = $str[$i];
|
||||
if ($u7 == '+') {
|
||||
$i++;
|
||||
$u7len--;
|
||||
$ch = '';
|
||||
|
||||
for (; $u7len > 0; $i++, $u7len--) {
|
||||
$u7 = $str[$i];
|
||||
|
||||
if (!$Index_64[ord($u7)]) {
|
||||
break;
|
||||
}
|
||||
|
||||
$ch .= $u7;
|
||||
}
|
||||
|
||||
if ($ch == '') {
|
||||
if ($u7 == '-') {
|
||||
$res .= '+';
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$res .= self::utf16_to_utf8(base64_decode($ch));
|
||||
}
|
||||
else {
|
||||
$res .= $u7;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
|
||||
*
|
||||
* @param string $str Input string
|
||||
*
|
||||
* @return string The converted string
|
||||
*/
|
||||
public static function utf16_to_utf8($str)
|
||||
{
|
||||
$len = strlen($str);
|
||||
$dec = '';
|
||||
|
||||
for ($i = 0; $i < $len; $i += 2) {
|
||||
$c = ord($str[$i]) << 8 | ord($str[$i + 1]);
|
||||
if ($c >= 0x0001 && $c <= 0x007F) {
|
||||
$dec .= chr($c);
|
||||
}
|
||||
else if ($c > 0x07FF) {
|
||||
$dec .= chr(0xE0 | (($c >> 12) & 0x0F));
|
||||
$dec .= chr(0x80 | (($c >> 6) & 0x3F));
|
||||
$dec .= chr(0x80 | (($c >> 0) & 0x3F));
|
||||
}
|
||||
else {
|
||||
$dec .= chr(0xC0 | (($c >> 6) & 0x1F));
|
||||
$dec .= chr(0x80 | (($c >> 0) & 0x3F));
|
||||
}
|
||||
}
|
||||
|
||||
return $dec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the data ($str) from RFC 2060's UTF-7 to UTF-8.
|
||||
* If input data is invalid, return the original input string.
|
||||
* RFC 2060 obviously intends the encoding to be unique (see
|
||||
* point 5 in section 5.1.3), so we reject any non-canonical
|
||||
* form, such as &ACY- (instead of &-) or &AMA-&AMA- (instead
|
||||
* of &AMAAwA-).
|
||||
*
|
||||
* Translated from C to PHP by Thomas Bruederli <roundcube@gmail.com>
|
||||
*
|
||||
* @param string $str Input string (UTF7-IMAP)
|
||||
*
|
||||
* @return string Output string (UTF-8)
|
||||
*/
|
||||
public static function utf7imap_to_utf8($str)
|
||||
{
|
||||
$Index_64 = array(
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, 63,-1,-1,-1,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
|
||||
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
|
||||
);
|
||||
|
||||
$u7len = strlen($str);
|
||||
$str = strval($str);
|
||||
$p = '';
|
||||
$err = '';
|
||||
|
||||
for ($i=0; $u7len > 0; $i++, $u7len--) {
|
||||
$u7 = $str[$i];
|
||||
if ($u7 == '&') {
|
||||
$i++;
|
||||
$u7len--;
|
||||
$u7 = $str[$i];
|
||||
|
||||
if ($u7len && $u7 == '-') {
|
||||
$p .= '&';
|
||||
continue;
|
||||
}
|
||||
|
||||
$ch = 0;
|
||||
$k = 10;
|
||||
for (; $u7len > 0; $i++, $u7len--) {
|
||||
$u7 = $str[$i];
|
||||
|
||||
if ((ord($u7) & 0x80) || ($b = $Index_64[ord($u7)]) == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($k > 0) {
|
||||
$ch |= $b << $k;
|
||||
$k -= 6;
|
||||
}
|
||||
else {
|
||||
$ch |= $b >> (-$k);
|
||||
if ($ch < 0x80) {
|
||||
// Printable US-ASCII
|
||||
if (0x20 <= $ch && $ch < 0x7f) {
|
||||
return $err;
|
||||
}
|
||||
$p .= chr($ch);
|
||||
}
|
||||
else if ($ch < 0x800) {
|
||||
$p .= chr(0xc0 | ($ch >> 6));
|
||||
$p .= chr(0x80 | ($ch & 0x3f));
|
||||
}
|
||||
else {
|
||||
$p .= chr(0xe0 | ($ch >> 12));
|
||||
$p .= chr(0x80 | (($ch >> 6) & 0x3f));
|
||||
$p .= chr(0x80 | ($ch & 0x3f));
|
||||
}
|
||||
|
||||
$ch = ($b << (16 + $k)) & 0xffff;
|
||||
$k += 10;
|
||||
}
|
||||
}
|
||||
|
||||
// Non-zero or too many extra bits
|
||||
if ($ch || $k < 6) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// BASE64 not properly terminated
|
||||
if (!$u7len || $u7 != '-') {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// Adjacent BASE64 sections
|
||||
if ($u7len > 2 && $str[$i+1] == '&' && $str[$i+2] != '-') {
|
||||
return $err;
|
||||
}
|
||||
}
|
||||
// Not printable US-ASCII
|
||||
else if (ord($u7) < 0x20 || ord($u7) >= 0x7f) {
|
||||
return $err;
|
||||
}
|
||||
else {
|
||||
$p .= $u7;
|
||||
}
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the data ($str) from UTF-8 to RFC 2060's UTF-7.
|
||||
* Unicode characters above U+FFFF are replaced by U+FFFE.
|
||||
* If input data is invalid, return an empty string.
|
||||
*
|
||||
* Translated from C to PHP by Thomas Bruederli <roundcube@gmail.com>
|
||||
*
|
||||
* @param string $str Input string (UTF-8)
|
||||
*
|
||||
* @return string Output string (UTF7-IMAP)
|
||||
*/
|
||||
public static function utf8_to_utf7imap($str)
|
||||
{
|
||||
$B64Chars = array(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
||||
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
|
||||
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', '+', ','
|
||||
);
|
||||
|
||||
$u8len = strlen($str);
|
||||
$base64 = 0;
|
||||
$i = 0;
|
||||
$p = '';
|
||||
$err = '';
|
||||
|
||||
while ($u8len) {
|
||||
$u8 = $str[$i];
|
||||
$c = ord($u8);
|
||||
|
||||
if ($c < 0x80) {
|
||||
$ch = $c;
|
||||
$n = 0;
|
||||
}
|
||||
else if ($c < 0xc2) {
|
||||
return $err;
|
||||
}
|
||||
else if ($c < 0xe0) {
|
||||
$ch = $c & 0x1f;
|
||||
$n = 1;
|
||||
}
|
||||
else if ($c < 0xf0) {
|
||||
$ch = $c & 0x0f;
|
||||
$n = 2;
|
||||
}
|
||||
else if ($c < 0xf8) {
|
||||
$ch = $c & 0x07;
|
||||
$n = 3;
|
||||
}
|
||||
else if ($c < 0xfc) {
|
||||
$ch = $c & 0x03;
|
||||
$n = 4;
|
||||
}
|
||||
else if ($c < 0xfe) {
|
||||
$ch = $c & 0x01;
|
||||
$n = 5;
|
||||
}
|
||||
else {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$i++;
|
||||
$u8len--;
|
||||
|
||||
if ($n > $u8len) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
for ($j=0; $j < $n; $j++) {
|
||||
$o = ord($str[$i+$j]);
|
||||
if (($o & 0xc0) != 0x80) {
|
||||
return $err;
|
||||
}
|
||||
$ch = ($ch << 6) | ($o & 0x3f);
|
||||
}
|
||||
|
||||
if ($n > 1 && !($ch >> ($n * 5 + 1))) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$i += $n;
|
||||
$u8len -= $n;
|
||||
|
||||
if ($ch < 0x20 || $ch >= 0x7f) {
|
||||
if (!$base64) {
|
||||
$p .= '&';
|
||||
$base64 = 1;
|
||||
$b = 0;
|
||||
$k = 10;
|
||||
}
|
||||
if ($ch & ~0xffff) {
|
||||
$ch = 0xfffe;
|
||||
}
|
||||
|
||||
$p .= $B64Chars[($b | $ch >> $k)];
|
||||
$k -= 6;
|
||||
for (; $k >= 0; $k -= 6) {
|
||||
$p .= $B64Chars[(($ch >> $k) & 0x3f)];
|
||||
}
|
||||
|
||||
$b = ($ch << (-$k)) & 0x3f;
|
||||
$k += 16;
|
||||
}
|
||||
else {
|
||||
if ($base64) {
|
||||
if ($k > 10) {
|
||||
$p .= $B64Chars[$b];
|
||||
}
|
||||
$p .= '-';
|
||||
$base64 = 0;
|
||||
}
|
||||
|
||||
$p .= chr($ch);
|
||||
if (chr($ch) == '&') {
|
||||
$p .= '-';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($base64) {
|
||||
if ($k > 10) {
|
||||
$p .= $B64Chars[$b];
|
||||
}
|
||||
$p .= '-';
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to guess character set of a string.
|
||||
*
|
||||
* @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 = 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
|
||||
if (substr($string, 0, 2) == "\xFE\xFF") return 'UTF-16BE'; // Big Endian
|
||||
if (substr($string, 0, 2) == "\xFF\xFE") return 'UTF-16LE'; // Little Endian
|
||||
if (substr($string, 0, 3) == "\xEF\xBB\xBF") return 'UTF-8';
|
||||
|
||||
// heuristics
|
||||
if ($string[0] == "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-32BE';
|
||||
if ($string[0] != "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] == "\0") return 'UTF-32LE';
|
||||
if ($string[0] == "\0" && $string[1] != "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-16BE';
|
||||
if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE';
|
||||
|
||||
if (empty($language)) {
|
||||
$rcube = rcube::get_instance();
|
||||
$language = $rcube->get_user_language();
|
||||
}
|
||||
|
||||
// Prioritize charsets according to current language (#1485669)
|
||||
switch ($language) {
|
||||
case 'ja_JP':
|
||||
$prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win');
|
||||
break;
|
||||
|
||||
case 'zh_CN':
|
||||
case 'zh_TW':
|
||||
$prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW');
|
||||
break;
|
||||
|
||||
case 'ko_KR':
|
||||
$prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR');
|
||||
break;
|
||||
|
||||
case 'ru_RU':
|
||||
$prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R');
|
||||
break;
|
||||
|
||||
case 'tr_TR':
|
||||
$prio = array('UTF-8', 'ISO-8859-9', 'WINDOWS-1254');
|
||||
break;
|
||||
}
|
||||
|
||||
// mb_detect_encoding() is not reliable for some charsets (#1490135)
|
||||
// use mb_check_encoding() to make charset priority lists really working
|
||||
if ($prio && function_exists('mb_check_encoding')) {
|
||||
foreach ($prio as $encoding) {
|
||||
if (mb_check_encoding($string, $encoding)) {
|
||||
return $encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('mb_detect_encoding')) {
|
||||
if (!$prio) {
|
||||
$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()));
|
||||
|
||||
if ($encoding = mb_detect_encoding($string, $encodings)) {
|
||||
return $encoding;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 $failover;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes non-unicode characters from input.
|
||||
*
|
||||
* @param mixed $input String or array.
|
||||
*
|
||||
* @return mixed String or array
|
||||
*/
|
||||
public static function clean($input)
|
||||
{
|
||||
// handle input of type array
|
||||
if (is_array($input)) {
|
||||
foreach ($input as $idx => $val) {
|
||||
$input[$idx] = self::clean($val);
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
|
||||
if (!is_string($input) || $input == '') {
|
||||
return $input;
|
||||
}
|
||||
|
||||
// iconv/mbstring are much faster (especially with long strings)
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
$msch = mb_substitute_character();
|
||||
mb_substitute_character('none');
|
||||
$res = mb_convert_encoding($input, 'UTF-8', 'UTF-8');
|
||||
mb_substitute_character($msch);
|
||||
|
||||
if ($res !== false) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('iconv')) {
|
||||
if (($res = @iconv('UTF-8', 'UTF-8//IGNORE', $input)) !== false) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
$seq = '';
|
||||
$out = '';
|
||||
$regexp = '/^('.
|
||||
// '[\x00-\x7F]'. // UTF8-1
|
||||
'|[\xC2-\xDF][\x80-\xBF]'. // UTF8-2
|
||||
'|\xE0[\xA0-\xBF][\x80-\xBF]'. // UTF8-3
|
||||
'|[\xE1-\xEC][\x80-\xBF][\x80-\xBF]'. // UTF8-3
|
||||
'|\xED[\x80-\x9F][\x80-\xBF]'. // UTF8-3
|
||||
'|[\xEE-\xEF][\x80-\xBF][\x80-\xBF]'. // UTF8-3
|
||||
'|\xF0[\x90-\xBF][\x80-\xBF][\x80-\xBF]'. // UTF8-4
|
||||
'|[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF]'.// UTF8-4
|
||||
'|\xF4[\x80-\x8F][\x80-\xBF][\x80-\xBF]'. // UTF8-4
|
||||
')$/';
|
||||
|
||||
for ($i = 0, $len = strlen($input); $i < $len; $i++) {
|
||||
$chr = $input[$i];
|
||||
$ord = ord($chr);
|
||||
|
||||
// 1-byte character
|
||||
if ($ord <= 0x7F) {
|
||||
if ($seq !== '') {
|
||||
$out .= preg_match($regexp, $seq) ? $seq : '';
|
||||
$seq = '';
|
||||
}
|
||||
|
||||
$out .= $chr;
|
||||
}
|
||||
// first byte of multibyte sequence
|
||||
else if ($ord >= 0xC0) {
|
||||
if ($seq !== '') {
|
||||
$out .= preg_match($regexp, $seq) ? $seq : '';
|
||||
$seq = '';
|
||||
}
|
||||
|
||||
$seq = $chr;
|
||||
}
|
||||
// next byte of multibyte sequence
|
||||
else if ($seq !== '') {
|
||||
$seq .= $chr;
|
||||
}
|
||||
}
|
||||
|
||||
if ($seq !== '') {
|
||||
$out .= preg_match($regexp, $seq) ? $seq : '';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
717
data/web/rc/program/lib/Roundcube/rcube_config.php
Normal file
717
data/web/rc/program/lib/Roundcube/rcube_config.php
Normal file
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2008-2014, 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: |
|
||||
| Class to read configuration settings |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration class for Roundcube
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
*/
|
||||
class rcube_config
|
||||
{
|
||||
const DEFAULT_SKIN = 'larry';
|
||||
|
||||
private $env = '';
|
||||
private $paths = array();
|
||||
private $prop = array();
|
||||
private $errors = array();
|
||||
private $userprefs = array();
|
||||
|
||||
|
||||
/**
|
||||
* Renamed options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $legacy_props = array(
|
||||
// new name => old name
|
||||
'mail_pagesize' => 'pagesize',
|
||||
'addressbook_pagesize' => 'pagesize',
|
||||
'reply_mode' => 'top_posting',
|
||||
'refresh_interval' => 'keep_alive',
|
||||
'min_refresh_interval' => 'min_keep_alive',
|
||||
'messages_cache_ttl' => 'message_cache_lifetime',
|
||||
'mail_read_time' => 'preview_pane_mark_read',
|
||||
'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl',
|
||||
);
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param string $env Environment suffix for config files to load
|
||||
*/
|
||||
public function __construct($env = '')
|
||||
{
|
||||
$this->env = $env;
|
||||
|
||||
if ($paths = getenv('RCUBE_CONFIG_PATH')) {
|
||||
$this->paths = explode(PATH_SEPARATOR, $paths);
|
||||
// make all paths absolute
|
||||
foreach ($this->paths as $i => $path) {
|
||||
if (!rcube_utils::is_absolute_path($path)) {
|
||||
if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) {
|
||||
$this->paths[$i] = unslashify($realpath) . '/';
|
||||
}
|
||||
else {
|
||||
unset($this->paths[$i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->paths[$i] = unslashify($path) . '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defined('RCUBE_CONFIG_DIR') && !in_array(RCUBE_CONFIG_DIR, $this->paths)) {
|
||||
$this->paths[] = RCUBE_CONFIG_DIR;
|
||||
}
|
||||
|
||||
if (empty($this->paths)) {
|
||||
$this->paths[] = RCUBE_INSTALL_PATH . 'config/';
|
||||
}
|
||||
|
||||
$this->load();
|
||||
|
||||
// Defaults, that we do not require you to configure,
|
||||
// but contain information that is used in various locations in the code:
|
||||
if (empty($this->prop['contactlist_fields'])) {
|
||||
$this->set('contactlist_fields', array('name', 'firstname', 'surname', 'email'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Guess the type the string may fit into.
|
||||
*
|
||||
* Look inside the string to determine what type might be best as a container.
|
||||
*
|
||||
* @param mixed $value The value to inspect
|
||||
*
|
||||
* @return The guess at the type.
|
||||
*/
|
||||
private function guess_type($value)
|
||||
{
|
||||
$type = 'string';
|
||||
|
||||
// array requires hint to be passed.
|
||||
|
||||
if (preg_match('/^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$/', $value) !== false) {
|
||||
$type = 'double';
|
||||
}
|
||||
else if (preg_match('/^\d+$/', $value) !== false) {
|
||||
$type = 'integer';
|
||||
}
|
||||
else if (preg_match('/(t(rue)?)|(f(alse)?)/i', $value) !== false) {
|
||||
$type = 'boolean';
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse environment variable into PHP type.
|
||||
*
|
||||
* Perform an appropriate parsing of the string to create the desired PHP type.
|
||||
*
|
||||
* @param string $string String to parse into PHP type
|
||||
* @param string $type Type of value to return
|
||||
*
|
||||
* @return Appropriately typed interpretation of $string.
|
||||
*/
|
||||
private function parse_env($string, $type)
|
||||
{
|
||||
$_ = $string;
|
||||
|
||||
switch ($type) {
|
||||
case 'boolean':
|
||||
$_ = (boolean) $_;
|
||||
break;
|
||||
case 'integer':
|
||||
$_ = (integer) $_;
|
||||
break;
|
||||
case 'double':
|
||||
$_ = (double) $_;
|
||||
break;
|
||||
case 'string':
|
||||
break;
|
||||
case 'array':
|
||||
$_ = json_decode($_, true);
|
||||
break;
|
||||
case 'object':
|
||||
$_ = json_decode($_, false);
|
||||
break;
|
||||
case 'resource':
|
||||
case 'NULL':
|
||||
default:
|
||||
$_ = $this->parse_env($_, $this->guess_type($_));
|
||||
}
|
||||
|
||||
return $_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get environment variable value.
|
||||
*
|
||||
* Retrieve an environment variable's value or if it's not found, return the
|
||||
* provided default value.
|
||||
*
|
||||
* @param string $varname Environment variable name
|
||||
* @param mixed $default_value Default value to return if necessary
|
||||
* @param string $type Type of value to return
|
||||
*
|
||||
* @return Value of the environment variable or default if not found.
|
||||
*/
|
||||
private function getenv_default($varname, $default_value, $type = null)
|
||||
{
|
||||
$value = getenv($varname);
|
||||
|
||||
if ($value === false) {
|
||||
$value = $default_value;
|
||||
}
|
||||
else {
|
||||
$value = $this->parse_env($value, $type ?: gettype($default_value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load config from local config file
|
||||
*
|
||||
* @todo Remove global $CONFIG
|
||||
*/
|
||||
private function load()
|
||||
{
|
||||
// Load default settings
|
||||
if (!$this->load_from_file('defaults.inc.php')) {
|
||||
$this->errors[] = 'defaults.inc.php was not found.';
|
||||
}
|
||||
|
||||
// load main config file
|
||||
if (!$this->load_from_file('config.inc.php')) {
|
||||
// Old configuration files
|
||||
if (!$this->load_from_file('main.inc.php') || !$this->load_from_file('db.inc.php')) {
|
||||
$this->errors[] = 'config.inc.php was not found.';
|
||||
}
|
||||
else if (rand(1,100) == 10) { // log warning on every 100th request (average)
|
||||
trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
// load host-specific configuration
|
||||
$this->load_host_config();
|
||||
|
||||
// set skin (with fallback to old 'skin_path' property)
|
||||
if (empty($this->prop['skin'])) {
|
||||
if (!empty($this->prop['skin_path'])) {
|
||||
$this->prop['skin'] = str_replace('skins/', '', unslashify($this->prop['skin_path']));
|
||||
}
|
||||
else {
|
||||
$this->prop['skin'] = self::DEFAULT_SKIN;
|
||||
}
|
||||
}
|
||||
|
||||
// larry is the new default skin :-)
|
||||
if ($this->prop['skin'] == 'default') {
|
||||
$this->prop['skin'] = self::DEFAULT_SKIN;
|
||||
}
|
||||
|
||||
// fix paths
|
||||
foreach (array('log_dir' => 'logs', 'temp_dir' => 'temp') as $key => $dir) {
|
||||
foreach (array($this->prop[$key], '../' . $this->prop[$key], RCUBE_INSTALL_PATH . $dir) as $path) {
|
||||
if ($path && ($realpath = realpath(unslashify($path)))) {
|
||||
$this->prop[$key] = $realpath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fix default imap folders encoding
|
||||
foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) {
|
||||
$this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCUBE_CHARSET, 'UTF7-IMAP');
|
||||
}
|
||||
|
||||
// set PHP error logging according to config
|
||||
if ($this->prop['debug_level'] & 1) {
|
||||
ini_set('log_errors', 1);
|
||||
|
||||
if ($this->prop['log_driver'] == 'syslog') {
|
||||
ini_set('error_log', 'syslog');
|
||||
}
|
||||
else {
|
||||
ini_set('error_log', $this->prop['log_dir'].'/errors');
|
||||
}
|
||||
}
|
||||
|
||||
// enable display_errors in 'show' level, but not for ajax requests
|
||||
ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
|
||||
|
||||
// remove deprecated properties
|
||||
unset($this->prop['dst_active']);
|
||||
|
||||
// export config data
|
||||
$GLOBALS['CONFIG'] = &$this->prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a host-specific config file if configured
|
||||
* This will merge the host specific configuration with the given one
|
||||
*/
|
||||
private function load_host_config()
|
||||
{
|
||||
if (empty($this->prop['include_host_config'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (array('HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR') as $key) {
|
||||
$fname = null;
|
||||
$name = $_SERVER[$key];
|
||||
|
||||
if (!$name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($this->prop['include_host_config'])) {
|
||||
$fname = $this->prop['include_host_config'][$name];
|
||||
}
|
||||
else {
|
||||
$fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $name) . '.inc.php';
|
||||
}
|
||||
|
||||
if ($fname && $this->load_from_file($fname)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read configuration from a file
|
||||
* and merge with the already stored config values
|
||||
*
|
||||
* @param string $file Name of the config file to be loaded
|
||||
*
|
||||
* @return booelan True on success, false on failure
|
||||
*/
|
||||
public function load_from_file($file)
|
||||
{
|
||||
$success = false;
|
||||
|
||||
foreach ($this->resolve_paths($file) as $fpath) {
|
||||
if ($fpath && is_file($fpath) && is_readable($fpath)) {
|
||||
// use output buffering, we don't need any output here
|
||||
ob_start();
|
||||
include($fpath);
|
||||
ob_end_clean();
|
||||
|
||||
if (is_array($config)) {
|
||||
$this->merge($config);
|
||||
$success = true;
|
||||
}
|
||||
// deprecated name of config variable
|
||||
if (is_array($rcmail_config)) {
|
||||
$this->merge($rcmail_config);
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to resolve absolute paths to the given config file.
|
||||
* This also takes the 'env' property into account.
|
||||
*
|
||||
* @param string $file Filename or absolute file path
|
||||
* @param boolean $use_env Return -$env file path if exists
|
||||
*
|
||||
* @return array List of candidates in config dir path(s)
|
||||
*/
|
||||
public function resolve_paths($file, $use_env = true)
|
||||
{
|
||||
$files = array();
|
||||
$abs_path = rcube_utils::is_absolute_path($file);
|
||||
|
||||
foreach ($this->paths as $basepath) {
|
||||
$realpath = $abs_path ? $file : realpath($basepath . '/' . $file);
|
||||
|
||||
// check if <file>-env.ini exists
|
||||
if ($realpath && $use_env && !empty($this->env)) {
|
||||
$envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $realpath);
|
||||
if (is_file($envfile)) {
|
||||
$realpath = $envfile;
|
||||
}
|
||||
}
|
||||
|
||||
if ($realpath) {
|
||||
$files[] = $realpath;
|
||||
|
||||
// no need to continue the loop if an absolute file path is given
|
||||
if ($abs_path) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for a specific config parameter
|
||||
*
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $def Default value if not set
|
||||
*
|
||||
* @return mixed The requested config value
|
||||
*/
|
||||
public function get($name, $def = null)
|
||||
{
|
||||
if (isset($this->prop[$name])) {
|
||||
$result = $this->prop[$name];
|
||||
}
|
||||
else {
|
||||
$result = $def;
|
||||
}
|
||||
|
||||
$result = $this->getenv_default('ROUNDCUBE_' . strtoupper($name), $result);
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
if ($name == 'timezone') {
|
||||
if (empty($result) || $result == 'auto') {
|
||||
$result = $this->client_timezone();
|
||||
}
|
||||
}
|
||||
else if ($name == 'client_mimetypes') {
|
||||
if (!$result && !$def) {
|
||||
$result = 'text/plain,text/html,text/xml'
|
||||
. ',image/jpeg,image/gif,image/png,image/bmp,image/tiff,image/webp'
|
||||
. ',application/x-javascript,application/pdf,application/x-shockwave-flash';
|
||||
}
|
||||
if ($result && is_string($result)) {
|
||||
$result = explode(',', $result);
|
||||
}
|
||||
}
|
||||
|
||||
$plugin = $rcube->plugins->exec_hook('config_get', array(
|
||||
'name' => $name, 'default' => $def, 'result' => $result));
|
||||
|
||||
return $plugin['result'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for a config parameter
|
||||
*
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $value Parameter value
|
||||
*/
|
||||
public function set($name, $value)
|
||||
{
|
||||
$this->prop[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override config options with the given values (eg. user prefs)
|
||||
*
|
||||
* @param array $prefs Hash array with config props to merge over
|
||||
*/
|
||||
public function merge($prefs)
|
||||
{
|
||||
$prefs = $this->fix_legacy_props($prefs);
|
||||
$this->prop = array_merge($this->prop, $prefs, $this->userprefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given prefs over the current config
|
||||
* and make sure that they survive further merging.
|
||||
*
|
||||
* @param array $prefs Hash array with user prefs
|
||||
*/
|
||||
public function set_user_prefs($prefs)
|
||||
{
|
||||
$prefs = $this->fix_legacy_props($prefs);
|
||||
|
||||
// Honor the dont_override setting for any existing user preferences
|
||||
$dont_override = $this->get('dont_override');
|
||||
if (is_array($dont_override) && !empty($dont_override)) {
|
||||
foreach ($dont_override as $key) {
|
||||
unset($prefs[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// larry is the new default skin :-)
|
||||
if ($prefs['skin'] == 'default') {
|
||||
$prefs['skin'] = self::DEFAULT_SKIN;
|
||||
}
|
||||
|
||||
$this->userprefs = $prefs;
|
||||
$this->prop = array_merge($this->prop, $prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for all config options
|
||||
*
|
||||
* @return array Hash array containing all config properties
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
$props = $this->prop;
|
||||
|
||||
foreach ($props as $prop_name => $prop_value) {
|
||||
$props[$prop_name] = $this->getenv_default('ROUNDCUBE_' . strtoupper($prop_name), $prop_value);
|
||||
}
|
||||
|
||||
$rcube = rcube::get_instance();
|
||||
$plugin = $rcube->plugins->exec_hook('config_get', array(
|
||||
'name' => '*', 'result' => $props));
|
||||
|
||||
return $plugin['result'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Special getter for user's timezone offset including DST
|
||||
*
|
||||
* @return float Timezone offset (in hours)
|
||||
* @deprecated
|
||||
*/
|
||||
public function get_timezone()
|
||||
{
|
||||
if ($tz = $this->get('timezone')) {
|
||||
try {
|
||||
$tz = new DateTimeZone($tz);
|
||||
return $tz->getOffset(new DateTime('now')) / 3600;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return requested DES crypto key.
|
||||
*
|
||||
* @param string $key Crypto key name
|
||||
*
|
||||
* @return string Crypto key
|
||||
*/
|
||||
public function get_crypto_key($key)
|
||||
{
|
||||
// Bomb out if the requested key does not exist
|
||||
if (!array_key_exists($key, $this->prop) || empty($this->prop[$key])) {
|
||||
rcube::raise_error(array(
|
||||
'code' => 500, 'type' => 'php',
|
||||
'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "Request for unconfigured crypto key \"$key\""
|
||||
), true, true);
|
||||
}
|
||||
|
||||
return $this->prop[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return configured crypto method.
|
||||
*
|
||||
* @return string Crypto method
|
||||
*/
|
||||
public function get_crypto_method()
|
||||
{
|
||||
return $this->get('cipher_method') ?: 'DES-EDE3-CBC';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to autodetect operating system and find the correct line endings
|
||||
*
|
||||
* @return string The appropriate mail header delimiter
|
||||
* @deprecated Since 1.3 we don't use mail()
|
||||
*/
|
||||
public function header_delimiter()
|
||||
{
|
||||
// use the configured delimiter for headers
|
||||
if (!empty($this->prop['mail_header_delimiter'])) {
|
||||
$delim = $this->prop['mail_header_delimiter'];
|
||||
if ($delim == "\n" || $delim == "\r\n") {
|
||||
return $delim;
|
||||
}
|
||||
else {
|
||||
rcube::raise_error(array(
|
||||
'code' => 500, 'type' => 'php',
|
||||
'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "Invalid mail_header_delimiter setting"
|
||||
), true, false);
|
||||
}
|
||||
}
|
||||
|
||||
$php_os = strtolower(substr(PHP_OS, 0, 3));
|
||||
|
||||
if ($php_os == 'win')
|
||||
return "\r\n";
|
||||
|
||||
if ($php_os == 'mac')
|
||||
return "\r\n";
|
||||
|
||||
return "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mail domain configured for the given host
|
||||
*
|
||||
* @param string $host IMAP host
|
||||
* @param boolean $encode If true, domain name will be converted to IDN ASCII
|
||||
*
|
||||
* @return string Resolved SMTP host
|
||||
*/
|
||||
public function mail_domain($host, $encode=true)
|
||||
{
|
||||
$domain = $host;
|
||||
|
||||
if (is_array($this->prop['mail_domain'])) {
|
||||
if (isset($this->prop['mail_domain'][$host])) {
|
||||
$domain = $this->prop['mail_domain'][$host];
|
||||
}
|
||||
}
|
||||
else if (!empty($this->prop['mail_domain'])) {
|
||||
$domain = rcube_utils::parse_host($this->prop['mail_domain']);
|
||||
}
|
||||
|
||||
if ($encode) {
|
||||
$domain = rcube_utils::idn_to_ascii($domain);
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for error state
|
||||
*
|
||||
* @return mixed Error message on error, False if no errors
|
||||
*/
|
||||
public function get_error()
|
||||
{
|
||||
return empty($this->errors) ? false : join("\n", $this->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal getter for client's (browser) timezone identifier
|
||||
*/
|
||||
private function client_timezone()
|
||||
{
|
||||
// @TODO: remove this legacy timezone handling in the future
|
||||
$props = $this->fix_legacy_props(array('timezone' => $_SESSION['timezone']));
|
||||
|
||||
if (!empty($props['timezone'])) {
|
||||
try {
|
||||
$tz = new DateTimeZone($props['timezone']);
|
||||
return $tz->getName();
|
||||
}
|
||||
catch (Exception $e) { /* gracefully ignore */ }
|
||||
}
|
||||
|
||||
// fallback to server's timezone
|
||||
return date_default_timezone_get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert legacy options into new ones
|
||||
*
|
||||
* @param array $props Hash array with config props
|
||||
*
|
||||
* @return array Converted config props
|
||||
*/
|
||||
private function fix_legacy_props($props)
|
||||
{
|
||||
foreach ($this->legacy_props as $new => $old) {
|
||||
if (isset($props[$old])) {
|
||||
if (!isset($props[$new])) {
|
||||
$props[$new] = $props[$old];
|
||||
}
|
||||
unset($props[$old]);
|
||||
}
|
||||
}
|
||||
|
||||
// convert deprecated numeric timezone value
|
||||
if (isset($props['timezone']) && is_numeric($props['timezone'])) {
|
||||
if ($tz = self::timezone_name_from_abbr($props['timezone'])) {
|
||||
$props['timezone'] = $tz;
|
||||
}
|
||||
else {
|
||||
unset($props['timezone']);
|
||||
}
|
||||
}
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* timezone_name_from_abbr() replacement. Converts timezone offset
|
||||
* into timezone name abbreviation.
|
||||
*
|
||||
* @param float $offset Timezone offset (in hours)
|
||||
*
|
||||
* @return string Timezone abbreviation
|
||||
*/
|
||||
static public function timezone_name_from_abbr($offset)
|
||||
{
|
||||
// List of timezones here is not complete - https://bugs.php.net/bug.php?id=44780
|
||||
if ($tz = timezone_name_from_abbr('', $offset * 3600, 0)) {
|
||||
return $tz;
|
||||
}
|
||||
|
||||
// try with more complete list (#1489261)
|
||||
$timezones = array(
|
||||
'-660' => "Pacific/Apia",
|
||||
'-600' => "Pacific/Honolulu",
|
||||
'-570' => "Pacific/Marquesas",
|
||||
'-540' => "America/Anchorage",
|
||||
'-480' => "America/Los_Angeles",
|
||||
'-420' => "America/Denver",
|
||||
'-360' => "America/Chicago",
|
||||
'-300' => "America/New_York",
|
||||
'-270' => "America/Caracas",
|
||||
'-240' => "America/Halifax",
|
||||
'-210' => "Canada/Newfoundland",
|
||||
'-180' => "America/Sao_Paulo",
|
||||
'-60' => "Atlantic/Azores",
|
||||
'0' => "Europe/London",
|
||||
'60' => "Europe/Paris",
|
||||
'120' => "Europe/Helsinki",
|
||||
'180' => "Europe/Moscow",
|
||||
'210' => "Asia/Tehran",
|
||||
'240' => "Asia/Dubai",
|
||||
'270' => "Asia/Kabul",
|
||||
'300' => "Asia/Karachi",
|
||||
'330' => "Asia/Kolkata",
|
||||
'345' => "Asia/Katmandu",
|
||||
'360' => "Asia/Yekaterinburg",
|
||||
'390' => "Asia/Rangoon",
|
||||
'420' => "Asia/Krasnoyarsk",
|
||||
'480' => "Asia/Shanghai",
|
||||
'525' => "Australia/Eucla",
|
||||
'540' => "Asia/Tokyo",
|
||||
'570' => "Australia/Adelaide",
|
||||
'600' => "Australia/Melbourne",
|
||||
'630' => "Australia/Lord_Howe",
|
||||
'660' => "Asia/Vladivostok",
|
||||
'690' => "Pacific/Norfolk",
|
||||
'720' => "Pacific/Auckland",
|
||||
'765' => "Pacific/Chatham",
|
||||
'780' => "Pacific/Enderbury",
|
||||
'840' => "Pacific/Kiritimati",
|
||||
);
|
||||
|
||||
return $timezones[(string) intval($offset * 60)];
|
||||
}
|
||||
}
|
||||
1039
data/web/rc/program/lib/Roundcube/rcube_contacts.php
Normal file
1039
data/web/rc/program/lib/Roundcube/rcube_contacts.php
Normal file
File diff suppressed because it is too large
Load Diff
57
data/web/rc/program/lib/Roundcube/rcube_content_filter.php
Normal file
57
data/web/rc/program/lib/Roundcube/rcube_content_filter.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2011, 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: |
|
||||
| PHP stream filter to detect evil content in mail attachments |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* PHP stream filter to detect html/javascript code in attachments
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_content_filter extends php_user_filter
|
||||
{
|
||||
private $buffer = '';
|
||||
private $cutoff = 2048;
|
||||
|
||||
function onCreate()
|
||||
{
|
||||
$this->cutoff = rand(2048, 3027);
|
||||
return true;
|
||||
}
|
||||
|
||||
function filter($in, $out, &$consumed, $closing)
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
$this->buffer .= $bucket->data;
|
||||
|
||||
// check for evil content and abort
|
||||
if (preg_match('/<(script|iframe|object)/i', $this->buffer)) {
|
||||
return PSFS_ERR_FATAL;
|
||||
}
|
||||
|
||||
// keep buffer small enough
|
||||
if (strlen($this->buffer) > 4096) {
|
||||
$this->buffer = substr($this->buffer, $this->cutoff);
|
||||
}
|
||||
|
||||
$consumed += $bucket->datalen;
|
||||
stream_bucket_append($out, $bucket);
|
||||
}
|
||||
|
||||
return PSFS_PASS_ON;
|
||||
}
|
||||
}
|
||||
663
data/web/rc/program/lib/Roundcube/rcube_csv2vcard.php
Normal file
663
data/web/rc/program/lib/Roundcube/rcube_csv2vcard.php
Normal file
@@ -0,0 +1,663 @@
|
||||
<?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: |
|
||||
| CSV to vCard data conversion |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* CSV to vCard data converter
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Addressbook
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_csv2vcard
|
||||
{
|
||||
/**
|
||||
* CSV to vCard fields mapping
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $csv2vcard_map = array(
|
||||
// MS Outlook 2010
|
||||
'anniversary' => 'anniversary',
|
||||
'assistants_name' => 'assistant',
|
||||
'assistants_phone' => 'phone:assistant',
|
||||
'birthday' => 'birthday',
|
||||
'business_city' => 'locality:work',
|
||||
'business_countryregion' => 'country:work',
|
||||
'business_fax' => 'phone:work,fax',
|
||||
'business_phone' => 'phone:work',
|
||||
'business_phone_2' => 'phone:work2',
|
||||
'business_postal_code' => 'zipcode:work',
|
||||
'business_state' => 'region:work',
|
||||
'business_street' => 'street:work',
|
||||
//'business_street_2' => '',
|
||||
//'business_street_3' => '',
|
||||
'car_phone' => 'phone:car',
|
||||
'categories' => 'groups',
|
||||
//'children' => '',
|
||||
'company' => 'organization',
|
||||
//'company_main_phone' => '',
|
||||
'department' => 'department',
|
||||
'email_2_address' => 'email:other',
|
||||
//'email_2_type' => '',
|
||||
'email_3_address' => 'email:other',
|
||||
//'email_3_type' => '',
|
||||
'email_address' => 'email:pref',
|
||||
//'email_type' => '',
|
||||
'first_name' => 'firstname',
|
||||
'gender' => 'gender',
|
||||
'home_city' => 'locality:home',
|
||||
'home_countryregion' => 'country:home',
|
||||
'home_fax' => 'phone:home,fax',
|
||||
'home_phone' => 'phone:home',
|
||||
'home_phone_2' => 'phone:home2',
|
||||
'home_postal_code' => 'zipcode:home',
|
||||
'home_state' => 'region:home',
|
||||
'home_street' => 'street:home',
|
||||
//'home_street_2' => '',
|
||||
//'home_street_3' => '',
|
||||
//'initials' => '',
|
||||
//'isdn' => '',
|
||||
'job_title' => 'jobtitle',
|
||||
//'keywords' => '',
|
||||
//'language' => '',
|
||||
'last_name' => 'surname',
|
||||
//'location' => '',
|
||||
'managers_name' => 'manager',
|
||||
'middle_name' => 'middlename',
|
||||
//'mileage' => '',
|
||||
'mobile_phone' => 'phone:cell',
|
||||
'notes' => 'notes',
|
||||
//'office_location' => '',
|
||||
'other_city' => 'locality:other',
|
||||
'other_countryregion' => 'country:other',
|
||||
'other_fax' => 'phone:other,fax',
|
||||
'other_phone' => 'phone:other',
|
||||
'other_postal_code' => 'zipcode:other',
|
||||
'other_state' => 'region:other',
|
||||
'other_street' => 'street:other',
|
||||
//'other_street_2' => '',
|
||||
//'other_street_3' => '',
|
||||
'pager' => 'phone:pager',
|
||||
'primary_phone' => 'phone:pref',
|
||||
//'profession' => '',
|
||||
//'radio_phone' => '',
|
||||
'spouse' => 'spouse',
|
||||
'suffix' => 'suffix',
|
||||
'title' => 'title',
|
||||
'web_page' => 'website:homepage',
|
||||
|
||||
// Thunderbird
|
||||
'birth_day' => 'birthday-d',
|
||||
'birth_month' => 'birthday-m',
|
||||
'birth_year' => 'birthday-y',
|
||||
'display_name' => 'displayname',
|
||||
'fax_number' => 'phone:fax',
|
||||
'home_address' => 'street:home',
|
||||
//'home_address_2' => '',
|
||||
'home_country' => 'country:home',
|
||||
'home_zipcode' => 'zipcode:home',
|
||||
'mobile_number' => 'phone:cell',
|
||||
'nickname' => 'nickname',
|
||||
'organization' => 'organization',
|
||||
'pager_number' => 'phone:pager',
|
||||
'primary_email' => 'email:pref',
|
||||
'secondary_email' => 'email:other',
|
||||
'web_page_1' => 'website:homepage',
|
||||
'web_page_2' => 'website:other',
|
||||
'work_phone' => 'phone:work',
|
||||
'work_address' => 'street:work',
|
||||
//'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',
|
||||
|
||||
// Atmail
|
||||
'date_of_birth' => 'birthday',
|
||||
'email' => 'email:pref',
|
||||
'home_mobile' => 'phone:cell',
|
||||
'home_zip' => 'zipcode:home',
|
||||
'info' => 'notes',
|
||||
'user_photo' => 'photo',
|
||||
'url' => 'website:homepage',
|
||||
'work_company' => 'organization',
|
||||
'work_dept' => 'departament',
|
||||
'work_fax' => 'phone:work,fax',
|
||||
'work_mobile' => 'phone:work,cell',
|
||||
'work_title' => 'jobtitle',
|
||||
'work_zip' => 'zipcode:work',
|
||||
'group' => 'groups',
|
||||
|
||||
// GMail
|
||||
'groups' => 'groups',
|
||||
'group_membership' => 'groups',
|
||||
'given_name' => 'firstname',
|
||||
'additional_name' => 'middlename',
|
||||
'family_name' => 'surname',
|
||||
'name' => 'displayname',
|
||||
'name_prefix' => 'prefix',
|
||||
'name_suffix' => 'suffix',
|
||||
);
|
||||
|
||||
/**
|
||||
* CSV label to text mapping for English
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $label_map = array(
|
||||
// MS Outlook 2010
|
||||
'anniversary' => "Anniversary",
|
||||
'assistants_name' => "Assistant's Name",
|
||||
'assistants_phone' => "Assistant's Phone",
|
||||
'birthday' => "Birthday",
|
||||
'business_city' => "Business City",
|
||||
'business_countryregion' => "Business Country/Region",
|
||||
'business_fax' => "Business Fax",
|
||||
'business_phone' => "Business Phone",
|
||||
'business_phone_2' => "Business Phone 2",
|
||||
'business_postal_code' => "Business Postal Code",
|
||||
'business_state' => "Business State",
|
||||
'business_street' => "Business Street",
|
||||
//'business_street_2' => "Business Street 2",
|
||||
//'business_street_3' => "Business Street 3",
|
||||
'car_phone' => "Car Phone",
|
||||
'categories' => "Categories",
|
||||
//'children' => "Children",
|
||||
'company' => "Company",
|
||||
//'company_main_phone' => "Company Main Phone",
|
||||
'department' => "Department",
|
||||
//'directory_server' => "Directory Server",
|
||||
'email_2_address' => "E-mail 2 Address",
|
||||
//'email_2_type' => "E-mail 2 Type",
|
||||
'email_3_address' => "E-mail 3 Address",
|
||||
//'email_3_type' => "E-mail 3 Type",
|
||||
'email_address' => "E-mail Address",
|
||||
//'email_type' => "E-mail Type",
|
||||
'first_name' => "First Name",
|
||||
'gender' => "Gender",
|
||||
'home_city' => "Home City",
|
||||
'home_countryregion' => "Home Country/Region",
|
||||
'home_fax' => "Home Fax",
|
||||
'home_phone' => "Home Phone",
|
||||
'home_phone_2' => "Home Phone 2",
|
||||
'home_postal_code' => "Home Postal Code",
|
||||
'home_state' => "Home State",
|
||||
'home_street' => "Home Street",
|
||||
//'home_street_2' => "Home Street 2",
|
||||
//'home_street_3' => "Home Street 3",
|
||||
//'initials' => "Initials",
|
||||
//'isdn' => "ISDN",
|
||||
'job_title' => "Job Title",
|
||||
//'keywords' => "Keywords",
|
||||
//'language' => "Language",
|
||||
'last_name' => "Last Name",
|
||||
//'location' => "Location",
|
||||
'managers_name' => "Manager's Name",
|
||||
'middle_name' => "Middle Name",
|
||||
//'mileage' => "Mileage",
|
||||
'mobile_phone' => "Mobile Phone",
|
||||
'notes' => "Notes",
|
||||
//'office_location' => "Office Location",
|
||||
'other_city' => "Other City",
|
||||
'other_countryregion' => "Other Country/Region",
|
||||
'other_fax' => "Other Fax",
|
||||
'other_phone' => "Other Phone",
|
||||
'other_postal_code' => "Other Postal Code",
|
||||
'other_state' => "Other State",
|
||||
'other_street' => "Other Street",
|
||||
//'other_street_2' => "Other Street 2",
|
||||
//'other_street_3' => "Other Street 3",
|
||||
'pager' => "Pager",
|
||||
'primary_phone' => "Primary Phone",
|
||||
//'profession' => "Profession",
|
||||
//'radio_phone' => "Radio Phone",
|
||||
'spouse' => "Spouse",
|
||||
'suffix' => "Suffix",
|
||||
'title' => "Title",
|
||||
'web_page' => "Web Page",
|
||||
|
||||
// Thunderbird
|
||||
'birth_day' => "Birth Day",
|
||||
'birth_month' => "Birth Month",
|
||||
'birth_year' => "Birth Year",
|
||||
'display_name' => "Display Name",
|
||||
'fax_number' => "Fax Number",
|
||||
'home_address' => "Home Address",
|
||||
//'home_address_2' => "Home Address 2",
|
||||
'home_country' => "Home Country",
|
||||
'home_zipcode' => "Home ZipCode",
|
||||
'mobile_number' => "Mobile Number",
|
||||
'nickname' => "Nickname",
|
||||
'organization' => "Organization",
|
||||
'pager_number' => "Pager Namber",
|
||||
'primary_email' => "Primary Email",
|
||||
'secondary_email' => "Secondary Email",
|
||||
'web_page_1' => "Web Page 1",
|
||||
'web_page_2' => "Web Page 2",
|
||||
'work_phone' => "Work Phone",
|
||||
'work_address' => "Work Address",
|
||||
//'work_address_2' => "Work Address 2",
|
||||
'work_city' => "Work City",
|
||||
'work_country' => "Work Country",
|
||||
'work_state' => "Work State",
|
||||
'work_zipcode' => "Work ZipCode",
|
||||
|
||||
// Atmail
|
||||
'date_of_birth' => "Date of Birth",
|
||||
'email' => "Email",
|
||||
//'email_2' => "Email2",
|
||||
//'email_3' => "Email3",
|
||||
//'email_4' => "Email4",
|
||||
//'email_5' => "Email5",
|
||||
'home_mobile' => "Home Mobile",
|
||||
'home_zip' => "Home Zip",
|
||||
'info' => "Info",
|
||||
'user_photo' => "User Photo",
|
||||
'url' => "URL",
|
||||
'work_company' => "Work Company",
|
||||
'work_dept' => "Work Dept",
|
||||
'work_fax' => "Work Fax",
|
||||
'work_mobile' => "Work Mobile",
|
||||
'work_title' => "Work Title",
|
||||
'work_zip' => "Work Zip",
|
||||
'group' => "Group",
|
||||
|
||||
// GMail
|
||||
'groups' => "Groups",
|
||||
'group_membership' => "Group Membership",
|
||||
'given_name' => "Given Name",
|
||||
'additional_name' => "Additional Name",
|
||||
'family_name' => "Family Name",
|
||||
'name' => "Name",
|
||||
'name_prefix' => "Name Prefix",
|
||||
'name_suffix' => "Name Suffix",
|
||||
);
|
||||
|
||||
/**
|
||||
* Special fields map for GMail format
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $gmail_label_map = array(
|
||||
'E-mail' => array(
|
||||
'Value' => array(
|
||||
'home' => 'email:home',
|
||||
'work' => 'email:work',
|
||||
'*' => 'email:other',
|
||||
),
|
||||
),
|
||||
'Phone' => array(
|
||||
'Value' => array(
|
||||
'home' => 'phone:home',
|
||||
'homefax' => 'phone:homefax',
|
||||
'main' => 'phone:pref',
|
||||
'pager' => 'phone:pager',
|
||||
'mobile' => 'phone:cell',
|
||||
'work' => 'phone:work',
|
||||
'workfax' => 'phone:workfax',
|
||||
),
|
||||
),
|
||||
'Relation' => array(
|
||||
'Value' => array(
|
||||
'spouse' => 'spouse',
|
||||
),
|
||||
),
|
||||
'Website' => array(
|
||||
'Value' => array(
|
||||
'profile' => 'website:profile',
|
||||
'blog' => 'website:blog',
|
||||
'homepage' => 'website:homepage',
|
||||
'work' => 'website:work',
|
||||
),
|
||||
),
|
||||
'Address' => array(
|
||||
'Street' => array(
|
||||
'home' => 'street:home',
|
||||
'work' => 'street:work',
|
||||
),
|
||||
'City' => array(
|
||||
'home' => 'locality:home',
|
||||
'work' => 'locality:work',
|
||||
),
|
||||
'Region' => array(
|
||||
'home' => 'region:home',
|
||||
'work' => 'region:work',
|
||||
),
|
||||
'Postal Code' => array(
|
||||
'home' => 'zipcode:home',
|
||||
'work' => 'zipcode:work',
|
||||
),
|
||||
'Country' => array(
|
||||
'home' => 'country:home',
|
||||
'work' => 'country:work',
|
||||
),
|
||||
),
|
||||
'Organization' => array(
|
||||
'Name' => array(
|
||||
'' => 'organization',
|
||||
),
|
||||
'Title' => array(
|
||||
'' => 'jobtitle',
|
||||
),
|
||||
'Department' => array(
|
||||
'' => 'department',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
protected $local_label_map = array();
|
||||
protected $vcards = array();
|
||||
protected $map = array();
|
||||
protected $gmail_map = array();
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $lang File language
|
||||
*/
|
||||
public function __construct($lang = 'en_US')
|
||||
{
|
||||
// Localize fields map
|
||||
if ($lang && $lang != 'en_US') {
|
||||
if (file_exists(RCUBE_LOCALIZATION_DIR . "$lang/csv2vcard.inc")) {
|
||||
include RCUBE_LOCALIZATION_DIR . "$lang/csv2vcard.inc";
|
||||
}
|
||||
|
||||
if (!empty($map)) {
|
||||
$this->local_label_map = array_merge($this->label_map, $map);
|
||||
}
|
||||
}
|
||||
|
||||
$this->label_map = array_flip($this->label_map);
|
||||
$this->local_label_map = array_flip($this->local_label_map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import contacts from CSV file
|
||||
*
|
||||
* @param string $csv Content of the CSV file
|
||||
*/
|
||||
public function import($csv)
|
||||
{
|
||||
// convert to UTF-8
|
||||
$head = substr($csv, 0, 4096);
|
||||
$charset = rcube_charset::detect($head, RCUBE_CHARSET);
|
||||
$csv = rcube_charset::convert($csv, $charset);
|
||||
$csv = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $csv); // also remove BOM
|
||||
$head = '';
|
||||
$prev_line = false;
|
||||
|
||||
$this->map = array();
|
||||
$this->gmail_map = array();
|
||||
|
||||
// Parse file
|
||||
foreach (preg_split("/[\r\n]+/", $csv) as $line) {
|
||||
if (!empty($prev_line)) {
|
||||
$line = '"' . $line;
|
||||
}
|
||||
|
||||
$elements = $this->parse_line($line);
|
||||
|
||||
if (empty($elements)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse header
|
||||
if (empty($this->map)) {
|
||||
$this->parse_header($elements);
|
||||
if (empty($this->map)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Parse data row
|
||||
else {
|
||||
// handle multiline elements (e.g. Gmail)
|
||||
if (!empty($prev_line)) {
|
||||
$first = array_shift($elements);
|
||||
|
||||
if ($first[0] == '"') {
|
||||
$prev_line[count($prev_line)-1] = '"' . $prev_line[count($prev_line)-1] . "\n" . substr($first, 1);
|
||||
}
|
||||
else {
|
||||
$prev_line[count($prev_line)-1] .= "\n" . $first;
|
||||
}
|
||||
|
||||
$elements = array_merge($prev_line, $elements);
|
||||
}
|
||||
|
||||
$last_element = $elements[count($elements)-1];
|
||||
if ($last_element[0] == '"') {
|
||||
$elements[count($elements)-1] = substr($last_element, 1);
|
||||
$prev_line = $elements;
|
||||
continue;
|
||||
}
|
||||
$this->csv_to_vcard($elements);
|
||||
$prev_line = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export vCards
|
||||
*
|
||||
* @return array rcube_vcard List of vcards
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
return $this->vcards;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$map1 = array();
|
||||
$map2 = array();
|
||||
$size = count($elements);
|
||||
|
||||
// check English labels
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$label = $this->label_map[$elements[$i]];
|
||||
if ($label && !empty($this->csv2vcard_map[$label])) {
|
||||
$map1[$i] = $this->csv2vcard_map[$label];
|
||||
}
|
||||
}
|
||||
|
||||
// check localized labels
|
||||
if (!empty($this->local_label_map)) {
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$label = $this->local_label_map[$elements[$i]];
|
||||
|
||||
// special localization label
|
||||
if ($label && $label[0] == '_') {
|
||||
$label = substr($label, 1);
|
||||
}
|
||||
|
||||
if ($label && !empty($this->csv2vcard_map[$label])) {
|
||||
$map2[$i] = $this->csv2vcard_map[$label];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->map = count($map1) >= count($map2) ? $map1 : $map2;
|
||||
|
||||
// support special Gmail format
|
||||
foreach ($this->gmail_label_map as $key => $items) {
|
||||
$num = 1;
|
||||
while (($_key = "$key $num - Type") && ($found = array_search($_key, $elements)) !== false) {
|
||||
$this->gmail_map["$key:$num"] = array('_key' => $key, '_idx' => $found);
|
||||
foreach (array_keys($items) as $item_key) {
|
||||
$_key = "$key $num - $item_key";
|
||||
if (($found = array_search($_key, $elements)) !== false) {
|
||||
$this->gmail_map["$key:$num"][$item_key] = $found;
|
||||
}
|
||||
}
|
||||
|
||||
$num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert CSV data row to vCard
|
||||
*/
|
||||
protected function csv_to_vcard($data)
|
||||
{
|
||||
$contact = array();
|
||||
foreach ($this->map as $idx => $name) {
|
||||
$value = $data[$idx];
|
||||
if ($value !== null && $value !== '') {
|
||||
if (!empty($contact[$name])) {
|
||||
$contact[$name] = (array) $contact[$name];
|
||||
$contact[$name][] = $value;
|
||||
}
|
||||
else {
|
||||
$contact[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gmail format support
|
||||
foreach ($this->gmail_map as $idx => $item) {
|
||||
$type = preg_replace('/[^a-z]/', '', strtolower($data[$item['_idx']]));
|
||||
$key = $item['_key'];
|
||||
|
||||
unset($item['_idx']);
|
||||
unset($item['_key']);
|
||||
|
||||
foreach ($item as $item_key => $item_idx) {
|
||||
$value = $data[$item_idx];
|
||||
if ($value !== null && $value !== '') {
|
||||
foreach (array($type, '*') as $_type) {
|
||||
if ($data_idx = $this->gmail_label_map[$key][$item_key][$_type]) {
|
||||
$value = explode(' ::: ', $value);
|
||||
|
||||
if (!empty($contact[$data_idx])) {
|
||||
$contact[$data_idx] = array_merge((array) $contact[$data_idx], $value);
|
||||
}
|
||||
else {
|
||||
$contact[$data_idx] = $value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($contact)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle special values
|
||||
if (!empty($contact['birthday-d']) && !empty($contact['birthday-m']) && !empty($contact['birthday-y'])) {
|
||||
$contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d'];
|
||||
}
|
||||
|
||||
if (!empty($contact['groups'])) {
|
||||
// categories/groups separator in vCard is ',' not ';'
|
||||
$contact['groups'] = str_replace(',', '', $contact['groups']);
|
||||
$contact['groups'] = str_replace(';', ',', $contact['groups']);
|
||||
|
||||
if (!empty($this->gmail_map)) {
|
||||
// remove "* " added by GMail
|
||||
$contact['groups'] = str_replace('* ', '', $contact['groups']);
|
||||
// replace strange delimiter
|
||||
$contact['groups'] = str_replace(' ::: ', ',', $contact['groups']);
|
||||
}
|
||||
}
|
||||
|
||||
// Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00"
|
||||
foreach (array('birthday', 'anniversary') as $key) {
|
||||
if (!empty($contact[$key])) {
|
||||
$date = preg_replace('/[0[:^word:]]/', '', $contact[$key]);
|
||||
if (empty($date)) {
|
||||
unset($contact[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($contact['gender']) && ($gender = strtolower($contact['gender']))) {
|
||||
if (!in_array($gender, array('male', 'female'))) {
|
||||
unset($contact['gender']);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
$name = explode(':', $name);
|
||||
if (is_array($value) && $name[0] != 'address') {
|
||||
foreach ((array) $value as $val) {
|
||||
$vcard->set($name[0], $val, $name[1]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$vcard->set($name[0], $value, $name[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// add to the list
|
||||
$this->vcards[] = $vcard;
|
||||
}
|
||||
}
|
||||
1367
data/web/rc/program/lib/Roundcube/rcube_db.php
Normal file
1367
data/web/rc/program/lib/Roundcube/rcube_db.php
Normal file
File diff suppressed because it is too large
Load Diff
190
data/web/rc/program/lib/Roundcube/rcube_db_mssql.php
Normal file
190
data/web/rc/program/lib/Roundcube/rcube_db_mssql.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Database wrapper class that implements PHP PDO functions |
|
||||
| for MS SQL Server database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database independent query interface
|
||||
* This is a wrapper for the PHP PDO
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Database
|
||||
*/
|
||||
class rcube_db_mssql extends rcube_db
|
||||
{
|
||||
public $db_provider = 'mssql';
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param string $db_dsnw DSN for read/write operations
|
||||
* @param string $db_dsnr Optional DSN for read only operations
|
||||
* @param bool $pconn Enables persistent connections
|
||||
*/
|
||||
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
|
||||
{
|
||||
parent::__construct($db_dsnw, $db_dsnr, $pconn);
|
||||
|
||||
$this->options['identifier_start'] = '[';
|
||||
$this->options['identifier_end'] = ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver-specific configuration of database connection
|
||||
*
|
||||
* @param array $dsn DSN for DB connections
|
||||
* @param PDO $dbh Connection handler
|
||||
*/
|
||||
protected function conn_configure($dsn, $dbh)
|
||||
{
|
||||
// Set date format in case of non-default language (#1488918)
|
||||
$dbh->query("SET DATEFORMAT ymd");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL function for current time and date
|
||||
*
|
||||
* @param int $interval Optional interval (in seconds) to add/subtract
|
||||
*
|
||||
* @return string SQL function to use in query
|
||||
*/
|
||||
public function now($interval = 0)
|
||||
{
|
||||
if ($interval) {
|
||||
$interval = intval($interval);
|
||||
return "dateadd(second, $interval, getdate())";
|
||||
}
|
||||
|
||||
return "getdate()";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL statement to convert a field value into a unix timestamp
|
||||
*
|
||||
* @param string $field Field name
|
||||
*
|
||||
* @return string SQL statement to use in query
|
||||
* @deprecated
|
||||
*/
|
||||
public function unixtimestamp($field)
|
||||
{
|
||||
return "DATEDIFF(second, '19700101', $field) + DATEDIFF(second, GETDATE(), GETUTCDATE())";
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract SQL statement for value concatenation
|
||||
*
|
||||
* @return string SQL statement to be used in query
|
||||
*/
|
||||
public function concat(/* col1, col2, ... */)
|
||||
{
|
||||
$args = func_get_args();
|
||||
|
||||
if (is_array($args[0])) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
return '(' . join('+', $args) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds TOP (LIMIT,OFFSET) clause to the query
|
||||
*
|
||||
* @param string $query SQL query
|
||||
* @param int $limit Number of rows
|
||||
* @param int $offset Offset
|
||||
*
|
||||
* @return string SQL query
|
||||
*/
|
||||
protected function set_limit($query, $limit = 0, $offset = 0)
|
||||
{
|
||||
$limit = intval($limit);
|
||||
$offset = intval($offset);
|
||||
$end = $offset + $limit;
|
||||
|
||||
// query without OFFSET
|
||||
if (!$offset) {
|
||||
$query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
|
||||
return $query;
|
||||
}
|
||||
|
||||
$orderby = stristr($query, 'ORDER BY');
|
||||
$offset += 1;
|
||||
|
||||
if ($orderby !== false) {
|
||||
$query = trim(substr($query, 0, -1 * strlen($orderby)));
|
||||
}
|
||||
else {
|
||||
// it shouldn't happen, paging without sorting has not much sense
|
||||
// @FIXME: I don't know how to build paging query without ORDER BY
|
||||
$orderby = "ORDER BY 1";
|
||||
}
|
||||
|
||||
$query = preg_replace('/^SELECT\s/i', '', $query);
|
||||
$query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
|
||||
. " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PDO DSN string from DSN array
|
||||
*/
|
||||
protected function dsn_string($dsn)
|
||||
{
|
||||
$params = array();
|
||||
$result = $dsn['phptype'] . ':';
|
||||
|
||||
if ($dsn['hostspec']) {
|
||||
$host = $dsn['hostspec'];
|
||||
if ($dsn['port']) {
|
||||
$host .= ',' . $dsn['port'];
|
||||
}
|
||||
$params[] = 'host=' . $host;
|
||||
}
|
||||
|
||||
if ($dsn['database']) {
|
||||
$params[] = 'dbname=' . $dsn['database'];
|
||||
}
|
||||
|
||||
if (!empty($params)) {
|
||||
$result .= implode(';', $params);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse SQL file and fix table names according to table prefix
|
||||
*/
|
||||
protected function fix_table_names($sql)
|
||||
{
|
||||
if (!$this->options['table_prefix']) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// replace sequence names, and other postgres-specific commands
|
||||
$sql = preg_replace_callback(
|
||||
'/((TABLE|(?<!ON )UPDATE|INSERT INTO|FROM(?! deleted)| ON(?! (DELETE|UPDATE|\[PRIMARY\]))'
|
||||
. '|REFERENCES|CONSTRAINT|TRIGGER|INDEX)\s+(\[dbo\]\.)?[\[\]]*)([^\[\]\( \r\n]+)/',
|
||||
array($this, 'fix_table_names_callback'),
|
||||
$sql
|
||||
);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
239
data/web/rc/program/lib/Roundcube/rcube_db_mysql.php
Normal file
239
data/web/rc/program/lib/Roundcube/rcube_db_mysql.php
Normal file
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Database wrapper class that implements PHP PDO functions |
|
||||
| for MySQL database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database independent query interface
|
||||
*
|
||||
* This is a wrapper for the PHP PDO
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Database
|
||||
*/
|
||||
class rcube_db_mysql extends rcube_db
|
||||
{
|
||||
public $db_provider = 'mysql';
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param string $db_dsnw DSN for read/write operations
|
||||
* @param string $db_dsnr Optional DSN for read only operations
|
||||
* @param bool $pconn Enables persistent connections
|
||||
*/
|
||||
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
|
||||
{
|
||||
parent::__construct($db_dsnw, $db_dsnr, $pconn);
|
||||
|
||||
// SQL identifiers quoting
|
||||
$this->options['identifier_start'] = '`';
|
||||
$this->options['identifier_end'] = '`';
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver-specific configuration of database connection
|
||||
*
|
||||
* @param array $dsn DSN for DB connections
|
||||
* @param PDO $dbh Connection handler
|
||||
*/
|
||||
protected function conn_configure($dsn, $dbh)
|
||||
{
|
||||
$dbh->query("SET NAMES 'utf8'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract SQL statement for value concatenation
|
||||
*
|
||||
* @return string SQL statement to be used in query
|
||||
*/
|
||||
public function concat(/* col1, col2, ... */)
|
||||
{
|
||||
$args = func_get_args();
|
||||
|
||||
if (is_array($args[0])) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
return 'CONCAT(' . join(', ', $args) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PDO DSN string from DSN array
|
||||
*
|
||||
* @param array $dsn DSN parameters
|
||||
*
|
||||
* @return string Connection string
|
||||
*/
|
||||
protected function dsn_string($dsn)
|
||||
{
|
||||
$params = array();
|
||||
$result = 'mysql:';
|
||||
|
||||
if ($dsn['database']) {
|
||||
$params[] = 'dbname=' . $dsn['database'];
|
||||
}
|
||||
|
||||
if ($dsn['hostspec']) {
|
||||
$params[] = 'host=' . $dsn['hostspec'];
|
||||
}
|
||||
|
||||
if ($dsn['port']) {
|
||||
$params[] = 'port=' . $dsn['port'];
|
||||
}
|
||||
|
||||
if ($dsn['socket']) {
|
||||
$params[] = 'unix_socket=' . $dsn['socket'];
|
||||
}
|
||||
|
||||
$params[] = 'charset=utf8';
|
||||
|
||||
if (!empty($params)) {
|
||||
$result .= implode(';', $params);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns driver-specific connection options
|
||||
*
|
||||
* @param array $dsn DSN parameters
|
||||
*
|
||||
* @return array Connection options
|
||||
*/
|
||||
protected function dsn_options($dsn)
|
||||
{
|
||||
$result = parent::dsn_options($dsn);
|
||||
|
||||
if (!empty($dsn['key'])) {
|
||||
$result[PDO::MYSQL_ATTR_SSL_KEY] = $dsn['key'];
|
||||
}
|
||||
|
||||
if (!empty($dsn['cipher'])) {
|
||||
$result[PDO::MYSQL_ATTR_SSL_CIPHER] = $dsn['cipher'];
|
||||
}
|
||||
|
||||
if (!empty($dsn['cert'])) {
|
||||
$result[PDO::MYSQL_ATTR_SSL_CERT] = $dsn['cert'];
|
||||
}
|
||||
|
||||
if (!empty($dsn['capath'])) {
|
||||
$result[PDO::MYSQL_ATTR_SSL_CAPATH] = $dsn['capath'];
|
||||
}
|
||||
|
||||
if (!empty($dsn['ca'])) {
|
||||
$result[PDO::MYSQL_ATTR_SSL_CA] = $dsn['ca'];
|
||||
}
|
||||
|
||||
// Always return matching (not affected only) rows count
|
||||
$result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
|
||||
|
||||
// Enable AUTOCOMMIT mode (#1488902)
|
||||
$result[PDO::ATTR_AUTOCOMMIT] = true;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of tables in a database
|
||||
*
|
||||
* @return array List of all tables of the current database
|
||||
*/
|
||||
public function list_tables()
|
||||
{
|
||||
// get tables if not cached
|
||||
if ($this->tables === null) {
|
||||
// first fetch current database name
|
||||
$d = $this->query("SELECT database()");
|
||||
$d = $this->fetch_array($d);
|
||||
|
||||
// get list of tables in current database
|
||||
$q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"
|
||||
. " WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'"
|
||||
. " ORDER BY TABLE_NAME", $d ? $d[0] : '');
|
||||
|
||||
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
|
||||
}
|
||||
|
||||
return $this->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database runtime variables
|
||||
*
|
||||
* @param string $varname Variable name
|
||||
* @param mixed $default Default value if variable is not set
|
||||
*
|
||||
* @return mixed Variable value or default
|
||||
*/
|
||||
public function get_variable($varname, $default = null)
|
||||
{
|
||||
if (!isset($this->variables)) {
|
||||
$this->variables = array();
|
||||
}
|
||||
|
||||
if (array_key_exists($varname, $this->variables)) {
|
||||
return $this->variables[$varname];
|
||||
}
|
||||
|
||||
// configured value has higher prio
|
||||
$conf_value = rcube::get_instance()->config->get('db_' . $varname);
|
||||
if ($conf_value !== null) {
|
||||
return $this->variables[$varname] = $conf_value;
|
||||
}
|
||||
|
||||
$result = $this->query('SHOW VARIABLES LIKE ?', $varname);
|
||||
|
||||
while ($row = $this->fetch_array($result)) {
|
||||
$this->variables[$row[0]] = $row[1];
|
||||
}
|
||||
|
||||
// not found, use default
|
||||
if (!isset($this->variables[$varname])) {
|
||||
$this->variables[$varname] = $default;
|
||||
}
|
||||
|
||||
return $this->variables[$varname];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle DB errors, re-issue the query on deadlock errors from InnoDB row-level locking
|
||||
*
|
||||
* @param string Query that triggered the error
|
||||
* @return mixed Result to be stored and returned
|
||||
*/
|
||||
protected function handle_error($query)
|
||||
{
|
||||
$error = $this->dbh->errorInfo();
|
||||
|
||||
// retry after "Deadlock found when trying to get lock" errors
|
||||
$retries = 2;
|
||||
while ($error[1] == 1213 && $retries >= 0) {
|
||||
usleep(50000); // wait 50 ms
|
||||
$result = $this->dbh->query($query);
|
||||
if ($result !== false) {
|
||||
return $result;
|
||||
}
|
||||
$error = $this->dbh->errorInfo();
|
||||
$retries--;
|
||||
}
|
||||
|
||||
return parent::handle_error($query);
|
||||
}
|
||||
|
||||
}
|
||||
618
data/web/rc/program/lib/Roundcube/rcube_db_oracle.php
Normal file
618
data/web/rc/program/lib/Roundcube/rcube_db_oracle.php
Normal file
@@ -0,0 +1,618 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2011-2014, Kolab Systems AG |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Database wrapper class that implements database functions |
|
||||
| for Oracle database using OCI8 extension |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database independent query interface
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Database
|
||||
*/
|
||||
class rcube_db_oracle extends rcube_db
|
||||
{
|
||||
public $db_provider = 'oracle';
|
||||
|
||||
|
||||
/**
|
||||
* Create connection instance
|
||||
*/
|
||||
protected function conn_create($dsn)
|
||||
{
|
||||
// Get database specific connection options
|
||||
$dsn_options = $this->dsn_options($dsn);
|
||||
|
||||
$function = $this->db_pconn ? 'oci_pconnect' : 'oci_connect';
|
||||
|
||||
if (!function_exists($function)) {
|
||||
$this->db_error = true;
|
||||
$this->db_error_msg = 'OCI8 extension not loaded. See http://php.net/manual/en/book.oci8.php';
|
||||
|
||||
rcube::raise_error(array('code' => 500, 'type' => 'db',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => $this->db_error_msg), true, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// connect
|
||||
$dbh = @$function($dsn['username'], $dsn['password'], $dsn_options['database'], $dsn_options['charset']);
|
||||
|
||||
if (!$dbh) {
|
||||
$error = oci_error();
|
||||
$this->db_error = true;
|
||||
$this->db_error_msg = $error['message'];
|
||||
|
||||
rcube::raise_error(array('code' => 500, 'type' => 'db',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => $this->db_error_msg), true, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// configure session
|
||||
$this->conn_configure($dsn, $dbh);
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver-specific configuration of database connection
|
||||
*
|
||||
* @param array $dsn DSN for DB connections
|
||||
* @param PDO $dbh Connection handler
|
||||
*/
|
||||
protected function conn_configure($dsn, $dbh)
|
||||
{
|
||||
$init_queries = array(
|
||||
"ALTER SESSION SET nls_date_format = 'YYYY-MM-DD'",
|
||||
"ALTER SESSION SET nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'",
|
||||
);
|
||||
|
||||
foreach ($init_queries as $query) {
|
||||
$stmt = oci_parse($dbh, $query);
|
||||
oci_execute($stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection state checker
|
||||
*
|
||||
* @return boolean True if in connected state
|
||||
*/
|
||||
public function is_connected()
|
||||
{
|
||||
return empty($this->dbh) ? false : $this->db_connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query with limits
|
||||
*
|
||||
* @param string $query SQL query to execute
|
||||
* @param int $offset Offset for LIMIT statement
|
||||
* @param int $numrows Number of rows for LIMIT statement
|
||||
* @param array $params Values to be inserted in query
|
||||
*
|
||||
* @return PDOStatement|bool Query handle or False on error
|
||||
*/
|
||||
protected function _query($query, $offset, $numrows, $params)
|
||||
{
|
||||
$query = ltrim($query);
|
||||
|
||||
$this->db_connect($this->dsn_select($query), true);
|
||||
|
||||
// check connection before proceeding
|
||||
if (!$this->is_connected()) {
|
||||
return $this->last_result = false;
|
||||
}
|
||||
|
||||
if ($numrows || $offset) {
|
||||
$query = $this->set_limit($query, $numrows, $offset);
|
||||
}
|
||||
|
||||
// replace self::DEFAULT_QUOTE with driver-specific quoting
|
||||
$query = $this->query_parse($query);
|
||||
|
||||
// Because in Roundcube we mostly use queries that are
|
||||
// executed only once, we will not use prepared queries
|
||||
$pos = 0;
|
||||
$idx = 0;
|
||||
$args = array();
|
||||
|
||||
if (count($params)) {
|
||||
while ($pos = strpos($query, '?', $pos)) {
|
||||
if ($query[$pos+1] == '?') { // skip escaped '?'
|
||||
$pos += 2;
|
||||
}
|
||||
else {
|
||||
$val = $this->quote($params[$idx++]);
|
||||
|
||||
// long strings are not allowed inline, need to be parametrized
|
||||
if (strlen($val) > 4000) {
|
||||
$key = ':param' . (count($args) + 1);
|
||||
$args[$key] = $params[$idx-1];
|
||||
$val = $key;
|
||||
}
|
||||
|
||||
unset($params[$idx-1]);
|
||||
$query = substr_replace($query, $val, $pos, 1);
|
||||
$pos += strlen($val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query = rtrim($query, " \t\n\r\0\x0B;");
|
||||
|
||||
// replace escaped '?' and quotes back to normal, see self::quote()
|
||||
$query = str_replace(
|
||||
array('??', self::DEFAULT_QUOTE.self::DEFAULT_QUOTE),
|
||||
array('?', self::DEFAULT_QUOTE),
|
||||
$query
|
||||
);
|
||||
|
||||
// log query
|
||||
$this->debug($query);
|
||||
|
||||
// destroy reference to previous result
|
||||
$this->last_result = null;
|
||||
$this->db_error_msg = null;
|
||||
|
||||
// prepare query
|
||||
$result = @oci_parse($this->dbh, $query);
|
||||
$mode = $this->in_transaction ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
|
||||
|
||||
if ($result) {
|
||||
foreach (array_keys($args) as $param) {
|
||||
oci_bind_by_name($result, $param, $args[$param], -1, SQLT_LNG);
|
||||
}
|
||||
}
|
||||
|
||||
// execute query
|
||||
if (!$result || !@oci_execute($result, $mode)) {
|
||||
$result = $this->handle_error($query, $result);
|
||||
}
|
||||
|
||||
return $this->last_result = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to handle DB errors.
|
||||
* This by default logs the error but could be overriden by a driver implementation
|
||||
*
|
||||
* @param string Query that triggered the error
|
||||
* @return mixed Result to be stored and returned
|
||||
*/
|
||||
protected function handle_error($query, $result = null)
|
||||
{
|
||||
$error = oci_error(is_resource($result) ? $result : $this->dbh);
|
||||
|
||||
// @TODO: Find error codes for key errors
|
||||
if (empty($this->options['ignore_key_errors']) || !in_array($error['code'], array('23000', '23505'))) {
|
||||
$this->db_error = true;
|
||||
$this->db_error_msg = sprintf('[%s] %s', $error['code'], $error['message']);
|
||||
|
||||
rcube::raise_error(array('code' => 500, 'type' => 'db',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => $this->db_error_msg . " (SQL Query: $query)"
|
||||
), true, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last inserted record ID
|
||||
*
|
||||
* @param string $table Table name (to find the incremented sequence)
|
||||
*
|
||||
* @return mixed ID or false on failure
|
||||
*/
|
||||
public function insert_id($table = null)
|
||||
{
|
||||
if (!$this->db_connected || $this->db_mode == 'r' || empty($table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sequence = $this->quote_identifier($this->sequence_name($table));
|
||||
$result = $this->query("SELECT $sequence.currval FROM dual");
|
||||
$result = $this->fetch_array($result);
|
||||
|
||||
return $result[0] ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of affected rows for the last query
|
||||
*
|
||||
* @param mixed $result Optional query handle
|
||||
*
|
||||
* @return int Number of (matching) rows
|
||||
*/
|
||||
public function affected_rows($result = null)
|
||||
{
|
||||
if ($result || ($result === null && ($result = $this->last_result))) {
|
||||
return oci_num_rows($result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of rows for a SQL query
|
||||
* If no query handle is specified, the last query will be taken as reference
|
||||
*
|
||||
* @param mixed $result Optional query handle
|
||||
* @return mixed Number of rows or false on failure
|
||||
* @deprecated This method shows very poor performance and should be avoided.
|
||||
*/
|
||||
public function num_rows($result = null)
|
||||
{
|
||||
// not implemented
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an associative array for one row
|
||||
* If no query handle is specified, the last query will be taken as reference
|
||||
*
|
||||
* @param mixed $result Optional query handle
|
||||
*
|
||||
* @return mixed Array with col values or false on failure
|
||||
*/
|
||||
public function fetch_assoc($result = null)
|
||||
{
|
||||
return $this->_fetch_row($result, OCI_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an index array for one row
|
||||
* If no query handle is specified, the last query will be taken as reference
|
||||
*
|
||||
* @param mixed $result Optional query handle
|
||||
*
|
||||
* @return mixed Array with col values or false on failure
|
||||
*/
|
||||
public function fetch_array($result = null)
|
||||
{
|
||||
return $this->_fetch_row($result, OCI_NUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get col values for a result row
|
||||
*
|
||||
* @param mixed $result Optional query handle
|
||||
* @param int $mode Fetch mode identifier
|
||||
*
|
||||
* @return mixed Array with col values or false on failure
|
||||
*/
|
||||
protected function _fetch_row($result, $mode)
|
||||
{
|
||||
if ($result || ($result === null && ($result = $this->last_result))) {
|
||||
return oci_fetch_array($result, $mode + OCI_RETURN_NULLS + OCI_RETURN_LOBS);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats input so it can be safely used in a query
|
||||
* PDO_OCI does not implement quote() method
|
||||
*
|
||||
* @param mixed $input Value to quote
|
||||
* @param string $type Type of data (integer, bool, ident)
|
||||
*
|
||||
* @return string Quoted/converted string for use in query
|
||||
*/
|
||||
public function quote($input, $type = null)
|
||||
{
|
||||
// handle int directly for better performance
|
||||
if ($type == 'integer' || $type == 'int') {
|
||||
return intval($input);
|
||||
}
|
||||
|
||||
if (is_null($input)) {
|
||||
return 'NULL';
|
||||
}
|
||||
|
||||
if ($type == 'ident') {
|
||||
return $this->quote_identifier($input);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bool':
|
||||
case 'integer':
|
||||
return intval($input);
|
||||
default:
|
||||
return "'" . strtr($input, array(
|
||||
'?' => '??',
|
||||
"'" => "''",
|
||||
rcube_db::DEFAULT_QUOTE => rcube_db::DEFAULT_QUOTE . rcube_db::DEFAULT_QUOTE
|
||||
)) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return correct name for a specific database sequence
|
||||
*
|
||||
* @param string $table Table name
|
||||
*
|
||||
* @return string Translated sequence name
|
||||
*/
|
||||
protected function sequence_name($table)
|
||||
{
|
||||
// Note: we support only one sequence per table
|
||||
// Note: The sequence name must be <table_name>_seq
|
||||
$sequence = $table . '_seq';
|
||||
|
||||
// modify sequence name if prefix is configured
|
||||
if ($prefix = $this->options['table_prefix']) {
|
||||
return $prefix . $sequence;
|
||||
}
|
||||
|
||||
return $sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL statement for case insensitive LIKE
|
||||
*
|
||||
* @param string $column Field name
|
||||
* @param string $value Search value
|
||||
*
|
||||
* @return string SQL statement to use in query
|
||||
*/
|
||||
public function ilike($column, $value)
|
||||
{
|
||||
return 'UPPER(' . $this->quote_identifier($column) . ') LIKE UPPER(' . $this->quote($value) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL function for current time and date
|
||||
*
|
||||
* @param int $interval Optional interval (in seconds) to add/subtract
|
||||
*
|
||||
* @return string SQL function to use in query
|
||||
*/
|
||||
public function now($interval = 0)
|
||||
{
|
||||
if ($interval) {
|
||||
$interval = intval($interval);
|
||||
return "current_timestamp + INTERVAL '$interval' SECOND";
|
||||
}
|
||||
|
||||
return "current_timestamp";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL statement to convert a field value into a unix timestamp
|
||||
*
|
||||
* @param string $field Field name
|
||||
*
|
||||
* @return string SQL statement to use in query
|
||||
* @deprecated
|
||||
*/
|
||||
public function unixtimestamp($field)
|
||||
{
|
||||
return "(($field - to_date('1970-01-01','YYYY-MM-DD')) * 60 * 60 * 24)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds TOP (LIMIT,OFFSET) clause to the query
|
||||
*
|
||||
* @param string $query SQL query
|
||||
* @param int $limit Number of rows
|
||||
* @param int $offset Offset
|
||||
*
|
||||
* @return string SQL query
|
||||
*/
|
||||
protected function set_limit($query, $limit = 0, $offset = 0)
|
||||
{
|
||||
$limit = intval($limit);
|
||||
$offset = intval($offset);
|
||||
$end = $offset + $limit;
|
||||
|
||||
// @TODO: Oracle 12g has better OFFSET support
|
||||
|
||||
if (!$offset) {
|
||||
$query = "SELECT * FROM ($query) a WHERE rownum <= $end";
|
||||
}
|
||||
else {
|
||||
$query = "SELECT * FROM (SELECT a.*, rownum as rn FROM ($query) a WHERE rownum <= $end) b WHERE rn > $offset";
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse SQL file and fix table names according to table prefix
|
||||
*/
|
||||
protected function fix_table_names($sql)
|
||||
{
|
||||
if (!$this->options['table_prefix']) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$sql = parent::fix_table_names($sql);
|
||||
|
||||
// replace sequence names, and other Oracle-specific commands
|
||||
$sql = preg_replace_callback('/((SEQUENCE ["]?)([^" \r\n]+)/',
|
||||
array($this, 'fix_table_names_callback'),
|
||||
$sql
|
||||
);
|
||||
|
||||
$sql = preg_replace_callback(
|
||||
'/([ \r\n]+["]?)([^"\' \r\n\.]+)(["]?\.nextval)/',
|
||||
array($this, 'fix_table_names_seq_callback'),
|
||||
$sql
|
||||
);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preg_replace callback for fix_table_names()
|
||||
*/
|
||||
protected function fix_table_names_seq_callback($matches)
|
||||
{
|
||||
return $matches[1] . $this->options['table_prefix'] . $matches[2] . $matches[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns connection options from DSN array
|
||||
*/
|
||||
protected function dsn_options($dsn)
|
||||
{
|
||||
$params = array();
|
||||
|
||||
if ($dsn['hostspec']) {
|
||||
$host = $dsn['hostspec'];
|
||||
if ($dsn['port']) {
|
||||
$host .= ':' . $dsn['port'];
|
||||
}
|
||||
|
||||
$params['database'] = $host . '/' . $dsn['database'];
|
||||
}
|
||||
|
||||
$params['charset'] = 'UTF8';
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given SQL script
|
||||
*
|
||||
* @param string $sql SQL queries to execute
|
||||
*
|
||||
* @return boolen True on success, False on error
|
||||
*/
|
||||
public function exec_script($sql)
|
||||
{
|
||||
$sql = $this->fix_table_names($sql);
|
||||
$buff = '';
|
||||
$body = false;
|
||||
|
||||
foreach (explode("\n", $sql) as $line) {
|
||||
$tok = strtolower(trim($line));
|
||||
if (preg_match('/^--/', $line) || $tok == '' || $tok == '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$buff .= $line . "\n";
|
||||
|
||||
// detect PL/SQL function bodies, don't break on semicolon
|
||||
if ($body && $tok == 'end;') {
|
||||
$body = false;
|
||||
}
|
||||
else if (!$body && $tok == 'begin') {
|
||||
$body = true;
|
||||
}
|
||||
|
||||
if (!$body && substr($tok, -1) == ';') {
|
||||
$this->query($buff);
|
||||
$buff = '';
|
||||
if ($this->db_error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$this->db_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start transaction
|
||||
*
|
||||
* @return bool True on success, False on failure
|
||||
*/
|
||||
public function startTransaction()
|
||||
{
|
||||
$this->db_connect('w', true);
|
||||
|
||||
// check connection before proceeding
|
||||
if (!$this->is_connected()) {
|
||||
return $this->last_result = false;
|
||||
}
|
||||
|
||||
$this->debug('BEGIN TRANSACTION');
|
||||
|
||||
return $this->last_result = $this->in_transaction = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit transaction
|
||||
*
|
||||
* @return bool True on success, False on failure
|
||||
*/
|
||||
public function endTransaction()
|
||||
{
|
||||
$this->db_connect('w', true);
|
||||
|
||||
// check connection before proceeding
|
||||
if (!$this->is_connected()) {
|
||||
return $this->last_result = false;
|
||||
}
|
||||
|
||||
$this->debug('COMMIT TRANSACTION');
|
||||
|
||||
if ($result = @oci_commit($this->dbh)) {
|
||||
$this->in_transaction = true;
|
||||
}
|
||||
else {
|
||||
$this->handle_error('COMMIT');
|
||||
}
|
||||
|
||||
return $this->last_result = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback transaction
|
||||
*
|
||||
* @return bool True on success, False on failure
|
||||
*/
|
||||
public function rollbackTransaction()
|
||||
{
|
||||
$this->db_connect('w', true);
|
||||
|
||||
// check connection before proceeding
|
||||
if (!$this->is_connected()) {
|
||||
return $this->last_result = false;
|
||||
}
|
||||
|
||||
$this->debug('ROLLBACK TRANSACTION');
|
||||
|
||||
if (@oci_rollback($this->dbh)) {
|
||||
$this->in_transaction = false;
|
||||
}
|
||||
else {
|
||||
$this->handle_error('ROLLBACK');
|
||||
}
|
||||
|
||||
return $this->last_result = $this->dbh->rollBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate database connection.
|
||||
*/
|
||||
public function closeConnection()
|
||||
{
|
||||
// release statement and close connection(s)
|
||||
$this->last_result = null;
|
||||
foreach ($this->dbhs as $dbh) {
|
||||
oci_close($dbh);
|
||||
}
|
||||
|
||||
parent::closeConnection();
|
||||
}
|
||||
}
|
||||
233
data/web/rc/program/lib/Roundcube/rcube_db_pgsql.php
Normal file
233
data/web/rc/program/lib/Roundcube/rcube_db_pgsql.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Database wrapper class that implements PHP PDO functions |
|
||||
| for PostgreSQL database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database independent query interface
|
||||
* This is a wrapper for the PHP PDO
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Database
|
||||
*/
|
||||
class rcube_db_pgsql extends rcube_db
|
||||
{
|
||||
public $db_provider = 'postgres';
|
||||
|
||||
/**
|
||||
* Driver-specific configuration of database connection
|
||||
*
|
||||
* @param array $dsn DSN for DB connections
|
||||
* @param PDO $dbh Connection handler
|
||||
*/
|
||||
protected function conn_configure($dsn, $dbh)
|
||||
{
|
||||
$dbh->query("SET NAMES 'utf8'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last inserted record ID
|
||||
*
|
||||
* @param string $table Table name (to find the incremented sequence)
|
||||
*
|
||||
* @return mixed ID or false on failure
|
||||
*/
|
||||
public function insert_id($table = null)
|
||||
{
|
||||
if (!$this->db_connected || $this->db_mode == 'r') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($table) {
|
||||
$table = $this->sequence_name($table);
|
||||
}
|
||||
|
||||
$id = $this->dbh->lastInsertId($table);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return correct name for a specific database sequence
|
||||
*
|
||||
* @param string $table Table name
|
||||
*
|
||||
* @return string Translated sequence name
|
||||
*/
|
||||
protected function sequence_name($table)
|
||||
{
|
||||
// Note: we support only one sequence per table
|
||||
// Note: The sequence name must be <table_name>_seq
|
||||
$sequence = $table . '_seq';
|
||||
|
||||
// modify sequence name if prefix is configured
|
||||
if ($prefix = $this->options['table_prefix']) {
|
||||
return $prefix . $sequence;
|
||||
}
|
||||
|
||||
return $sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL statement to convert a field value into a unix timestamp
|
||||
*
|
||||
* @param string $field Field name
|
||||
*
|
||||
* @return string SQL statement to use in query
|
||||
* @deprecated
|
||||
*/
|
||||
public function unixtimestamp($field)
|
||||
{
|
||||
return "EXTRACT (EPOCH FROM $field)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL function for current time and date
|
||||
*
|
||||
* @param int $interval Optional interval (in seconds) to add/subtract
|
||||
*
|
||||
* @return string SQL function to use in query
|
||||
*/
|
||||
public function now($interval = 0)
|
||||
{
|
||||
if ($interval) {
|
||||
$add = ' ' . ($interval > 0 ? '+' : '-') . " interval '";
|
||||
$add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
|
||||
$add .= " seconds'";
|
||||
}
|
||||
|
||||
return "now()" . $add;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL statement for case insensitive LIKE
|
||||
*
|
||||
* @param string $column Field name
|
||||
* @param string $value Search value
|
||||
*
|
||||
* @return string SQL statement to use in query
|
||||
*/
|
||||
public function ilike($column, $value)
|
||||
{
|
||||
return $this->quote_identifier($column) . ' ILIKE ' . $this->quote($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database runtime variables
|
||||
*
|
||||
* @param string $varname Variable name
|
||||
* @param mixed $default Default value if variable is not set
|
||||
*
|
||||
* @return mixed Variable value or default
|
||||
*/
|
||||
public function get_variable($varname, $default = null)
|
||||
{
|
||||
// There's a known case when max_allowed_packet is queried
|
||||
// PostgreSQL doesn't have such limit, return immediately
|
||||
if ($varname == 'max_allowed_packet') {
|
||||
return rcube::get_instance()->config->get('db_' . $varname, $default);
|
||||
}
|
||||
|
||||
$this->variables[$varname] = rcube::get_instance()->config->get('db_' . $varname);
|
||||
|
||||
if (!isset($this->variables)) {
|
||||
$this->variables = array();
|
||||
|
||||
$result = $this->query('SHOW ALL');
|
||||
|
||||
while ($row = $this->fetch_array($result)) {
|
||||
$this->variables[$row[0]] = $row[1];
|
||||
}
|
||||
}
|
||||
|
||||
return isset($this->variables[$varname]) ? $this->variables[$varname] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of tables in a database
|
||||
*
|
||||
* @return array List of all tables of the current database
|
||||
*/
|
||||
public function list_tables()
|
||||
{
|
||||
// get tables if not cached
|
||||
if ($this->tables === null) {
|
||||
$q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"
|
||||
. " WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('pg_catalog', 'information_schema')"
|
||||
. " ORDER BY TABLE_NAME");
|
||||
|
||||
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
|
||||
}
|
||||
|
||||
return $this->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PDO DSN string from DSN array
|
||||
*
|
||||
* @param array $dsn DSN parameters
|
||||
*
|
||||
* @return string DSN string
|
||||
*/
|
||||
protected function dsn_string($dsn)
|
||||
{
|
||||
$params = array();
|
||||
$result = 'pgsql:';
|
||||
|
||||
if ($dsn['hostspec']) {
|
||||
$params[] = 'host=' . $dsn['hostspec'];
|
||||
}
|
||||
else if ($dsn['socket']) {
|
||||
$params[] = 'host=' . $dsn['socket'];
|
||||
}
|
||||
|
||||
if ($dsn['port']) {
|
||||
$params[] = 'port=' . $dsn['port'];
|
||||
}
|
||||
|
||||
if ($dsn['database']) {
|
||||
$params[] = 'dbname=' . $dsn['database'];
|
||||
}
|
||||
|
||||
if (!empty($params)) {
|
||||
$result .= implode(';', $params);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse SQL file and fix table names according to table prefix
|
||||
*/
|
||||
protected function fix_table_names($sql)
|
||||
{
|
||||
if (!$this->options['table_prefix']) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$sql = parent::fix_table_names($sql);
|
||||
|
||||
// replace sequence names, and other postgres-specific commands
|
||||
$sql = preg_replace_callback(
|
||||
'/((SEQUENCE |RENAME TO |nextval\()["\']*)([^"\' \r\n]+)/',
|
||||
array($this, 'fix_table_names_callback'),
|
||||
$sql
|
||||
);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
161
data/web/rc/program/lib/Roundcube/rcube_db_sqlite.php
Normal file
161
data/web/rc/program/lib/Roundcube/rcube_db_sqlite.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Database wrapper class that implements PHP PDO functions |
|
||||
| for SQLite database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database independent query interface
|
||||
* This is a wrapper for the PHP PDO
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Database
|
||||
*/
|
||||
class rcube_db_sqlite extends rcube_db
|
||||
{
|
||||
public $db_provider = 'sqlite';
|
||||
|
||||
/**
|
||||
* Prepare connection
|
||||
*/
|
||||
protected function conn_prepare($dsn)
|
||||
{
|
||||
// Create database file, required by PDO to exist on connection
|
||||
if (!empty($dsn['database']) && !file_exists($dsn['database'])) {
|
||||
$created = touch($dsn['database']);
|
||||
|
||||
// File mode setting, for compat. with MDB2
|
||||
if (!empty($dsn['mode']) && $created) {
|
||||
chmod($dsn['database'], octdec($dsn['mode']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure connection, create database if not exists
|
||||
*/
|
||||
protected function conn_configure($dsn, $dbh)
|
||||
{
|
||||
// Initialize database structure in file is empty
|
||||
if (!empty($dsn['database']) && !filesize($dsn['database'])) {
|
||||
$data = file_get_contents(RCUBE_INSTALL_PATH . 'SQL/sqlite.initial.sql');
|
||||
|
||||
if (strlen($data)) {
|
||||
$this->debug('INITIALIZE DATABASE');
|
||||
|
||||
$q = $dbh->exec($data);
|
||||
|
||||
if ($q === false) {
|
||||
$error = $dbh->errorInfo();
|
||||
$this->db_error = true;
|
||||
$this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
|
||||
|
||||
rcube::raise_error(array('code' => 500, 'type' => 'db',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => $this->db_error_msg), true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL statement to convert a field value into a unix timestamp
|
||||
*
|
||||
* @param string $field Field name
|
||||
*
|
||||
* @return string SQL statement to use in query
|
||||
* @deprecated
|
||||
*/
|
||||
public function unixtimestamp($field)
|
||||
{
|
||||
return "strftime('%s', $field)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL function for current time and date
|
||||
*
|
||||
* @param int $interval Optional interval (in seconds) to add/subtract
|
||||
*
|
||||
* @return string SQL function to use in query
|
||||
*/
|
||||
public function now($interval = 0)
|
||||
{
|
||||
if ($interval) {
|
||||
$add = ($interval > 0 ? '+' : '') . intval($interval) . ' seconds';
|
||||
}
|
||||
|
||||
return "datetime('now'" . ($add ? ",'$add'" : "") . ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of tables in database
|
||||
*
|
||||
* @return array List of all tables of the current database
|
||||
*/
|
||||
public function list_tables()
|
||||
{
|
||||
if ($this->tables === null) {
|
||||
$q = $this->query('SELECT name FROM sqlite_master'
|
||||
.' WHERE type = \'table\' ORDER BY name');
|
||||
|
||||
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
|
||||
}
|
||||
|
||||
return $this->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of columns in database table
|
||||
*
|
||||
* @param string $table Table name
|
||||
*
|
||||
* @return array List of table cols
|
||||
*/
|
||||
public function list_cols($table)
|
||||
{
|
||||
$q = $this->query('SELECT sql FROM sqlite_master WHERE type = ? AND name = ?',
|
||||
array('table', $table));
|
||||
|
||||
$columns = array();
|
||||
|
||||
if ($sql = $this->fetch_array($q)) {
|
||||
$sql = $sql[0];
|
||||
$start_pos = strpos($sql, '(');
|
||||
$end_pos = strrpos($sql, ')');
|
||||
$sql = substr($sql, $start_pos+1, $end_pos-$start_pos-1);
|
||||
$lines = explode(',', $sql);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = explode(' ', trim($line));
|
||||
|
||||
if ($line[0] && strpos($line[0], '--') !== 0) {
|
||||
$column = $line[0];
|
||||
$columns[] = trim($column, '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build DSN string for PDO constructor
|
||||
*/
|
||||
protected function dsn_string($dsn)
|
||||
{
|
||||
return $dsn['phptype'] . ':' . $dsn['database'];
|
||||
}
|
||||
}
|
||||
57
data/web/rc/program/lib/Roundcube/rcube_db_sqlsrv.php
Normal file
57
data/web/rc/program/lib/Roundcube/rcube_db_sqlsrv.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Database wrapper class that implements PHP PDO functions |
|
||||
| for MS SQL Server database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database independent query interface
|
||||
* This is a wrapper for the PHP PDO
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Database
|
||||
*/
|
||||
class rcube_db_sqlsrv extends rcube_db_mssql
|
||||
{
|
||||
/**
|
||||
* Returns PDO DSN string from DSN array
|
||||
*/
|
||||
protected function dsn_string($dsn)
|
||||
{
|
||||
$params = array();
|
||||
$result = 'sqlsrv:';
|
||||
|
||||
if ($dsn['hostspec']) {
|
||||
$host = $dsn['hostspec'];
|
||||
|
||||
if ($dsn['port']) {
|
||||
$host .= ',' . $dsn['port'];
|
||||
}
|
||||
|
||||
$params[] = 'Server=' . $host;
|
||||
}
|
||||
|
||||
if ($dsn['database']) {
|
||||
$params[] = 'Database=' . $dsn['database'];
|
||||
}
|
||||
|
||||
if (!empty($params)) {
|
||||
$result .= implode(';', $params);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
150
data/web/rc/program/lib/Roundcube/rcube_enriched.php
Normal file
150
data/web/rc/program/lib/Roundcube/rcube_enriched.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Helper class to convert Enriched to HTML format (RFC 1523, 1896) |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Ryo Chijiiwa (IlohaMail) |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for Enriched to HTML conversion
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_enriched
|
||||
{
|
||||
protected static function convert_newlines($body)
|
||||
{
|
||||
// remove single newlines, convert N newlines to N-1
|
||||
$body = str_replace("\r\n", "\n", $body);
|
||||
$len = strlen($body);
|
||||
$nl = 0;
|
||||
$out = '';
|
||||
|
||||
for ($i=0; $i<$len; $i++) {
|
||||
$c = $body[$i];
|
||||
if (ord($c) == 10)
|
||||
$nl++;
|
||||
if ($nl && ord($c) != 10)
|
||||
$nl = 0;
|
||||
if ($nl != 1)
|
||||
$out .= $c;
|
||||
else
|
||||
$out .= ' ';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected static function convert_formatting($body)
|
||||
{
|
||||
$replace = array(
|
||||
'<bold>' => '<b>', '</bold>' => '</b>',
|
||||
'<italic>' => '<i>', '</italic>' => '</i>',
|
||||
'<fixed>' => '<tt>', '</fixed>' => '</tt>',
|
||||
'<smaller>' => '<font size=-1>', '</smaller>'=> '</font>',
|
||||
'<bigger>' => '<font size=+1>', '</bigger>' => '</font>',
|
||||
'<underline>' => '<span style="text-decoration: underline">', '</underline>' => '</span>',
|
||||
'<flushleft>' => '<span style="text-align: left">', '</flushleft>' => '</span>',
|
||||
'<flushright>' => '<span style="text-align: right">', '</flushright>' => '</span>',
|
||||
'<flushboth>' => '<span style="text-align: justified">', '</flushboth>' => '</span>',
|
||||
'<indent>' => '<span style="padding-left: 20px">', '</indent>' => '</span>',
|
||||
'<indentright>' => '<span style="padding-right: 20px">', '</indentright>' => '</span>',
|
||||
);
|
||||
|
||||
return str_ireplace(array_keys($replace), array_values($replace), $body);
|
||||
}
|
||||
|
||||
protected static function convert_font($body)
|
||||
{
|
||||
$pattern = '/(.*)\<fontfamily\>\<param\>(.*)\<\/param\>(.*)\<\/fontfamily\>(.*)/ims';
|
||||
|
||||
while (preg_match($pattern, $body, $a)) {
|
||||
if (count($a) != 5)
|
||||
continue;
|
||||
|
||||
$body = $a[1].'<span style="font-family: '.$a[2].'">'.$a[3].'</span>'.$a[4];
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
protected static function convert_color($body)
|
||||
{
|
||||
$pattern = '/(.*)\<color\>\<param\>(.*)\<\/param\>(.*)\<\/color\>(.*)/ims';
|
||||
|
||||
while (preg_match($pattern, $body, $a)) {
|
||||
if (count($a) != 5)
|
||||
continue;
|
||||
|
||||
// extract color (either by name, or ####,####,####)
|
||||
if (strpos($a[2],',')) {
|
||||
$rgb = explode(',',$a[2]);
|
||||
$color = '#';
|
||||
for ($i=0; $i<3; $i++)
|
||||
$color .= substr($rgb[$i], 0, 2); // just take first 2 bytes
|
||||
}
|
||||
else {
|
||||
$color = $a[2];
|
||||
}
|
||||
|
||||
// put it all together
|
||||
$body = $a[1].'<span style="color: '.$color.'">'.$a[3].'</span>'.$a[4];
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
protected static function convert_excerpt($body)
|
||||
{
|
||||
$pattern = '/(.*)\<excerpt\>(.*)\<\/excerpt\>(.*)/i';
|
||||
|
||||
while (preg_match($pattern, $body, $a)) {
|
||||
if (count($a) != 4)
|
||||
continue;
|
||||
|
||||
$quoted = '';
|
||||
$lines = explode('<br>', $a[2]);
|
||||
|
||||
foreach ($lines as $line)
|
||||
$quoted .= '>'.$line.'<br>';
|
||||
|
||||
$body = $a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Enriched text into HTML format
|
||||
*
|
||||
* @param string $body Enriched text
|
||||
*
|
||||
* @return string HTML text
|
||||
*/
|
||||
public static function to_html($body)
|
||||
{
|
||||
$body = str_replace('<<','<',$body);
|
||||
$body = self::convert_newlines($body);
|
||||
$body = str_replace("\n", '<br>', $body);
|
||||
$body = self::convert_formatting($body);
|
||||
$body = self::convert_color($body);
|
||||
$body = self::convert_font($body);
|
||||
$body = self::convert_excerpt($body);
|
||||
//$body = nl2br($body);
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
722
data/web/rc/program/lib/Roundcube/rcube_html2text.php
Normal file
722
data/web/rc/program/lib/Roundcube/rcube_html2text.php
Normal file
@@ -0,0 +1,722 @@
|
||||
<?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@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@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Jon Abernathy <jon@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
|
||||
'/^.*<body[^>]*>\n*/is', // Anything before <body>
|
||||
'/<head[^>]*>.*?<\/head>/is', // <head>
|
||||
'/<script[^>]*>.*?<\/script>/is', // <script>
|
||||
'/<style[^>]*>.*?<\/style>/is', // <style>
|
||||
'/[\n\t]+/', // Newlines and tabs
|
||||
'/<p[^>]*>/i', // <p>
|
||||
'/<\/p>[\s\n\t]*<div[^>]*>/i', // </p> before <div>
|
||||
'/<br[^>]*>[\s\n\t]*<div[^>]*>/i', // <br> before <div>
|
||||
'/<br[^>]*>\s*/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
|
||||
'', // Anything before <body>
|
||||
'', // <head>
|
||||
'', // <script>
|
||||
'', // <style>
|
||||
' ', // Newlines and tabs
|
||||
"\n\n", // <p>
|
||||
"\n<div>", // </p> before <div>
|
||||
'<div>', // <br> before <div>
|
||||
"\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(
|
||||
"\xC2\xA0", // 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 = $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 &$text 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);
|
||||
|
||||
// Replace unicode nbsp to regular spaces
|
||||
$text = preg_replace('/\xC2\xA0/', ' ', $text);
|
||||
|
||||
// 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 "&quot;" 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;
|
||||
}
|
||||
|
||||
// skip links with href == content (#1490434)
|
||||
if ($link === $display) {
|
||||
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 &$text 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 &$text HTML content
|
||||
*/
|
||||
protected function _convert_blockquotes(&$text)
|
||||
{
|
||||
$level = 0;
|
||||
$offset = 0;
|
||||
while (($start = stripos($text, '<blockquote', $offset)) !== false) {
|
||||
$offset = $start + 12;
|
||||
do {
|
||||
$end = stripos($text, '</blockquote>', $offset);
|
||||
$next = stripos($text, '<blockquote', $offset);
|
||||
|
||||
// nested <blockquote>, skip
|
||||
if ($next !== false && $next < $end) {
|
||||
$offset = $next + 12;
|
||||
$level++;
|
||||
}
|
||||
// nested </blockquote> tag
|
||||
if ($end !== false && $level > 0) {
|
||||
$offset = $end + 12;
|
||||
$level--;
|
||||
}
|
||||
// found matching end tag
|
||||
else if ($end !== false && $level == 0) {
|
||||
$taglen = strpos($text, '>', $start) - $start;
|
||||
$startpos = $start + $taglen + 1;
|
||||
|
||||
// get blockquote content
|
||||
$body = trim(substr($text, $startpos, $end - $startpos));
|
||||
|
||||
// adjust text wrapping width
|
||||
$p_width = $this->width;
|
||||
if ($this->width > 0) $this->width -= 2;
|
||||
|
||||
// replace content with inner blockquotes
|
||||
$this->_converter($body);
|
||||
|
||||
// resore text width
|
||||
$this->width = $p_width;
|
||||
|
||||
// Add citation markers and create <pre> block
|
||||
$body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_callback'), trim($body));
|
||||
$body = '<pre>' . htmlspecialchars($body) . '</pre>';
|
||||
|
||||
$text = substr_replace($text, $body . "\n", $start, $end + 13 - $start);
|
||||
$offset = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
// abort on invalid tag structure (e.g. no closing tag found)
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ($end || $next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to correctly add citation markers for blockquote contents
|
||||
*/
|
||||
public function blockquote_citation_callback($m)
|
||||
{
|
||||
$line = ltrim($m[2]);
|
||||
$space = $line[0] == '>' ? '' : ' ';
|
||||
|
||||
return $m[1] . '>' . $space . $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for preg_replace_callback use.
|
||||
*
|
||||
* @param array $matches 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 $matches 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;
|
||||
}
|
||||
}
|
||||
460
data/web/rc/program/lib/Roundcube/rcube_image.php
Normal file
460
data/web/rc/program/lib/Roundcube/rcube_image.php
Normal file
@@ -0,0 +1,460 @@
|
||||
<?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 |
|
||||
| |
|
||||
| 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: |
|
||||
| Image resizer and converter |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Image resizer and converter
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_image
|
||||
{
|
||||
private $image_file;
|
||||
|
||||
const TYPE_GIF = 1;
|
||||
const TYPE_JPG = 2;
|
||||
const TYPE_PNG = 3;
|
||||
const TYPE_TIF = 4;
|
||||
|
||||
public static $extensions = array(
|
||||
self::TYPE_GIF => 'gif',
|
||||
self::TYPE_JPG => 'jpg',
|
||||
self::TYPE_PNG => 'png',
|
||||
self::TYPE_TIF => 'tif',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $filename Image file name/path
|
||||
*/
|
||||
function __construct($filename)
|
||||
{
|
||||
$this->image_file = $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image properties.
|
||||
*
|
||||
* @return mixed Hash array with image props like type, width, height
|
||||
*/
|
||||
public function props()
|
||||
{
|
||||
// use GD extension
|
||||
if (function_exists('getimagesize') && ($imsize = @getimagesize($this->image_file))) {
|
||||
$width = $imsize[0];
|
||||
$height = $imsize[1];
|
||||
$gd_type = $imsize[2];
|
||||
$type = image_type_to_extension($gd_type, false);
|
||||
$channels = $imsize['channels'];
|
||||
}
|
||||
|
||||
// use ImageMagick
|
||||
if (!$type && ($data = $this->identify())) {
|
||||
list($type, $width, $height) = $data;
|
||||
$channels = null;
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
return array(
|
||||
'type' => $type,
|
||||
'gd_type' => $gd_type,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'channels' => $channels,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize image to a given size. Use only to shrink an image.
|
||||
* If an image is smaller than specified size it will be not resized.
|
||||
*
|
||||
* @param int $size Max width/height size
|
||||
* @param string $filename Output filename
|
||||
* @param boolean $browser_compat Convert to image type displayable by any browser
|
||||
*
|
||||
* @return mixed Output type on success, False on failure
|
||||
*/
|
||||
public function resize($size, $filename = null, $browser_compat = false)
|
||||
{
|
||||
$result = false;
|
||||
$rcube = rcube::get_instance();
|
||||
$convert = $rcube->config->get('im_convert_path', false);
|
||||
$props = $this->props();
|
||||
|
||||
if (empty($props)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$filename) {
|
||||
$filename = $this->image_file;
|
||||
}
|
||||
|
||||
// use Imagemagick
|
||||
if ($convert || class_exists('Imagick', false)) {
|
||||
$p['out'] = $filename;
|
||||
$p['in'] = $this->image_file;
|
||||
$type = $props['type'];
|
||||
|
||||
if (!$type && ($data = $this->identify())) {
|
||||
$type = $data[0];
|
||||
}
|
||||
|
||||
$type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
|
||||
$p['intype'] = $type;
|
||||
|
||||
// convert to an image format every browser can display
|
||||
if ($browser_compat && !in_array($type, array('jpg','gif','png'))) {
|
||||
$type = 'jpg';
|
||||
}
|
||||
|
||||
// If only one dimension is greater than the limit convert doesn't
|
||||
// work as expected, we need to calculate new dimensions
|
||||
$scale = $size / max($props['width'], $props['height']);
|
||||
|
||||
// if file is smaller than the limit, we do nothing
|
||||
// but copy original file to destination file
|
||||
if ($scale >= 1 && $p['intype'] == $type) {
|
||||
$result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false;
|
||||
}
|
||||
else {
|
||||
$valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif";
|
||||
|
||||
if (in_array($type, explode(',', $valid_types))) { // Valid type?
|
||||
if ($scale >= 1) {
|
||||
$width = $props['width'];
|
||||
$height = $props['height'];
|
||||
}
|
||||
else {
|
||||
$width = intval($props['width'] * $scale);
|
||||
$height = intval($props['height'] * $scale);
|
||||
}
|
||||
|
||||
// use ImageMagick in command line
|
||||
if ($convert) {
|
||||
$p += array(
|
||||
'type' => $type,
|
||||
'quality' => 75,
|
||||
'size' => $width . 'x' . $height,
|
||||
);
|
||||
|
||||
$result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
|
||||
. ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
|
||||
}
|
||||
// use PHP's Imagick class
|
||||
else {
|
||||
try {
|
||||
$image = new Imagick($this->image_file);
|
||||
|
||||
try {
|
||||
// it throws exception on formats not supporting these features
|
||||
$image->setImageBackgroundColor('white');
|
||||
$image->setImageAlphaChannel(11);
|
||||
$image->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// ignore errors
|
||||
}
|
||||
|
||||
$image->setImageColorspace(Imagick::COLORSPACE_SRGB);
|
||||
$image->setImageCompressionQuality(75);
|
||||
$image->setImageFormat($type);
|
||||
$image->stripImage();
|
||||
$image->scaleImage($width, $height);
|
||||
|
||||
if ($image->writeImage($filename)) {
|
||||
$result = '';
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
rcube::raise_error($e, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($result === '') {
|
||||
@chmod($filename, 0600);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
// do we have enough memory? (#1489937)
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && !$this->mem_check($props)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// use GD extension
|
||||
if ($props['gd_type']) {
|
||||
if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
|
||||
$image = imagecreatefromjpeg($this->image_file);
|
||||
$type = 'jpg';
|
||||
}
|
||||
else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
|
||||
$image = imagecreatefromgif($this->image_file);
|
||||
$type = 'gif';
|
||||
}
|
||||
else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
|
||||
$image = imagecreatefrompng($this->image_file);
|
||||
$type = 'png';
|
||||
}
|
||||
else {
|
||||
// @TODO: print error to the log?
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($image === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scale = $size / max($props['width'], $props['height']);
|
||||
|
||||
// Imagemagick resize is implemented in shrinking mode (see -resize argument above)
|
||||
// we do the same here, if an image is smaller than specified size
|
||||
// we do nothing but copy original file to destination file
|
||||
if ($scale >= 1) {
|
||||
$result = $this->image_file == $filename || copy($this->image_file, $filename);
|
||||
}
|
||||
else {
|
||||
$width = intval($props['width'] * $scale);
|
||||
$height = intval($props['height'] * $scale);
|
||||
$new_image = imagecreatetruecolor($width, $height);
|
||||
|
||||
if ($new_image === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fix transparency of gif/png image
|
||||
if ($props['gd_type'] != IMAGETYPE_JPEG) {
|
||||
imagealphablending($new_image, false);
|
||||
imagesavealpha($new_image, true);
|
||||
$transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
|
||||
imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
|
||||
}
|
||||
|
||||
imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
|
||||
$image = $new_image;
|
||||
|
||||
// fix rotation of image if EXIF data exists and specifies rotation (GD strips the EXIF data)
|
||||
if ($this->image_file && $type == 'jpg' && function_exists('exif_read_data')) {
|
||||
$exif = exif_read_data($this->image_file);
|
||||
if ($exif && $exif['Orientation']) {
|
||||
switch ($exif['Orientation']) {
|
||||
case 3:
|
||||
$image = imagerotate($image, 180, 0);
|
||||
break;
|
||||
case 6:
|
||||
$image = imagerotate($image, -90, 0);
|
||||
break;
|
||||
case 8:
|
||||
$image = imagerotate($image, 90, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($props['gd_type'] == IMAGETYPE_JPEG) {
|
||||
$result = imagejpeg($image, $filename, 75);
|
||||
}
|
||||
elseif($props['gd_type'] == IMAGETYPE_GIF) {
|
||||
$result = imagegif($image, $filename);
|
||||
}
|
||||
elseif($props['gd_type'] == IMAGETYPE_PNG) {
|
||||
$result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
@chmod($filename, 0600);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: print error to the log?
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert image to a given type
|
||||
*
|
||||
* @param int $type Destination file type (see class constants)
|
||||
* @param string $filename Output filename (if empty, original file will be used
|
||||
* and filename extension will be modified)
|
||||
*
|
||||
* @return bool True on success, False on failure
|
||||
*/
|
||||
public function convert($type, $filename = null)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$convert = $rcube->config->get('im_convert_path', false);
|
||||
|
||||
if (!$filename) {
|
||||
$filename = $this->image_file;
|
||||
|
||||
// modify extension
|
||||
if ($extension = self::$extensions[$type]) {
|
||||
$filename = preg_replace('/\.[^.]+$/', '', $filename) . '.' . $extension;
|
||||
}
|
||||
}
|
||||
|
||||
// use ImageMagick in command line
|
||||
if ($convert) {
|
||||
$p['in'] = $this->image_file;
|
||||
$p['out'] = $filename;
|
||||
$p['type'] = self::$extensions[$type];
|
||||
|
||||
$result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p);
|
||||
|
||||
if ($result === '') {
|
||||
chmod($filename, 0600);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// use PHP's Imagick class
|
||||
if (class_exists('Imagick', false)) {
|
||||
try {
|
||||
$image = new Imagick($this->image_file);
|
||||
|
||||
$image->setImageColorspace(Imagick::COLORSPACE_SRGB);
|
||||
$image->setImageCompressionQuality(75);
|
||||
$image->setImageFormat(self::$extensions[$type]);
|
||||
$image->stripImage();
|
||||
|
||||
if ($image->writeImage($filename)) {
|
||||
@chmod($filename, 0600);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
rcube::raise_error($e, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
// use GD extension (TIFF isn't supported)
|
||||
$props = $this->props();
|
||||
|
||||
// do we have enough memory? (#1489937)
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && !$this->mem_check($props)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 && function_exists('imagecreatefromgif')) {
|
||||
$image = imagecreatefromgif($this->image_file);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else if ($type == self::TYPE_GIF) {
|
||||
$result = imagegif($image, $filename);
|
||||
}
|
||||
else if ($type == self::TYPE_PNG) {
|
||||
$result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
@chmod($filename, 0600);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: print error to the log?
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if image format conversion is supported
|
||||
*
|
||||
* @return boolean True if specified format can be converted to another format
|
||||
*/
|
||||
public static function is_convertable($mimetype = null)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
// @TODO: check if specified mimetype is really supported
|
||||
return class_exists('Imagick', false) || $rcube->config->get('im_convert_path');
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageMagick based image properties read.
|
||||
*/
|
||||
private function identify()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
// use ImageMagick in command line
|
||||
if ($cmd = $rcube->config->get('im_identify_path')) {
|
||||
$args = array('in' => $this->image_file, 'format' => "%m %[fx:w] %[fx:h]");
|
||||
$id = rcube::exec($cmd. ' 2>/dev/null -format {format} {in}', $args);
|
||||
|
||||
if ($id) {
|
||||
return explode(' ', strtolower($id));
|
||||
}
|
||||
}
|
||||
|
||||
// use PHP's Imagick class
|
||||
if (class_exists('Imagick', false)) {
|
||||
try {
|
||||
$image = new Imagick($this->image_file);
|
||||
|
||||
return array(
|
||||
strtolower($image->getImageFormat()),
|
||||
$image->getImageWidth(),
|
||||
$image->getImageHeight(),
|
||||
);
|
||||
}
|
||||
catch (Exception $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have enough memory to load specified image
|
||||
*/
|
||||
private function mem_check($props)
|
||||
{
|
||||
// image size is unknown, we can't calculate required memory
|
||||
if (!$props['width']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// channels: CMYK - 4, RGB - 3
|
||||
$multip = ($props['channels'] ?: 3) + 1;
|
||||
|
||||
// calculate image size in memory (in bytes)
|
||||
$size = $props['width'] * $props['height'] * $multip;
|
||||
return rcube_utils::mem_check($size);
|
||||
}
|
||||
}
|
||||
4403
data/web/rc/program/lib/Roundcube/rcube_imap.php
Normal file
4403
data/web/rc/program/lib/Roundcube/rcube_imap.php
Normal file
File diff suppressed because it is too large
Load Diff
1297
data/web/rc/program/lib/Roundcube/rcube_imap_cache.php
Normal file
1297
data/web/rc/program/lib/Roundcube/rcube_imap_cache.php
Normal file
File diff suppressed because it is too large
Load Diff
4094
data/web/rc/program/lib/Roundcube/rcube_imap_generic.php
Normal file
4094
data/web/rc/program/lib/Roundcube/rcube_imap_generic.php
Normal file
File diff suppressed because it is too large
Load Diff
229
data/web/rc/program/lib/Roundcube/rcube_imap_search.php
Normal file
229
data/web/rc/program/lib/Roundcube/rcube_imap_search.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| |
|
||||
| Copyright (C) 2013, The Roundcube Dev Team |
|
||||
| Copyright (C) 2014, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Execute (multi-threaded) searches in multiple IMAP folders |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to control search jobs on multiple IMAP folders.
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
*/
|
||||
class rcube_imap_search
|
||||
{
|
||||
public $options = array();
|
||||
|
||||
protected $jobs = array();
|
||||
protected $timelimit = 0;
|
||||
protected $results;
|
||||
protected $conn;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public function __construct($options, $conn)
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke search request to IMAP server
|
||||
*
|
||||
* @param array $folders List of IMAP folders to search in
|
||||
* @param string $str Search criteria
|
||||
* @param string $charset Search charset
|
||||
* @param string $sort_field Header field to sort by
|
||||
* @param boolean $threading True if threaded listing is active
|
||||
*/
|
||||
public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null)
|
||||
{
|
||||
$start = floor(microtime(true));
|
||||
$results = new rcube_result_multifolder($folders);
|
||||
|
||||
// start a search job for every folder to search in
|
||||
foreach ($folders as $folder) {
|
||||
// a complete result for this folder already exists
|
||||
$result = $this->results ? $this->results->get_set($folder) : false;
|
||||
if ($result && !$result->incomplete) {
|
||||
$results->add($result);
|
||||
}
|
||||
else {
|
||||
$search = is_array($str) && $str[$folder] ? $str[$folder] : $str;
|
||||
$job = new rcube_imap_search_job($folder, $search, $charset, $sort_field, $threading);
|
||||
$job->worker = $this;
|
||||
$this->jobs[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
// execute jobs and gather results
|
||||
foreach ($this->jobs as $job) {
|
||||
// only run search if within the configured time limit
|
||||
// TODO: try to estimate the required time based on folder size and previous search performance
|
||||
if (!$this->timelimit || floor(microtime(true)) - $start < $this->timelimit) {
|
||||
$job->run();
|
||||
}
|
||||
|
||||
// add result (may have ->incomplete flag set)
|
||||
$results->add($job->get_result());
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for timelimt property
|
||||
*/
|
||||
public function set_timelimit($seconds)
|
||||
{
|
||||
$this->timelimit = $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for previous (potentially incomplete) search results
|
||||
*/
|
||||
public function set_results($res)
|
||||
{
|
||||
$this->results = $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection to the IMAP server
|
||||
* (used for single-thread mode)
|
||||
*/
|
||||
public function get_imap()
|
||||
{
|
||||
return $this->conn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stackable item to run the search on a specific IMAP folder
|
||||
*/
|
||||
class rcube_imap_search_job /* extends Stackable */
|
||||
{
|
||||
private $folder;
|
||||
private $search;
|
||||
private $charset;
|
||||
private $sort_field;
|
||||
private $threading;
|
||||
private $result;
|
||||
|
||||
public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
|
||||
{
|
||||
$this->folder = $folder;
|
||||
$this->search = $str;
|
||||
$this->charset = $charset;
|
||||
$this->sort_field = $sort_field;
|
||||
$this->threading = $threading;
|
||||
|
||||
$this->result = new rcube_result_index($folder);
|
||||
$this->result->incomplete = true;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->result = $this->search_index();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of rcube_imap::search_index()
|
||||
*/
|
||||
protected function search_index()
|
||||
{
|
||||
$criteria = $this->search;
|
||||
$charset = $this->charset;
|
||||
$imap = $this->worker->get_imap();
|
||||
|
||||
if (!$imap->connected()) {
|
||||
trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);
|
||||
|
||||
if ($this->threading) {
|
||||
return new rcube_result_thread($this->folder);
|
||||
}
|
||||
else {
|
||||
return new rcube_result_index($this->folder);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) {
|
||||
$criteria = 'UNDELETED '.$criteria;
|
||||
}
|
||||
|
||||
// unset CHARSET if criteria string is ASCII, this way
|
||||
// SEARCH won't be re-sent after "unsupported charset" response
|
||||
if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
|
||||
$charset = 'US-ASCII';
|
||||
}
|
||||
|
||||
if ($this->threading) {
|
||||
$threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset);
|
||||
|
||||
// Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
|
||||
// but I've seen that Courier doesn't support UTF-8)
|
||||
if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
|
||||
$threads = $imap->thread($this->folder, $this->threading,
|
||||
rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
|
||||
}
|
||||
|
||||
return $threads;
|
||||
}
|
||||
|
||||
if ($this->sort_field) {
|
||||
$messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset);
|
||||
|
||||
// Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
|
||||
// but I've seen Courier with disabled UTF-8 support)
|
||||
if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
|
||||
$messages = $imap->sort($this->folder, $this->sort_field,
|
||||
rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$messages || $messages->is_error()) {
|
||||
$messages = $imap->search($this->folder,
|
||||
($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);
|
||||
|
||||
// Error, try with US-ASCII (some servers may support only US-ASCII)
|
||||
if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
|
||||
$messages = $imap->search($this->folder,
|
||||
rcube_imap::convert_criteria($criteria, $charset), true);
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
public function get_search_set()
|
||||
{
|
||||
return array(
|
||||
$this->search,
|
||||
$this->result,
|
||||
$this->charset,
|
||||
$this->sort_field,
|
||||
$this->threading,
|
||||
);
|
||||
}
|
||||
|
||||
public function get_result()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
}
|
||||
2141
data/web/rc/program/lib/Roundcube/rcube_ldap.php
Normal file
2141
data/web/rc/program/lib/Roundcube/rcube_ldap.php
Normal file
File diff suppressed because it is too large
Load Diff
366
data/web/rc/program/lib/Roundcube/rcube_ldap_generic.php
Normal file
366
data/web/rc/program/lib/Roundcube/rcube_ldap_generic.php
Normal file
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| Roundcube/rcube_ldap_generic.php |
|
||||
| |
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2006-2014, The Roundcube Dev Team |
|
||||
| Copyright (C) 2012-2015, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Provide basic functionality for accessing LDAP directories |
|
||||
| |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Model class to access an LDAP directories
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage LDAP
|
||||
*/
|
||||
class rcube_ldap_generic extends Net_LDAP3
|
||||
{
|
||||
/** private properties */
|
||||
protected $cache = null;
|
||||
protected $attributes = array('dn');
|
||||
protected $error;
|
||||
|
||||
function __construct($config = null)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->config_set('log_hook', array($this, 'log'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a connection to the LDAP server
|
||||
*/
|
||||
public function connect($host = null)
|
||||
{
|
||||
// Net_LDAP3 does not support IDNA yet
|
||||
// also parse_host() here is very Roundcube specific
|
||||
$host = rcube_utils::parse_host($host, $this->config['mail_domain']);
|
||||
$host = rcube_utils::idn_to_ascii($host);
|
||||
|
||||
return parent::connect($host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific LDAP entry, identified by its DN
|
||||
*
|
||||
* @param string $dn Record identifier
|
||||
* @param array $attributes Attributes to return
|
||||
*
|
||||
* @return array Hash array
|
||||
*/
|
||||
function get_entry($dn, $attributes = array())
|
||||
{
|
||||
return parent::get_entry($dn, !empty($attributes) ? $attributes : $this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints debug/error info to the log
|
||||
*/
|
||||
public function log($level, $msg)
|
||||
{
|
||||
$msg = implode("\n", $msg);
|
||||
|
||||
switch ($level) {
|
||||
case LOG_DEBUG:
|
||||
case LOG_INFO:
|
||||
case LOG_NOTICE:
|
||||
if ($this->config['debug']) {
|
||||
rcube::write_log('ldap', $msg);
|
||||
}
|
||||
break;
|
||||
|
||||
case LOG_EMERGE:
|
||||
case LOG_ALERT:
|
||||
case LOG_CRIT:
|
||||
rcube::raise_error($msg, true, true);
|
||||
break;
|
||||
|
||||
case LOG_ERR:
|
||||
case LOG_WARNING:
|
||||
$this->error = $msg;
|
||||
rcube::raise_error($msg, true, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last LDAP error occurred
|
||||
*
|
||||
* @return mixed Error message string or null if no error occured
|
||||
*/
|
||||
function get_error()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function set_debug($dbg = true)
|
||||
{
|
||||
$this->config['debug'] = (bool) $dbg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function set_cache($cache_engine)
|
||||
{
|
||||
$this->config['cache'] = $cache_engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function scope2func($scope, &$ns_function = null)
|
||||
{
|
||||
return self::scope_to_function($scope, $ns_function);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function set_config($opt, $val = null)
|
||||
{
|
||||
$this->config_set($opt, $val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function add($dn, $entry)
|
||||
{
|
||||
return $this->add_entry($dn, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function delete($dn)
|
||||
{
|
||||
return $this->delete_entry($dn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ldap_mod_replace()
|
||||
*
|
||||
* @see ldap_mod_replace()
|
||||
*/
|
||||
public function mod_replace($dn, $entry)
|
||||
{
|
||||
$this->_debug("C: Replace $dn: ".print_r($entry, true));
|
||||
|
||||
if (!ldap_mod_replace($this->conn, $dn, $entry)) {
|
||||
$this->_error("ldap_mod_replace() failed with " . ldap_error($this->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_debug("S: OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ldap_mod_add()
|
||||
*
|
||||
* @see ldap_mod_add()
|
||||
*/
|
||||
public function mod_add($dn, $entry)
|
||||
{
|
||||
$this->_debug("C: Add $dn: ".print_r($entry, true));
|
||||
|
||||
if (!ldap_mod_add($this->conn, $dn, $entry)) {
|
||||
$this->_error("ldap_mod_add() failed with " . ldap_error($this->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_debug("S: OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ldap_mod_del()
|
||||
*
|
||||
* @see ldap_mod_del()
|
||||
*/
|
||||
public function mod_del($dn, $entry)
|
||||
{
|
||||
$this->_debug("C: Delete $dn: ".print_r($entry, true));
|
||||
|
||||
if (!ldap_mod_del($this->conn, $dn, $entry)) {
|
||||
$this->_error("ldap_mod_del() failed with " . ldap_error($this->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_debug("S: OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ldap_rename()
|
||||
*
|
||||
* @see ldap_rename()
|
||||
*/
|
||||
public function rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true)
|
||||
{
|
||||
$this->_debug("C: Rename $dn to $newrdn");
|
||||
|
||||
if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) {
|
||||
$this->_error("ldap_rename() failed with " . ldap_error($this->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_debug("S: OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ldap_list() + ldap_get_entries()
|
||||
*
|
||||
* @see ldap_list()
|
||||
* @see ldap_get_entries()
|
||||
*/
|
||||
public function list_entries($dn, $filter, $attributes = array('dn'))
|
||||
{
|
||||
$list = array();
|
||||
$this->_debug("C: List $dn [{$filter}]");
|
||||
|
||||
if ($result = ldap_list($this->conn, $dn, $filter, $attributes)) {
|
||||
$list = ldap_get_entries($this->conn, $result);
|
||||
|
||||
if ($list === false) {
|
||||
$this->_error("ldap_get_entries() failed with " . ldap_error($this->conn));
|
||||
return array();
|
||||
}
|
||||
|
||||
$count = $list['count'];
|
||||
unset($list['count']);
|
||||
|
||||
$this->_debug("S: $count record(s)");
|
||||
}
|
||||
else {
|
||||
$this->_error("ldap_list() failed with " . ldap_error($this->conn));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ldap_read() + ldap_get_entries()
|
||||
*
|
||||
* @see ldap_read()
|
||||
* @see ldap_get_entries()
|
||||
*/
|
||||
public function read_entries($dn, $filter, $attributes = null)
|
||||
{
|
||||
$this->_debug("C: Read $dn [{$filter}]");
|
||||
|
||||
if ($this->conn && $dn) {
|
||||
$result = @ldap_read($this->conn, $dn, $filter, $attributes, 0, (int)$this->config['sizelimit'], (int)$this->config['timelimit']);
|
||||
if ($result === false) {
|
||||
$this->_error("ldap_read() failed with " . ldap_error($this->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_debug("S: OK");
|
||||
return ldap_get_entries($this->conn, $result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn an LDAP entry into a regular PHP array with attributes as keys.
|
||||
*
|
||||
* @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries()
|
||||
* @param bool $flat Convert one-element-array values into strings (not implemented)
|
||||
*
|
||||
* @return array Hash array with attributes as keys
|
||||
*/
|
||||
public static function normalize_entry($entry, $flat = false)
|
||||
{
|
||||
if (!isset($entry['count'])) {
|
||||
return $entry;
|
||||
}
|
||||
|
||||
$rec = array();
|
||||
|
||||
for ($i=0; $i < $entry['count']; $i++) {
|
||||
$attr = $entry[$i];
|
||||
if ($entry[$attr]['count'] == 1) {
|
||||
switch ($attr) {
|
||||
case 'objectclass':
|
||||
$rec[$attr] = array(strtolower($entry[$attr][0]));
|
||||
break;
|
||||
default:
|
||||
$rec[$attr] = $entry[$attr][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ($j=0; $j < $entry[$attr]['count']; $j++) {
|
||||
$rec[$attr][$j] = $entry[$attr][$j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose an LDAP filter string matching all words from the search string
|
||||
* in the given list of attributes.
|
||||
*
|
||||
* @param string $value Search value
|
||||
* @param mixed $attrs List of LDAP attributes to search
|
||||
* @param int $mode Matching mode:
|
||||
* 0 - partial (*abc*),
|
||||
* 1 - strict (=),
|
||||
* 2 - prefix (abc*)
|
||||
* @return string LDAP filter
|
||||
*/
|
||||
public static function fulltext_search_filter($value, $attributes, $mode = 1)
|
||||
{
|
||||
if (empty($attributes)) {
|
||||
$attributes = array('cn');
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
$value = str_replace('*', '', $value);
|
||||
$words = $mode == 0 ? rcube_utils::tokenize_string($value, 1) : array($value);
|
||||
|
||||
// set wildcards
|
||||
$wp = $ws = '';
|
||||
if ($mode != 1) {
|
||||
$ws = '*';
|
||||
$wp = !$mode ? '*' : '';
|
||||
}
|
||||
|
||||
// search each word in all listed attributes
|
||||
foreach ($words as $word) {
|
||||
$parts = array();
|
||||
foreach ($attributes as $attr) {
|
||||
$parts[] = "($attr=$wp" . self::quote_string($word) . "$ws)";
|
||||
}
|
||||
$groups[] = '(|' . join('', $parts) . ')';
|
||||
}
|
||||
|
||||
return count($groups) > 1 ? '(&' . join('', $groups) . ')' : join('', $groups);
|
||||
}
|
||||
}
|
||||
|
||||
// for backward compat.
|
||||
class rcube_ldap_result extends Net_LDAP3_Result {}
|
||||
1110
data/web/rc/program/lib/Roundcube/rcube_message.php
Normal file
1110
data/web/rc/program/lib/Roundcube/rcube_message.php
Normal file
File diff suppressed because it is too large
Load Diff
325
data/web/rc/program/lib/Roundcube/rcube_message_header.php
Normal file
325
data/web/rc/program/lib/Roundcube/rcube_message_header.php
Normal file
@@ -0,0 +1,325 @@
|
||||
<?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 |
|
||||
| |
|
||||
| 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: |
|
||||
| E-mail message headers representation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Struct representing an e-mail message header
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_message_header
|
||||
{
|
||||
/**
|
||||
* Message sequence number
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Message unique identifier
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uid;
|
||||
|
||||
/**
|
||||
* Message subject
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $subject;
|
||||
|
||||
/**
|
||||
* Message sender (From)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $from;
|
||||
|
||||
/**
|
||||
* Message recipient (To)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $to;
|
||||
|
||||
/**
|
||||
* Message additional recipients (Cc)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $cc;
|
||||
|
||||
/**
|
||||
* Message Reply-To header
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $replyto;
|
||||
|
||||
/**
|
||||
* Message In-Reply-To header
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $in_reply_to;
|
||||
|
||||
/**
|
||||
* Message date (Date)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $date;
|
||||
|
||||
/**
|
||||
* Message identifier (Message-ID)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $messageID;
|
||||
|
||||
/**
|
||||
* Message size
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $size;
|
||||
|
||||
/**
|
||||
* Message encoding
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $encoding;
|
||||
|
||||
/**
|
||||
* Message charset
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $charset;
|
||||
|
||||
/**
|
||||
* Message Content-type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $ctype;
|
||||
|
||||
/**
|
||||
* Message timestamp (based on message date)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timestamp;
|
||||
|
||||
/**
|
||||
* IMAP bodystructure string
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $bodystructure;
|
||||
|
||||
/**
|
||||
* IMAP internal date
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $internaldate;
|
||||
|
||||
/**
|
||||
* Message References header
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $references;
|
||||
|
||||
/**
|
||||
* Message priority (X-Priority)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $priority;
|
||||
|
||||
/**
|
||||
* Message receipt recipient
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $mdn_to;
|
||||
|
||||
/**
|
||||
* IMAP folder this message is stored in
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $folder;
|
||||
|
||||
/**
|
||||
* Other message headers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $others = array();
|
||||
|
||||
/**
|
||||
* Message flags
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $flags = array();
|
||||
|
||||
// map header to rcube_message_header object property
|
||||
private $obj_headers = array(
|
||||
'date' => 'date',
|
||||
'from' => 'from',
|
||||
'to' => 'to',
|
||||
'subject' => 'subject',
|
||||
'reply-to' => 'replyto',
|
||||
'cc' => 'cc',
|
||||
'bcc' => 'bcc',
|
||||
'mbox' => 'folder',
|
||||
'folder' => 'folder',
|
||||
'content-transfer-encoding' => 'encoding',
|
||||
'in-reply-to' => 'in_reply_to',
|
||||
'content-type' => 'ctype',
|
||||
'charset' => 'charset',
|
||||
'references' => 'references',
|
||||
'return-receipt-to' => 'mdn_to',
|
||||
'disposition-notification-to' => 'mdn_to',
|
||||
'x-confirm-reading-to' => 'mdn_to',
|
||||
'message-id' => 'messageID',
|
||||
'x-priority' => 'priority',
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns header value
|
||||
*/
|
||||
public function get($name, $decode = true)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
if (isset($this->obj_headers[$name])) {
|
||||
$value = $this->{$this->obj_headers[$name]};
|
||||
}
|
||||
else {
|
||||
$value = $this->others[$name];
|
||||
}
|
||||
|
||||
if ($decode) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $val) {
|
||||
$val = rcube_mime::decode_header($val, $this->charset);
|
||||
$value[$key] = rcube_charset::clean($val);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$value = rcube_mime::decode_header($value, $this->charset);
|
||||
$value = rcube_charset::clean($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets header value
|
||||
*/
|
||||
public function set($name, $value)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
if (isset($this->obj_headers[$name])) {
|
||||
$this->{$this->obj_headers[$name]} = $value;
|
||||
}
|
||||
else {
|
||||
$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 Framework
|
||||
* @subpackage Storage
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_message_header_sorter
|
||||
{
|
||||
private $uids = array();
|
||||
|
||||
|
||||
/**
|
||||
* Set the predetermined sort order.
|
||||
*
|
||||
* @param array $index Numerically indexed array of IMAP UIDs
|
||||
*/
|
||||
function set_index($index)
|
||||
{
|
||||
$index = array_flip($index);
|
||||
|
||||
$this->uids = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the array of header objects
|
||||
*
|
||||
* @param array $headers Array of rcube_message_header objects indexed by UID
|
||||
*/
|
||||
function sort_headers(&$headers)
|
||||
{
|
||||
uksort($headers, array($this, "compare_uids"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort method called by uksort()
|
||||
*
|
||||
* @param int $a Array key (UID)
|
||||
* @param int $b Array key (UID)
|
||||
*/
|
||||
function compare_uids($a, $b)
|
||||
{
|
||||
// then find each sequence number in my ordered list
|
||||
$posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
|
||||
$posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1;
|
||||
|
||||
// return the relative position as the comparison value
|
||||
return $posa - $posb;
|
||||
}
|
||||
}
|
||||
94
data/web/rc/program/lib/Roundcube/rcube_message_part.php
Normal file
94
data/web/rc/program/lib/Roundcube/rcube_message_part.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?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 |
|
||||
| |
|
||||
| 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: |
|
||||
| Class representing a message part |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a message part
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_message_part
|
||||
{
|
||||
/**
|
||||
* Part MIME identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $mime_id = '';
|
||||
|
||||
/**
|
||||
* Content main type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $ctype_primary = 'text';
|
||||
|
||||
/**
|
||||
* Content subtype
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $ctype_secondary = 'plain';
|
||||
|
||||
/**
|
||||
* Complete content type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $mimetype = 'text/plain';
|
||||
|
||||
/**
|
||||
* Part size in bytes
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $size = 0;
|
||||
|
||||
/**
|
||||
* Part headers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $headers = array();
|
||||
|
||||
public $disposition = '';
|
||||
public $filename = '';
|
||||
public $encoding = '8bit';
|
||||
public $charset = '';
|
||||
public $d_parameters = array();
|
||||
public $ctype_parameters = array();
|
||||
|
||||
|
||||
/**
|
||||
* Clone handler.
|
||||
*/
|
||||
function __clone()
|
||||
{
|
||||
if (isset($this->parts)) {
|
||||
foreach ($this->parts as $idx => $part) {
|
||||
if (is_object($part)) {
|
||||
$this->parts[$idx] = clone $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
878
data/web/rc/program/lib/Roundcube/rcube_mime.php
Normal file
878
data/web/rc/program/lib/Roundcube/rcube_mime.php
Normal file
@@ -0,0 +1,878 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2016, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011-2016, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| MIME message parsing utilities |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for parsing MIME messages
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_mime
|
||||
{
|
||||
private static $default_charset;
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*/
|
||||
function __construct($default_charset = null)
|
||||
{
|
||||
self::$default_charset = $default_charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns message/object character set name
|
||||
*
|
||||
* @return string Characted set name
|
||||
*/
|
||||
public static function get_charset()
|
||||
{
|
||||
if (self::$default_charset) {
|
||||
return self::$default_charset;
|
||||
}
|
||||
|
||||
if ($charset = rcube::get_instance()->config->get('default_charset')) {
|
||||
return $charset;
|
||||
}
|
||||
|
||||
return RCUBE_CHARSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given raw message source and return a structure
|
||||
* of rcube_message_part objects.
|
||||
*
|
||||
* It makes use of the rcube_mime_decode library
|
||||
*
|
||||
* @param string $raw_body The message source
|
||||
*
|
||||
* @return object rcube_message_part The message structure
|
||||
*/
|
||||
public static function parse_message($raw_body)
|
||||
{
|
||||
$conf = array(
|
||||
'include_bodies' => true,
|
||||
'decode_bodies' => true,
|
||||
'decode_headers' => false,
|
||||
'default_charset' => self::get_charset(),
|
||||
);
|
||||
|
||||
$mime = new rcube_mime_decode($conf);
|
||||
|
||||
return $mime->decode($raw_body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split an address list into a structured array list
|
||||
*
|
||||
* @param string $input Input string
|
||||
* @param int $max List only this number of addresses
|
||||
* @param boolean $decode Decode address strings
|
||||
* @param string $fallback Fallback charset if none specified
|
||||
* @param boolean $addronly Return flat array with e-mail addresses only
|
||||
*
|
||||
* @return array Indexed list of addresses
|
||||
*/
|
||||
static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false)
|
||||
{
|
||||
$a = self::parse_address_list($input, $decode, $fallback);
|
||||
$out = array();
|
||||
$j = 0;
|
||||
|
||||
// Special chars as defined by RFC 822 need to in quoted string (or escaped).
|
||||
$special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
|
||||
|
||||
if (!is_array($a)) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
foreach ($a as $val) {
|
||||
$j++;
|
||||
$address = trim($val['address']);
|
||||
|
||||
if ($addronly) {
|
||||
$out[$j] = $address;
|
||||
}
|
||||
else {
|
||||
$name = trim($val['name']);
|
||||
if ($name && $address && $name != $address)
|
||||
$string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
|
||||
else if ($address)
|
||||
$string = $address;
|
||||
else if ($name)
|
||||
$string = $name;
|
||||
|
||||
$out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string);
|
||||
}
|
||||
|
||||
if ($max && $j==$max)
|
||||
break;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a message header value
|
||||
*
|
||||
* @param string $input Header value
|
||||
* @param string $fallback Fallback charset if none specified
|
||||
*
|
||||
* @return string Decoded string
|
||||
*/
|
||||
public static function decode_header($input, $fallback = null)
|
||||
{
|
||||
$str = self::decode_mime_string((string)$input, $fallback);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a mime-encoded string to internal charset
|
||||
*
|
||||
* @param string $input Header value
|
||||
* @param string $fallback Fallback charset if none specified
|
||||
*
|
||||
* @return string Decoded string
|
||||
*/
|
||||
public static function decode_mime_string($input, $fallback = null)
|
||||
{
|
||||
$default_charset = $fallback ?: self::get_charset();
|
||||
|
||||
// rfc: all line breaks or other characters not found
|
||||
// in the Base64 Alphabet must be ignored by decoding software
|
||||
// delete all blanks between MIME-lines, differently we can
|
||||
// receive unnecessary blanks and broken utf-8 symbols
|
||||
$input = preg_replace("/\?=\s+=\?/", '?==?', $input);
|
||||
|
||||
// encoded-word regexp
|
||||
$re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/';
|
||||
|
||||
// Find all RFC2047's encoded words
|
||||
if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
|
||||
// Initialize variables
|
||||
$tmp = array();
|
||||
$out = '';
|
||||
$start = 0;
|
||||
|
||||
foreach ($matches as $idx => $m) {
|
||||
$pos = $m[0][1];
|
||||
$charset = $m[1][0];
|
||||
$encoding = $m[2][0];
|
||||
$text = $m[3][0];
|
||||
$length = strlen($m[0][0]);
|
||||
|
||||
// Append everything that is before the text to be decoded
|
||||
if ($start != $pos) {
|
||||
$substr = substr($input, $start, $pos-$start);
|
||||
$out .= rcube_charset::convert($substr, $default_charset);
|
||||
$start = $pos;
|
||||
}
|
||||
$start += $length;
|
||||
|
||||
// Per RFC2047, each string part "MUST represent an integral number
|
||||
// of characters . A multi-octet character may not be split across
|
||||
// adjacent encoded-words." However, some mailers break this, so we
|
||||
// try to handle characters spanned across parts anyway by iterating
|
||||
// through and aggregating sequential encoded parts with the same
|
||||
// character set and encoding, then perform the decoding on the
|
||||
// aggregation as a whole.
|
||||
|
||||
$tmp[] = $text;
|
||||
if ($next_match = $matches[$idx+1]) {
|
||||
if ($next_match[0][1] == $start
|
||||
&& $next_match[1][0] == $charset
|
||||
&& $next_match[2][0] == $encoding
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($tmp);
|
||||
$text = '';
|
||||
|
||||
// Decode and join encoded-word's chunks
|
||||
if ($encoding == 'B' || $encoding == 'b') {
|
||||
// base64 must be decoded a segment at a time
|
||||
for ($i=0; $i<$count; $i++)
|
||||
$text .= base64_decode($tmp[$i]);
|
||||
}
|
||||
else { //if ($encoding == 'Q' || $encoding == 'q') {
|
||||
// quoted printable can be combined and processed at once
|
||||
for ($i=0; $i<$count; $i++)
|
||||
$text .= $tmp[$i];
|
||||
|
||||
$text = str_replace('_', ' ', $text);
|
||||
$text = quoted_printable_decode($text);
|
||||
}
|
||||
|
||||
$out .= rcube_charset::convert($text, $charset);
|
||||
$tmp = array();
|
||||
}
|
||||
|
||||
// add the last part of the input string
|
||||
if ($start != strlen($input)) {
|
||||
$out .= rcube_charset::convert(substr($input, $start), $default_charset);
|
||||
}
|
||||
|
||||
// return the results
|
||||
return $out;
|
||||
}
|
||||
|
||||
// no encoding information, use fallback
|
||||
return rcube_charset::convert($input, $default_charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a mime part
|
||||
*
|
||||
* @param string $input Input string
|
||||
* @param string $encoding Part encoding
|
||||
*
|
||||
* @return string Decoded string
|
||||
*/
|
||||
public static function decode($input, $encoding = '7bit')
|
||||
{
|
||||
switch (strtolower($encoding)) {
|
||||
case 'quoted-printable':
|
||||
return quoted_printable_decode($input);
|
||||
case 'base64':
|
||||
return base64_decode($input);
|
||||
case 'x-uuencode':
|
||||
case 'x-uue':
|
||||
case 'uue':
|
||||
case 'uuencode':
|
||||
return convert_uudecode($input);
|
||||
case '7bit':
|
||||
default:
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split RFC822 header string into an associative array
|
||||
*/
|
||||
public static function parse_headers($headers)
|
||||
{
|
||||
$a_headers = array();
|
||||
$headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers);
|
||||
$lines = explode("\n", $headers);
|
||||
$count = count($lines);
|
||||
|
||||
for ($i=0; $i<$count; $i++) {
|
||||
if ($p = strpos($lines[$i], ': ')) {
|
||||
$field = strtolower(substr($lines[$i], 0, $p));
|
||||
$value = trim(substr($lines[$i], $p+1));
|
||||
if (!empty($value)) {
|
||||
$a_headers[$field] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $a_headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-mail address list parser
|
||||
*/
|
||||
private static function parse_address_list($str, $decode = true, $fallback = null)
|
||||
{
|
||||
// remove any newlines and carriage returns before
|
||||
$str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str);
|
||||
|
||||
// extract list items, remove comments
|
||||
$str = self::explode_header_string(',;', $str, true);
|
||||
$result = array();
|
||||
|
||||
// simplified regexp, supporting quoted local part
|
||||
$email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
|
||||
|
||||
foreach ($str as $key => $val) {
|
||||
$name = '';
|
||||
$address = '';
|
||||
$val = trim($val);
|
||||
|
||||
if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) {
|
||||
$address = $m[2];
|
||||
$name = trim($m[1]);
|
||||
}
|
||||
else if (preg_match('/^('.$email_rx.')$/', $val, $m)) {
|
||||
$address = $m[1];
|
||||
$name = '';
|
||||
}
|
||||
// special case (#1489092)
|
||||
else if (preg_match('/(\s*<MAILER-DAEMON>)$/', $val, $m)) {
|
||||
$address = 'MAILER-DAEMON';
|
||||
$name = substr($val, 0, -strlen($m[1]));
|
||||
}
|
||||
else if (preg_match('/('.$email_rx.')/', $val, $m)) {
|
||||
$name = $m[1];
|
||||
}
|
||||
else {
|
||||
$name = $val;
|
||||
}
|
||||
|
||||
// dequote and/or decode name
|
||||
if ($name) {
|
||||
if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
|
||||
$name = substr($name, 1, -1);
|
||||
$name = stripslashes($name);
|
||||
}
|
||||
if ($decode) {
|
||||
$name = self::decode_header($name, $fallback);
|
||||
// some clients encode addressee name with quotes around it
|
||||
if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
|
||||
$name = substr($name, 1, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$address && $name) {
|
||||
$address = $name;
|
||||
$name = '';
|
||||
}
|
||||
|
||||
if ($address) {
|
||||
$address = self::fix_email($address);
|
||||
$result[$key] = array('name' => $name, 'address' => $address);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explodes header (e.g. address-list) string into array of strings
|
||||
* using specified separator characters with proper handling
|
||||
* of quoted-strings and comments (RFC2822)
|
||||
*
|
||||
* @param string $separator String containing separator characters
|
||||
* @param string $str Header string
|
||||
* @param bool $remove_comments Enable to remove comments
|
||||
*
|
||||
* @return array Header items
|
||||
*/
|
||||
public static function explode_header_string($separator, $str, $remove_comments = false)
|
||||
{
|
||||
$length = strlen($str);
|
||||
$result = array();
|
||||
$quoted = false;
|
||||
$comment = 0;
|
||||
$out = '';
|
||||
|
||||
for ($i=0; $i<$length; $i++) {
|
||||
// we're inside a quoted string
|
||||
if ($quoted) {
|
||||
if ($str[$i] == '"') {
|
||||
$quoted = false;
|
||||
}
|
||||
else if ($str[$i] == "\\") {
|
||||
if ($comment <= 0) {
|
||||
$out .= "\\";
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
// we are inside a comment string
|
||||
else if ($comment > 0) {
|
||||
if ($str[$i] == ')') {
|
||||
$comment--;
|
||||
}
|
||||
else if ($str[$i] == '(') {
|
||||
$comment++;
|
||||
}
|
||||
else if ($str[$i] == "\\") {
|
||||
$i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// separator, add to result array
|
||||
else if (strpos($separator, $str[$i]) !== false) {
|
||||
if ($out) {
|
||||
$result[] = $out;
|
||||
}
|
||||
$out = '';
|
||||
continue;
|
||||
}
|
||||
// start of quoted string
|
||||
else if ($str[$i] == '"') {
|
||||
$quoted = true;
|
||||
}
|
||||
// start of comment
|
||||
else if ($remove_comments && $str[$i] == '(') {
|
||||
$comment++;
|
||||
}
|
||||
|
||||
if ($comment <= 0) {
|
||||
$out .= $str[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if ($out && $comment <= 0) {
|
||||
$result[] = $out;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret a format=flowed message body according to RFC 2646
|
||||
*
|
||||
* @param string $text Raw body formatted as flowed text
|
||||
* @param string $mark Mark each flowed line with specified character
|
||||
*
|
||||
* @return string Interpreted text with unwrapped lines and stuffed space removed
|
||||
*/
|
||||
public static function unfold_flowed($text, $mark = null)
|
||||
{
|
||||
$text = preg_split('/\r?\n/', $text);
|
||||
$last = -1;
|
||||
$q_level = 0;
|
||||
$marks = array();
|
||||
|
||||
foreach ($text as $idx => $line) {
|
||||
if ($q = strspn($line, '>')) {
|
||||
// remove quote chars
|
||||
$line = substr($line, $q);
|
||||
// remove (optional) space-staffing
|
||||
if ($line[0] === ' ') $line = substr($line, 1);
|
||||
|
||||
// The same paragraph (We join current line with the previous one) when:
|
||||
// - the same level of quoting
|
||||
// - previous line was flowed
|
||||
// - previous line contains more than only one single space (and quote char(s))
|
||||
if ($q == $q_level
|
||||
&& isset($text[$last]) && $text[$last][strlen($text[$last])-1] == ' '
|
||||
&& !preg_match('/^>+ {0,1}$/', $text[$last])
|
||||
) {
|
||||
$text[$last] .= $line;
|
||||
unset($text[$idx]);
|
||||
|
||||
if ($mark) {
|
||||
$marks[$last] = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$last = $idx;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($line == '-- ') {
|
||||
$last = $idx;
|
||||
}
|
||||
else {
|
||||
// remove space-stuffing
|
||||
if ($line[0] === ' ') $line = substr($line, 1);
|
||||
|
||||
if (isset($text[$last]) && $line && !$q_level
|
||||
&& $text[$last] != '-- '
|
||||
&& $text[$last][strlen($text[$last])-1] == ' '
|
||||
) {
|
||||
$text[$last] .= $line;
|
||||
unset($text[$idx]);
|
||||
|
||||
if ($mark) {
|
||||
$marks[$last] = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$text[$idx] = $line;
|
||||
$last = $idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
$q_level = $q;
|
||||
}
|
||||
|
||||
if (!empty($marks)) {
|
||||
foreach (array_keys($marks) as $mk) {
|
||||
$text[$mk] = $mark . $text[$mk];
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\r\n", $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given text to comply with RFC 2646
|
||||
*
|
||||
* @param string $text Text to wrap
|
||||
* @param int $length Length
|
||||
* @param string $charset Character encoding of $text
|
||||
*
|
||||
* @return string Wrapped text
|
||||
*/
|
||||
public static function format_flowed($text, $length = 72, $charset=null)
|
||||
{
|
||||
$text = preg_split('/\r?\n/', $text);
|
||||
|
||||
foreach ($text as $idx => $line) {
|
||||
if ($line != '-- ') {
|
||||
if ($level = strspn($line, '>')) {
|
||||
// remove quote chars
|
||||
$line = substr($line, $level);
|
||||
// remove (optional) space-staffing and spaces before the line end
|
||||
$line = rtrim($line, ' ');
|
||||
if ($line[0] === ' ') $line = substr($line, 1);
|
||||
|
||||
$prefix = str_repeat('>', $level) . ' ';
|
||||
$line = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix", false, $charset);
|
||||
}
|
||||
else if ($line) {
|
||||
$line = self::wordwrap(rtrim($line), $length - 2, " \r\n", false, $charset);
|
||||
// space-stuffing
|
||||
$line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
|
||||
}
|
||||
|
||||
$text[$idx] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\r\n", $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved wordwrap function with multibyte support.
|
||||
* The code is based on Zend_Text_MultiByte::wordWrap().
|
||||
*
|
||||
* @param string $string Text to wrap
|
||||
* @param int $width Line width
|
||||
* @param string $break Line separator
|
||||
* @param bool $cut Enable to cut word
|
||||
* @param string $charset Charset of $string
|
||||
* @param bool $wrap_quoted When enabled quoted lines will not be wrapped
|
||||
*
|
||||
* @return string Text
|
||||
*/
|
||||
public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true)
|
||||
{
|
||||
// Note: Never try to use iconv instead of mbstring functions here
|
||||
// Iconv's substr/strlen are 100x slower (#1489113)
|
||||
|
||||
if ($charset && $charset != RCUBE_CHARSET) {
|
||||
mb_internal_encoding($charset);
|
||||
}
|
||||
|
||||
// Convert \r\n to \n, this is our line-separator
|
||||
$string = str_replace("\r\n", "\n", $string);
|
||||
$separator = "\n"; // must be 1 character length
|
||||
$result = array();
|
||||
|
||||
while (($stringLength = mb_strlen($string)) > 0) {
|
||||
$breakPos = mb_strpos($string, $separator, 0);
|
||||
|
||||
// quoted line (do not wrap)
|
||||
if ($wrap_quoted && $string[0] == '>') {
|
||||
if ($breakPos === $stringLength - 1 || $breakPos === false) {
|
||||
$subString = $string;
|
||||
$cutLength = null;
|
||||
}
|
||||
else {
|
||||
$subString = mb_substr($string, 0, $breakPos);
|
||||
$cutLength = $breakPos + 1;
|
||||
}
|
||||
}
|
||||
// next line found and current line is shorter than the limit
|
||||
else if ($breakPos !== false && $breakPos < $width) {
|
||||
if ($breakPos === $stringLength - 1) {
|
||||
$subString = $string;
|
||||
$cutLength = null;
|
||||
}
|
||||
else {
|
||||
$subString = mb_substr($string, 0, $breakPos);
|
||||
$cutLength = $breakPos + 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$subString = mb_substr($string, 0, $width);
|
||||
|
||||
// last line
|
||||
if ($breakPos === false && $subString === $string) {
|
||||
$cutLength = null;
|
||||
}
|
||||
else {
|
||||
$nextChar = mb_substr($string, $width, 1);
|
||||
|
||||
if ($nextChar === ' ' || $nextChar === $separator) {
|
||||
$afterNextChar = mb_substr($string, $width + 1, 1);
|
||||
|
||||
// Note: mb_substr() does never return False
|
||||
if ($afterNextChar === false || $afterNextChar === '') {
|
||||
$subString .= $nextChar;
|
||||
}
|
||||
|
||||
$cutLength = mb_strlen($subString) + 1;
|
||||
}
|
||||
else {
|
||||
$spacePos = mb_strrpos($subString, ' ', 0);
|
||||
|
||||
if ($spacePos !== false) {
|
||||
$subString = mb_substr($subString, 0, $spacePos);
|
||||
$cutLength = $spacePos + 1;
|
||||
}
|
||||
else if ($cut === false) {
|
||||
$spacePos = mb_strpos($string, ' ', 0);
|
||||
|
||||
if ($spacePos !== false && ($breakPos === false || $spacePos < $breakPos)) {
|
||||
$subString = mb_substr($string, 0, $spacePos);
|
||||
$cutLength = $spacePos + 1;
|
||||
}
|
||||
else if ($breakPos === false) {
|
||||
$subString = $string;
|
||||
$cutLength = null;
|
||||
}
|
||||
else {
|
||||
$subString = mb_substr($string, 0, $breakPos);
|
||||
$cutLength = $breakPos + 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$cutLength = $width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $subString;
|
||||
|
||||
if ($cutLength !== null) {
|
||||
$string = mb_substr($string, $cutLength, ($stringLength - $cutLength));
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($charset && $charset != RCUBE_CHARSET) {
|
||||
mb_internal_encoding(RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
return implode($break, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to guess the mime_type of an attachment.
|
||||
*
|
||||
* @param string $path Path to the file or file contents
|
||||
* @param string $name File name (with suffix)
|
||||
* @param string $failover Mime type supplied for failover
|
||||
* @param boolean $is_stream Set to True if $path contains file contents
|
||||
* @param boolean $skip_suffix Set to True if the config/mimetypes.php mappig should be ignored
|
||||
*
|
||||
* @return string
|
||||
* @author Till Klampaeckel <till@php.net>
|
||||
* @see http://de2.php.net/manual/en/ref.fileinfo.php
|
||||
* @see http://de2.php.net/mime_content_type
|
||||
*/
|
||||
public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false)
|
||||
{
|
||||
static $mime_ext = array();
|
||||
|
||||
$mime_type = null;
|
||||
$config = rcube::get_instance()->config;
|
||||
$mime_magic = $config->get('mime_magic');
|
||||
|
||||
if (!$skip_suffix && empty($mime_ext)) {
|
||||
foreach ($config->resolve_paths('mimetypes.php') as $fpath) {
|
||||
$mime_ext = array_merge($mime_ext, (array) @include($fpath));
|
||||
}
|
||||
}
|
||||
|
||||
// use file name suffix with hard-coded mime-type map
|
||||
if (!$skip_suffix && is_array($mime_ext) && $name) {
|
||||
if ($suffix = substr($name, strrpos($name, '.')+1)) {
|
||||
$mime_type = $mime_ext[strtolower($suffix)];
|
||||
}
|
||||
}
|
||||
|
||||
// try fileinfo extension if available
|
||||
if (!$mime_type && function_exists('finfo_open')) {
|
||||
// null as a 2nd argument should be the same as no argument
|
||||
// this however is not true on all systems/versions
|
||||
if ($mime_magic) {
|
||||
$finfo = finfo_open(FILEINFO_MIME, $mime_magic);
|
||||
}
|
||||
else {
|
||||
$finfo = finfo_open(FILEINFO_MIME);
|
||||
}
|
||||
|
||||
if ($finfo) {
|
||||
if ($is_stream)
|
||||
$mime_type = finfo_buffer($finfo, $path);
|
||||
else
|
||||
$mime_type = finfo_file($finfo, $path);
|
||||
finfo_close($finfo);
|
||||
}
|
||||
}
|
||||
|
||||
// try PHP's mime_content_type
|
||||
if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
|
||||
$mime_type = @mime_content_type($path);
|
||||
}
|
||||
|
||||
// fall back to user-submitted string
|
||||
if (!$mime_type) {
|
||||
$mime_type = $failover;
|
||||
}
|
||||
else {
|
||||
// Sometimes (PHP-5.3?) content-type contains charset definition,
|
||||
// Remove it (#1487122) also "charset=binary" is useless
|
||||
$mime_type = array_shift(preg_split('/[; ]/', $mime_type));
|
||||
}
|
||||
|
||||
return $mime_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mimetype => file extension mapping
|
||||
*
|
||||
* @param string Mime-Type to get extensions for
|
||||
*
|
||||
* @return array List of extensions matching the given mimetype or a hash array
|
||||
* with ext -> mimetype mappings if $mimetype is not given
|
||||
*/
|
||||
public static function get_mime_extensions($mimetype = null)
|
||||
{
|
||||
static $mime_types, $mime_extensions;
|
||||
|
||||
// return cached data
|
||||
if (is_array($mime_types)) {
|
||||
return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
|
||||
}
|
||||
|
||||
// load mapping file
|
||||
$file_paths = array();
|
||||
|
||||
if ($mime_types = rcube::get_instance()->config->get('mime_types')) {
|
||||
$file_paths[] = $mime_types;
|
||||
}
|
||||
|
||||
// try common locations
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
|
||||
$file_paths[] = 'C:/xampp/apache/conf/mime.types.';
|
||||
}
|
||||
else {
|
||||
$file_paths[] = '/etc/mime.types';
|
||||
$file_paths[] = '/etc/httpd/mime.types';
|
||||
$file_paths[] = '/etc/httpd2/mime.types';
|
||||
$file_paths[] = '/etc/apache/mime.types';
|
||||
$file_paths[] = '/etc/apache2/mime.types';
|
||||
$file_paths[] = '/etc/nginx/mime.types';
|
||||
$file_paths[] = '/usr/local/etc/httpd/conf/mime.types';
|
||||
$file_paths[] = '/usr/local/etc/apache/conf/mime.types';
|
||||
$file_paths[] = '/usr/local/etc/apache24/mime.types';
|
||||
}
|
||||
|
||||
foreach ($file_paths as $fp) {
|
||||
if (@is_readable($fp)) {
|
||||
$lines = file($fp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$mime_types = $mime_extensions = array();
|
||||
$regex = "/([\w\+\-\.\/]+)\s+([\w\s]+)/i";
|
||||
foreach((array)$lines as $line) {
|
||||
// skip comments or mime types w/o any extensions
|
||||
if ($line[0] == '#' || !preg_match($regex, $line, $matches))
|
||||
continue;
|
||||
|
||||
$mime = $matches[1];
|
||||
foreach (explode(' ', $matches[2]) as $ext) {
|
||||
$ext = trim($ext);
|
||||
$mime_types[$mime][] = $ext;
|
||||
$mime_extensions[$ext] = $mime;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to some well-known types most important for daily emails
|
||||
if (empty($mime_types)) {
|
||||
foreach (rcube::get_instance()->config->resolve_paths('mimetypes.php') as $fpath) {
|
||||
$mime_extensions = array_merge($mime_extensions, (array) @include($fpath));
|
||||
}
|
||||
|
||||
foreach ($mime_extensions as $ext => $mime) {
|
||||
$mime_types[$mime][] = $ext;
|
||||
}
|
||||
}
|
||||
|
||||
// Add some known aliases that aren't included by some mime.types (#1488891)
|
||||
// the order is important here so standard extensions have higher prio
|
||||
$aliases = array(
|
||||
'image/gif' => array('gif'),
|
||||
'image/png' => array('png'),
|
||||
'image/x-png' => array('png'),
|
||||
'image/jpeg' => array('jpg', 'jpeg', 'jpe'),
|
||||
'image/jpg' => array('jpg', 'jpeg', 'jpe'),
|
||||
'image/pjpeg' => array('jpg', 'jpeg', 'jpe'),
|
||||
'image/tiff' => array('tif'),
|
||||
'message/rfc822' => array('eml'),
|
||||
'text/x-mail' => array('eml'),
|
||||
);
|
||||
|
||||
foreach ($aliases as $mime => $exts) {
|
||||
$mime_types[$mime] = array_unique(array_merge((array) $mime_types[$mime], $exts));
|
||||
|
||||
foreach ($exts as $ext) {
|
||||
if (!isset($mime_extensions[$ext])) {
|
||||
$mime_extensions[$ext] = $mime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect image type of the given binary data by checking magic numbers.
|
||||
*
|
||||
* @param string $data Binary file content
|
||||
*
|
||||
* @return string Detected mime-type or jpeg as fallback
|
||||
*/
|
||||
public static function image_content_type($data)
|
||||
{
|
||||
$type = 'jpeg';
|
||||
if (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png';
|
||||
else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif';
|
||||
else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico';
|
||||
// else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg';
|
||||
|
||||
return 'image/' . $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to fix invalid email addresses
|
||||
*/
|
||||
public static function fix_email($email)
|
||||
{
|
||||
$parts = rcube_utils::explode_quoted_string('@', $email);
|
||||
foreach ($parts as $idx => $part) {
|
||||
// remove redundant quoting (#1490040)
|
||||
if ($part[0] == '"' && preg_match('/^"([a-zA-Z0-9._+=-]+)"$/', $part, $m)) {
|
||||
$parts[$idx] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
return implode('@', $parts);
|
||||
}
|
||||
}
|
||||
399
data/web/rc/program/lib/Roundcube/rcube_mime_decode.php
Normal file
399
data/web/rc/program/lib/Roundcube/rcube_mime_decode.php
Normal file
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2015, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011-2015, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| MIME message parsing utilities derived from Mail_mimeDecode |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Richard Heyes <richard@phpguru.org> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for parsing MIME messages
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_mime_decode
|
||||
{
|
||||
/**
|
||||
* Class configuration parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params = array(
|
||||
'include_bodies' => true,
|
||||
'decode_bodies' => true,
|
||||
'decode_headers' => true,
|
||||
'crlf' => "\r\n",
|
||||
'default_charset' => RCUBE_CHARSET,
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Sets up the object, initialise the variables, and splits and
|
||||
* stores the header and body of the input.
|
||||
*
|
||||
* @param array $params An array of various parameters that determine
|
||||
* various things:
|
||||
* include_bodies - Whether to include the body in the returned
|
||||
* object.
|
||||
* decode_bodies - Whether to decode the bodies
|
||||
* of the parts. (Transfer encoding)
|
||||
* decode_headers - Whether to decode headers
|
||||
* crlf - CRLF type to use (CRLF/LF/CR)
|
||||
*/
|
||||
public function __construct($params = array())
|
||||
{
|
||||
if (!empty($params)) {
|
||||
$this->params = array_merge($this->params, (array) $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the decoding process.
|
||||
*
|
||||
* @param string $input The input to decode
|
||||
* @param bool $convert Convert result to rcube_message_part structure
|
||||
*
|
||||
* @return object|bool Decoded results or False on failure
|
||||
*/
|
||||
public function decode($input, $convert = true)
|
||||
{
|
||||
list($header, $body) = $this->splitBodyHeader($input);
|
||||
|
||||
$struct = $this->do_decode($header, $body);
|
||||
|
||||
if ($struct && $convert) {
|
||||
$struct = $this->structure_part($struct);
|
||||
}
|
||||
|
||||
return $struct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the decoding. Decodes the body string passed to it
|
||||
* If it finds certain content-types it will call itself in a
|
||||
* recursive fashion
|
||||
*
|
||||
* @param string $headers Header section
|
||||
* @param string $body Body section
|
||||
* @param string $default_ctype Default content type
|
||||
*
|
||||
* @return object|bool Decoded results or False on error
|
||||
*/
|
||||
protected function do_decode($headers, $body, $default_ctype = 'text/plain')
|
||||
{
|
||||
$return = new stdClass;
|
||||
$headers = $this->parseHeaders($headers);
|
||||
|
||||
while (list($key, $value) = each($headers)) {
|
||||
$header_name = strtolower($value['name']);
|
||||
|
||||
if (isset($return->headers[$header_name]) && !is_array($return->headers[$header_name])) {
|
||||
$return->headers[$header_name] = array($return->headers[$header_name]);
|
||||
$return->headers[$header_name][] = $value['value'];
|
||||
}
|
||||
else if (isset($return->headers[$header_name])) {
|
||||
$return->headers[$header_name][] = $value['value'];
|
||||
}
|
||||
else {
|
||||
$return->headers[$header_name] = $value['value'];
|
||||
}
|
||||
|
||||
switch ($header_name) {
|
||||
case 'content-type':
|
||||
$content_type = $this->parseHeaderValue($value['value']);
|
||||
|
||||
if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
|
||||
$return->ctype_primary = $regs[1];
|
||||
$return->ctype_secondary = $regs[2];
|
||||
}
|
||||
|
||||
if (isset($content_type['other'])) {
|
||||
while (list($p_name, $p_value) = each($content_type['other'])) {
|
||||
$return->ctype_parameters[$p_name] = $p_value;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'content-disposition';
|
||||
$content_disposition = $this->parseHeaderValue($value['value']);
|
||||
$return->disposition = $content_disposition['value'];
|
||||
|
||||
if (isset($content_disposition['other'])) {
|
||||
while (list($p_name, $p_value) = each($content_disposition['other'])) {
|
||||
$return->d_parameters[$p_name] = $p_value;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'content-transfer-encoding':
|
||||
$content_transfer_encoding = $this->parseHeaderValue($value['value']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($content_type)) {
|
||||
$ctype = strtolower($content_type['value']);
|
||||
|
||||
switch ($ctype) {
|
||||
case 'text/plain':
|
||||
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
||||
|
||||
if ($this->params['include_bodies']) {
|
||||
$return->body = $this->params['decode_bodies'] ? rcube_mime::decode($body, $encoding) : $body;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'text/html':
|
||||
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
||||
|
||||
if ($this->params['include_bodies']) {
|
||||
$return->body = $this->params['decode_bodies'] ? rcube_mime::decode($body, $encoding) : $body;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'multipart/digest':
|
||||
case 'multipart/alternative':
|
||||
case 'multipart/related':
|
||||
case 'multipart/mixed':
|
||||
case 'multipart/signed':
|
||||
case 'multipart/encrypted':
|
||||
if (!isset($content_type['other']['boundary'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$default_ctype = $ctype === 'multipart/digest' ? 'message/rfc822' : 'text/plain';
|
||||
$parts = $this->boundarySplit($body, $content_type['other']['boundary']);
|
||||
|
||||
for ($i = 0; $i < count($parts); $i++) {
|
||||
list($part_header, $part_body) = $this->splitBodyHeader($parts[$i]);
|
||||
$return->parts[] = $this->do_decode($part_header, $part_body, $default_ctype);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'message/rfc822':
|
||||
$obj = new rcube_mime_decode($this->params);
|
||||
$return->parts[] = $obj->decode($body, false);
|
||||
unset($obj);
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($this->params['include_bodies']) {
|
||||
$return->body = $this->params['decode_bodies'] ? rcube_mime::decode($body, $content_transfer_encoding['value']) : $body;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$ctype = explode('/', $default_ctype);
|
||||
$return->ctype_primary = $ctype[0];
|
||||
$return->ctype_secondary = $ctype[1];
|
||||
|
||||
if ($this->params['include_bodies']) {
|
||||
$return->body = $this->params['decode_bodies'] ? rcube_mime::decode($body) : $body;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string containing a header and body
|
||||
* section, this function will split them (at the first
|
||||
* blank line) and return them.
|
||||
*
|
||||
* @param string $input Input to split apart
|
||||
*
|
||||
* @return array Contains header and body section
|
||||
*/
|
||||
protected function splitBodyHeader($input)
|
||||
{
|
||||
$pos = strpos($input, $this->params['crlf'] . $this->params['crlf']);
|
||||
if ($pos === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$crlf_len = strlen($this->params['crlf']);
|
||||
$header = substr($input, 0, $pos);
|
||||
$body = substr($input, $pos + 2 * $crlf_len);
|
||||
|
||||
if (substr_compare($body, $this->params['crlf'], -$crlf_len) === 0) {
|
||||
$body = substr($body, 0, -$crlf_len);
|
||||
}
|
||||
|
||||
return array($header, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse headers given in $input and return as assoc array.
|
||||
*
|
||||
* @param string $input Headers to parse
|
||||
*
|
||||
* @return array Contains parsed headers
|
||||
*/
|
||||
protected function parseHeaders($input)
|
||||
{
|
||||
if ($input !== '') {
|
||||
// Unfold the input
|
||||
$input = preg_replace('/' . $this->params['crlf'] . "(\t| )/", ' ', $input);
|
||||
$headers = explode($this->params['crlf'], trim($input));
|
||||
|
||||
foreach ($headers as $value) {
|
||||
$hdr_name = substr($value, 0, $pos = strpos($value, ':'));
|
||||
$hdr_value = substr($value, $pos+1);
|
||||
|
||||
if ($hdr_value[0] == ' ') {
|
||||
$hdr_value = substr($hdr_value, 1);
|
||||
}
|
||||
|
||||
$return[] = array(
|
||||
'name' => $hdr_name,
|
||||
'value' => $this->params['decode_headers'] ? $this->decodeHeader($hdr_value) : $hdr_value,
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$return = array();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to parse a header value, extract first part, and any secondary
|
||||
* parts (after ;) This function is not as robust as it could be.
|
||||
* Eg. header comments in the wrong place will probably break it.
|
||||
*
|
||||
* @param string $input Header value to parse
|
||||
*
|
||||
* @return array Contains parsed result
|
||||
*/
|
||||
protected function parseHeaderValue($input)
|
||||
{
|
||||
$parts = preg_split('/;\s*/', $input);
|
||||
|
||||
if (!empty($parts)) {
|
||||
$return['value'] = trim($parts[0]);
|
||||
|
||||
for ($n = 1; $n < count($parts); $n++) {
|
||||
if (preg_match_all('/(([[:alnum:]]+)="?([^"]*)"?\s?;?)+/i', $parts[$n], $matches)) {
|
||||
for ($i = 0; $i < count($matches[2]); $i++) {
|
||||
$return['other'][strtolower($matches[2][$i])] = $matches[3][$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$return['value'] = trim($input);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function splits the input based on the given boundary
|
||||
*
|
||||
* @param string $input Input to parse
|
||||
* @param string $boundary Boundary
|
||||
*
|
||||
* @return array Contains array of resulting mime parts
|
||||
*/
|
||||
protected function boundarySplit($input, $boundary)
|
||||
{
|
||||
$tmp = explode('--' . $boundary, $input);
|
||||
|
||||
for ($i = 1; $i < count($tmp)-1; $i++) {
|
||||
$parts[] = $tmp[$i];
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a header, this function will decode it according to RFC2047.
|
||||
* Probably not *exactly* conformant, but it does pass all the given
|
||||
* examples (in RFC2047).
|
||||
*
|
||||
* @param string $input Input header value to decode
|
||||
*
|
||||
* @return string Decoded header value
|
||||
*/
|
||||
protected function decodeHeader($input)
|
||||
{
|
||||
return rcube_mime::decode_mime_string($input, $this->params['default_charset']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to convert a rcube_mime_decode structure
|
||||
* into a rcube_message_part object.
|
||||
*
|
||||
* @param object $part A message part struct
|
||||
* @param int $count Part count
|
||||
* @param string $parent Parent MIME ID
|
||||
*
|
||||
* @return object rcube_message_part
|
||||
* @see self::decode()
|
||||
*/
|
||||
protected function structure_part($part, $count = 0, $parent = '')
|
||||
{
|
||||
$struct = new rcube_message_part;
|
||||
$struct->mime_id = $part->mime_id ?: (empty($parent) ? (string)$count : "$parent.$count");
|
||||
$struct->headers = $part->headers;
|
||||
$struct->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
|
||||
$struct->ctype_primary = $part->ctype_primary;
|
||||
$struct->ctype_secondary = $part->ctype_secondary;
|
||||
$struct->ctype_parameters = $part->ctype_parameters;
|
||||
|
||||
if ($part->headers['content-transfer-encoding']) {
|
||||
$struct->encoding = $part->headers['content-transfer-encoding'];
|
||||
}
|
||||
|
||||
if ($part->ctype_parameters['charset']) {
|
||||
$struct->charset = $part->ctype_parameters['charset'];
|
||||
}
|
||||
|
||||
$part_charset = $struct->charset ?: $this->params['default_charset'];
|
||||
|
||||
// determine filename
|
||||
if (($filename = $part->d_parameters['filename']) || ($filename = $part->ctype_parameters['name'])) {
|
||||
if (!$this->params['decode_headers']) {
|
||||
$filename = $this->decodeHeader($filename);
|
||||
}
|
||||
|
||||
$struct->filename = $filename;
|
||||
}
|
||||
|
||||
$struct->body = $part->body;
|
||||
$struct->size = strlen($part->body);
|
||||
$struct->disposition = $part->disposition;
|
||||
|
||||
$count = 0;
|
||||
foreach ((array)$part->parts as $child_part) {
|
||||
$struct->parts[] = $this->structure_part($child_part, ++$count, $struct->mime_id);
|
||||
}
|
||||
|
||||
return $struct;
|
||||
}
|
||||
}
|
||||
286
data/web/rc/program/lib/Roundcube/rcube_output.php
Normal file
286
data/web/rc/program/lib/Roundcube/rcube_output.php
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube PHP suite |
|
||||
| Copyright (C) 2005-2014 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@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for output generation
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage View
|
||||
*/
|
||||
abstract class rcube_output
|
||||
{
|
||||
public $browser;
|
||||
|
||||
protected $app;
|
||||
protected $config;
|
||||
protected $charset = RCUBE_CHARSET;
|
||||
protected $env = array();
|
||||
protected $skins = array();
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->app = rcube::get_instance();
|
||||
$this->config = $this->app->config;
|
||||
$this->browser = new rcube_browser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter
|
||||
*/
|
||||
public function __get($var)
|
||||
{
|
||||
// allow read-only access to some members
|
||||
switch ($var) {
|
||||
case 'env': return $this->env;
|
||||
case 'skins': return $this->skins;
|
||||
case 'charset': return $this->charset;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for output charset.
|
||||
* To be specified in a meta tag and sent as http-header
|
||||
*
|
||||
* @param string $charset Charset name
|
||||
*/
|
||||
public function set_charset($charset)
|
||||
{
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for output charset
|
||||
*
|
||||
* @return string Output charset name
|
||||
*/
|
||||
public function get_charset()
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set environment variable
|
||||
*
|
||||
* @param string $name Property name
|
||||
* @param mixed $value Property value
|
||||
*/
|
||||
public function set_env($name, $value)
|
||||
{
|
||||
$this->env[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment variable getter.
|
||||
*
|
||||
* @param string $name Property name
|
||||
*
|
||||
* @return mixed Property value
|
||||
*/
|
||||
public function get_env($name)
|
||||
{
|
||||
return $this->env[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored env variables and commands
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->env = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke display_message command
|
||||
*
|
||||
* @param string $message Message to display
|
||||
* @param string $type Message type [notice|confirm|error]
|
||||
* @param array $vars Key-value pairs to be replaced in localized text
|
||||
* @param boolean $override Override last set message
|
||||
* @param int $timeout Message displaying time in seconds
|
||||
*/
|
||||
abstract function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0);
|
||||
|
||||
/**
|
||||
* Redirect to a certain url.
|
||||
*
|
||||
* @param mixed $p Either a string with the action or url parameters as key-value pairs
|
||||
* @param int $delay Delay in seconds
|
||||
*/
|
||||
abstract function redirect($p = array(), $delay = 1);
|
||||
|
||||
/**
|
||||
* Send output to the client.
|
||||
*/
|
||||
abstract function send();
|
||||
|
||||
/**
|
||||
* Send HTTP headers to prevent caching a page
|
||||
*/
|
||||
public function nocacheing_headers()
|
||||
{
|
||||
if (headers_sent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
|
||||
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
|
||||
|
||||
// We need to set the following headers to make downloads work using IE in HTTPS mode.
|
||||
if ($this->browser->ie && rcube_utils::https_check()) {
|
||||
header('Pragma: private');
|
||||
header("Cache-Control: private, must-revalidate");
|
||||
}
|
||||
else {
|
||||
header("Cache-Control: private, no-cache, no-store, must-revalidate, post-check=0, pre-check=0");
|
||||
header("Pragma: no-cache");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send header with expire date 30 days in future
|
||||
*
|
||||
* @param int Expiration time in seconds
|
||||
*/
|
||||
public function future_expire_header($offset = 2600000)
|
||||
{
|
||||
if (headers_sent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
header("Expires: " . gmdate("D, d M Y H:i:s", time()+$offset) . " GMT");
|
||||
header("Cache-Control: max-age=$offset");
|
||||
header("Pragma: ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send browser compatibility/security/etc. headers
|
||||
*/
|
||||
public function common_headers()
|
||||
{
|
||||
if (headers_sent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlock IE compatibility mode
|
||||
if ($this->browser->ie) {
|
||||
header('X-UA-Compatible: IE=edge');
|
||||
}
|
||||
|
||||
// Request browser to disable DNS prefetching (CVE-2010-0464)
|
||||
header("X-DNS-Prefetch-Control: off");
|
||||
|
||||
// send CSRF and clickjacking protection headers
|
||||
if ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')) {
|
||||
header('X-Frame-Options: ' . $xframe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error page and terminate script execution
|
||||
*
|
||||
* @param int $code Error code
|
||||
* @param string $message Error message
|
||||
*/
|
||||
public function raise_error($code, $message)
|
||||
{
|
||||
// STUB: to be overloaded by specific output classes
|
||||
fputs(STDERR, "Error $code: $message\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an edit field for inclusion on a form
|
||||
*
|
||||
* @param string $col Field name
|
||||
* @param string $value Field value
|
||||
* @param array $attrib HTML element attributes for the field
|
||||
* @param string $type HTML element type (default 'text')
|
||||
*
|
||||
* @return string HTML field definition
|
||||
*/
|
||||
public static function get_edit_field($col, $value, $attrib, $type = 'text')
|
||||
{
|
||||
static $colcounts = array();
|
||||
|
||||
$fname = '_'.$col;
|
||||
$attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
|
||||
$attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
|
||||
|
||||
if ($type == 'checkbox') {
|
||||
$attrib['value'] = '1';
|
||||
$input = new html_checkbox($attrib);
|
||||
}
|
||||
else if ($type == 'textarea') {
|
||||
$attrib['cols'] = $attrib['size'];
|
||||
$input = new html_textarea($attrib);
|
||||
}
|
||||
else if ($type == 'select') {
|
||||
$input = new html_select($attrib);
|
||||
$input->add('---', '');
|
||||
$input->add(array_values($attrib['options']), array_keys($attrib['options']));
|
||||
}
|
||||
else if ($type == 'password' || $attrib['type'] == 'password') {
|
||||
$input = new html_passwordfield($attrib);
|
||||
}
|
||||
else {
|
||||
if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden') {
|
||||
$attrib['type'] = 'text';
|
||||
}
|
||||
$input = new html_inputfield($attrib);
|
||||
}
|
||||
|
||||
// use value from post
|
||||
if (isset($_POST[$fname])) {
|
||||
$postvalue = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
|
||||
$value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
|
||||
}
|
||||
|
||||
$out = $input->show($value);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a variable into a javascript object notation
|
||||
*
|
||||
* @param mixed $input Input value
|
||||
* @param boolean $pretty Enable JSON formatting
|
||||
*
|
||||
* @return string Serialized JSON string
|
||||
*/
|
||||
public static function json_serialize($input, $pretty = false)
|
||||
{
|
||||
$input = rcube_charset::clean($input);
|
||||
$options = 0;
|
||||
|
||||
if ($pretty) {
|
||||
$options |= JSON_PRETTY_PRINT;
|
||||
}
|
||||
|
||||
// sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
|
||||
// that's why we have @ here
|
||||
return @json_encode($input, $options);
|
||||
}
|
||||
}
|
||||
451
data/web/rc/program/lib/Roundcube/rcube_plugin.php
Normal file
451
data/web/rc/program/lib/Roundcube/rcube_plugin.php
Normal file
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2008-2014, 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: |
|
||||
| Abstract plugins interface/class |
|
||||
| All plugins need to extend this class |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Plugin interface class
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage PluginAPI
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* A list of config option names that can be modified
|
||||
* by the user via user interface (with save-prefs command)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $allowed_prefs;
|
||||
|
||||
protected $home;
|
||||
protected $urlbase;
|
||||
private $mytask;
|
||||
private $loaded_config = array();
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Provide information about this
|
||||
*
|
||||
* @return array Meta information about a plugin or false if not implemented:
|
||||
* As hash array with the following keys:
|
||||
* name: The plugin name
|
||||
* vendor: Name of the plugin developer
|
||||
* version: Plugin version name
|
||||
* license: License name (short form according to http://spdx.org/licenses/)
|
||||
* uri: The URL to the plugin homepage or source repository
|
||||
* src_uri: Direct download URL to the source code of this plugin
|
||||
* require: List of plugins required for this one (as array of plugin names)
|
||||
*/
|
||||
public static function info()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load the given plugin which is optional for the current plugin
|
||||
*
|
||||
* @param string Plugin name
|
||||
* @return boolean True on success, false on failure
|
||||
*/
|
||||
public function include_plugin($plugin_name)
|
||||
{
|
||||
return $this->api->load_plugin($plugin_name, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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')
|
||||
{
|
||||
if (in_array($fname, $this->loaded_config)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->loaded_config[] = $fname;
|
||||
|
||||
$fpath = $this->home.'/'.$fname;
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
if (($is_local = 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;
|
||||
}
|
||||
else if (!$is_local) {
|
||||
// Search plugin_name.inc.php file in any configured path
|
||||
return $rcube->config->load_from_file($this->ID . '.inc.php');
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 && method_exists($rcube->output, 'add_label')) {
|
||||
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 add_label() adding the plugin ID as domain
|
||||
*/
|
||||
public function add_label()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
if (method_exists($rcube->output, 'add_label')) {
|
||||
$args = func_get_args();
|
||||
if (count($args) == 1 && is_array($args[0])) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$args = array_map(array($this, 'label_map_callback'), $args);
|
||||
$rcube->output->add_label($args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for rcube::gettext() adding the plugin ID as domain
|
||||
*
|
||||
* @param string $p Message identifier
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$skins = array_keys((array)$rcube->output->skins);
|
||||
if (empty($skins)) {
|
||||
$skins = (array) $rcube->config->get('skin');
|
||||
}
|
||||
foreach ($skins as $skin) {
|
||||
$skin_path = 'skins/' . $skin;
|
||||
if (is_dir(realpath(slashify($this->home) . $skin_path))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $skin_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for array_map
|
||||
*
|
||||
* @param string $key Array key.
|
||||
* @return string
|
||||
*/
|
||||
private function label_map_callback($key)
|
||||
{
|
||||
if (strpos($key, $this->ID.'.') === 0) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
return $this->ID.'.'.$key;
|
||||
}
|
||||
}
|
||||
671
data/web/rc/program/lib/Roundcube/rcube_plugin_api.php
Normal file
671
data/web/rc/program/lib/Roundcube/rcube_plugin_api.php
Normal file
@@ -0,0 +1,671 @@
|
||||
<?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: |
|
||||
| Plugins repository |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
// location where plugins are loade from
|
||||
if (!defined('RCUBE_PLUGINS_DIR')) {
|
||||
define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
|
||||
}
|
||||
|
||||
/**
|
||||
* The plugin loader and global API
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage PluginAPI
|
||||
*/
|
||||
class rcube_plugin_api
|
||||
{
|
||||
static protected $instance;
|
||||
|
||||
public $dir;
|
||||
public $url = 'plugins/';
|
||||
public $task = '';
|
||||
public $initialized = false;
|
||||
|
||||
public $output;
|
||||
public $handlers = array();
|
||||
public $allowed_prefs = array();
|
||||
public $allowed_session_prefs = array();
|
||||
public $active_plugins = array();
|
||||
|
||||
protected $plugins = array();
|
||||
protected $plugins_initialized = array();
|
||||
protected $tasks = array();
|
||||
protected $actions = array();
|
||||
protected $actionmap = array();
|
||||
protected $objectsmap = array();
|
||||
protected $template_contents = array();
|
||||
protected $exec_stack = array();
|
||||
protected $deprecated_hooks = array();
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
foreach ($this->plugins as $plugin) {
|
||||
// ... task, request type and framed mode
|
||||
if (!$this->plugins_initialized[$plugin->ID] && !$this->filter($plugin)) {
|
||||
$plugin->init();
|
||||
$this->plugins_initialized[$plugin->ID] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
// we have finished initializing all plugins
|
||||
$this->initialized = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the specified plugin
|
||||
*
|
||||
* @param string Plugin name
|
||||
* @param boolean Force loading of the plugin even if it doesn't match the filter
|
||||
* @param boolean Require loading of the plugin, error if it doesn't exist
|
||||
*
|
||||
* @return boolean True on success, false if not loaded or failure
|
||||
*/
|
||||
public function load_plugin($plugin_name, $force = false, $require = true)
|
||||
{
|
||||
static $plugins_dir;
|
||||
|
||||
if (!$plugins_dir) {
|
||||
$dir = dir($this->dir);
|
||||
$plugins_dir = unslashify($dir->path);
|
||||
}
|
||||
|
||||
// plugin already loaded?
|
||||
if (!$this->plugins[$plugin_name]) {
|
||||
$fn = "$plugins_dir/$plugin_name/$plugin_name.php";
|
||||
|
||||
if (!is_readable($fn)) {
|
||||
if ($require) {
|
||||
rcube::raise_error(array('code' => 520, 'type' => 'php',
|
||||
'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "Failed to load plugin file $fn"), true, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!class_exists($plugin_name, false)) {
|
||||
include $fn;
|
||||
}
|
||||
|
||||
// instantiate class if exists
|
||||
if (!class_exists($plugin_name, false)) {
|
||||
rcube::raise_error(array('code' => 520, 'type' => 'php',
|
||||
'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "No plugin class $plugin_name found in $fn"),
|
||||
true, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$plugin = new $plugin_name($this);
|
||||
$this->active_plugins[] = $plugin_name;
|
||||
|
||||
// check inheritance...
|
||||
if (is_subclass_of($plugin, 'rcube_plugin')) {
|
||||
// call onload method on plugin if it exists.
|
||||
// this is useful if you want to be called early in the boot process
|
||||
if (method_exists($plugin, 'onload')) {
|
||||
$plugin->onload();
|
||||
}
|
||||
|
||||
if (!empty($plugin->allowed_prefs)) {
|
||||
$this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs);
|
||||
}
|
||||
|
||||
$this->plugins[$plugin_name] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
if ($plugin = $this->plugins[$plugin_name]) {
|
||||
// init a plugin only if $force is set or if we're called after initialization
|
||||
if (($force || $this->initialized) && !$this->plugins_initialized[$plugin_name] && ($force || !$this->filter($plugin))) {
|
||||
$plugin->init();
|
||||
$this->plugins_initialized[$plugin_name] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if we should prevent this plugin from initialising
|
||||
*
|
||||
* @param $plugin
|
||||
* @return bool
|
||||
*/
|
||||
private function filter($plugin)
|
||||
{
|
||||
return ($plugin->noajax && !(is_object($this->output) && $this->output->type == 'html'))
|
||||
|| ($plugin->task && !preg_match('/^('.$plugin->task.')$/i', $this->task))
|
||||
|| ($plugin->noframe && !empty($_REQUEST['_framed']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a specific plugin.
|
||||
* This is either provided my a plugin's info() method or extracted from a package.xml or a composer.json file
|
||||
*
|
||||
* @param string Plugin name
|
||||
* @return array Meta information about a plugin or False if plugin was not found
|
||||
*/
|
||||
public function get_info($plugin_name)
|
||||
{
|
||||
static $composer_lock, $license_uris = array(
|
||||
'Apache' => 'http://www.apache.org/licenses/LICENSE-2.0.html',
|
||||
'Apache-2' => 'http://www.apache.org/licenses/LICENSE-2.0.html',
|
||||
'Apache-1' => 'http://www.apache.org/licenses/LICENSE-1.0',
|
||||
'Apache-1.1' => 'http://www.apache.org/licenses/LICENSE-1.1',
|
||||
'GPL' => 'http://www.gnu.org/licenses/gpl.html',
|
||||
'GPLv2' => 'http://www.gnu.org/licenses/gpl-2.0.html',
|
||||
'GPL-2.0' => 'http://www.gnu.org/licenses/gpl-2.0.html',
|
||||
'GPLv3' => 'http://www.gnu.org/licenses/gpl-3.0.html',
|
||||
'GPLv3+' => 'http://www.gnu.org/licenses/gpl-3.0.html',
|
||||
'GPL-3.0' => 'http://www.gnu.org/licenses/gpl-3.0.html',
|
||||
'GPL-3.0+' => 'http://www.gnu.org/licenses/gpl.html',
|
||||
'GPL-2.0+' => 'http://www.gnu.org/licenses/gpl.html',
|
||||
'AGPLv3' => 'http://www.gnu.org/licenses/agpl.html',
|
||||
'AGPLv3+' => 'http://www.gnu.org/licenses/agpl.html',
|
||||
'AGPL-3.0' => 'http://www.gnu.org/licenses/agpl.html',
|
||||
'LGPL' => 'http://www.gnu.org/licenses/lgpl.html',
|
||||
'LGPLv2' => 'http://www.gnu.org/licenses/lgpl-2.0.html',
|
||||
'LGPLv2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html',
|
||||
'LGPLv3' => 'http://www.gnu.org/licenses/lgpl.html',
|
||||
'LGPL-2.0' => 'http://www.gnu.org/licenses/lgpl-2.0.html',
|
||||
'LGPL-2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html',
|
||||
'LGPL-3.0' => 'http://www.gnu.org/licenses/lgpl.html',
|
||||
'LGPL-3.0+' => 'http://www.gnu.org/licenses/lgpl.html',
|
||||
'BSD' => 'http://opensource.org/licenses/bsd-license.html',
|
||||
'BSD-2-Clause' => 'http://opensource.org/licenses/BSD-2-Clause',
|
||||
'BSD-3-Clause' => 'http://opensource.org/licenses/BSD-3-Clause',
|
||||
'FreeBSD' => 'http://opensource.org/licenses/BSD-2-Clause',
|
||||
'MIT' => 'http://www.opensource.org/licenses/mit-license.php',
|
||||
'PHP' => 'http://opensource.org/licenses/PHP-3.0',
|
||||
'PHP-3' => 'http://www.php.net/license/3_01.txt',
|
||||
'PHP-3.0' => 'http://www.php.net/license/3_0.txt',
|
||||
'PHP-3.01' => 'http://www.php.net/license/3_01.txt',
|
||||
);
|
||||
|
||||
$dir = dir($this->dir);
|
||||
$fn = unslashify($dir->path) . "/$plugin_name/$plugin_name.php";
|
||||
$info = false;
|
||||
|
||||
if (!class_exists($plugin_name, false)) {
|
||||
if (is_readable($fn)) {
|
||||
include($fn);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (class_exists($plugin_name)) {
|
||||
$info = $plugin_name::info();
|
||||
}
|
||||
|
||||
// fall back to composer.json file
|
||||
if (!$info) {
|
||||
$composer = INSTALL_PATH . "/plugins/$plugin_name/composer.json";
|
||||
if (is_readable($composer) && ($json = @json_decode(file_get_contents($composer), true))) {
|
||||
list($info['vendor'], $info['name']) = explode('/', $json['name']);
|
||||
$info['version'] = $json['version'];
|
||||
$info['license'] = $json['license'];
|
||||
$info['uri'] = $json['homepage'];
|
||||
$info['require'] = array_filter(array_keys((array)$json['require']), function($pname) {
|
||||
if (strpos($pname, '/') == false) {
|
||||
return false;
|
||||
}
|
||||
list($vendor, $name) = explode('/', $pname);
|
||||
return !($name == 'plugin-installer' || $vendor == 'pear-pear');
|
||||
});
|
||||
}
|
||||
|
||||
// read local composer.lock file (once)
|
||||
if (!isset($composer_lock)) {
|
||||
$composer_lock = @json_decode(@file_get_contents(INSTALL_PATH . "/composer.lock"), true);
|
||||
if ($composer_lock['packages']) {
|
||||
foreach ($composer_lock['packages'] as $i => $package) {
|
||||
$composer_lock['installed'][$package['name']] = $package;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load additional information from local composer.lock file
|
||||
if ($lock = $composer_lock['installed'][$json['name']]) {
|
||||
$info['version'] = $lock['version'];
|
||||
$info['uri'] = $lock['homepage'] ?: $lock['source']['uri'];
|
||||
$info['src_uri'] = $lock['dist']['uri'] ?: $lock['source']['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to package.xml file
|
||||
if (!$info) {
|
||||
$package = INSTALL_PATH . "/plugins/$plugin_name/package.xml";
|
||||
if (is_readable($package) && ($file = file_get_contents($package))) {
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadXML($file);
|
||||
$xpath = new DOMXPath($doc);
|
||||
$xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0");
|
||||
|
||||
// XPaths of plugin metadata elements
|
||||
$metadata = array(
|
||||
'name' => 'string(//rc:package/rc:name)',
|
||||
'version' => 'string(//rc:package/rc:version/rc:release)',
|
||||
'license' => 'string(//rc:package/rc:license)',
|
||||
'license_uri' => 'string(//rc:package/rc:license/@uri)',
|
||||
'src_uri' => 'string(//rc:package/rc:srcuri)',
|
||||
'uri' => 'string(//rc:package/rc:uri)',
|
||||
);
|
||||
|
||||
foreach ($metadata as $key => $path) {
|
||||
$info[$key] = $xpath->evaluate($path);
|
||||
}
|
||||
|
||||
// dependent required plugins (can be used, but not included in config)
|
||||
$deps = $xpath->evaluate('//rc:package/rc:dependencies/rc:required/rc:package/rc:name');
|
||||
for ($i = 0; $i < $deps->length; $i++) {
|
||||
$dn = $deps->item($i)->nodeValue;
|
||||
$info['require'][] = $dn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At least provide the name
|
||||
if (!$info && class_exists($plugin_name)) {
|
||||
$info = array('name' => $plugin_name, 'version' => '--');
|
||||
}
|
||||
else if ($info['license'] && empty($info['license_uri']) && ($license_uri = $license_uris[$info['license']])) {
|
||||
$info['license_uri'] = $license_uri;
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, (array) $this->handlers[$hook]);
|
||||
if ($callback_id !== false) {
|
||||
// array_splice() removes the element and re-indexes keys
|
||||
// that is required by the 'for' loop in exec_hook() below
|
||||
array_splice($this->handlers[$hook], $callback_id, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// TODO: avoid recursion by checking in_array($hook, $this->exec_stack) ?
|
||||
|
||||
$args += array('abort' => false);
|
||||
array_push($this->exec_stack, $hook);
|
||||
|
||||
// Use for loop here, so handlers added in the hook will be executed too
|
||||
for ($i = 0; $i < count($this->handlers[$hook]); $i++) {
|
||||
$ret = call_user_func($this->handlers[$hook][$i], $args);
|
||||
if ($ret && is_array($ret)) {
|
||||
$args = $ret + $args;
|
||||
}
|
||||
|
||||
if ($args['break']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($this->exec_stack);
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, true)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 count($this->exec_stack) > 0 && (!$hook || in_array($hook, $this->exec_stack));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns loaded plugin
|
||||
*
|
||||
* @return rcube_plugin Plugin instance
|
||||
*/
|
||||
public function get_plugin($name)
|
||||
{
|
||||
return $this->plugins[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
419
data/web/rc/program/lib/Roundcube/rcube_result_index.php
Normal file
419
data/web/rc/program/lib/Roundcube/rcube_result_index.php
Normal file
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2011, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| SORT/SEARCH/ESEARCH response handler |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for accessing IMAP's SORT/SEARCH/ESEARCH result
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
*/
|
||||
class rcube_result_index
|
||||
{
|
||||
public $incomplete = false;
|
||||
|
||||
protected $raw_data;
|
||||
protected $mailbox;
|
||||
protected $meta = array();
|
||||
protected $params = array();
|
||||
protected $order = 'ASC';
|
||||
|
||||
const SEPARATOR_ELEMENT = ' ';
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*/
|
||||
public function __construct($mailbox = null, $data = null, $order = null)
|
||||
{
|
||||
$this->mailbox = $mailbox;
|
||||
$this->order = $order == 'DESC' ? 'DESC' : 'ASC';
|
||||
$this->init($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes object with SORT command response
|
||||
*
|
||||
* @param string $data IMAP response string
|
||||
*/
|
||||
public function init($data = null)
|
||||
{
|
||||
$this->meta = array();
|
||||
|
||||
$data = explode('*', (string)$data);
|
||||
|
||||
// ...skip unilateral untagged server responses
|
||||
for ($i=0, $len=count($data); $i<$len; $i++) {
|
||||
$data_item = &$data[$i];
|
||||
if (preg_match('/^ SORT/i', $data_item)) {
|
||||
// valid response, initialize raw_data for is_error()
|
||||
$this->raw_data = '';
|
||||
$data_item = substr($data_item, 5);
|
||||
break;
|
||||
}
|
||||
else if (preg_match('/^ (E?SEARCH)/i', $data_item, $m)) {
|
||||
// valid response, initialize raw_data for is_error()
|
||||
$this->raw_data = '';
|
||||
$data_item = substr($data_item, strlen($m[0]));
|
||||
|
||||
if (strtoupper($m[1]) == 'ESEARCH') {
|
||||
$data_item = trim($data_item);
|
||||
// remove MODSEQ response
|
||||
if (preg_match('/\(MODSEQ ([0-9]+)\)$/i', $data_item, $m)) {
|
||||
$data_item = substr($data_item, 0, -strlen($m[0]));
|
||||
$this->params['MODSEQ'] = $m[1];
|
||||
}
|
||||
// remove TAG response part
|
||||
if (preg_match('/^\(TAG ["a-z0-9]+\)\s*/i', $data_item, $m)) {
|
||||
$data_item = substr($data_item, strlen($m[0]));
|
||||
}
|
||||
// remove UID
|
||||
$data_item = preg_replace('/^UID\s*/i', '', $data_item);
|
||||
|
||||
// ESEARCH parameters
|
||||
while (preg_match('/^([a-z]+) ([0-9:,]+)\s*/i', $data_item, $m)) {
|
||||
$param = strtoupper($m[1]);
|
||||
$value = $m[2];
|
||||
|
||||
$this->params[$param] = $value;
|
||||
$data_item = substr($data_item, strlen($m[0]));
|
||||
|
||||
if (in_array($param, array('COUNT', 'MIN', 'MAX'))) {
|
||||
$this->meta[strtolower($param)] = (int) $value;
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: Implement compression using compressMessageSet() in __sleep() and __wakeup() ?
|
||||
// @TODO: work with compressed result?!
|
||||
if (isset($this->params['ALL'])) {
|
||||
$data_item = implode(self::SEPARATOR_ELEMENT,
|
||||
rcube_imap_generic::uncompressMessageSet($this->params['ALL']));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
unset($data[$i]);
|
||||
}
|
||||
|
||||
$data = array_filter($data);
|
||||
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = array_shift($data);
|
||||
$data = trim($data);
|
||||
$data = preg_replace('/[\r\n]/', '', $data);
|
||||
$data = preg_replace('/\s+/', ' ', $data);
|
||||
|
||||
$this->raw_data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the result from IMAP command
|
||||
*
|
||||
* @return bool True if the result is an error, False otherwise
|
||||
*/
|
||||
public function is_error()
|
||||
{
|
||||
return $this->raw_data === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the result is empty
|
||||
*
|
||||
* @return bool True if the result is empty, False otherwise
|
||||
*/
|
||||
public function is_empty()
|
||||
{
|
||||
return empty($this->raw_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in the result
|
||||
*
|
||||
* @return int Number of elements
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ($this->meta['count'] !== null)
|
||||
return $this->meta['count'];
|
||||
|
||||
if (empty($this->raw_data)) {
|
||||
$this->meta['count'] = 0;
|
||||
$this->meta['length'] = 0;
|
||||
}
|
||||
else {
|
||||
$this->meta['count'] = 1 + substr_count($this->raw_data, self::SEPARATOR_ELEMENT);
|
||||
}
|
||||
|
||||
return $this->meta['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in the result.
|
||||
* Alias for count() for compatibility with rcube_result_thread
|
||||
*
|
||||
* @return int Number of elements
|
||||
*/
|
||||
public function count_messages()
|
||||
{
|
||||
return $this->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns maximal message identifier in the result
|
||||
*
|
||||
* @return int Maximal message identifier
|
||||
*/
|
||||
public function max()
|
||||
{
|
||||
if (!isset($this->meta['max'])) {
|
||||
$this->meta['max'] = (int) @max($this->get());
|
||||
}
|
||||
|
||||
return $this->meta['max'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns minimal message identifier in the result
|
||||
*
|
||||
* @return int Minimal message identifier
|
||||
*/
|
||||
public function min()
|
||||
{
|
||||
if (!isset($this->meta['min'])) {
|
||||
$this->meta['min'] = (int) @min($this->get());
|
||||
}
|
||||
|
||||
return $this->meta['min'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Slices data set.
|
||||
*
|
||||
* @param $offset Offset (as for PHP's array_slice())
|
||||
* @param $length Number of elements (as for PHP's array_slice())
|
||||
*/
|
||||
public function slice($offset, $length)
|
||||
{
|
||||
$data = $this->get();
|
||||
$data = array_slice($data, $offset, $length);
|
||||
|
||||
$this->meta = array();
|
||||
$this->meta['count'] = count($data);
|
||||
$this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data set. Removes elements not listed in $ids list.
|
||||
*
|
||||
* @param array $ids List of IDs to remove.
|
||||
*/
|
||||
public function filter($ids = array())
|
||||
{
|
||||
$data = $this->get();
|
||||
$data = array_intersect($data, $ids);
|
||||
|
||||
$this->meta = array();
|
||||
$this->meta['count'] = count($data);
|
||||
$this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts order of elements in the result
|
||||
*/
|
||||
public function revert()
|
||||
{
|
||||
$this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
|
||||
|
||||
if (empty($this->raw_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->get();
|
||||
$data = array_reverse($data);
|
||||
$this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
|
||||
|
||||
$this->meta['pos'] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given message ID exists in the object
|
||||
*
|
||||
* @param int $msgid Message ID
|
||||
* @param bool $get_index When enabled element's index will be returned.
|
||||
* Elements are indexed starting with 0
|
||||
*
|
||||
* @return mixed False if message ID doesn't exist, True if exists or
|
||||
* index of the element if $get_index=true
|
||||
*/
|
||||
public function exists($msgid, $get_index = false)
|
||||
{
|
||||
if (empty($this->raw_data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$msgid = (int) $msgid;
|
||||
$begin = implode('|', array('^', preg_quote(self::SEPARATOR_ELEMENT, '/')));
|
||||
$end = implode('|', array('$', preg_quote(self::SEPARATOR_ELEMENT, '/')));
|
||||
|
||||
if (preg_match("/($begin)$msgid($end)/", $this->raw_data, $m,
|
||||
$get_index ? PREG_OFFSET_CAPTURE : null)
|
||||
) {
|
||||
if ($get_index) {
|
||||
$idx = 0;
|
||||
if ($m[0][1]) {
|
||||
$idx = 1 + substr_count($this->raw_data, self::SEPARATOR_ELEMENT, 0, $m[0][1]);
|
||||
}
|
||||
// cache position of this element, so we can use it in get_element()
|
||||
$this->meta['pos'][$idx] = (int)$m[0][1];
|
||||
|
||||
return $idx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all messages in the result.
|
||||
*
|
||||
* @return array List of message IDs
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
if (empty($this->raw_data)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return explode(self::SEPARATOR_ELEMENT, $this->raw_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all messages in the result.
|
||||
*
|
||||
* @return array List of message IDs
|
||||
*/
|
||||
public function get_compressed()
|
||||
{
|
||||
if (empty($this->raw_data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return rcube_imap_generic::compressMessageSet($this->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result element at specified index
|
||||
*
|
||||
* @param int|string $index Element's index or "FIRST" or "LAST"
|
||||
*
|
||||
* @return int Element value
|
||||
*/
|
||||
public function get_element($index)
|
||||
{
|
||||
$count = $this->count();
|
||||
|
||||
if (!$count) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// first element
|
||||
if ($index === 0 || $index === '0' || $index === 'FIRST') {
|
||||
$pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT);
|
||||
if ($pos === false)
|
||||
$result = (int) $this->raw_data;
|
||||
else
|
||||
$result = (int) substr($this->raw_data, 0, $pos);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// last element
|
||||
if ($index === 'LAST' || $index == $count-1) {
|
||||
$pos = strrpos($this->raw_data, self::SEPARATOR_ELEMENT);
|
||||
if ($pos === false)
|
||||
$result = (int) $this->raw_data;
|
||||
else
|
||||
$result = (int) substr($this->raw_data, $pos);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// do we know the position of the element or the neighbour of it?
|
||||
if (!empty($this->meta['pos'])) {
|
||||
if (isset($this->meta['pos'][$index]))
|
||||
$pos = $this->meta['pos'][$index];
|
||||
else if (isset($this->meta['pos'][$index-1]))
|
||||
$pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT,
|
||||
$this->meta['pos'][$index-1] + 1);
|
||||
else if (isset($this->meta['pos'][$index+1]))
|
||||
$pos = strrpos($this->raw_data, self::SEPARATOR_ELEMENT,
|
||||
$this->meta['pos'][$index+1] - $this->length() - 1);
|
||||
|
||||
if (isset($pos) && preg_match('/([0-9]+)/', $this->raw_data, $m, null, $pos)) {
|
||||
return (int) $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Finally use less effective method
|
||||
$data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
|
||||
|
||||
return $data[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns response parameters, e.g. ESEARCH's MIN/MAX/COUNT/ALL/MODSEQ
|
||||
* or internal data e.g. MAILBOX, ORDER
|
||||
*
|
||||
* @param string $param Parameter name
|
||||
*
|
||||
* @return array|string Response parameters or parameter value
|
||||
*/
|
||||
public function get_parameters($param=null)
|
||||
{
|
||||
$params = $this->params;
|
||||
$params['MAILBOX'] = $this->mailbox;
|
||||
$params['ORDER'] = $this->order;
|
||||
|
||||
if ($param !== null) {
|
||||
return $params[$param];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of internal data representation
|
||||
*
|
||||
* @return int Data length
|
||||
*/
|
||||
protected function length()
|
||||
{
|
||||
if (!isset($this->meta['length'])) {
|
||||
$this->meta['length'] = strlen($this->raw_data);
|
||||
}
|
||||
|
||||
return $this->meta['length'];
|
||||
}
|
||||
}
|
||||
348
data/web/rc/program/lib/Roundcube/rcube_result_multifolder.php
Normal file
348
data/web/rc/program/lib/Roundcube/rcube_result_multifolder.php
Normal file
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2011, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| SORT/SEARCH/ESEARCH response handler |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class holding a set of rcube_result_index instances that together form a
|
||||
* result set of a multi-folder search
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
*/
|
||||
class rcube_result_multifolder
|
||||
{
|
||||
public $multi = true;
|
||||
public $sets = array();
|
||||
public $incomplete = false;
|
||||
public $folder;
|
||||
|
||||
protected $meta = array();
|
||||
protected $index = array();
|
||||
protected $folders = array();
|
||||
protected $sdata = array();
|
||||
protected $order = 'ASC';
|
||||
protected $sorting;
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*/
|
||||
public function __construct($folders = array())
|
||||
{
|
||||
$this->folders = $folders;
|
||||
$this->meta = array('count' => 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes object with SORT command response
|
||||
*
|
||||
* @param string $data IMAP response string
|
||||
*/
|
||||
public function add($result)
|
||||
{
|
||||
$this->sets[] = $result;
|
||||
|
||||
if ($result->count()) {
|
||||
$this->append_result($result);
|
||||
}
|
||||
else if ($result->incomplete) {
|
||||
$this->incomplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append message UIDs from the given result to our index
|
||||
*/
|
||||
protected function append_result($result)
|
||||
{
|
||||
$this->meta['count'] += $result->count();
|
||||
|
||||
// append UIDs to global index
|
||||
$folder = $result->get_parameters('MAILBOX');
|
||||
$index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get());
|
||||
|
||||
$this->index = array_merge($this->index, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a global index of (sorted) message UIDs
|
||||
*/
|
||||
public function set_message_index($headers, $sort_field, $sort_order)
|
||||
{
|
||||
$this->index = array();
|
||||
foreach ($headers as $header) {
|
||||
$this->index[] = $header->uid . '-' . $header->folder;
|
||||
}
|
||||
|
||||
$this->sorting = $sort_field;
|
||||
$this->order = $sort_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the result from IMAP command
|
||||
*
|
||||
* @return bool True if the result is an error, False otherwise
|
||||
*/
|
||||
public function is_error()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the result is empty
|
||||
*
|
||||
* @return bool True if the result is empty, False otherwise
|
||||
*/
|
||||
public function is_empty()
|
||||
{
|
||||
return empty($this->sets) || $this->meta['count'] == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in the result
|
||||
*
|
||||
* @return int Number of elements
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->meta['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in the result.
|
||||
* Alias for count() for compatibility with rcube_result_thread
|
||||
*
|
||||
* @return int Number of elements
|
||||
*/
|
||||
public function count_messages()
|
||||
{
|
||||
return $this->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts order of elements in the result
|
||||
*/
|
||||
public function revert()
|
||||
{
|
||||
$this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
|
||||
$this->index = array_reverse($this->index);
|
||||
|
||||
// revert order in all sub-sets
|
||||
foreach ($this->sets as $set) {
|
||||
if ($this->order != $set->get_parameters('ORDER')) {
|
||||
$set->revert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given message ID exists in the object
|
||||
*
|
||||
* @param int $msgid Message ID
|
||||
* @param bool $get_index When enabled element's index will be returned.
|
||||
* Elements are indexed starting with 0
|
||||
* @return mixed False if message ID doesn't exist, True if exists or
|
||||
* index of the element if $get_index=true
|
||||
*/
|
||||
public function exists($msgid, $get_index = false)
|
||||
{
|
||||
if (!empty($this->folder)) {
|
||||
$msgid .= '-' . $this->folder;
|
||||
}
|
||||
|
||||
return array_search($msgid, $this->index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data set. Removes elements listed in $ids list.
|
||||
*
|
||||
* @param array $ids List of IDs to remove.
|
||||
* @param string $folder IMAP folder
|
||||
*/
|
||||
public function filter($ids = array(), $folder = null)
|
||||
{
|
||||
$this->meta['count'] = 0;
|
||||
foreach ($this->sets as $set) {
|
||||
if ($set->get_parameters('MAILBOX') == $folder) {
|
||||
$set->filter($ids);
|
||||
}
|
||||
|
||||
$this->meta['count'] += $set->count();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Slices data set.
|
||||
*
|
||||
* @param int $offset Offset (as for PHP's array_slice())
|
||||
* @param int $length Number of elements (as for PHP's array_slice())
|
||||
*/
|
||||
public function slice($offset, $length)
|
||||
{
|
||||
$data = array_slice($this->get(), $offset, $length);
|
||||
|
||||
$this->index = $data;
|
||||
$this->meta['count'] = count($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data set. Removes elements not listed in $ids list.
|
||||
*
|
||||
* @param array $ids List of IDs to keep.
|
||||
*/
|
||||
public function intersect($ids = array())
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all messages in the result.
|
||||
*
|
||||
* @return array List of message IDs
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all messages in the result in compressed form
|
||||
*
|
||||
* @return string List of message IDs in compressed form
|
||||
*/
|
||||
public function get_compressed()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result element at specified index
|
||||
*
|
||||
* @param int|string $index Element's index or "FIRST" or "LAST"
|
||||
*
|
||||
* @return int Element value
|
||||
*/
|
||||
public function get_element($idx)
|
||||
{
|
||||
switch ($idx) {
|
||||
case 'FIRST': return $this->index[0];
|
||||
case 'LAST': return end($this->index);
|
||||
default: return $this->index[$idx];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns response parameters, e.g. ESEARCH's MIN/MAX/COUNT/ALL/MODSEQ
|
||||
* or internal data e.g. MAILBOX, ORDER
|
||||
*
|
||||
* @param string $param Parameter name
|
||||
*
|
||||
* @return array|string Response parameters or parameter value
|
||||
*/
|
||||
public function get_parameters($param=null)
|
||||
{
|
||||
$params = array(
|
||||
'SORT' => $this->sorting,
|
||||
'ORDER' => $this->order,
|
||||
'MAILBOX' => $this->folders,
|
||||
);
|
||||
|
||||
if ($param !== null) {
|
||||
return $params[$param];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored result object for a particular folder
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return false|object rcube_result_* instance of false if none found
|
||||
*/
|
||||
public function get_set($folder)
|
||||
{
|
||||
foreach ($this->sets as $set) {
|
||||
if ($set->get_parameters('MAILBOX') == $folder) {
|
||||
return $set;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of internal data representation
|
||||
*
|
||||
* @return int Data length
|
||||
*/
|
||||
protected function length()
|
||||
{
|
||||
return $this->count();
|
||||
}
|
||||
|
||||
|
||||
/* Serialize magic methods */
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
$this->sdata = array('incomplete' => array(), 'error' => array());
|
||||
|
||||
foreach ($this->sets as $set) {
|
||||
if ($set->incomplete) {
|
||||
$this->sdata['incomplete'][] = $set->get_parameters('MAILBOX');
|
||||
}
|
||||
else if ($set->is_error()) {
|
||||
$this->sdata['error'][] = $set->get_parameters('MAILBOX');
|
||||
}
|
||||
}
|
||||
|
||||
return array('sdata', 'index', 'folders', 'sorting', 'order');
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->meta = array('count' => count($this->index));
|
||||
$this->incomplete = count($this->sdata['incomplete']) > 0;
|
||||
|
||||
// restore result sets from saved index
|
||||
$data = array();
|
||||
foreach ($this->index as $item) {
|
||||
list($uid, $folder) = explode('-', $item, 2);
|
||||
$data[$folder] .= ' ' . $uid;
|
||||
}
|
||||
|
||||
foreach ($this->folders as $folder) {
|
||||
if (in_array($folder, $this->sdata['error'])) {
|
||||
$data_str = null;
|
||||
}
|
||||
else {
|
||||
$data_str = '* SORT' . $data[$folder];
|
||||
}
|
||||
|
||||
$set = new rcube_result_index($folder, $data_str, strtoupper($this->order));
|
||||
|
||||
if (in_array($folder, $this->sdata['incomplete'])) {
|
||||
$set->incomplete = true;
|
||||
}
|
||||
|
||||
$this->sets[] = $set;
|
||||
}
|
||||
}
|
||||
}
|
||||
118
data/web/rc/program/lib/Roundcube/rcube_result_set.php
Normal file
118
data/web/rc/program/lib/Roundcube/rcube_result_set.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2006-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Class representing an address directory result set |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Roundcube result set class
|
||||
*
|
||||
* Representing an address directory result set.
|
||||
* Implenets Iterator and thus be used in foreach() loops.
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Addressbook
|
||||
*/
|
||||
class rcube_result_set implements Iterator, ArrayAccess
|
||||
{
|
||||
public $count = 0;
|
||||
public $first = 0;
|
||||
public $searchonly = false;
|
||||
public $records = array();
|
||||
|
||||
private $current = 0;
|
||||
|
||||
function __construct($c=0, $f=0)
|
||||
{
|
||||
$this->count = (int)$c;
|
||||
$this->first = (int)$f;
|
||||
}
|
||||
|
||||
function add($rec)
|
||||
{
|
||||
$this->records[] = $rec;
|
||||
}
|
||||
|
||||
function iterate()
|
||||
{
|
||||
return $this->records[$this->current++];
|
||||
}
|
||||
|
||||
function first()
|
||||
{
|
||||
$this->current = 0;
|
||||
return $this->records[$this->current];
|
||||
}
|
||||
|
||||
function seek($i)
|
||||
{
|
||||
$this->current = $i;
|
||||
}
|
||||
|
||||
/*** Implement PHP ArrayAccess interface ***/
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$offset = count($this->records);
|
||||
$this->records[] = $value;
|
||||
}
|
||||
else {
|
||||
$this->records[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->records[$offset]);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->records[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->records[$offset];
|
||||
}
|
||||
|
||||
/*** PHP 5 Iterator interface ***/
|
||||
|
||||
function rewind()
|
||||
{
|
||||
$this->current = 0;
|
||||
}
|
||||
|
||||
function current()
|
||||
{
|
||||
return $this->records[$this->current];
|
||||
}
|
||||
|
||||
function key()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
function next()
|
||||
{
|
||||
return $this->iterate();
|
||||
}
|
||||
|
||||
function valid()
|
||||
{
|
||||
return isset($this->records[$this->current]);
|
||||
}
|
||||
}
|
||||
649
data/web/rc/program/lib/Roundcube/rcube_result_thread.php
Normal file
649
data/web/rc/program/lib/Roundcube/rcube_result_thread.php
Normal file
@@ -0,0 +1,649 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2011, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| THREAD response handler |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for accessing IMAP's THREAD result
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
*/
|
||||
class rcube_result_thread
|
||||
{
|
||||
public $incomplete = false;
|
||||
|
||||
protected $raw_data;
|
||||
protected $mailbox;
|
||||
protected $meta = array();
|
||||
protected $order = 'ASC';
|
||||
|
||||
const SEPARATOR_ELEMENT = ' ';
|
||||
const SEPARATOR_ITEM = '~';
|
||||
const SEPARATOR_LEVEL = ':';
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*/
|
||||
public function __construct($mailbox = null, $data = null)
|
||||
{
|
||||
$this->mailbox = $mailbox;
|
||||
$this->init($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes object with IMAP command response
|
||||
*
|
||||
* @param string $data IMAP response string
|
||||
*/
|
||||
public function init($data = null)
|
||||
{
|
||||
$this->meta = array();
|
||||
|
||||
$data = explode('*', (string)$data);
|
||||
|
||||
// ...skip unilateral untagged server responses
|
||||
for ($i=0, $len=count($data); $i<$len; $i++) {
|
||||
if (preg_match('/^ THREAD/i', $data[$i])) {
|
||||
// valid response, initialize raw_data for is_error()
|
||||
$this->raw_data = '';
|
||||
$data[$i] = substr($data[$i], 7);
|
||||
break;
|
||||
}
|
||||
|
||||
unset($data[$i]);
|
||||
}
|
||||
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = array_shift($data);
|
||||
$data = trim($data);
|
||||
$data = preg_replace('/[\r\n]/', '', $data);
|
||||
$data = preg_replace('/\s+/', ' ', $data);
|
||||
|
||||
$this->raw_data = $this->parse_thread($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the result from IMAP command
|
||||
*
|
||||
* @return bool True if the result is an error, False otherwise
|
||||
*/
|
||||
public function is_error()
|
||||
{
|
||||
return $this->raw_data === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the result is empty
|
||||
*
|
||||
* @return bool True if the result is empty, False otherwise
|
||||
*/
|
||||
public function is_empty()
|
||||
{
|
||||
return empty($this->raw_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements (threads) in the result
|
||||
*
|
||||
* @return int Number of elements
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ($this->meta['count'] !== null)
|
||||
return $this->meta['count'];
|
||||
|
||||
if (empty($this->raw_data)) {
|
||||
$this->meta['count'] = 0;
|
||||
}
|
||||
else {
|
||||
$this->meta['count'] = 1 + substr_count($this->raw_data, self::SEPARATOR_ELEMENT);
|
||||
}
|
||||
|
||||
if (!$this->meta['count'])
|
||||
$this->meta['messages'] = 0;
|
||||
|
||||
return $this->meta['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of all messages in the result
|
||||
*
|
||||
* @return int Number of elements
|
||||
*/
|
||||
public function count_messages()
|
||||
{
|
||||
if ($this->meta['messages'] !== null)
|
||||
return $this->meta['messages'];
|
||||
|
||||
if (empty($this->raw_data)) {
|
||||
$this->meta['messages'] = 0;
|
||||
}
|
||||
else {
|
||||
$this->meta['messages'] = 1
|
||||
+ substr_count($this->raw_data, self::SEPARATOR_ELEMENT)
|
||||
+ substr_count($this->raw_data, self::SEPARATOR_ITEM);
|
||||
}
|
||||
|
||||
if ($this->meta['messages'] == 0 || $this->meta['messages'] == 1)
|
||||
$this->meta['count'] = $this->meta['messages'];
|
||||
|
||||
return $this->meta['messages'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns maximum message identifier in the result
|
||||
*
|
||||
* @return int Maximum message identifier
|
||||
*/
|
||||
public function max()
|
||||
{
|
||||
if (!isset($this->meta['max'])) {
|
||||
$this->meta['max'] = (int) @max($this->get());
|
||||
}
|
||||
return $this->meta['max'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns minimum message identifier in the result
|
||||
*
|
||||
* @return int Minimum message identifier
|
||||
*/
|
||||
public function min()
|
||||
{
|
||||
if (!isset($this->meta['min'])) {
|
||||
$this->meta['min'] = (int) @min($this->get());
|
||||
}
|
||||
return $this->meta['min'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Slices data set.
|
||||
*
|
||||
* @param $offset Offset (as for PHP's array_slice())
|
||||
* @param $length Number of elements (as for PHP's array_slice())
|
||||
*/
|
||||
public function slice($offset, $length)
|
||||
{
|
||||
$data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
|
||||
$data = array_slice($data, $offset, $length);
|
||||
|
||||
$this->meta = array();
|
||||
$this->meta['count'] = count($data);
|
||||
$this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data set. Removes threads not listed in $roots list.
|
||||
*
|
||||
* @param array $roots List of IDs of thread roots.
|
||||
*/
|
||||
public function filter($roots)
|
||||
{
|
||||
$datalen = strlen($this->raw_data);
|
||||
$roots = array_flip($roots);
|
||||
$result = '';
|
||||
$start = 0;
|
||||
|
||||
$this->meta = array();
|
||||
$this->meta['count'] = 0;
|
||||
|
||||
while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
|
||||
|| ($start < $datalen && ($pos = $datalen))
|
||||
) {
|
||||
$len = $pos - $start;
|
||||
$elem = substr($this->raw_data, $start, $len);
|
||||
$start = $pos + 1;
|
||||
|
||||
// extract root message ID
|
||||
if ($npos = strpos($elem, self::SEPARATOR_ITEM)) {
|
||||
$root = (int) substr($elem, 0, $npos);
|
||||
}
|
||||
else {
|
||||
$root = $elem;
|
||||
}
|
||||
|
||||
if (isset($roots[$root])) {
|
||||
$this->meta['count']++;
|
||||
$result .= self::SEPARATOR_ELEMENT . $elem;
|
||||
}
|
||||
}
|
||||
|
||||
$this->raw_data = ltrim($result, self::SEPARATOR_ELEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts order of elements in the result
|
||||
*/
|
||||
public function revert()
|
||||
{
|
||||
$this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
|
||||
|
||||
if (empty($this->raw_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
|
||||
$data = array_reverse($data);
|
||||
$this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
|
||||
|
||||
$this->meta['pos'] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given message ID exists in the object
|
||||
*
|
||||
* @param int $msgid Message ID
|
||||
* @param bool $get_index When enabled element's index will be returned.
|
||||
* Elements are indexed starting with 0
|
||||
*
|
||||
* @return boolean True on success, False if message ID doesn't exist
|
||||
*/
|
||||
public function exists($msgid, $get_index = false)
|
||||
{
|
||||
$msgid = (int) $msgid;
|
||||
$begin = implode('|', array(
|
||||
'^',
|
||||
preg_quote(self::SEPARATOR_ELEMENT, '/'),
|
||||
preg_quote(self::SEPARATOR_LEVEL, '/'),
|
||||
));
|
||||
$end = implode('|', array(
|
||||
'$',
|
||||
preg_quote(self::SEPARATOR_ELEMENT, '/'),
|
||||
preg_quote(self::SEPARATOR_ITEM, '/'),
|
||||
));
|
||||
|
||||
if (preg_match("/($begin)$msgid($end)/", $this->raw_data, $m,
|
||||
$get_index ? PREG_OFFSET_CAPTURE : null)
|
||||
) {
|
||||
if ($get_index) {
|
||||
$idx = 0;
|
||||
if ($m[0][1]) {
|
||||
$idx = substr_count($this->raw_data, self::SEPARATOR_ELEMENT, 0, $m[0][1]+1)
|
||||
+ substr_count($this->raw_data, self::SEPARATOR_ITEM, 0, $m[0][1]+1);
|
||||
}
|
||||
// cache position of this element, so we can use it in get_element()
|
||||
$this->meta['pos'][$idx] = (int)$m[0][1];
|
||||
|
||||
return $idx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return IDs of all messages in the result. Threaded data will be flattened.
|
||||
*
|
||||
* @return array List of message identifiers
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
if (empty($this->raw_data)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$regexp = '/(' . preg_quote(self::SEPARATOR_ELEMENT, '/')
|
||||
. '|' . preg_quote(self::SEPARATOR_ITEM, '/') . '[0-9]+' . preg_quote(self::SEPARATOR_LEVEL, '/')
|
||||
.')/';
|
||||
|
||||
return preg_split($regexp, $this->raw_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all messages in the result.
|
||||
*
|
||||
* @return array List of message identifiers
|
||||
*/
|
||||
public function get_compressed()
|
||||
{
|
||||
if (empty($this->raw_data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return rcube_imap_generic::compressMessageSet($this->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result element at specified index (all messages, not roots)
|
||||
*
|
||||
* @param int|string $index Element's index or "FIRST" or "LAST"
|
||||
*
|
||||
* @return int Element value
|
||||
*/
|
||||
public function get_element($index)
|
||||
{
|
||||
$count = $this->count();
|
||||
|
||||
if (!$count) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// first element
|
||||
if ($index === 0 || $index === '0' || $index === 'FIRST') {
|
||||
preg_match('/^([0-9]+)/', $this->raw_data, $m);
|
||||
$result = (int) $m[1];
|
||||
return $result;
|
||||
}
|
||||
|
||||
// last element
|
||||
if ($index === 'LAST' || $index == $count-1) {
|
||||
preg_match('/([0-9]+)$/', $this->raw_data, $m);
|
||||
$result = (int) $m[1];
|
||||
return $result;
|
||||
}
|
||||
|
||||
// do we know the position of the element or the neighbour of it?
|
||||
if (!empty($this->meta['pos'])) {
|
||||
$element = preg_quote(self::SEPARATOR_ELEMENT, '/');
|
||||
$item = preg_quote(self::SEPARATOR_ITEM, '/') . '[0-9]+' . preg_quote(self::SEPARATOR_LEVEL, '/') .'?';
|
||||
$regexp = '(' . $element . '|' . $item . ')';
|
||||
|
||||
if (isset($this->meta['pos'][$index])) {
|
||||
if (preg_match('/([0-9]+)/', $this->raw_data, $m, null, $this->meta['pos'][$index]))
|
||||
$result = $m[1];
|
||||
}
|
||||
else if (isset($this->meta['pos'][$index-1])) {
|
||||
// get chunk of data after previous element
|
||||
$data = substr($this->raw_data, $this->meta['pos'][$index-1]+1, 50);
|
||||
$data = preg_replace('/^[0-9]+/', '', $data); // remove UID at $index position
|
||||
$data = preg_replace("/^$regexp/", '', $data); // remove separator
|
||||
if (preg_match('/^([0-9]+)/', $data, $m))
|
||||
$result = $m[1];
|
||||
}
|
||||
else if (isset($this->meta['pos'][$index+1])) {
|
||||
// get chunk of data before next element
|
||||
$pos = max(0, $this->meta['pos'][$index+1] - 50);
|
||||
$len = min(50, $this->meta['pos'][$index+1]);
|
||||
$data = substr($this->raw_data, $pos, $len);
|
||||
$data = preg_replace("/$regexp\$/", '', $data); // remove separator
|
||||
|
||||
if (preg_match('/([0-9]+)$/', $data, $m))
|
||||
$result = $m[1];
|
||||
}
|
||||
|
||||
if (isset($result)) {
|
||||
return (int) $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally use less effective method
|
||||
$data = $this->get();
|
||||
|
||||
return $data[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns response parameters e.g. MAILBOX, ORDER
|
||||
*
|
||||
* @param string $param Parameter name
|
||||
*
|
||||
* @return array|string Response parameters or parameter value
|
||||
*/
|
||||
public function get_parameters($param=null)
|
||||
{
|
||||
$params = array();
|
||||
$params['MAILBOX'] = $this->mailbox;
|
||||
$params['ORDER'] = $this->order;
|
||||
|
||||
if ($param !== null) {
|
||||
return $params[$param];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* THREAD=REFS sorting implementation (based on provided index)
|
||||
*
|
||||
* @param rcube_result_index $index Sorted message identifiers
|
||||
*/
|
||||
public function sort($index)
|
||||
{
|
||||
$this->sort_order = $index->get_parameters('ORDER');
|
||||
|
||||
if (empty($this->raw_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// when sorting search result it's good to make the index smaller
|
||||
if ($index->count() != $this->count_messages()) {
|
||||
$index->filter($this->get());
|
||||
}
|
||||
|
||||
$result = array_fill_keys($index->get(), null);
|
||||
$datalen = strlen($this->raw_data);
|
||||
$start = 0;
|
||||
|
||||
// Here we're parsing raw_data twice, we want only one big array
|
||||
// in memory at a time
|
||||
|
||||
// Assign roots
|
||||
while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
|
||||
|| ($start < $datalen && ($pos = $datalen))
|
||||
) {
|
||||
$len = $pos - $start;
|
||||
$elem = substr($this->raw_data, $start, $len);
|
||||
$start = $pos + 1;
|
||||
|
||||
$items = explode(self::SEPARATOR_ITEM, $elem);
|
||||
$root = (int) array_shift($items);
|
||||
|
||||
if ($root) {
|
||||
$result[$root] = $root;
|
||||
foreach ($items as $item) {
|
||||
list($lv, $id) = explode(self::SEPARATOR_LEVEL, $item);
|
||||
$result[$id] = $root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get only unique roots
|
||||
$result = array_filter($result); // make sure there are no nulls
|
||||
$result = array_unique($result);
|
||||
|
||||
// Re-sort raw data
|
||||
$result = array_fill_keys($result, null);
|
||||
$start = 0;
|
||||
|
||||
while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
|
||||
|| ($start < $datalen && ($pos = $datalen))
|
||||
) {
|
||||
$len = $pos - $start;
|
||||
$elem = substr($this->raw_data, $start, $len);
|
||||
$start = $pos + 1;
|
||||
|
||||
$npos = strpos($elem, self::SEPARATOR_ITEM);
|
||||
$root = (int) ($npos ? substr($elem, 0, $npos) : $elem);
|
||||
|
||||
$result[$root] = $elem;
|
||||
}
|
||||
|
||||
$this->raw_data = implode(self::SEPARATOR_ELEMENT, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data as tree
|
||||
*
|
||||
* @return array Data tree
|
||||
*/
|
||||
public function get_tree()
|
||||
{
|
||||
$datalen = strlen($this->raw_data);
|
||||
$result = array();
|
||||
$start = 0;
|
||||
|
||||
while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
|
||||
|| ($start < $datalen && ($pos = $datalen))
|
||||
) {
|
||||
$len = $pos - $start;
|
||||
$elem = substr($this->raw_data, $start, $len);
|
||||
$items = explode(self::SEPARATOR_ITEM, $elem);
|
||||
$result[array_shift($items)] = $this->build_thread($items);
|
||||
$start = $pos + 1;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns thread depth and children data
|
||||
*
|
||||
* @return array Thread data
|
||||
*/
|
||||
public function get_thread_data()
|
||||
{
|
||||
$data = $this->get_tree();
|
||||
$depth = array();
|
||||
$children = array();
|
||||
|
||||
$this->build_thread_data($data, $depth, $children);
|
||||
|
||||
return array($depth, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates 'depth' and 'children' arrays from stored thread 'tree' data.
|
||||
*/
|
||||
protected function build_thread_data($data, &$depth, &$children, $level = 0)
|
||||
{
|
||||
foreach ((array)$data as $key => $val) {
|
||||
$empty = empty($val) || !is_array($val);
|
||||
$children[$key] = !$empty;
|
||||
$depth[$key] = $level;
|
||||
if (!$empty) {
|
||||
$this->build_thread_data($val, $depth, $children, $level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts part of the raw thread into an array
|
||||
*/
|
||||
protected function build_thread($items, $level = 1, &$pos = 0)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
for ($len=count($items); $pos < $len; $pos++) {
|
||||
list($lv, $id) = explode(self::SEPARATOR_LEVEL, $items[$pos]);
|
||||
if ($level == $lv) {
|
||||
$pos++;
|
||||
$result[$id] = $this->build_thread($items, $level+1, $pos);
|
||||
}
|
||||
else {
|
||||
$pos--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* IMAP THREAD response parser
|
||||
*/
|
||||
protected function parse_thread($str, $begin = 0, $end = 0, $depth = 0)
|
||||
{
|
||||
// Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about
|
||||
// 7 times instead :-) See comments on http://uk2.php.net/references and this article:
|
||||
// http://derickrethans.nl/files/phparch-php-variables-article.pdf
|
||||
$node = '';
|
||||
if (!$end) {
|
||||
$end = strlen($str);
|
||||
}
|
||||
|
||||
// Let's try to store data in max. compacted stracture as a string,
|
||||
// arrays handling is much more expensive
|
||||
// For the following structure: THREAD (2)(3 6 (4 23)(44 7 96))
|
||||
// -- 2
|
||||
// -- 3
|
||||
// \-- 6
|
||||
// |-- 4
|
||||
// | \-- 23
|
||||
// |
|
||||
// \-- 44
|
||||
// \-- 7
|
||||
// \-- 96
|
||||
//
|
||||
// The output will be: 2,3^1:6^2:4^3:23^2:44^3:7^4:96
|
||||
|
||||
if ($str[$begin] != '(') {
|
||||
// find next bracket
|
||||
$stop = $begin + strcspn($str, '()', $begin, $end - $begin);
|
||||
$messages = explode(' ', trim(substr($str, $begin, $stop - $begin)));
|
||||
|
||||
if (empty($messages)) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
foreach ($messages as $msg) {
|
||||
if ($msg) {
|
||||
$node .= ($depth ? self::SEPARATOR_ITEM.$depth.self::SEPARATOR_LEVEL : '').$msg;
|
||||
$this->meta['messages']++;
|
||||
$depth++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($stop < $end) {
|
||||
$node .= $this->parse_thread($str, $stop, $end, $depth);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$off = $begin;
|
||||
while ($off < $end) {
|
||||
$start = $off;
|
||||
$off++;
|
||||
$n = 1;
|
||||
while ($n > 0) {
|
||||
$p = strpos($str, ')', $off);
|
||||
if ($p === false) {
|
||||
// error, wrong structure, mismatched brackets in IMAP THREAD response
|
||||
// @TODO: write error to the log or maybe set $this->raw_data = null;
|
||||
return $node;
|
||||
}
|
||||
$p1 = strpos($str, '(', $off);
|
||||
if ($p1 !== false && $p1 < $p) {
|
||||
$off = $p1 + 1;
|
||||
$n++;
|
||||
}
|
||||
else {
|
||||
$off = $p + 1;
|
||||
$n--;
|
||||
}
|
||||
}
|
||||
|
||||
$thread = $this->parse_thread($str, $start + 1, $off - 1, $depth);
|
||||
if ($thread) {
|
||||
if (!$depth) {
|
||||
if ($node) {
|
||||
$node .= self::SEPARATOR_ELEMENT;
|
||||
}
|
||||
}
|
||||
$node .= $thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
687
data/web/rc/program/lib/Roundcube/rcube_session.php
Normal file
687
data/web/rc/program/lib/Roundcube/rcube_session.php
Normal file
@@ -0,0 +1,687 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2014, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Provide database supported session management |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Cor Bosman <cor@roundcu.be> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract class to provide database supported session storage
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
abstract class rcube_session
|
||||
{
|
||||
protected $config;
|
||||
protected $key;
|
||||
protected $ip;
|
||||
protected $changed;
|
||||
protected $start;
|
||||
protected $vars;
|
||||
protected $now;
|
||||
protected $time_diff = 0;
|
||||
protected $reloaded = false;
|
||||
protected $appends = array();
|
||||
protected $unsets = array();
|
||||
protected $gc_enabled = 0;
|
||||
protected $gc_handlers = array();
|
||||
protected $cookiename = 'roundcube_sessauth';
|
||||
protected $ip_check = false;
|
||||
protected $logging = false;
|
||||
|
||||
|
||||
/**
|
||||
* Blocks session data from being written to database.
|
||||
* Can be used if write-race conditions are to be expected
|
||||
* @var boolean
|
||||
*/
|
||||
public $nowrite = false;
|
||||
|
||||
/**
|
||||
* Factory, returns driver-specific instance of the class
|
||||
*
|
||||
* @param object $config
|
||||
* @return Object rcube_session
|
||||
*/
|
||||
public static function factory($config)
|
||||
{
|
||||
// get session storage driver
|
||||
$storage = $config->get('session_storage', 'db');
|
||||
|
||||
// class name for this storage
|
||||
$class = "rcube_session_" . $storage;
|
||||
|
||||
// try to instantiate class
|
||||
if (class_exists($class)) {
|
||||
return new $class($config);
|
||||
}
|
||||
|
||||
// no storage found, raise error
|
||||
rcube::raise_error(array('code' => 604, 'type' => 'session',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Failed to find session driver. Check session_storage config option"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Object $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
// set ip check
|
||||
$this->set_ip_check($this->config->get('ip_check'));
|
||||
|
||||
// set cookie name
|
||||
if ($this->config->get('session_auth_name')) {
|
||||
$this->set_cookiename($this->config->get('session_auth_name'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* register session handler
|
||||
*/
|
||||
public function register_session_handler()
|
||||
{
|
||||
ini_set('session.serialize_handler', 'php');
|
||||
|
||||
// set custom functions for PHP session management
|
||||
session_set_save_handler(
|
||||
array($this, 'open'),
|
||||
array($this, 'close'),
|
||||
array($this, 'read'),
|
||||
array($this, 'sess_write'),
|
||||
array($this, 'destroy'),
|
||||
array($this, 'gc')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for session_start()
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
$this->start = microtime(true);
|
||||
$this->ip = rcube_utils::remote_addr();
|
||||
$this->logging = $this->config->get('log_session', false);
|
||||
|
||||
$lifetime = $this->config->get('session_lifetime', 1) * 60;
|
||||
$this->set_lifetime($lifetime);
|
||||
|
||||
session_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract methods should be implemented by driver classes
|
||||
*/
|
||||
abstract function open($save_path, $session_name);
|
||||
abstract function close();
|
||||
abstract function destroy($key);
|
||||
abstract function read($key);
|
||||
abstract function write($key, $vars);
|
||||
abstract function update($key, $newvars, $oldvars);
|
||||
|
||||
/**
|
||||
* session write handler. This calls the implementation methods for write/update after some initial checks.
|
||||
*
|
||||
* @param $key
|
||||
* @param $vars
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sess_write($key, $vars)
|
||||
{
|
||||
if ($this->nowrite) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check cache
|
||||
$oldvars = $this->get_cache($key);
|
||||
|
||||
// if there are cached vars, update store, else insert new data
|
||||
if ($oldvars) {
|
||||
$newvars = $this->_fixvars($vars, $oldvars);
|
||||
return $this->update($key, $newvars, $oldvars);
|
||||
}
|
||||
else {
|
||||
return $this->write($key, $vars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for session_write_close()
|
||||
*/
|
||||
public function write_close()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
// write_close() is called on script shutdown, see rcube::shutdown()
|
||||
// execute cleanup functionality if enabled by session gc handler
|
||||
// we do this after closing the session for better performance
|
||||
$this->gc_shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new (separate) session
|
||||
*
|
||||
* @param array Session data
|
||||
*
|
||||
* @return string Session identifier (on success)
|
||||
*/
|
||||
public function create($data)
|
||||
{
|
||||
$length = strlen(session_id());
|
||||
$key = rcube_utils::random_bytes($length);
|
||||
|
||||
// create new session
|
||||
if ($this->write($key, $this->serialize($data))) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge vars with old vars and apply unsets
|
||||
*/
|
||||
protected function _fixvars($vars, $oldvars)
|
||||
{
|
||||
if ($oldvars !== null) {
|
||||
$a_oldvars = $this->unserialize($oldvars);
|
||||
if (is_array($a_oldvars)) {
|
||||
// remove unset keys on oldvars
|
||||
foreach ((array)$this->unsets as $var) {
|
||||
if (isset($a_oldvars[$var])) {
|
||||
unset($a_oldvars[$var]);
|
||||
}
|
||||
else {
|
||||
$path = explode('.', $var);
|
||||
$k = array_pop($path);
|
||||
$node = &$this->get_node($path, $a_oldvars);
|
||||
unset($node[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$newvars = $this->serialize(array_merge(
|
||||
(array)$a_oldvars, (array)$this->unserialize($vars)));
|
||||
}
|
||||
else {
|
||||
$newvars = $vars;
|
||||
}
|
||||
}
|
||||
|
||||
$this->unsets = array();
|
||||
return $newvars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute registered garbage collector routines
|
||||
*/
|
||||
public function gc($maxlifetime)
|
||||
{
|
||||
// move gc execution to the script shutdown function
|
||||
// see rcube::shutdown() and rcube_session::write_close()
|
||||
$this->gc_enabled = $maxlifetime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collector handler to run on script shutdown
|
||||
*/
|
||||
protected function gc_shutdown()
|
||||
{
|
||||
if ($this->gc_enabled) {
|
||||
foreach ($this->gc_handlers as $fct) {
|
||||
call_user_func($fct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and set new session id
|
||||
*
|
||||
* @param boolean $destroy If enabled the current session will be destroyed
|
||||
* @return bool
|
||||
*/
|
||||
public function regenerate_id($destroy=true)
|
||||
{
|
||||
session_regenerate_id($destroy);
|
||||
|
||||
$this->vars = null;
|
||||
$this->key = session_id();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we have vars of this key already cached, and if so, return them.
|
||||
*
|
||||
* @param string $key Session ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_cache($key)
|
||||
{
|
||||
// no session data in cache (read() returns false)
|
||||
if (!$this->key) {
|
||||
$cache = null;
|
||||
}
|
||||
// use internal data for fast requests (up to 0.5 sec.)
|
||||
else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
|
||||
$cache = $this->vars;
|
||||
}
|
||||
else { // else read data again
|
||||
$cache = $this->read($key);
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given value to the certain node in the session data array
|
||||
*
|
||||
* Warning: Do not use if you already modified $_SESSION in the same request (#1490608)
|
||||
*
|
||||
* @param string Path denoting the session variable where to append the value
|
||||
* @param string Key name under which to append the new value (use null for appending to an indexed list)
|
||||
* @param mixed Value to append to the session data array
|
||||
*/
|
||||
public function append($path, $key, $value)
|
||||
{
|
||||
// re-read session data from DB because it might be outdated
|
||||
if (!$this->reloaded && microtime(true) - $this->start > 0.5) {
|
||||
$this->reload();
|
||||
$this->reloaded = true;
|
||||
$this->start = microtime(true);
|
||||
}
|
||||
|
||||
$node = &$this->get_node(explode('.', $path), $_SESSION);
|
||||
|
||||
if ($key !== null) {
|
||||
$node[$key] = $value;
|
||||
$path .= '.' . $key;
|
||||
}
|
||||
else {
|
||||
$node[] = $value;
|
||||
}
|
||||
|
||||
$this->appends[] = $path;
|
||||
|
||||
// when overwriting a previously unset variable
|
||||
if ($this->unsets[$path]) {
|
||||
unset($this->unsets[$path]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a session variable
|
||||
*
|
||||
* @param string Variable name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5)
|
||||
* @return boolean True on success
|
||||
*/
|
||||
public function remove($var=null)
|
||||
{
|
||||
if (empty($var)) {
|
||||
return $this->destroy(session_id());
|
||||
}
|
||||
|
||||
$this->unsets[] = $var;
|
||||
|
||||
if (isset($_SESSION[$var])) {
|
||||
unset($_SESSION[$var]);
|
||||
}
|
||||
else {
|
||||
$path = explode('.', $var);
|
||||
$key = array_pop($path);
|
||||
$node = &$this->get_node($path, $_SESSION);
|
||||
unset($node[$key]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill this session
|
||||
*/
|
||||
public function kill()
|
||||
{
|
||||
$this->vars = null;
|
||||
$this->ip = rcube_utils::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()
|
||||
{
|
||||
// collect updated data from previous appends
|
||||
$merge_data = array();
|
||||
foreach ((array)$this->appends as $var) {
|
||||
$path = explode('.', $var);
|
||||
$value = $this->get_node($path, $_SESSION);
|
||||
$k = array_pop($path);
|
||||
$node = &$this->get_node($path, $merge_data);
|
||||
$node[$k] = $value;
|
||||
}
|
||||
|
||||
if ($this->key) {
|
||||
$data = $this->read($this->key);
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
session_decode($data);
|
||||
|
||||
// apply appends and unsets to reloaded data
|
||||
$_SESSION = array_merge_recursive($_SESSION, $merge_data);
|
||||
|
||||
foreach ((array)$this->unsets as $var) {
|
||||
if (isset($_SESSION[$var])) {
|
||||
unset($_SESSION[$var]);
|
||||
}
|
||||
else {
|
||||
$path = explode('.', $var);
|
||||
$k = array_pop($path);
|
||||
$node = &$this->get_node($path, $_SESSION);
|
||||
unset($node[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the node in data array referenced by the given path.
|
||||
* e.g. ['compose','attachments'] will return $_SESSION['compose']['attachments']
|
||||
*/
|
||||
protected function &get_node($path, &$data_arr)
|
||||
{
|
||||
$node = &$data_arr;
|
||||
if (!empty($path)) {
|
||||
foreach ((array)$path as $key) {
|
||||
if (!isset($node[$key]))
|
||||
$node[$key] = array();
|
||||
$node = &$node[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize session data
|
||||
*/
|
||||
protected 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
|
||||
*/
|
||||
protected 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 . '}' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = null)
|
||||
{
|
||||
// generate random hash and store in session
|
||||
if (!$secret) {
|
||||
if (!empty($_SESSION['auth_secret'])) {
|
||||
$secret = $_SESSION['auth_secret'];
|
||||
}
|
||||
else {
|
||||
$secret = rcube_utils::random_bytes(strlen($this->key));
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['auth_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 ? rcube_utils::remote_addr() == $this->ip : true;
|
||||
|
||||
if (!$result) {
|
||||
$this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . rcube_utils::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
|
||||
*/
|
||||
public 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 for specified time slot.
|
||||
*
|
||||
* @param int Time slot to use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _mkcookie($timeslot)
|
||||
{
|
||||
// make sure the secret key exists
|
||||
$this->set_secret();
|
||||
|
||||
// no need to hash this, it's just a random string
|
||||
return $_SESSION['auth_secret'] . '-' . $timeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes debug information to the log
|
||||
*/
|
||||
function log($line)
|
||||
{
|
||||
if ($this->logging) {
|
||||
rcube::write_log('session', $line);
|
||||
}
|
||||
}
|
||||
}
|
||||
178
data/web/rc/program/lib/Roundcube/rcube_session_db.php
Normal file
178
data/web/rc/program/lib/Roundcube/rcube_session_db.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2014, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Provide database supported session management |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Cor Bosman <cor@roundcu.be> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to provide database session storage
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
* @author Cor Bosman <cor@roundcu.be>
|
||||
*/
|
||||
class rcube_session_db extends rcube_session
|
||||
{
|
||||
private $db;
|
||||
private $table_name;
|
||||
|
||||
/**
|
||||
* @param Object $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
// get db instance
|
||||
$this->db = rcube::get_instance()->get_dbh();
|
||||
|
||||
// session table name
|
||||
$this->table_name = $this->db->table_name('session', true);
|
||||
|
||||
// register sessions handler
|
||||
$this->register_session_handler();
|
||||
|
||||
// register db gc handler
|
||||
$this->register_gc_handler(array($this, 'gc_db'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $save_path
|
||||
* @param $session_name
|
||||
* @return bool
|
||||
*/
|
||||
public function open($save_path, $session_name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for session_destroy()
|
||||
*
|
||||
* @param $key
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy($key)
|
||||
{
|
||||
if ($key) {
|
||||
$this->db->query("DELETE FROM {$this->table_name} WHERE `sess_id` = ?", $key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session data from database
|
||||
*
|
||||
* @param string Session ID
|
||||
*
|
||||
* @return string Session vars
|
||||
*/
|
||||
public function read($key)
|
||||
{
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT `vars`, `ip`, `changed`, " . $this->db->now() . " AS ts"
|
||||
. " FROM {$this->table_name} WHERE `sess_id` = ?", $key);
|
||||
|
||||
if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
|
||||
$this->time_diff = time() - strtotime($sql_arr['ts']);
|
||||
$this->changed = strtotime($sql_arr['changed']);
|
||||
$this->ip = $sql_arr['ip'];
|
||||
$this->vars = base64_decode($sql_arr['vars']);
|
||||
$this->key = $key;
|
||||
|
||||
$this->db->reset();
|
||||
|
||||
return !empty($this->vars) ? (string) $this->vars : '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* insert new data into db session store
|
||||
*
|
||||
* @param $key
|
||||
* @param $vars
|
||||
* @return bool
|
||||
*/
|
||||
public function write($key, $vars)
|
||||
{
|
||||
$now = $this->db->now();
|
||||
|
||||
$this->db->query("INSERT INTO {$this->table_name}"
|
||||
. " (`sess_id`, `vars`, `ip`, `changed`)"
|
||||
. " VALUES (?, ?, ?, $now)",
|
||||
$key, base64_encode($vars), (string)$this->ip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* update session data
|
||||
*
|
||||
* @param $key
|
||||
* @param $newvars
|
||||
* @param $oldvars
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update($key, $newvars, $oldvars)
|
||||
{
|
||||
$now = $this->db->now();
|
||||
$ts = microtime(true);
|
||||
|
||||
// if new and old data are not the same, update data
|
||||
// else update expire timestamp only when certain conditions are met
|
||||
if ($newvars !== $oldvars) {
|
||||
$this->db->query("UPDATE {$this->table_name} "
|
||||
. "SET `changed` = $now, `vars` = ? WHERE `sess_id` = ?",
|
||||
base64_encode($newvars), $key);
|
||||
}
|
||||
else if ($ts - $this->changed + $this->time_diff > $this->lifetime / 2) {
|
||||
$this->db->query("UPDATE {$this->table_name} SET `changed` = $now"
|
||||
. " WHERE `sess_id` = ?", $key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up db sessions.
|
||||
*/
|
||||
public function gc_db()
|
||||
{
|
||||
// just clean all old sessions when this GC is called
|
||||
$this->db->query("DELETE FROM " . $this->db->table_name('session')
|
||||
. " WHERE changed < " . $this->db->now(-$this->gc_enabled));
|
||||
|
||||
$this->log("Session GC (DB): remove records < "
|
||||
. date('Y-m-d H:i:s', time() - $this->gc_enabled)
|
||||
. '; rows = ' . intval($this->db->affected_rows()));
|
||||
}
|
||||
}
|
||||
179
data/web/rc/program/lib/Roundcube/rcube_session_memcache.php
Normal file
179
data/web/rc/program/lib/Roundcube/rcube_session_memcache.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2014, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Provide database supported session management |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Cor Bosman <cor@roundcu.bet> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to provide memcache session storage
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
* @author Cor Bosman <cor@roundcu.be>
|
||||
*/
|
||||
class rcube_session_memcache extends rcube_session
|
||||
{
|
||||
private $memcache;
|
||||
private $debug;
|
||||
|
||||
/**
|
||||
* @param Object $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->memcache = rcube::get_instance()->get_memcache();
|
||||
$this->debug = $config->get('memcache_debug');
|
||||
|
||||
if (!$this->memcache) {
|
||||
rcube::raise_error(array(
|
||||
'code' => 604, 'type' => 'db',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Failed to connect to memcached. Please check configuration"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
// register sessions handler
|
||||
$this->register_session_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $save_path
|
||||
* @param $session_name
|
||||
* @return bool
|
||||
*/
|
||||
public function open($save_path, $session_name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for session_destroy() with memcache backend
|
||||
*
|
||||
* @param $key
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy($key)
|
||||
{
|
||||
if ($key) {
|
||||
// #1488592: use 2nd argument
|
||||
$result = $this->memcache->delete($key, 0);
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('delete', $key, null, $result);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session data from memcache
|
||||
*
|
||||
* @param $key
|
||||
* @return null|string
|
||||
*/
|
||||
public function 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;
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('get', $key, $value);
|
||||
}
|
||||
|
||||
return $this->vars ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to memcache storage
|
||||
*
|
||||
* @param $key
|
||||
* @param $vars
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function write($key, $vars)
|
||||
{
|
||||
$data = serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $vars));
|
||||
$result = $this->memcache->set($key, $data, MEMCACHE_COMPRESSED, $this->lifetime + 60);
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('set', $key, $data, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update memcache session data
|
||||
*
|
||||
* @param $key
|
||||
* @param $newvars
|
||||
* @param $oldvars
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update($key, $newvars, $oldvars)
|
||||
{
|
||||
$ts = microtime(true);
|
||||
|
||||
if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) {
|
||||
$data = serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars));
|
||||
$result = $this->memcache->set($key, $data, MEMCACHE_COMPRESSED, $this->lifetime + 60);
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug('set', $key, $data, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write memcache debug info to the log
|
||||
*/
|
||||
protected function debug($type, $key, $data = null, $result = null)
|
||||
{
|
||||
$line = strtoupper($type) . ' ' . $key;
|
||||
|
||||
if ($data !== null) {
|
||||
$line .= ' ' . $data;
|
||||
}
|
||||
|
||||
rcube::debug('memcache', $line, $result);
|
||||
}
|
||||
}
|
||||
76
data/web/rc/program/lib/Roundcube/rcube_session_php.php
Normal file
76
data/web/rc/program/lib/Roundcube/rcube_session_php.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2014, The Roundcube Dev Team |
|
||||
| Copyright (C) 2011, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Provide database supported session management |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Cor Bosman <cor@roundcu.be> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to provide native php session storage
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
* @author Cor Bosman <cor@roundcu.be>
|
||||
*/
|
||||
class rcube_session_php extends rcube_session {
|
||||
|
||||
/**
|
||||
* native php sessions don't need a save handler
|
||||
* we do need to define abstract function implementations but they are not used.
|
||||
*/
|
||||
|
||||
public function open($save_path, $session_name) {}
|
||||
public function close() {}
|
||||
public function destroy($key) {}
|
||||
public function read($key) {}
|
||||
public function write($key, $vars) {}
|
||||
public function update($key, $newvars, $oldvars) {}
|
||||
|
||||
/**
|
||||
* @param Object $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for session_write_close()
|
||||
*/
|
||||
public function write_close()
|
||||
{
|
||||
$_SESSION['__IP'] = $this->ip;
|
||||
$_SESSION['__MTIME'] = time();
|
||||
|
||||
parent::write_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for session_start()
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
parent::start();
|
||||
|
||||
$this->key = session_id();
|
||||
$this->ip = $_SESSION['__IP'];
|
||||
$this->changed = $_SESSION['__MTIME'];
|
||||
|
||||
}
|
||||
}
|
||||
209
data/web/rc/program/lib/Roundcube/rcube_session_redis.php
Normal file
209
data/web/rc/program/lib/Roundcube/rcube_session_redis.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2014, 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: |
|
||||
| Provide redis supported session management |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Cor Bosman <cor@roundcu.be> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to provide redis session storage
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
* @author Cor Bosman <cor@roundcu.be>
|
||||
*/
|
||||
class rcube_session_redis extends rcube_session {
|
||||
|
||||
private $redis;
|
||||
|
||||
/**
|
||||
* @param Object $config
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
// instantiate Redis object
|
||||
$this->redis = new Redis();
|
||||
|
||||
if (!$this->redis) {
|
||||
rcube::raise_error(array('code' => 604, 'type' => 'session',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Failed to find Redis. Make sure php-redis is included"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
// get config instance
|
||||
$hosts = $this->config->get('redis_hosts', array('localhost'));
|
||||
|
||||
// host config is wrong
|
||||
if (!is_array($hosts) || empty($hosts)) {
|
||||
rcube::raise_error(array('code' => 604, 'type' => 'session',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Redis host not configured"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
// only allow 1 host for now until we support clustering
|
||||
if (count($hosts) > 1) {
|
||||
rcube::raise_error(array('code' => 604, 'type' => 'session',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Redis cluster not yet supported"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
foreach ($hosts as $host) {
|
||||
// explode individual fields
|
||||
list($host, $port, $database, $password) = array_pad(explode(':', $host, 4), 4, null);
|
||||
|
||||
// set default values if not set
|
||||
$host = ($host !== null) ? $host : '127.0.0.1';
|
||||
$port = ($port !== null) ? $port : 6379;
|
||||
$database = ($database !== null) ? $database : 0;
|
||||
|
||||
if ($this->redis->connect($host, $port) === false) {
|
||||
rcube::raise_error(
|
||||
array(
|
||||
'code' => 604,
|
||||
'type' => 'session',
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not connect to Redis server. Please check host and port"
|
||||
),
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ($password != null && $this->redis->auth($password) === false) {
|
||||
rcube::raise_error(
|
||||
array(
|
||||
'code' => 604,
|
||||
'type' => 'session',
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not authenticate with Redis server. Please check password."
|
||||
),
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ($database != 0 && $this->redis->select($database) === false) {
|
||||
rcube::raise_error(
|
||||
array(
|
||||
'code' => 604,
|
||||
'type' => 'session',
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not select Redis database. Please check database setting."
|
||||
),
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// register sessions handler
|
||||
$this->register_session_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $save_path
|
||||
* @param $session_name
|
||||
* @return bool
|
||||
*/
|
||||
public function open($save_path, $session_name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove data from store
|
||||
*
|
||||
* @param $key
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy($key)
|
||||
{
|
||||
if ($key) {
|
||||
$this->redis->del($key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* read data from redis store
|
||||
*
|
||||
* @param $key
|
||||
* @return null
|
||||
*/
|
||||
public function read($key)
|
||||
{
|
||||
if ($value = $this->redis->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 '';
|
||||
}
|
||||
|
||||
/**
|
||||
* write data to redis store
|
||||
*
|
||||
* @param $key
|
||||
* @param $newvars
|
||||
* @param $oldvars
|
||||
* @return bool
|
||||
*/
|
||||
public function update($key, $newvars, $oldvars)
|
||||
{
|
||||
$ts = microtime(true);
|
||||
|
||||
if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) {
|
||||
$data = serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars));
|
||||
$this->redis->setex($key, $this->lifetime + 60, $data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* write data to redis store
|
||||
*
|
||||
* @param $key
|
||||
* @param $vars
|
||||
* @return bool
|
||||
*/
|
||||
public function write($key, $vars)
|
||||
{
|
||||
$data = serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $vars));
|
||||
|
||||
return $this->redis->setex($key, $this->lifetime + 60, $data);
|
||||
}
|
||||
}
|
||||
486
data/web/rc/program/lib/Roundcube/rcube_smtp.php
Normal file
486
data/web/rc/program/lib/Roundcube/rcube_smtp.php
Normal file
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Provide SMTP functionality using socket connections |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to provide SMTP functionality using PEAR Net_SMTP
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Mail
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
class rcube_smtp
|
||||
{
|
||||
private $conn;
|
||||
private $response;
|
||||
private $error;
|
||||
private $anonymize_log = 0;
|
||||
|
||||
// define headers delimiter
|
||||
const SMTP_MIME_CRLF = "\r\n";
|
||||
|
||||
const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \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)
|
||||
{
|
||||
$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 ?: $rcube->config->get('smtp_server'),
|
||||
'smtp_port' => $port ?: $rcube->config->get('smtp_port', 25),
|
||||
'smtp_user' => $user !== null ? $user : $rcube->config->get('smtp_user'),
|
||||
'smtp_pass' => $pass !== null ? $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_conn_options' => $rcube->config->get('smtp_conn_options'),
|
||||
'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'];
|
||||
}
|
||||
|
||||
// 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']);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Handle per-host socket options
|
||||
rcube_utils::parse_socket_options($CONFIG['smtp_conn_options'], $smtp_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';
|
||||
}
|
||||
|
||||
// IDNA Support
|
||||
$smtp_host = rcube_utils::idn_to_ascii($smtp_host);
|
||||
|
||||
$this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host, false, 0, $CONFIG['smtp_conn_options']);
|
||||
|
||||
if ($rcube->config->get('smtp_debug')) {
|
||||
$this->conn->setDebug(true, array($this, 'debug_handler'));
|
||||
$this->anonymize_log = 0;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// try to connect to server and exit on failure
|
||||
$result = $this->conn->connect($CONFIG['smtp_timeout']);
|
||||
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
$this->response[] = "Connection failed: " . $result->getMessage();
|
||||
|
||||
list($code,) = $this->conn->getResponse();
|
||||
$this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $code));
|
||||
$this->conn = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
$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 = $CONFIG['smtp_auth_type'] ?: null;
|
||||
|
||||
if (!empty($CONFIG['smtp_auth_cid'])) {
|
||||
$smtp_authz = $smtp_user;
|
||||
$smtp_user = $CONFIG['smtp_auth_cid'];
|
||||
$smtp_pass = $CONFIG['smtp_auth_pw'];
|
||||
}
|
||||
|
||||
// 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 (is_a($result, 'PEAR_Error')) {
|
||||
list($code,) = $this->conn->getResponse();
|
||||
$this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $code));
|
||||
$this->response[] = 'Authentication failure: ' . $result->getMessage()
|
||||
. ' (Code: ' . $result->getCode() . ')';
|
||||
|
||||
$this->reset();
|
||||
$this->disconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
list($from, $text_headers) = $headerElements;
|
||||
}
|
||||
else if (is_string($headers)) {
|
||||
$text_headers = $headers;
|
||||
}
|
||||
|
||||
// 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
|
||||
$result = $this->conn->mailFrom($from, $from_params);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
$err = $this->conn->getResponse();
|
||||
$this->error = array('label' => 'smtpfromerror', 'vars' => array(
|
||||
'from' => $from, 'code' => $err[0], 'msg' => $err[1]));
|
||||
$this->response[] = "Failed to set sender '$from'. "
|
||||
. $err[1] . ' (Code: ' . $err[0] . ')';
|
||||
$this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// prepare list of recipients
|
||||
$recipients = $this->_parse_rfc822($recipients);
|
||||
if (is_a($recipients, 'PEAR_Error')) {
|
||||
$this->error = array('label' => 'smtprecipientserror');
|
||||
$this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// set mail recipients
|
||||
foreach ($recipients as $recipient) {
|
||||
$result = $this->conn->rcptTo($recipient, $recipient_params);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
$err = $this->conn->getResponse();
|
||||
$this->error = array('label' => 'smtptoerror', 'vars' => array(
|
||||
'to' => $recipient, 'code' => $err[0], 'msg' => $err[1]));
|
||||
$this->response[] = "Failed to add recipient '$recipient'. "
|
||||
. $err[1] . ' (Code: ' . $err[0] . ')';
|
||||
$this->reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_resource($body)) {
|
||||
// file handle
|
||||
$data = $body;
|
||||
|
||||
if ($text_headers) {
|
||||
$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.
|
||||
$result = $this->conn->data($data, $text_headers);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
$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. " . $msg;
|
||||
$this->reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->response[] = join(': ', $this->conn->getResponse());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the global SMTP connection
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if (is_object($this->conn)) {
|
||||
$this->conn->rset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the global SMTP connection
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
if (is_object($this->conn)) {
|
||||
$this->conn->disconnect();
|
||||
$this->conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is our own debug handler for the SMTP connection
|
||||
*/
|
||||
public function debug_handler(&$smtp, $message)
|
||||
{
|
||||
// catch AUTH commands and set anonymization flag for subsequent sends
|
||||
if (preg_match('/^Send: AUTH ([A-Z]+)/', $message, $m)) {
|
||||
$this->anonymize_log = $m[1] == 'LOGIN' ? 2 : 1;
|
||||
}
|
||||
// anonymize this log entry
|
||||
else if ($this->anonymize_log > 0 && strpos($message, 'Send:') === 0 && --$this->anonymize_log == 0) {
|
||||
$message = sprintf('Send: ****** [%d]', strlen($message) - 8);
|
||||
}
|
||||
|
||||
if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
|
||||
$diff = $len - self::DEBUG_LINE_LENGTH;
|
||||
$message = substr($message, 0, self::DEBUG_LINE_LENGTH)
|
||||
. "... [truncated $diff bytes]";
|
||||
}
|
||||
|
||||
rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error message
|
||||
*/
|
||||
public function get_error()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server response messages array
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private function _prepare_headers($headers)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
return array($from, join(self::SMTP_MIME_CRLF, $lines) . self::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).
|
||||
*/
|
||||
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 = preg_replace('/[\s\t]*\r?\n/', '', $recipients);
|
||||
$recipients = rcube_utils::explode_quoted_string(',', $recipients);
|
||||
|
||||
reset($recipients);
|
||||
foreach ($recipients as $recipient) {
|
||||
$a = rcube_utils::explode_quoted_string(' ', $recipient);
|
||||
foreach ($a as $word) {
|
||||
$word = trim($word);
|
||||
$len = strlen($word);
|
||||
|
||||
if ($len && strpos($word, "@") > 0 && $word[$len-1] != '"') {
|
||||
$word = preg_replace('/^<|>$/', '', $word);
|
||||
if (!in_array($word, $addresses)) {
|
||||
array_push($addresses, $word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $addresses;
|
||||
}
|
||||
}
|
||||
202
data/web/rc/program/lib/Roundcube/rcube_spellcheck_atd.php
Normal file
202
data/web/rc/program/lib/Roundcube/rcube_spellcheck_atd.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| |
|
||||
| Copyright (C) 2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Spellchecking backend implementation for afterthedeadline services |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spellchecking backend implementation to work with an After the Deadline service
|
||||
* See http://www.afterthedeadline.com/ for more information
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_spellcheck_atd extends rcube_spellcheck_engine
|
||||
{
|
||||
const SERVICE_HOST = 'service.afterthedeadline.com';
|
||||
const SERVICE_PORT = 80;
|
||||
|
||||
private $matches = array();
|
||||
private $content;
|
||||
private $langhosts = array(
|
||||
'fr' => 'fr.',
|
||||
'de' => 'de.',
|
||||
'pt' => 'pt.',
|
||||
'es' => 'es.',
|
||||
);
|
||||
|
||||
/**
|
||||
* Return a list of languages supported by this backend
|
||||
*
|
||||
* @see rcube_spellcheck_engine::languages()
|
||||
*/
|
||||
function languages()
|
||||
{
|
||||
$langs = array_values($this->langhosts);
|
||||
$langs[] = 'en';
|
||||
return $langs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content and check spelling
|
||||
*
|
||||
* @see rcube_spellcheck_engine::check()
|
||||
*/
|
||||
function check($text)
|
||||
{
|
||||
$this->content = $text;
|
||||
|
||||
// spell check uri is configured
|
||||
$rcube = rcube::get_instance();
|
||||
$url = $rcube->config->get('spellcheck_uri');
|
||||
$key = $rcube->config->get('spellcheck_atd_key');
|
||||
|
||||
if ($url) {
|
||||
$a_uri = parse_url($url);
|
||||
$ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl');
|
||||
$port = $a_uri['port'] ?: ($ssl ? 443 : 80);
|
||||
$host = ($ssl ? 'ssl://' : '') . $a_uri['host'];
|
||||
$path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang;
|
||||
}
|
||||
else {
|
||||
$host = self::SERVICE_HOST;
|
||||
$port = self::SERVICE_PORT;
|
||||
$path = '/checkDocument';
|
||||
|
||||
// prefix host for other languages than 'en'
|
||||
$lang = substr($this->lang, 0, 2);
|
||||
if ($this->langhosts[$lang])
|
||||
$host = $this->langhosts[$lang] . $host;
|
||||
}
|
||||
|
||||
$postdata = 'data=' . urlencode($text);
|
||||
|
||||
if (!empty($key))
|
||||
$postdata .= '&key=' . urlencode($key);
|
||||
|
||||
$response = $headers = '';
|
||||
$in_header = true;
|
||||
if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) {
|
||||
$out = "POST $path HTTP/1.0\r\n";
|
||||
$out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n";
|
||||
$out .= "Content-Length: " . strlen($postdata) . "\r\n";
|
||||
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
|
||||
$out .= "Connection: Close\r\n\r\n";
|
||||
$out .= $postdata;
|
||||
fwrite($fp, $out);
|
||||
|
||||
while (!feof($fp)) {
|
||||
if ($in_header) {
|
||||
$line = fgets($fp, 512);
|
||||
$headers .= $line;
|
||||
if (trim($line) == '')
|
||||
$in_header = false;
|
||||
}
|
||||
else {
|
||||
$response .= fgets($fp, 1024);
|
||||
}
|
||||
}
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// parse HTTP response headers
|
||||
if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $headers, $m)) {
|
||||
$http_status = $m[1];
|
||||
if ($http_status != '200')
|
||||
$this->error = 'HTTP ' . $m[1] . $m[2];
|
||||
}
|
||||
|
||||
if (!$response) {
|
||||
$this->error = "Empty result from spelling engine";
|
||||
}
|
||||
|
||||
try {
|
||||
$result = new SimpleXMLElement($response);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->error = "Unexpected response from server: " . $response;
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($result->error as $error) {
|
||||
if (strval($error->type) == 'spelling') {
|
||||
$word = strval($error->string);
|
||||
|
||||
// skip exceptions
|
||||
if ($this->dictionary->is_exception($word)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefix = strval($error->precontext);
|
||||
$start = $prefix ? mb_strpos($text, $prefix) : 0;
|
||||
$pos = mb_strpos($text, $word, $start);
|
||||
$len = mb_strlen($word);
|
||||
$num = 0;
|
||||
|
||||
$match = array($word, $pos, $len, null, array());
|
||||
foreach ($error->suggestions->option as $option) {
|
||||
$match[4][] = strval($option);
|
||||
if (++$num == self::MAX_SUGGESTIONS)
|
||||
break;
|
||||
}
|
||||
$matches[] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
$this->matches = $matches;
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions for the specified word
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_words()
|
||||
*/
|
||||
function get_suggestions($word)
|
||||
{
|
||||
$matches = $word ? $this->check($word) : $this->matches;
|
||||
|
||||
if ($matches[0][4]) {
|
||||
return $matches[0][4];
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns misspelled words
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_suggestions()
|
||||
*/
|
||||
function get_words($text = null)
|
||||
{
|
||||
if ($text) {
|
||||
$matches = $this->check($text);
|
||||
}
|
||||
else {
|
||||
$matches = $this->matches;
|
||||
$text = $this->content;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($matches as $m) {
|
||||
$result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
181
data/web/rc/program/lib/Roundcube/rcube_spellcheck_enchant.php
Normal file
181
data/web/rc/program/lib/Roundcube/rcube_spellcheck_enchant.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| |
|
||||
| Copyright (C) 2011-2013, Kolab Systems AG |
|
||||
| Copyright (C) 20011-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Spellchecking backend implementation to work with Enchant |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spellchecking backend implementation to work with Pspell
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_spellcheck_enchant extends rcube_spellcheck_engine
|
||||
{
|
||||
private $enchant_broker;
|
||||
private $enchant_dictionary;
|
||||
private $matches = array();
|
||||
|
||||
/**
|
||||
* Return a list of languages supported by this backend
|
||||
*
|
||||
* @see rcube_spellcheck_engine::languages()
|
||||
*/
|
||||
function languages()
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$langs = array();
|
||||
if ($dicts = enchant_broker_list_dicts($this->enchant_broker)) {
|
||||
foreach ($dicts as $dict) {
|
||||
$langs[] = preg_replace('/-.*$/', '', $dict['lang_tag']);
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($langs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Enchant dictionary
|
||||
*/
|
||||
private function init()
|
||||
{
|
||||
if (!$this->enchant_broker) {
|
||||
if (!extension_loaded('enchant')) {
|
||||
$this->error = "Enchant extension not available";
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enchant_broker = enchant_broker_init();
|
||||
}
|
||||
|
||||
if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) {
|
||||
$this->error = "Unable to load dictionary for selected language using Enchant";
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content and check spelling
|
||||
*
|
||||
* @see rcube_spellcheck_engine::check()
|
||||
*/
|
||||
function check($text)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if (!$this->enchant_dictionary) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// tokenize
|
||||
$text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
|
||||
|
||||
$diff = 0;
|
||||
$matches = array();
|
||||
|
||||
foreach ($text as $w) {
|
||||
$word = trim($w[0]);
|
||||
$pos = $w[1] - $diff;
|
||||
$len = mb_strlen($word);
|
||||
|
||||
// skip exceptions
|
||||
if ($this->dictionary->is_exception($word)) {
|
||||
}
|
||||
else if (!enchant_dict_check($this->enchant_dictionary, $word)) {
|
||||
$suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
|
||||
|
||||
if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
|
||||
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
|
||||
}
|
||||
|
||||
$matches[] = array($word, $pos, $len, null, $suggestions);
|
||||
}
|
||||
|
||||
$diff += (strlen($word) - $len);
|
||||
}
|
||||
|
||||
$this->matches = $matches;
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions for the specified word
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_words()
|
||||
*/
|
||||
function get_suggestions($word)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if (!$this->enchant_dictionary) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$suggestions = enchant_dict_suggest($this->enchant_dictionary, $word);
|
||||
|
||||
if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
|
||||
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
|
||||
|
||||
return is_array($suggestions) ? $suggestions : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns misspelled words
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_suggestions()
|
||||
*/
|
||||
function get_words($text = null)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
if ($text) {
|
||||
// init spellchecker
|
||||
$this->init();
|
||||
|
||||
if (!$this->enchant_dictionary) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// With Enchant we don't need to get suggestions to return misspelled words
|
||||
$text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
|
||||
|
||||
foreach ($text as $w) {
|
||||
$word = trim($w[0]);
|
||||
|
||||
// skip exceptions
|
||||
if ($this->dictionary->is_exception($word)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!enchant_dict_check($this->enchant_dictionary, $word)) {
|
||||
$result[] = $word;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($this->matches as $m) {
|
||||
$result[] = $m[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| |
|
||||
| Copyright (C) 2011-2013, Kolab Systems AG |
|
||||
| Copyright (C) 2008-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Interface class for a spell-checking backend |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface class for a spell-checking backend
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
abstract class rcube_spellcheck_engine
|
||||
{
|
||||
const MAX_SUGGESTIONS = 10;
|
||||
|
||||
protected $lang;
|
||||
protected $error;
|
||||
protected $dictionary;
|
||||
protected $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.](?=\W|$)/';
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public function __construct($dict, $lang)
|
||||
{
|
||||
$this->dictionary = $dict;
|
||||
$this->lang = $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of languages supported by this backend
|
||||
*
|
||||
* @return array Indexed list of language codes
|
||||
*/
|
||||
abstract function languages();
|
||||
|
||||
/**
|
||||
* Set content and check spelling
|
||||
*
|
||||
* @param string $text Text content for spellchecking
|
||||
*
|
||||
* @return bool True when no mispelling found, otherwise false
|
||||
*/
|
||||
abstract function check($text);
|
||||
|
||||
/**
|
||||
* Returns suggestions for the specified word
|
||||
*
|
||||
* @param string $word The word
|
||||
*
|
||||
* @return array Suggestions list
|
||||
*/
|
||||
abstract function get_suggestions($word);
|
||||
|
||||
/**
|
||||
* Returns misspelled words
|
||||
*
|
||||
* @param string $text The content for spellchecking. If empty content
|
||||
* used for check() method will be used.
|
||||
*
|
||||
* @return array List of misspelled words
|
||||
*/
|
||||
abstract function get_words($text = null);
|
||||
|
||||
/**
|
||||
* Returns error message
|
||||
*
|
||||
* @return string Error message
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
177
data/web/rc/program/lib/Roundcube/rcube_spellcheck_googie.php
Normal file
177
data/web/rc/program/lib/Roundcube/rcube_spellcheck_googie.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| |
|
||||
| Copyright (C) 2008-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Spellchecking backend implementation to work with Googiespell |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spellchecking backend implementation to work with a Googiespell service
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_spellcheck_googie extends rcube_spellcheck_engine
|
||||
{
|
||||
const GOOGIE_HOST = 'ssl://spell.roundcube.net';
|
||||
const GOOGIE_PORT = 443;
|
||||
|
||||
private $matches = array();
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* Return a list of languages supported by this backend
|
||||
*
|
||||
* @see rcube_spellcheck_engine::languages()
|
||||
*/
|
||||
function languages()
|
||||
{
|
||||
return array('am','ar','ar','bg','br','ca','cs','cy','da',
|
||||
'de_CH','de_DE','el','en_GB','en_US',
|
||||
'eo','es','et','eu','fa','fi','fr_FR','ga','gl','gl',
|
||||
'he','hr','hu','hy','is','it','ku','lt','lv','nl',
|
||||
'pl','pt_BR','pt_PT','ro','ru',
|
||||
'sk','sl','sv','uk');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content and check spelling
|
||||
*
|
||||
* @see rcube_spellcheck_engine::check()
|
||||
*/
|
||||
function check($text)
|
||||
{
|
||||
$this->content = $text;
|
||||
|
||||
if (empty($text)) {
|
||||
return $this->matches = array();
|
||||
}
|
||||
|
||||
// spell check uri is configured
|
||||
$url = rcube::get_instance()->config->get('spellcheck_uri');
|
||||
|
||||
if ($url) {
|
||||
$a_uri = parse_url($url);
|
||||
$ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl');
|
||||
$port = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80);
|
||||
$host = ($ssl ? 'ssl://' : '') . $a_uri['host'];
|
||||
$path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang;
|
||||
}
|
||||
else {
|
||||
$host = self::GOOGIE_HOST;
|
||||
$port = self::GOOGIE_PORT;
|
||||
$path = '/tbproxy/spell?lang=' . $this->lang;
|
||||
}
|
||||
|
||||
$path .= sprintf('&key=%06d', $_SESSION['user_id']);
|
||||
|
||||
$gtext = '<?xml version="1.0" encoding="utf-8" ?>'
|
||||
.'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
|
||||
.'<text>' . htmlspecialchars($text, ENT_QUOTES, RCUBE_CHARSET) . '</text>'
|
||||
.'</spellrequest>';
|
||||
|
||||
$store = '';
|
||||
if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) {
|
||||
$out = "POST $path HTTP/1.0\r\n";
|
||||
$out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n";
|
||||
$out .= "User-Agent: Roundcube Webmail/" . RCUBE_VERSION . " (Googiespell Wrapper)\r\n";
|
||||
$out .= "Content-Length: " . strlen($gtext) . "\r\n";
|
||||
$out .= "Content-Type: text/xml\r\n";
|
||||
$out .= "Connection: Close\r\n\r\n";
|
||||
$out .= $gtext;
|
||||
fwrite($fp, $out);
|
||||
|
||||
while (!feof($fp))
|
||||
$store .= fgets($fp, 128);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// parse HTTP response
|
||||
if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) {
|
||||
$http_status = $m[1];
|
||||
if ($http_status != '200') {
|
||||
$this->error = 'HTTP ' . $m[1] . rtrim($m[2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$store) {
|
||||
$this->error = "Empty result from spelling engine";
|
||||
}
|
||||
else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m) && $m[1]) {
|
||||
$this->error = "Error code $m[1] returned";
|
||||
$this->error .= preg_match('/<errortext>([^<]+)/', $store, $m) ? ": " . html_entity_decode($m[1]) : '';
|
||||
}
|
||||
|
||||
preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
|
||||
|
||||
// skip exceptions (if appropriate options are enabled)
|
||||
foreach ($matches as $idx => $m) {
|
||||
$word = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET);
|
||||
// skip exceptions
|
||||
if ($this->dictionary->is_exception($word)) {
|
||||
unset($matches[$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->matches = $matches;
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions for the specified word
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_words()
|
||||
*/
|
||||
function get_suggestions($word)
|
||||
{
|
||||
$matches = $word ? $this->check($word) : $this->matches;
|
||||
|
||||
if ($matches[0][4]) {
|
||||
$suggestions = explode("\t", $matches[0][4]);
|
||||
if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
|
||||
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns misspelled words
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_suggestions()
|
||||
*/
|
||||
function get_words($text = null)
|
||||
{
|
||||
if ($text) {
|
||||
$matches = $this->check($text);
|
||||
}
|
||||
else {
|
||||
$matches = $this->matches;
|
||||
$text = $this->content;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($matches as $m) {
|
||||
$result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
187
data/web/rc/program/lib/Roundcube/rcube_spellcheck_pspell.php
Normal file
187
data/web/rc/program/lib/Roundcube/rcube_spellcheck_pspell.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| |
|
||||
| Copyright (C) 2008-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Spellchecking backend implementation to work with Pspell |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spellchecking backend implementation to work with Pspell
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_spellcheck_pspell extends rcube_spellcheck_engine
|
||||
{
|
||||
private $plink;
|
||||
private $matches = array();
|
||||
|
||||
/**
|
||||
* Return a list of languages supported by this backend
|
||||
*
|
||||
* @see rcube_spellcheck_engine::languages()
|
||||
*/
|
||||
function languages()
|
||||
{
|
||||
$defaults = array('en');
|
||||
$langs = array();
|
||||
|
||||
// get aspell dictionaries
|
||||
exec('aspell dump dicts', $dicts);
|
||||
if (!empty($dicts)) {
|
||||
$seen = array();
|
||||
foreach ($dicts as $lang) {
|
||||
$lang = preg_replace('/-.*$/', '', $lang);
|
||||
$langc = strlen($lang) == 2 ? $lang.'_'.strtoupper($lang) : $lang;
|
||||
if (!$seen[$langc]++)
|
||||
$langs[] = $lang;
|
||||
}
|
||||
$langs = array_unique($langs);
|
||||
}
|
||||
else {
|
||||
$langs = $defaults;
|
||||
}
|
||||
|
||||
return $langs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes PSpell dictionary
|
||||
*/
|
||||
private function init()
|
||||
{
|
||||
if (!$this->plink) {
|
||||
if (!extension_loaded('pspell')) {
|
||||
$this->error = "Pspell extension not available";
|
||||
return;
|
||||
}
|
||||
|
||||
$this->plink = pspell_new($this->lang, null, null, RCUBE_CHARSET, PSPELL_FAST);
|
||||
}
|
||||
|
||||
if (!$this->plink) {
|
||||
$this->error = "Unable to load Pspell engine for selected language";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content and check spelling
|
||||
*
|
||||
* @see rcube_spellcheck_engine::check()
|
||||
*/
|
||||
function check($text)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if (!$this->plink) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// tokenize
|
||||
$text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
|
||||
|
||||
$diff = 0;
|
||||
$matches = array();
|
||||
|
||||
foreach ($text as $w) {
|
||||
$word = trim($w[0]);
|
||||
$pos = $w[1] - $diff;
|
||||
$len = mb_strlen($word);
|
||||
|
||||
// skip exceptions
|
||||
if ($this->dictionary->is_exception($word)) {
|
||||
}
|
||||
else if (!pspell_check($this->plink, $word)) {
|
||||
$suggestions = pspell_suggest($this->plink, $word);
|
||||
|
||||
if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
|
||||
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
|
||||
}
|
||||
|
||||
$matches[] = array($word, $pos, $len, null, $suggestions);
|
||||
}
|
||||
|
||||
$diff += (strlen($word) - $len);
|
||||
}
|
||||
|
||||
$this->matches = $matches;
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions for the specified word
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_words()
|
||||
*/
|
||||
function get_suggestions($word)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
if (!$this->plink) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$suggestions = pspell_suggest($this->plink, $word);
|
||||
|
||||
if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
|
||||
$suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
|
||||
|
||||
return is_array($suggestions) ? $suggestions : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns misspelled words
|
||||
*
|
||||
* @see rcube_spellcheck_engine::get_suggestions()
|
||||
*/
|
||||
function get_words($text = null)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
if ($text) {
|
||||
// init spellchecker
|
||||
$this->init();
|
||||
|
||||
if (!$this->plink) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// With PSpell we don't need to get suggestions to return misspelled words
|
||||
$text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
|
||||
|
||||
foreach ($text as $w) {
|
||||
$word = trim($w[0]);
|
||||
|
||||
// skip exceptions
|
||||
if ($this->dictionary->is_exception($word)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pspell_check($this->plink, $word)) {
|
||||
$result[] = $word;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($this->matches as $m) {
|
||||
$result[] = $m[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
418
data/web/rc/program/lib/Roundcube/rcube_spellchecker.php
Normal file
418
data/web/rc/program/lib/Roundcube/rcube_spellchecker.php
Normal file
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2011-2013, Kolab Systems AG |
|
||||
| Copyright (C) 2008-2013, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Spellchecking using different backends |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper class for spellchecking with Googielspell and PSpell support.
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_spellchecker
|
||||
{
|
||||
private $matches = array();
|
||||
private $engine;
|
||||
private $backend;
|
||||
private $lang;
|
||||
private $rc;
|
||||
private $error;
|
||||
private $options = array();
|
||||
private $dict;
|
||||
private $have_dict;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $lang Language code
|
||||
*/
|
||||
function __construct($lang = 'en')
|
||||
{
|
||||
$this->rc = rcube::get_instance();
|
||||
$this->engine = $this->rc->config->get('spellcheck_engine', 'googie');
|
||||
$this->lang = $lang ?: 'en';
|
||||
|
||||
$this->options = array(
|
||||
'ignore_syms' => $this->rc->config->get('spellcheck_ignore_syms'),
|
||||
'ignore_nums' => $this->rc->config->get('spellcheck_ignore_nums'),
|
||||
'ignore_caps' => $this->rc->config->get('spellcheck_ignore_caps'),
|
||||
'dictionary' => $this->rc->config->get('spellcheck_dictionary'),
|
||||
);
|
||||
|
||||
$cls = 'rcube_spellcheck_' . $this->engine;
|
||||
if (class_exists($cls)) {
|
||||
$this->backend = new $cls($this, $this->lang);
|
||||
$this->backend->options = $this->options;
|
||||
}
|
||||
else {
|
||||
$this->error = "Unknown spellcheck engine '$this->engine'";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of supported languages
|
||||
*/
|
||||
function languages()
|
||||
{
|
||||
// trust configuration
|
||||
$configured = $this->rc->config->get('spellcheck_languages');
|
||||
if (!empty($configured) && is_array($configured) && !$configured[0]) {
|
||||
return $configured;
|
||||
}
|
||||
else if (!empty($configured)) {
|
||||
$langs = (array)$configured;
|
||||
}
|
||||
else if ($this->backend) {
|
||||
$langs = $this->backend->languages();
|
||||
}
|
||||
|
||||
// load index
|
||||
@include(RCUBE_LOCALIZATION_DIR . 'index.inc');
|
||||
|
||||
// add correct labels
|
||||
$languages = array();
|
||||
foreach ($langs as $lang) {
|
||||
$langc = strtolower(substr($lang, 0, 2));
|
||||
$alias = $rcube_language_aliases[$langc];
|
||||
if (!$alias) {
|
||||
$alias = $langc.'_'.strtoupper($langc);
|
||||
}
|
||||
if ($rcube_languages[$lang]) {
|
||||
$languages[$lang] = $rcube_languages[$lang];
|
||||
}
|
||||
else if ($rcube_languages[$alias]) {
|
||||
$languages[$lang] = $rcube_languages[$alias];
|
||||
}
|
||||
else {
|
||||
$languages[$lang] = ucfirst($lang);
|
||||
}
|
||||
}
|
||||
|
||||
// remove possible duplicates (#1489395)
|
||||
$languages = array_unique($languages);
|
||||
|
||||
asort($languages);
|
||||
|
||||
return $languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content and check spelling
|
||||
*
|
||||
* @param string $text Text content for spellchecking
|
||||
* @param bool $is_html Enables HTML-to-Text conversion
|
||||
*
|
||||
* @return bool True when no mispelling found, otherwise false
|
||||
*/
|
||||
function check($text, $is_html = false)
|
||||
{
|
||||
// convert to plain text
|
||||
if ($is_html) {
|
||||
$this->content = $this->html2text($text);
|
||||
}
|
||||
else {
|
||||
$this->content = $text;
|
||||
}
|
||||
|
||||
if ($this->backend) {
|
||||
$this->matches = $this->backend->check($this->content);
|
||||
}
|
||||
|
||||
return $this->found() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of mispellings found (after check)
|
||||
*
|
||||
* @return int Number of mispellings
|
||||
*/
|
||||
function found()
|
||||
{
|
||||
return count($this->matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggestions for the specified word
|
||||
*
|
||||
* @param string $word The word
|
||||
*
|
||||
* @return array Suggestions list
|
||||
*/
|
||||
function get_suggestions($word)
|
||||
{
|
||||
if ($this->backend) {
|
||||
return $this->backend->get_suggestions($word);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns misspelled words
|
||||
*
|
||||
* @param string $text The content for spellchecking. If empty content
|
||||
* used for check() method will be used.
|
||||
*
|
||||
* @return array List of misspelled words
|
||||
*/
|
||||
function get_words($text = null, $is_html=false)
|
||||
{
|
||||
if ($is_html) {
|
||||
$text = $this->html2text($text);
|
||||
}
|
||||
|
||||
if ($this->backend) {
|
||||
return $this->backend->get_words($text);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns checking result in XML (Googiespell) format
|
||||
*
|
||||
* @return string XML content
|
||||
*/
|
||||
function get_xml()
|
||||
{
|
||||
// send output
|
||||
$out = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">';
|
||||
|
||||
foreach ((array)$this->matches as $item) {
|
||||
$out .= '<c o="'.$item[1].'" l="'.$item[2].'">';
|
||||
$out .= is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
|
||||
$out .= '</c>';
|
||||
}
|
||||
|
||||
$out .= '</spellresult>';
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns checking result (misspelled words with suggestions)
|
||||
*
|
||||
* @return array Spellchecking result. An array indexed by word.
|
||||
*/
|
||||
function get()
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ((array)$this->matches as $item) {
|
||||
if ($this->engine == 'pspell') {
|
||||
$word = $item[0];
|
||||
}
|
||||
else {
|
||||
$word = mb_substr($this->content, $item[1], $item[2], RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
if (is_array($item[4])) {
|
||||
$suggestions = $item[4];
|
||||
}
|
||||
else if (empty($item[4])) {
|
||||
$suggestions = array();
|
||||
}
|
||||
else {
|
||||
$suggestions = explode("\t", $item[4]);
|
||||
}
|
||||
|
||||
$result[$word] = $suggestions;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error message
|
||||
*
|
||||
* @return string Error message
|
||||
*/
|
||||
function error()
|
||||
{
|
||||
return $this->error ?: ($this->backend ? $this->backend->error() : false);
|
||||
}
|
||||
|
||||
private function html2text($text)
|
||||
{
|
||||
$h2t = new rcube_html2text($text, false, false, 0);
|
||||
return $h2t->get_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the specified word is an exception accoring to
|
||||
* spellcheck options.
|
||||
*
|
||||
* @param string $word The word
|
||||
*
|
||||
* @return bool True if the word is an exception, False otherwise
|
||||
*/
|
||||
public function is_exception($word)
|
||||
{
|
||||
// Contain only symbols (e.g. "+9,0", "2:2")
|
||||
if (!$word || preg_match('/^[0-9@#$%^&_+~*<>=:;?!,.-]+$/', $word))
|
||||
return true;
|
||||
|
||||
// Contain symbols (e.g. "g@@gle"), all symbols excluding separators
|
||||
if (!empty($this->options['ignore_syms']) && preg_match('/[@#$%^&_+~*=-]/', $word))
|
||||
return true;
|
||||
|
||||
// Contain numbers (e.g. "g00g13")
|
||||
if (!empty($this->options['ignore_nums']) && preg_match('/[0-9]/', $word))
|
||||
return true;
|
||||
|
||||
// Blocked caps (e.g. "GOOGLE")
|
||||
if (!empty($this->options['ignore_caps']) && $word == mb_strtoupper($word))
|
||||
return true;
|
||||
|
||||
// Use exceptions from dictionary
|
||||
if (!empty($this->options['dictionary'])) {
|
||||
$this->load_dict();
|
||||
|
||||
// @TODO: should dictionary be case-insensitive?
|
||||
if (!empty($this->dict) && in_array($word, $this->dict))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a word to dictionary
|
||||
*
|
||||
* @param string $word The word to add
|
||||
*/
|
||||
public function add_word($word)
|
||||
{
|
||||
$this->load_dict();
|
||||
|
||||
foreach (explode(' ', $word) as $word) {
|
||||
// sanity check
|
||||
if (strlen($word) < 512) {
|
||||
$this->dict[] = $word;
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($valid) {
|
||||
$this->dict = array_unique($this->dict);
|
||||
$this->update_dict();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a word from dictionary
|
||||
*
|
||||
* @param string $word The word to remove
|
||||
*/
|
||||
public function remove_word($word)
|
||||
{
|
||||
$this->load_dict();
|
||||
|
||||
if (($key = array_search($word, $this->dict)) !== false) {
|
||||
unset($this->dict[$key]);
|
||||
$this->update_dict();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dictionary row in DB
|
||||
*/
|
||||
private function update_dict()
|
||||
{
|
||||
if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
|
||||
$userid = $this->rc->get_user_id();
|
||||
}
|
||||
|
||||
$plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array(
|
||||
'userid' => $userid, 'language' => $this->lang, 'dictionary' => $this->dict));
|
||||
|
||||
if (!empty($plugin['abort'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->have_dict) {
|
||||
if (!empty($this->dict)) {
|
||||
$this->rc->db->query(
|
||||
"UPDATE " . $this->rc->db->table_name('dictionary', true)
|
||||
." SET `data` = ?"
|
||||
." WHERE `user_id` " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
|
||||
." AND `language` = ?",
|
||||
implode(' ', $plugin['dictionary']), $plugin['language']);
|
||||
}
|
||||
// don't store empty dict
|
||||
else {
|
||||
$this->rc->db->query(
|
||||
"DELETE FROM " . $this->rc->db->table_name('dictionary', true)
|
||||
." WHERE `user_id` " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
|
||||
." AND `language` = ?",
|
||||
$plugin['language']);
|
||||
}
|
||||
}
|
||||
else if (!empty($this->dict)) {
|
||||
$this->rc->db->query(
|
||||
"INSERT INTO " . $this->rc->db->table_name('dictionary', true)
|
||||
." (`user_id`, `language`, `data`) VALUES (?, ?, ?)",
|
||||
$plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dictionary from DB
|
||||
*/
|
||||
private function load_dict()
|
||||
{
|
||||
if (is_array($this->dict)) {
|
||||
return $this->dict;
|
||||
}
|
||||
|
||||
if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
|
||||
$userid = $this->rc->get_user_id();
|
||||
}
|
||||
|
||||
$plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array(
|
||||
'userid' => $userid, 'language' => $this->lang, 'dictionary' => array()));
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$dict = array();
|
||||
$sql_result = $this->rc->db->query(
|
||||
"SELECT `data` FROM " . $this->rc->db->table_name('dictionary', true)
|
||||
." WHERE `user_id` ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
|
||||
." AND `language` = ?",
|
||||
$plugin['language']);
|
||||
|
||||
if ($sql_arr = $this->rc->db->fetch_assoc($sql_result)) {
|
||||
$this->have_dict = true;
|
||||
if (!empty($sql_arr['data'])) {
|
||||
$dict = explode(' ', $sql_arr['data']);
|
||||
}
|
||||
}
|
||||
|
||||
$plugin['dictionary'] = array_merge((array)$plugin['dictionary'], $dict);
|
||||
}
|
||||
|
||||
if (!empty($plugin['dictionary']) && is_array($plugin['dictionary'])) {
|
||||
$this->dict = $plugin['dictionary'];
|
||||
}
|
||||
else {
|
||||
$this->dict = array();
|
||||
}
|
||||
|
||||
return $this->dict;
|
||||
}
|
||||
}
|
||||
980
data/web/rc/program/lib/Roundcube/rcube_storage.php
Normal file
980
data/web/rc/program/lib/Roundcube/rcube_storage.php
Normal file
@@ -0,0 +1,980 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| Copyright (C) 2012, Kolab Systems AG |
|
||||
| |
|
||||
| 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: |
|
||||
| Mail Storage Engine |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract class for accessing mail messages storage server
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
* @author Thomas Bruederli <roundcube@gmail.com>
|
||||
* @author Aleksander Machniak <alec@alec.pl>
|
||||
*/
|
||||
abstract class rcube_storage
|
||||
{
|
||||
/**
|
||||
* Instance of connection object e.g. rcube_imap_generic
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $conn;
|
||||
|
||||
/**
|
||||
* List of supported special folder types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $folder_types = array('drafts', 'sent', 'junk', 'trash');
|
||||
|
||||
protected $folder = 'INBOX';
|
||||
protected $default_charset = 'ISO-8859-1';
|
||||
protected $search_set;
|
||||
protected $options = array('auth_type' => 'check');
|
||||
protected $page_size = 10;
|
||||
protected $list_page = 1;
|
||||
protected $threading = false;
|
||||
|
||||
/**
|
||||
* All (additional) headers used (in any way) by Roundcube
|
||||
* Not listed here: DATE, FROM, TO, CC, REPLY-TO, SUBJECT, CONTENT-TYPE, LIST-POST
|
||||
* (used for messages listing) are hardcoded in rcube_imap_generic::fetchHeaders()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $all_headers = array(
|
||||
'IN-REPLY-TO',
|
||||
'BCC',
|
||||
'SENDER',
|
||||
'MESSAGE-ID',
|
||||
'CONTENT-TRANSFER-ENCODING',
|
||||
'REFERENCES',
|
||||
'X-DRAFT-INFO',
|
||||
'MAIL-FOLLOWUP-TO',
|
||||
'MAIL-REPLY-TO',
|
||||
'RETURN-PATH',
|
||||
);
|
||||
|
||||
const UNKNOWN = 0;
|
||||
const NOPERM = 1;
|
||||
const READONLY = 2;
|
||||
const TRYCREATE = 3;
|
||||
const INUSE = 4;
|
||||
const OVERQUOTA = 5;
|
||||
const ALREADYEXISTS = 6;
|
||||
const NONEXISTENT = 7;
|
||||
const CONTACTADMIN = 8;
|
||||
|
||||
|
||||
/**
|
||||
* Connect to the server
|
||||
*
|
||||
* @param string $host Host to connect
|
||||
* @param string $user Username for IMAP account
|
||||
* @param string $pass Password for IMAP account
|
||||
* @param integer $port Port to connect to
|
||||
* @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection
|
||||
*
|
||||
* @return boolean TRUE on success, FALSE on failure
|
||||
*/
|
||||
abstract function connect($host, $user, $pass, $port = 143, $use_ssl = null);
|
||||
|
||||
/**
|
||||
* Close connection. Usually done on script shutdown
|
||||
*/
|
||||
abstract function close();
|
||||
|
||||
/**
|
||||
* Checks connection state.
|
||||
*
|
||||
* @return boolean TRUE on success, FALSE on failure
|
||||
*/
|
||||
abstract function is_connected();
|
||||
|
||||
/**
|
||||
* Check connection state, connect if not connected.
|
||||
*
|
||||
* @return bool Connection state.
|
||||
*/
|
||||
abstract function check_connection();
|
||||
|
||||
/**
|
||||
* Returns code of last error
|
||||
*
|
||||
* @return int Error code
|
||||
*/
|
||||
abstract function get_error_code();
|
||||
|
||||
/**
|
||||
* Returns message of last error
|
||||
*
|
||||
* @return string Error message
|
||||
*/
|
||||
abstract function get_error_str();
|
||||
|
||||
/**
|
||||
* Returns code of last command response
|
||||
*
|
||||
* @return int Response code (class constant)
|
||||
*/
|
||||
abstract function get_response_code();
|
||||
|
||||
/**
|
||||
* Set connection and class options
|
||||
*
|
||||
* @param array $opt Options array
|
||||
*/
|
||||
public function set_options($opt)
|
||||
{
|
||||
$this->options = array_merge($this->options, (array)$opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection/class option
|
||||
*
|
||||
* @param string $name Option name
|
||||
*
|
||||
* @param mixed Option value
|
||||
*/
|
||||
public function get_option($name)
|
||||
{
|
||||
return $this->options[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate/deactivate debug mode.
|
||||
*
|
||||
* @param boolean $dbg True if conversation with the server should be logged
|
||||
*/
|
||||
abstract function set_debug($dbg = true);
|
||||
|
||||
/**
|
||||
* Set default message charset.
|
||||
*
|
||||
* This will be used for message decoding if a charset specification is not available
|
||||
*
|
||||
* @param string $cs Charset string
|
||||
*/
|
||||
public function set_charset($cs)
|
||||
{
|
||||
$this->default_charset = $cs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set internal folder reference.
|
||||
* All operations will be perfomed on this folder.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*/
|
||||
public function set_folder($folder)
|
||||
{
|
||||
if ($this->folder === $folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->folder = $folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently used folder name
|
||||
*
|
||||
* @return string Name of the folder
|
||||
*/
|
||||
public function get_folder()
|
||||
{
|
||||
return $this->folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set internal list page number.
|
||||
*
|
||||
* @param int $page Page number to list
|
||||
*/
|
||||
public function set_page($page)
|
||||
{
|
||||
if ($page = intval($page)) {
|
||||
$this->list_page = $page;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets internal list page number.
|
||||
*
|
||||
* @return int Page number
|
||||
*/
|
||||
public function get_page()
|
||||
{
|
||||
return $this->list_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set internal page size
|
||||
*
|
||||
* @param int $size Number of messages to display on one page
|
||||
*/
|
||||
public function set_pagesize($size)
|
||||
{
|
||||
$this->page_size = (int) $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get internal page size
|
||||
*
|
||||
* @return int Number of messages to display on one page
|
||||
*/
|
||||
public function get_pagesize()
|
||||
{
|
||||
return $this->page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a search result for future message listing methods.
|
||||
*
|
||||
* @param mixed $set Search set in driver specific format
|
||||
*/
|
||||
abstract function set_search_set($set);
|
||||
|
||||
/**
|
||||
* Return the saved search set.
|
||||
*
|
||||
* @return array Search set in driver specific format, NULL if search wasn't initialized
|
||||
*/
|
||||
abstract function get_search_set();
|
||||
|
||||
/**
|
||||
* Returns the storage server's (IMAP) capability
|
||||
*
|
||||
* @param string $cap Capability name
|
||||
*
|
||||
* @return mixed Capability value or TRUE if supported, FALSE if not
|
||||
*/
|
||||
abstract function get_capability($cap);
|
||||
|
||||
/**
|
||||
* Sets threading flag to the best supported THREAD algorithm.
|
||||
* Enable/Disable threaded mode.
|
||||
*
|
||||
* @param boolean $enable TRUE to enable and FALSE
|
||||
*
|
||||
* @return mixed Threading algorithm or False if THREAD is not supported
|
||||
*/
|
||||
public function set_threading($enable = false)
|
||||
{
|
||||
$this->threading = false;
|
||||
|
||||
if ($enable && ($caps = $this->get_capability('THREAD'))) {
|
||||
$methods = array('REFS', 'REFERENCES', 'ORDEREDSUBJECT');
|
||||
$methods = array_intersect($methods, $caps);
|
||||
|
||||
$this->threading = array_shift($methods);
|
||||
}
|
||||
|
||||
return $this->threading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current threading flag.
|
||||
*
|
||||
* @return mixed Threading algorithm or False if THREAD is not supported or disabled
|
||||
*/
|
||||
public function get_threading()
|
||||
{
|
||||
return $this->threading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the PERMANENTFLAGS capability of the current folder
|
||||
* and returns true if the given flag is supported by the server.
|
||||
*
|
||||
* @param string $flag Permanentflag name
|
||||
*
|
||||
* @return boolean True if this flag is supported
|
||||
*/
|
||||
abstract function check_permflag($flag);
|
||||
|
||||
/**
|
||||
* Returns the delimiter that is used by the server
|
||||
* for folder hierarchy separation.
|
||||
*
|
||||
* @return string Delimiter string
|
||||
*/
|
||||
abstract function get_hierarchy_delimiter();
|
||||
|
||||
/**
|
||||
* Get namespace
|
||||
*
|
||||
* @param string $name Namespace array index: personal, other, shared, prefix
|
||||
*
|
||||
* @return array Namespace data
|
||||
*/
|
||||
abstract function get_namespace($name = null);
|
||||
|
||||
/**
|
||||
* Get messages count for a specific folder.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @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()
|
||||
*
|
||||
* @return int Number of messages
|
||||
*/
|
||||
abstract function count($folder = null, $mode = 'ALL', $force = false, $status = true);
|
||||
|
||||
/**
|
||||
* Public method for listing message flags
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param array $uids Message UIDs
|
||||
* @param int $mod_seq Optional MODSEQ value
|
||||
*
|
||||
* @return array Indexed array with message flags
|
||||
*/
|
||||
abstract function list_flags($folder, $uids, $mod_seq = null);
|
||||
|
||||
/**
|
||||
* Public method for listing headers.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param int $page Current page to list
|
||||
* @param string $sort_field Header field to sort by
|
||||
* @param string $sort_order Sort order [ASC|DESC]
|
||||
* @param int $slice Number of slice items to extract from result array
|
||||
*
|
||||
* @return array Indexed array with message header objects
|
||||
*/
|
||||
abstract function list_messages($folder = null, $page = null, $sort_field = null, $sort_order = null, $slice = 0);
|
||||
|
||||
/**
|
||||
* Return sorted list of message UIDs
|
||||
*
|
||||
* @param string $folder Folder to get index from
|
||||
* @param string $sort_field Sort column
|
||||
* @param string $sort_order Sort order [ASC, DESC]
|
||||
*
|
||||
* @return rcube_result_index|rcube_result_thread List of messages (UIDs)
|
||||
*/
|
||||
abstract function index($folder = null, $sort_field = null, $sort_order = null);
|
||||
|
||||
/**
|
||||
* Invoke search request to the server.
|
||||
*
|
||||
* @param string $folder Folder name to search in
|
||||
* @param string $str Search criteria
|
||||
* @param string $charset Search charset
|
||||
* @param string $sort_field Header field to sort by
|
||||
*
|
||||
* @todo: Search criteria should be provided in non-IMAP format, eg. array
|
||||
*/
|
||||
abstract function search($folder = null, $str = 'ALL', $charset = null, $sort_field = null);
|
||||
|
||||
/**
|
||||
* Direct (real and simple) search request (without result sorting and caching).
|
||||
*
|
||||
* @param string $folder Folder name to search in
|
||||
* @param string $str Search string
|
||||
*
|
||||
* @return rcube_result_index Search result (UIDs)
|
||||
*/
|
||||
abstract function search_once($folder = null, $str = 'ALL');
|
||||
|
||||
/**
|
||||
* Refresh saved search set
|
||||
*
|
||||
* @return array Current search set
|
||||
*/
|
||||
abstract function refresh_search();
|
||||
|
||||
|
||||
/* --------------------------------
|
||||
* messages management
|
||||
* --------------------------------*/
|
||||
|
||||
/**
|
||||
* Fetch message headers and body structure from the server and build
|
||||
* an object structure.
|
||||
*
|
||||
* @param int $uid Message UID to fetch
|
||||
* @param string $folder Folder to read from
|
||||
*
|
||||
* @return object rcube_message_header Message data
|
||||
*/
|
||||
abstract function get_message($uid, $folder = null);
|
||||
|
||||
/**
|
||||
* Return message headers object of a specific message
|
||||
*
|
||||
* @param int $id Message sequence ID or UID
|
||||
* @param string $folder Folder to read from
|
||||
* @param bool $force True to skip cache
|
||||
*
|
||||
* @return rcube_message_header Message headers
|
||||
*/
|
||||
abstract function get_message_headers($uid, $folder = null, $force = false);
|
||||
|
||||
/**
|
||||
* Fetch message body of a specific message from the server
|
||||
*
|
||||
* @param int $uid Message UID
|
||||
* @param string $part Part number
|
||||
* @param rcube_message_part $o_part Part object created by get_structure()
|
||||
* @param mixed $print True to print part, ressource to write part contents in
|
||||
* @param resource $fp File pointer to save the message part
|
||||
* @param boolean $skip_charset_conv Disables charset conversion
|
||||
*
|
||||
* @return string Message/part body if not printed
|
||||
*/
|
||||
abstract function get_message_part($uid, $part = 1, $o_part = null, $print = null, $fp = null, $skip_charset_conv = false);
|
||||
|
||||
/**
|
||||
* Fetch message body of a specific message from the server
|
||||
*
|
||||
* @param int $uid Message UID
|
||||
*
|
||||
* @return string $part Message/part body
|
||||
* @see rcube_imap::get_message_part()
|
||||
*/
|
||||
public function get_body($uid, $part = 1)
|
||||
{
|
||||
$headers = $this->get_message_headers($uid);
|
||||
return rcube_charset::convert($this->get_message_part($uid, $part, null),
|
||||
$headers->charset ?: $this->default_charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole message source as string (or saves to a file)
|
||||
*
|
||||
* @param int $uid Message UID
|
||||
* @param resource $fp File pointer to save the message
|
||||
* @param string $part Optional message part ID
|
||||
*
|
||||
* @return string Message source string
|
||||
*/
|
||||
abstract function get_raw_body($uid, $fp = null, $part = null);
|
||||
|
||||
/**
|
||||
* Returns the message headers as string
|
||||
*
|
||||
* @param int $uid Message UID
|
||||
* @param string $part Optional message part ID
|
||||
*
|
||||
* @return string Message headers string
|
||||
*/
|
||||
abstract function get_raw_headers($uid, $part = null);
|
||||
|
||||
/**
|
||||
* Sends the whole message source to stdout
|
||||
*
|
||||
* @param int $uid Message UID
|
||||
* @param bool $formatted Enables line-ending formatting
|
||||
*/
|
||||
abstract function print_raw_body($uid, $formatted = true);
|
||||
|
||||
/**
|
||||
* Set message flag to one or several messages
|
||||
*
|
||||
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
|
||||
* @param string $flag Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
|
||||
* @param string $folder Folder name
|
||||
* @param boolean $skip_cache True to skip message cache clean up
|
||||
*
|
||||
* @return bool Operation status
|
||||
*/
|
||||
abstract function set_flag($uids, $flag, $folder = null, $skip_cache = false);
|
||||
|
||||
/**
|
||||
* Remove message flag for one or several messages
|
||||
*
|
||||
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
|
||||
* @param string $flag Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return bool Operation status
|
||||
* @see set_flag
|
||||
*/
|
||||
public function unset_flag($uids, $flag, $folder = null)
|
||||
{
|
||||
return $this->set_flag($uids, 'UN'.$flag, $folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a mail message (source) to a specific folder.
|
||||
*
|
||||
* @param string $folder Target folder
|
||||
* @param string|array $message The message source string or filename
|
||||
* or array (of strings and file pointers)
|
||||
* @param string $headers Headers string if $message contains only the body
|
||||
* @param boolean $is_file True if $message is a filename
|
||||
* @param array $flags Message flags
|
||||
* @param mixed $date Message internal date
|
||||
*
|
||||
* @return int|bool Appended message UID or True on success, False on error
|
||||
*/
|
||||
abstract function save_message($folder, &$message, $headers = '', $is_file = false, $flags = array(), $date = null);
|
||||
|
||||
/**
|
||||
* Move message(s) from one folder to another.
|
||||
*
|
||||
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
|
||||
* @param string $to Target folder
|
||||
* @param string $from Source folder
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function move_message($uids, $to, $from = null);
|
||||
|
||||
/**
|
||||
* Copy message(s) from one mailbox to another.
|
||||
*
|
||||
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
|
||||
* @param string $to Target folder
|
||||
* @param string $from Source folder
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function copy_message($uids, $to, $from = null);
|
||||
|
||||
/**
|
||||
* Mark message(s) as deleted and expunge.
|
||||
*
|
||||
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
|
||||
* @param string $folder Source folder
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function delete_message($uids, $folder = null);
|
||||
|
||||
/**
|
||||
* Expunge message(s) and clear the cache.
|
||||
*
|
||||
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
|
||||
* @param string $folder Folder name
|
||||
* @param boolean $clear_cache False if cache should not be cleared
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function expunge_message($uids, $folder = null, $clear_cache = true);
|
||||
|
||||
/**
|
||||
* Parse message UIDs input
|
||||
*
|
||||
* @param mixed $uids UIDs array or comma-separated list or '*' or '1:*'
|
||||
*
|
||||
* @return array Two elements array with UIDs converted to list and ALL flag
|
||||
*/
|
||||
protected function parse_uids($uids)
|
||||
{
|
||||
if ($uids === '*' || $uids === '1:*') {
|
||||
if (empty($this->search_set)) {
|
||||
$uids = '1:*';
|
||||
$all = true;
|
||||
}
|
||||
// get UIDs from current search set
|
||||
else {
|
||||
$uids = join(',', $this->search_set->get());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (is_array($uids)) {
|
||||
$uids = join(',', $uids);
|
||||
}
|
||||
else if (strpos($uids, ':')) {
|
||||
$uids = join(',', rcube_imap_generic::uncompressMessageSet($uids));
|
||||
}
|
||||
|
||||
if (preg_match('/[^0-9,]/', $uids)) {
|
||||
$uids = '';
|
||||
}
|
||||
}
|
||||
|
||||
return array($uids, (bool) $all);
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------
|
||||
* folder managment
|
||||
* --------------------------------*/
|
||||
|
||||
/**
|
||||
* Get a list of subscribed folders.
|
||||
*
|
||||
* @param string $root Optional root folder
|
||||
* @param string $name Optional name pattern
|
||||
* @param string $filter Optional filter
|
||||
* @param string $rights Optional ACL requirements
|
||||
* @param bool $skip_sort Enable to return unsorted list (for better performance)
|
||||
*
|
||||
* @return array List of folders
|
||||
*/
|
||||
abstract function list_folders_subscribed($root = '', $name = '*', $filter = null, $rights = null, $skip_sort = false);
|
||||
|
||||
/**
|
||||
* Get a list of all folders available on the server.
|
||||
*
|
||||
* @param string $root IMAP root dir
|
||||
* @param string $name Optional name pattern
|
||||
* @param mixed $filter Optional filter
|
||||
* @param string $rights Optional ACL requirements
|
||||
* @param bool $skip_sort Enable to return unsorted list (for better performance)
|
||||
*
|
||||
* @return array Indexed array with folder names
|
||||
*/
|
||||
abstract function list_folders($root = '', $name = '*', $filter = null, $rights = null, $skip_sort = false);
|
||||
|
||||
/**
|
||||
* Subscribe to a specific folder(s)
|
||||
*
|
||||
* @param array $folders Folder name(s)
|
||||
*
|
||||
* @return boolean True on success
|
||||
*/
|
||||
abstract function subscribe($folders);
|
||||
|
||||
/**
|
||||
* Unsubscribe folder(s)
|
||||
*
|
||||
* @param array $folders Folder name(s)
|
||||
*
|
||||
* @return boolean True on success
|
||||
*/
|
||||
abstract function unsubscribe($folders);
|
||||
|
||||
/**
|
||||
* Create a new folder on the server.
|
||||
*
|
||||
* @param string $folder New folder name
|
||||
* @param boolean $subscribe True if the newvfolder should be subscribed
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function create_folder($folder, $subscribe = false);
|
||||
|
||||
/**
|
||||
* Set a new name to an existing folder
|
||||
*
|
||||
* @param string $folder Folder to rename
|
||||
* @param string $new_name New folder name
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function rename_folder($folder, $new_name);
|
||||
|
||||
/**
|
||||
* Remove a folder from the server.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
abstract function delete_folder($folder);
|
||||
|
||||
/**
|
||||
* Send expunge command and clear the cache.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param boolean $clear_cache False if cache should not be cleared
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
public function expunge_folder($folder = null, $clear_cache = true)
|
||||
{
|
||||
return $this->expunge_message('*', $folder, $clear_cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all messages in a folder..
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
public function clear_folder($folder = null)
|
||||
{
|
||||
return $this->delete_message('*', $folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if folder exists and is subscribed
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param boolean $subscription Enable subscription checking
|
||||
*
|
||||
* @return boolean True if folder exists, False otherwise
|
||||
*/
|
||||
abstract function folder_exists($folder, $subscription = false);
|
||||
|
||||
/**
|
||||
* Get folder size (size of all messages in a folder)
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return int Folder size in bytes, False on error
|
||||
*/
|
||||
abstract function folder_size($folder);
|
||||
|
||||
/**
|
||||
* Returns the namespace where the folder is in
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return string One of 'personal', 'other' or 'shared'
|
||||
*/
|
||||
abstract function folder_namespace($folder);
|
||||
|
||||
/**
|
||||
* Gets folder attributes (from LIST response, e.g. \Noselect, \Noinferiors).
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param bool $force Set to True if attributes should be refreshed
|
||||
*
|
||||
* @return array Options list
|
||||
*/
|
||||
abstract function folder_attributes($folder, $force = false);
|
||||
|
||||
/**
|
||||
* Gets connection (and current folder) data: UIDVALIDITY, EXISTS, RECENT,
|
||||
* PERMANENTFLAGS, UIDNEXT, UNSEEN
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return array Data
|
||||
*/
|
||||
abstract function folder_data($folder);
|
||||
|
||||
/**
|
||||
* Returns extended information about the folder.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return array Data
|
||||
*/
|
||||
abstract function folder_info($folder);
|
||||
|
||||
/**
|
||||
* Returns current status of a folder (compared to the last time use)
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param array $diff Difference data
|
||||
*
|
||||
* @return int Folder status
|
||||
*/
|
||||
abstract function folder_status($folder = null, &$diff = array());
|
||||
|
||||
/**
|
||||
* Synchronizes messages cache.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*/
|
||||
abstract function folder_sync($folder);
|
||||
|
||||
/**
|
||||
* Modify folder name according to namespace.
|
||||
* For output it removes prefix of the personal namespace if it's possible.
|
||||
* For input it adds the prefix. Use it before creating a folder in root
|
||||
* of the folders tree.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param string $mode Mode name (out/in)
|
||||
*
|
||||
* @return string Folder name
|
||||
*/
|
||||
abstract function mod_folder($folder, $mode = 'out');
|
||||
|
||||
/**
|
||||
* Create all folders specified as default
|
||||
*/
|
||||
public function create_default_folders()
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
// create default folders if they do not exist
|
||||
foreach (self::$folder_types as $type) {
|
||||
if ($folder = $rcube->config->get($type . '_mbox')) {
|
||||
if (!$this->folder_exists($folder)) {
|
||||
$this->create_folder($folder, true, $type);
|
||||
}
|
||||
else if (!$this->folder_exists($folder, true)) {
|
||||
$this->subscribe($folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if specified folder is a special folder
|
||||
*/
|
||||
public function is_special_folder($name)
|
||||
{
|
||||
return $name == 'INBOX' || in_array($name, $this->get_special_folders());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return configured special folders
|
||||
*/
|
||||
public function get_special_folders($forced = false)
|
||||
{
|
||||
// getting config might be expensive, store special folders in memory
|
||||
if (!isset($this->icache['special-folders'])) {
|
||||
$rcube = rcube::get_instance();
|
||||
$this->icache['special-folders'] = array();
|
||||
|
||||
foreach (self::$folder_types as $type) {
|
||||
if ($folder = $rcube->config->get($type . '_mbox')) {
|
||||
$this->icache['special-folders'][$type] = $folder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->icache['special-folders'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set special folder associations stored in backend
|
||||
*/
|
||||
public function set_special_folders($specials)
|
||||
{
|
||||
// should be overriden by storage class if backend supports special folders (SPECIAL-USE)
|
||||
unset($this->icache['special-folders']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mailbox quota information.
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return mixed Quota info or False if not supported
|
||||
*/
|
||||
abstract function get_quota($folder = null);
|
||||
|
||||
|
||||
/* -----------------------------------------
|
||||
* ACL and METADATA methods
|
||||
* ----------------------------------------*/
|
||||
|
||||
/**
|
||||
* Changes the ACL on the specified folder (SETACL)
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param string $user User name
|
||||
* @param string $acl ACL string
|
||||
*
|
||||
* @return boolean True on success, False on failure
|
||||
*/
|
||||
abstract function set_acl($folder, $user, $acl);
|
||||
|
||||
/**
|
||||
* Removes any <identifier,rights> pair for the
|
||||
* specified user from the ACL for the specified
|
||||
* folder (DELETEACL).
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param string $user User name
|
||||
*
|
||||
* @return boolean True on success, False on failure
|
||||
*/
|
||||
abstract function delete_acl($folder, $user);
|
||||
|
||||
/**
|
||||
* Returns the access control list for a folder (GETACL).
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return array User-rights array on success, NULL on error
|
||||
*/
|
||||
abstract function get_acl($folder);
|
||||
|
||||
/**
|
||||
* Returns information about what rights can be granted to the
|
||||
* user (identifier) in the ACL for the folder (LISTRIGHTS).
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
* @param string $user User name
|
||||
*
|
||||
* @return array List of user rights
|
||||
*/
|
||||
abstract function list_rights($folder, $user);
|
||||
|
||||
/**
|
||||
* Returns the set of rights that the current user has to a folder (MYRIGHTS).
|
||||
*
|
||||
* @param string $folder Folder name
|
||||
*
|
||||
* @return array MYRIGHTS response on success, NULL on error
|
||||
*/
|
||||
abstract function my_rights($folder);
|
||||
|
||||
/**
|
||||
* Sets metadata/annotations (SETMETADATA/SETANNOTATION)
|
||||
*
|
||||
* @param string $folder Folder name (empty for server metadata)
|
||||
* @param array $entries Entry-value array (use NULL value as NIL)
|
||||
*
|
||||
* @return boolean True on success, False on failure
|
||||
*/
|
||||
abstract function set_metadata($folder, $entries);
|
||||
|
||||
/**
|
||||
* Unsets metadata/annotations (SETMETADATA/SETANNOTATION)
|
||||
*
|
||||
* @param string $folder Folder name (empty for server metadata)
|
||||
* @param array $entries Entry names array
|
||||
*
|
||||
* @return boolean True on success, False on failure
|
||||
*/
|
||||
abstract function delete_metadata($folder, $entries);
|
||||
|
||||
/**
|
||||
* Returns folder metadata/annotations (GETMETADATA/GETANNOTATION).
|
||||
*
|
||||
* @param string $folder Folder name (empty for server metadata)
|
||||
* @param array $entries Entries
|
||||
* @param array $options Command options (with MAXSIZE and DEPTH keys)
|
||||
* @param bool $force Disables cache use
|
||||
*
|
||||
* @return array Metadata entry-value hash array on success, NULL on error
|
||||
*/
|
||||
abstract function get_metadata($folder, $entries, $options = array(), $force = false);
|
||||
|
||||
/* -----------------------------------------
|
||||
* Cache related functions
|
||||
* ----------------------------------------*/
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*
|
||||
* @param string $key Cache key name or pattern
|
||||
* @param boolean $prefix_mode Enable it to clear all keys starting
|
||||
* with prefix specified in $key
|
||||
*/
|
||||
abstract function clear_cache($key = null, $prefix_mode = false);
|
||||
|
||||
/**
|
||||
* Returns cached value
|
||||
*
|
||||
* @param string $key Cache key
|
||||
*
|
||||
* @return mixed Cached value
|
||||
*/
|
||||
abstract function get_cache($key);
|
||||
|
||||
/**
|
||||
* Delete outdated cache entries
|
||||
*/
|
||||
abstract function cache_gc();
|
||||
}
|
||||
257
data/web/rc/program/lib/Roundcube/rcube_string_replacer.php
Normal file
257
data/web/rc/program/lib/Roundcube/rcube_string_replacer.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2009-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: |
|
||||
| Handle string replacements based on preg_replace_callback |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper class for string replacements based on preg_replace_callback
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_string_replacer
|
||||
{
|
||||
public static $pattern = '/##str_replacement_(\d+)##/';
|
||||
public $mailto_pattern;
|
||||
public $link_pattern;
|
||||
public $linkref_index;
|
||||
public $linkref_pattern;
|
||||
|
||||
protected $values = array();
|
||||
protected $options = array();
|
||||
protected $linkrefs = array();
|
||||
protected $urls = array();
|
||||
protected $noword = '[^\w@.#-]';
|
||||
|
||||
|
||||
function __construct($options = array())
|
||||
{
|
||||
// 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%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*\x80-\xFE-';
|
||||
|
||||
// Supported link prefixes
|
||||
$link_prefix = "([\w]+:\/\/|{$this->noword}[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)";
|
||||
|
||||
$this->options = $options;
|
||||
$this->linkref_index = '/\[([^\]#]+)\](:?\s*##str_replacement_(\d+)##)/';
|
||||
$this->linkref_pattern = '/\[([^\]#]+)\]/';
|
||||
$this->link_pattern = "/$link_prefix($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];
|
||||
}
|
||||
else if (preg_match("/^({$this->noword}*)(www\.)$/i", $matches[1], $m)) {
|
||||
$url = $m[2] . $matches[2];
|
||||
$url_prefix = 'http://';
|
||||
$prefix = $m[1];
|
||||
}
|
||||
|
||||
if ($url) {
|
||||
$suffix = $this->parse_url_brackets($url);
|
||||
$attrib = (array)$this->options['link_attribs'];
|
||||
$attrib['href'] = $url_prefix . $url;
|
||||
|
||||
$i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix);
|
||||
$this->urls[$i] = $attrib['href'];
|
||||
}
|
||||
|
||||
// Return valid link for recognized schemes, otherwise
|
||||
// return the unmodified string for unrecognized schemes.
|
||||
return $i >= 0 ? $prefix . $this->get_replacement($i) : $matches[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to add an entry to the link index
|
||||
*/
|
||||
public function linkref_addindex($matches)
|
||||
{
|
||||
$key = $matches[1];
|
||||
$this->linkrefs[$key] = $this->urls[$matches[3]];
|
||||
|
||||
return $this->get_replacement($this->add('['.$key.']')) . $matches[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to replace link references with real links
|
||||
*/
|
||||
public function linkref_callback($matches)
|
||||
{
|
||||
$i = 0;
|
||||
if ($url = $this->linkrefs[$matches[1]]) {
|
||||
$attrib = (array)$this->options['link_attribs'];
|
||||
$attrib['href'] = $url;
|
||||
$i = $this->add(html::a($attrib, rcube::Q($matches[1])));
|
||||
}
|
||||
|
||||
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);
|
||||
// resolve link references
|
||||
$str = preg_replace_callback($this->linkref_index, array($this, 'linkref_addindex'), $str);
|
||||
$str = preg_replace_callback($this->linkref_pattern, array($this, 'linkref_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;
|
||||
}
|
||||
}
|
||||
317
data/web/rc/program/lib/Roundcube/rcube_text2html.php
Normal file
317
data/web/rc/program/lib/Roundcube/rcube_text2html.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2008-2014, 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: |
|
||||
| Converts plain text to HTML |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts plain text to HTML
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcube_text2html
|
||||
{
|
||||
/**
|
||||
* Contains the HTML content after conversion.
|
||||
*
|
||||
* @var string $html
|
||||
*/
|
||||
protected $html;
|
||||
|
||||
/**
|
||||
* Contains the plain text.
|
||||
*
|
||||
* @var string $text
|
||||
*/
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
*
|
||||
* @var array $config
|
||||
*/
|
||||
protected $config = array(
|
||||
// non-breaking space
|
||||
'space' => "\xC2\xA0",
|
||||
// enables format=flowed parser
|
||||
'flowed' => false,
|
||||
// enables wrapping for non-flowed text
|
||||
'wrap' => true,
|
||||
// line-break tag
|
||||
'break' => "<br>\n",
|
||||
// prefix and suffix (wrapper element)
|
||||
'begin' => '<div class="pre">',
|
||||
'end' => '</div>',
|
||||
// enables links replacement
|
||||
'links' => true,
|
||||
// string replacer class
|
||||
'replacer' => 'rcube_string_replacer',
|
||||
// prefix and suffix of unwrappable line
|
||||
'nobr_start' => '<span style="white-space:nowrap">',
|
||||
'nobr_end' => '</span>',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* If the plain text source string (or file) is supplied, the class
|
||||
* will instantiate with that source propagated, all that has
|
||||
* to be done it to call get_html().
|
||||
*
|
||||
* @param string $source Plain text
|
||||
* @param boolean $from_file Indicates $source is a file to pull content from
|
||||
* @param array $config Class configuration
|
||||
*/
|
||||
function __construct($source = '', $from_file = false, $config = array())
|
||||
{
|
||||
if (!empty($source)) {
|
||||
$this->set_text($source, $from_file);
|
||||
}
|
||||
|
||||
if (!empty($config) && is_array($config)) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads source text into memory, either from $source string or a file.
|
||||
*
|
||||
* @param string $source Plain text
|
||||
* @param boolean $from_file Indicates $source is a file to pull content from
|
||||
*/
|
||||
function set_text($source, $from_file = false)
|
||||
{
|
||||
if ($from_file && file_exists($source)) {
|
||||
$this->text = file_get_contents($source);
|
||||
}
|
||||
else {
|
||||
$this->text = $source;
|
||||
}
|
||||
|
||||
$this->_converted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML content.
|
||||
*
|
||||
* @return string HTML content
|
||||
*/
|
||||
function get_html()
|
||||
{
|
||||
if (!$this->_converted) {
|
||||
$this->_convert();
|
||||
}
|
||||
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the HTML.
|
||||
*/
|
||||
function print_html()
|
||||
{
|
||||
print $this->get_html();
|
||||
}
|
||||
|
||||
/**
|
||||
* Workhorse function that does actual conversion (calls _converter() method).
|
||||
*/
|
||||
protected function _convert()
|
||||
{
|
||||
// Convert TXT to HTML
|
||||
$this->html = $this->_converter($this->text);
|
||||
$this->_converted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workhorse function that does actual conversion.
|
||||
*
|
||||
* @param string Plain text
|
||||
*/
|
||||
protected function _converter($text)
|
||||
{
|
||||
// make links and email-addresses clickable
|
||||
$attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
|
||||
$replacer = new $this->config['replacer']($attribs);
|
||||
|
||||
if ($this->config['flowed']) {
|
||||
$flowed_char = 0x01;
|
||||
$text = rcube_mime::unfold_flowed($text, chr($flowed_char));
|
||||
}
|
||||
|
||||
// search for patterns like links and e-mail addresses and replace with tokens
|
||||
if ($this->config['links']) {
|
||||
$text = $replacer->replace($text);
|
||||
}
|
||||
|
||||
// split body into single lines
|
||||
$text = preg_split('/\r?\n/', $text);
|
||||
$quote_level = 0;
|
||||
$last = null;
|
||||
|
||||
// wrap quoted lines with <blockquote>
|
||||
for ($n = 0, $cnt = count($text); $n < $cnt; $n++) {
|
||||
$flowed = false;
|
||||
if ($this->config['flowed'] && ord($text[$n][0]) == $flowed_char) {
|
||||
$flowed = true;
|
||||
$text[$n] = substr($text[$n], 1);
|
||||
}
|
||||
|
||||
if ($text[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $text[$n], $regs)) {
|
||||
$q = substr_count($regs[0], '>');
|
||||
$text[$n] = substr($text[$n], strlen($regs[0]));
|
||||
$text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
|
||||
$_length = strlen(str_replace(' ', '', $text[$n]));
|
||||
|
||||
if ($q > $quote_level) {
|
||||
if ($last !== null) {
|
||||
$text[$last] .= (!$length ? "\n" : '')
|
||||
. $replacer->get_replacement($replacer->add(
|
||||
str_repeat('<blockquote>', $q - $quote_level)))
|
||||
. $text[$n];
|
||||
|
||||
unset($text[$n]);
|
||||
}
|
||||
else {
|
||||
$text[$n] = $replacer->get_replacement($replacer->add(
|
||||
str_repeat('<blockquote>', $q - $quote_level))) . $text[$n];
|
||||
|
||||
$last = $n;
|
||||
}
|
||||
}
|
||||
else if ($q < $quote_level) {
|
||||
$text[$last] .= (!$length ? "\n" : '')
|
||||
. $replacer->get_replacement($replacer->add(
|
||||
str_repeat('</blockquote>', $quote_level - $q)))
|
||||
. $text[$n];
|
||||
|
||||
unset($text[$n]);
|
||||
}
|
||||
else {
|
||||
$last = $n;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
|
||||
$q = 0;
|
||||
$_length = strlen(str_replace(' ', '', $text[$n]));
|
||||
|
||||
if ($quote_level > 0) {
|
||||
$text[$last] .= (!$length ? "\n" : '')
|
||||
. $replacer->get_replacement($replacer->add(
|
||||
str_repeat('</blockquote>', $quote_level)))
|
||||
. $text[$n];
|
||||
|
||||
unset($text[$n]);
|
||||
}
|
||||
else {
|
||||
$last = $n;
|
||||
}
|
||||
}
|
||||
|
||||
$quote_level = $q;
|
||||
$length = $_length;
|
||||
}
|
||||
|
||||
if ($quote_level > 0) {
|
||||
$text[$last] .= $replacer->get_replacement($replacer->add(
|
||||
str_repeat('</blockquote>', $quote_level)));
|
||||
}
|
||||
|
||||
$text = join("\n", $text);
|
||||
|
||||
// colorize signature (up to <sig_max_lines> lines)
|
||||
$len = strlen($text);
|
||||
$sig_sep = "--" . $this->config['space'] . "\n";
|
||||
$sig_max_lines = rcube::get_instance()->config->get('sig_max_lines', 15);
|
||||
|
||||
while (($sp = strrpos($text, $sig_sep, $sp ? -$len+$sp-1 : 0)) !== false) {
|
||||
if ($sp == 0 || $text[$sp-1] == "\n") {
|
||||
// do not touch blocks with more that X lines
|
||||
if (substr_count($text, "\n", $sp) < $sig_max_lines) {
|
||||
$text = substr($text, 0, max(0, $sp))
|
||||
.'<span class="sig">'.substr($text, $sp).'</span>';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// insert url/mailto links and citation tags
|
||||
$text = $replacer->resolve($text);
|
||||
|
||||
// replace line breaks
|
||||
$text = str_replace("\n", $this->config['break'], $text);
|
||||
|
||||
return $this->config['begin'] . $text . $this->config['end'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts spaces in line of text
|
||||
*/
|
||||
protected function _convert_line($text, $is_flowed)
|
||||
{
|
||||
static $table;
|
||||
|
||||
if (empty($table)) {
|
||||
$table = get_html_translation_table(HTML_SPECIALCHARS);
|
||||
unset($table['?']);
|
||||
|
||||
// replace some whitespace characters
|
||||
$table["\r"] = '';
|
||||
$table["\t"] = ' ';
|
||||
}
|
||||
|
||||
// skip signature separator
|
||||
if ($text == '-- ') {
|
||||
return '--' . $this->config['space'];
|
||||
}
|
||||
|
||||
// replace HTML special and whitespace characters
|
||||
$text = strtr($text, $table);
|
||||
|
||||
$nbsp = $this->config['space'];
|
||||
|
||||
// replace spaces with non-breaking spaces
|
||||
if ($is_flowed) {
|
||||
$pos = 0;
|
||||
$diff = 0;
|
||||
$len = strlen($nbsp);
|
||||
$copy = $text;
|
||||
|
||||
while (($pos = strpos($text, ' ', $pos)) !== false) {
|
||||
if ($pos == 0 || $text[$pos-1] == ' ') {
|
||||
$copy = substr_replace($copy, $nbsp, $pos + $diff, 1);
|
||||
$diff += $len - 1;
|
||||
}
|
||||
$pos++;
|
||||
}
|
||||
|
||||
$text = $copy;
|
||||
}
|
||||
// make the whole line non-breakable if needed
|
||||
else if ($text !== '' && preg_match('/[^a-zA-Z0-9_]/', $text)) {
|
||||
// use non-breakable spaces to correctly display
|
||||
// trailing/leading spaces and multi-space inside
|
||||
$text = str_replace(' ', $nbsp, $text);
|
||||
// wrap in nobr element, so it's not wrapped on e.g. - or /
|
||||
$text = $this->config['nobr_start'] . $text . $this->config['nobr_end'];
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
341
data/web/rc/program/lib/Roundcube/rcube_tnef_decoder.php
Normal file
341
data/web/rc/program/lib/Roundcube/rcube_tnef_decoder.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2008-2014, The Roundcube Dev Team |
|
||||
| Copyright (C) 2002-2010, The Horde Project (http://www.horde.org/) |
|
||||
| |
|
||||
| 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: |
|
||||
| MS-TNEF format decoder |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Jan Schneider <jan@horde.org> |
|
||||
| Author: Michael Slusarz <slusarz@horde.org> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* MS-TNEF format decoder based on code by:
|
||||
* Graham Norbury <gnorbury@bondcar.com>
|
||||
* Original design by:
|
||||
* Thomas Boll <tb@boll.ch>, Mark Simpson <damned@world.std.com>
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Storage
|
||||
*/
|
||||
class rcube_tnef_decoder
|
||||
{
|
||||
const SIGNATURE = 0x223e9f78;
|
||||
const LVL_MESSAGE = 0x01;
|
||||
const LVL_ATTACHMENT = 0x02;
|
||||
|
||||
const ASUBJECT = 0x88004;
|
||||
const AMCLASS = 0x78008;
|
||||
const ATTACHDATA = 0x6800f;
|
||||
const AFILENAME = 0x18010;
|
||||
const ARENDDATA = 0x69002;
|
||||
const AMAPIATTRS = 0x69005;
|
||||
const AVERSION = 0x89006;
|
||||
|
||||
const MAPI_NULL = 0x0001;
|
||||
const MAPI_SHORT = 0x0002;
|
||||
const MAPI_INT = 0x0003;
|
||||
const MAPI_FLOAT = 0x0004;
|
||||
const MAPI_DOUBLE = 0x0005;
|
||||
const MAPI_CURRENCY = 0x0006;
|
||||
const MAPI_APPTIME = 0x0007;
|
||||
const MAPI_ERROR = 0x000a;
|
||||
const MAPI_BOOLEAN = 0x000b;
|
||||
const MAPI_OBJECT = 0x000d;
|
||||
const MAPI_INT8BYTE = 0x0014;
|
||||
const MAPI_STRING = 0x001e;
|
||||
const MAPI_UNICODE_STRING = 0x001f;
|
||||
const MAPI_SYSTIME = 0x0040;
|
||||
const MAPI_CLSID = 0x0048;
|
||||
const MAPI_BINARY = 0x0102;
|
||||
|
||||
const MAPI_ATTACH_LONG_FILENAME = 0x3707;
|
||||
const MAPI_ATTACH_MIME_TAG = 0x370E;
|
||||
|
||||
const MAPI_NAMED_TYPE_ID = 0x0000;
|
||||
const MAPI_NAMED_TYPE_STRING = 0x0001;
|
||||
const MAPI_MV_FLAG = 0x1000;
|
||||
|
||||
/**
|
||||
* Decompress the data.
|
||||
*
|
||||
* @param string $data The data to decompress.
|
||||
* @param array $params An array of arguments needed to decompress the
|
||||
* data.
|
||||
*
|
||||
* @return mixed The decompressed data.
|
||||
*/
|
||||
public function decompress($data, $params = array())
|
||||
{
|
||||
$out = array();
|
||||
|
||||
if ($this->_geti($data, 32) == self::SIGNATURE) {
|
||||
$this->_geti($data, 16);
|
||||
|
||||
while (strlen($data) > 0) {
|
||||
switch ($this->_geti($data, 8)) {
|
||||
case self::LVL_MESSAGE:
|
||||
$this->_decodeMessage($data);
|
||||
break;
|
||||
|
||||
case self::LVL_ATTACHMENT:
|
||||
$this->_decodeAttachment($data, $out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_reverse($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param string &$data The data string.
|
||||
* @param integer $bits How many bits to retrieve.
|
||||
*
|
||||
* @return TODO
|
||||
*/
|
||||
protected function _getx(&$data, $bits)
|
||||
{
|
||||
$value = null;
|
||||
|
||||
if (strlen($data) >= $bits) {
|
||||
$value = substr($data, 0, $bits);
|
||||
$data = substr_replace($data, '', 0, $bits);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param string &$data The data string.
|
||||
* @param integer $bits How many bits to retrieve.
|
||||
*
|
||||
* @return TODO
|
||||
*/
|
||||
protected function _geti(&$data, $bits)
|
||||
{
|
||||
$bytes = $bits / 8;
|
||||
$value = null;
|
||||
|
||||
if (strlen($data) >= $bytes) {
|
||||
$value = ord($data[0]);
|
||||
if ($bytes >= 2) {
|
||||
$value += (ord($data[1]) << 8);
|
||||
}
|
||||
if ($bytes >= 4) {
|
||||
$value += (ord($data[2]) << 16) + (ord($data[3]) << 24);
|
||||
}
|
||||
$data = substr_replace($data, '', 0, $bytes);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param string &$data The data string.
|
||||
* @param string $attribute TODO
|
||||
*/
|
||||
protected function _decodeAttribute(&$data, $attribute)
|
||||
{
|
||||
/* Data. */
|
||||
$this->_getx($data, $this->_geti($data, 32));
|
||||
|
||||
/* Checksum. */
|
||||
$this->_geti($data, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param string $data The data string.
|
||||
* @param array &$attachment_data TODO
|
||||
*/
|
||||
protected function _extractMapiAttributes($data, &$attachment_data)
|
||||
{
|
||||
/* Number of attributes. */
|
||||
$number = $this->_geti($data, 32);
|
||||
|
||||
while ((strlen($data) > 0) && $number--) {
|
||||
$have_mval = false;
|
||||
$num_mval = 1;
|
||||
$named_id = $value = null;
|
||||
$attr_type = $this->_geti($data, 16);
|
||||
$attr_name = $this->_geti($data, 16);
|
||||
|
||||
if (($attr_type & self::MAPI_MV_FLAG) != 0) {
|
||||
$have_mval = true;
|
||||
$attr_type = $attr_type & ~self::MAPI_MV_FLAG;
|
||||
}
|
||||
|
||||
if (($attr_name >= 0x8000) && ($attr_name < 0xFFFE)) {
|
||||
$this->_getx($data, 16);
|
||||
$named_type = $this->_geti($data, 32);
|
||||
|
||||
switch ($named_type) {
|
||||
case self::MAPI_NAMED_TYPE_ID:
|
||||
$named_id = $this->_geti($data, 32);
|
||||
$attr_name = $named_id;
|
||||
break;
|
||||
|
||||
case self::MAPI_NAMED_TYPE_STRING:
|
||||
$attr_name = 0x9999;
|
||||
$idlen = $this->_geti($data, 32);
|
||||
$datalen = $idlen + ((4 - ($idlen % 4)) % 4);
|
||||
$named_id = substr($this->_getx($data, $datalen), 0, $idlen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($have_mval) {
|
||||
$num_mval = $this->_geti($data, 32);
|
||||
}
|
||||
|
||||
switch ($attr_type) {
|
||||
case self::MAPI_SHORT:
|
||||
$value = $this->_geti($data, 16);
|
||||
break;
|
||||
|
||||
case self::MAPI_INT:
|
||||
case self::MAPI_BOOLEAN:
|
||||
for ($i = 0; $i < $num_mval; $i++) {
|
||||
$value = $this->_geti($data, 32);
|
||||
}
|
||||
break;
|
||||
|
||||
case self::MAPI_FLOAT:
|
||||
case self::MAPI_ERROR:
|
||||
$value = $this->_getx($data, 4);
|
||||
break;
|
||||
|
||||
case self::MAPI_DOUBLE:
|
||||
case self::MAPI_APPTIME:
|
||||
case self::MAPI_CURRENCY:
|
||||
case self::MAPI_INT8BYTE:
|
||||
case self::MAPI_SYSTIME:
|
||||
$value = $this->_getx($data, 8);
|
||||
break;
|
||||
|
||||
case self::MAPI_STRING:
|
||||
case self::MAPI_UNICODE_STRING:
|
||||
case self::MAPI_BINARY:
|
||||
case self::MAPI_OBJECT:
|
||||
$num_vals = $have_mval ? $num_mval : $this->_geti($data, 32);
|
||||
for ($i = 0; $i < $num_vals; $i++) {
|
||||
$length = $this->_geti($data, 32);
|
||||
|
||||
/* Pad to next 4 byte boundary. */
|
||||
$datalen = $length + ((4 - ($length % 4)) % 4);
|
||||
|
||||
if ($attr_type == self::MAPI_STRING) {
|
||||
--$length;
|
||||
}
|
||||
|
||||
/* Read and truncate to length. */
|
||||
$value = substr($this->_getx($data, $datalen), 0, $length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Store any interesting attributes. */
|
||||
switch ($attr_name) {
|
||||
case self::MAPI_ATTACH_LONG_FILENAME:
|
||||
$value = str_replace("\0", '', $value);
|
||||
/* Used in preference to AFILENAME value. */
|
||||
$attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value);
|
||||
break;
|
||||
|
||||
case self::MAPI_ATTACH_MIME_TAG:
|
||||
$value = str_replace("\0", '', $value);
|
||||
/* Is this ever set, and what is format? */
|
||||
$attachment_data[0]['type'] = preg_replace('/^(.*)\/.*/', '\1', $value);
|
||||
$attachment_data[0]['subtype'] = preg_replace('/.*\/(.*)$/', '\1', $value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param string &$data The data string.
|
||||
*/
|
||||
protected function _decodeMessage(&$data)
|
||||
{
|
||||
$this->_decodeAttribute($data, $this->_geti($data, 32));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param string &$data The data string.
|
||||
* @param array &$attachment_data TODO
|
||||
*/
|
||||
protected function _decodeAttachment(&$data, &$attachment_data)
|
||||
{
|
||||
$attribute = $this->_geti($data, 32);
|
||||
|
||||
switch ($attribute) {
|
||||
case self::ARENDDATA:
|
||||
/* Marks start of new attachment. */
|
||||
$this->_getx($data, $this->_geti($data, 32));
|
||||
|
||||
/* Checksum */
|
||||
$this->_geti($data, 16);
|
||||
|
||||
/* Add a new default data block to hold details of this
|
||||
attachment. Reverse order is easier to handle later! */
|
||||
array_unshift($attachment_data, array('type' => 'application',
|
||||
'subtype' => 'octet-stream',
|
||||
'name' => 'unknown',
|
||||
'stream' => ''));
|
||||
break;
|
||||
|
||||
case self::AFILENAME:
|
||||
$value = $this->_getx($data, $this->_geti($data, 32));
|
||||
$value = str_replace("\0", '', $value);
|
||||
/* Strip path. */
|
||||
$attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value);
|
||||
|
||||
/* Checksum */
|
||||
$this->_geti($data, 16);
|
||||
break;
|
||||
|
||||
case self::ATTACHDATA:
|
||||
/* The attachment itself. */
|
||||
$length = $this->_geti($data, 32);
|
||||
$attachment_data[0]['size'] = $length;
|
||||
$attachment_data[0]['stream'] = $this->_getx($data, $length);
|
||||
|
||||
/* Checksum */
|
||||
$this->_geti($data, 16);
|
||||
break;
|
||||
|
||||
case self::AMAPIATTRS:
|
||||
$length = $this->_geti($data, 32);
|
||||
$value = $this->_getx($data, $length);
|
||||
|
||||
/* Checksum */
|
||||
$this->_geti($data, 16);
|
||||
$this->_extractMapiAttributes($value, $attachment_data);
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->_decodeAttribute($data, $attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
842
data/web/rc/program/lib/Roundcube/rcube_user.php
Normal file
842
data/web/rc/program/lib/Roundcube/rcube_user.php
Normal file
@@ -0,0 +1,842 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2012, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| This class represents a system user linked and provides access |
|
||||
| to the related database records. |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a system user
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Core
|
||||
*/
|
||||
class rcube_user
|
||||
{
|
||||
public $ID;
|
||||
public $data;
|
||||
public $language;
|
||||
public $prefs;
|
||||
|
||||
/**
|
||||
* Holds database connection.
|
||||
*
|
||||
* @var rcube_db
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Framework object.
|
||||
*
|
||||
* @var rcube
|
||||
*/
|
||||
private $rc;
|
||||
|
||||
/**
|
||||
* Internal identities cache
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $identities = array();
|
||||
|
||||
/**
|
||||
* Internal emails cache
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $emails;
|
||||
|
||||
|
||||
const SEARCH_ADDRESSBOOK = 1;
|
||||
const SEARCH_MAIL = 2;
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param int $id User id
|
||||
* @param array $sql_arr SQL result set
|
||||
*/
|
||||
function __construct($id = null, $sql_arr = null)
|
||||
{
|
||||
$this->rc = rcube::get_instance();
|
||||
$this->db = $this->rc->get_dbh();
|
||||
|
||||
if ($id && !$sql_arr) {
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT * FROM " . $this->db->table_name('users', true)
|
||||
. " WHERE `user_id` = ?", $id);
|
||||
$sql_arr = $this->db->fetch_assoc($sql_result);
|
||||
}
|
||||
|
||||
if (!empty($sql_arr)) {
|
||||
$this->ID = $sql_arr['user_id'];
|
||||
$this->data = $sql_arr;
|
||||
$this->language = $sql_arr['language'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a user name string (as e-mail address)
|
||||
*
|
||||
* @param string $part Username part (empty or 'local' or 'domain', 'mail')
|
||||
* @return string Full user name or its part
|
||||
*/
|
||||
function get_username($part = null)
|
||||
{
|
||||
if ($this->data['username']) {
|
||||
// return real name
|
||||
if (!$part) {
|
||||
return $this->data['username'];
|
||||
}
|
||||
|
||||
list($local, $domain) = explode('@', $this->data['username']);
|
||||
|
||||
// at least we should always have the local part
|
||||
if ($part == 'local') {
|
||||
return $local;
|
||||
}
|
||||
// if no domain was provided...
|
||||
if (empty($domain)) {
|
||||
$domain = $this->rc->config->mail_domain($this->data['mail_host']);
|
||||
}
|
||||
|
||||
if ($part == 'domain') {
|
||||
return $domain;
|
||||
}
|
||||
|
||||
if (!empty($domain))
|
||||
return $local . '@' . $domain;
|
||||
else
|
||||
return $local;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preferences saved for this user
|
||||
*
|
||||
* @return array Hash array with prefs
|
||||
*/
|
||||
function get_prefs()
|
||||
{
|
||||
if (isset($this->prefs)) {
|
||||
return $this->prefs;
|
||||
}
|
||||
|
||||
$this->prefs = array();
|
||||
|
||||
if (!empty($this->language))
|
||||
$this->prefs['language'] = $this->language;
|
||||
|
||||
if ($this->ID) {
|
||||
// Preferences from session (write-master is unavailable)
|
||||
if (!empty($_SESSION['preferences'])) {
|
||||
// Check last write attempt time, try to write again (every 5 minutes)
|
||||
if ($_SESSION['preferences_time'] < time() - 5 * 60) {
|
||||
$saved_prefs = unserialize($_SESSION['preferences']);
|
||||
$this->rc->session->remove('preferences');
|
||||
$this->rc->session->remove('preferences_time');
|
||||
$this->save_prefs($saved_prefs);
|
||||
}
|
||||
else {
|
||||
$this->data['preferences'] = $_SESSION['preferences'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->data['preferences']) {
|
||||
$this->prefs += (array)unserialize($this->data['preferences']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->prefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given user prefs to the user's record
|
||||
*
|
||||
* @param array $a_user_prefs User prefs to save
|
||||
* @param bool $no_session Simplified language/preferences handling
|
||||
*
|
||||
* @return boolean True on success, False on failure
|
||||
*/
|
||||
function save_prefs($a_user_prefs, $no_session = false)
|
||||
{
|
||||
if (!$this->ID)
|
||||
return false;
|
||||
|
||||
$plugin = $this->rc->plugins->exec_hook('preferences_update', array(
|
||||
'userid' => $this->ID, 'prefs' => $a_user_prefs, 'old' => (array)$this->get_prefs()));
|
||||
|
||||
if (!empty($plugin['abort'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$a_user_prefs = $plugin['prefs'];
|
||||
$old_prefs = $plugin['old'];
|
||||
$config = $this->rc->config;
|
||||
|
||||
// merge (partial) prefs array with existing settings
|
||||
$this->prefs = $save_prefs = $a_user_prefs + $old_prefs;
|
||||
unset($save_prefs['language']);
|
||||
|
||||
// don't save prefs with default values if they haven't been changed yet
|
||||
foreach ($a_user_prefs as $key => $value) {
|
||||
if ($value === null || (!isset($old_prefs[$key]) && ($value == $config->get($key)))) {
|
||||
unset($save_prefs[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$save_prefs = serialize($save_prefs);
|
||||
if (!$no_session) {
|
||||
$this->language = $_SESSION['language'];
|
||||
}
|
||||
|
||||
$this->db->query(
|
||||
"UPDATE ".$this->db->table_name('users', true).
|
||||
" SET `preferences` = ?, `language` = ?".
|
||||
" WHERE `user_id` = ?",
|
||||
$save_prefs,
|
||||
$this->language,
|
||||
$this->ID);
|
||||
|
||||
// Update success
|
||||
if ($this->db->affected_rows() !== false) {
|
||||
$this->data['preferences'] = $save_prefs;
|
||||
|
||||
if (!$no_session) {
|
||||
$config->set_user_prefs($this->prefs);
|
||||
|
||||
if (isset($_SESSION['preferences'])) {
|
||||
$this->rc->session->remove('preferences');
|
||||
$this->rc->session->remove('preferences_time');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// Update error, but we are using replication (we have read-only DB connection)
|
||||
// and we are storing session not in the SQL database
|
||||
// we can store preferences in session and try to write later (see get_prefs())
|
||||
else if (!$no_session && $this->db->is_replicated()
|
||||
&& $config->get('session_storage', 'db') != 'db'
|
||||
) {
|
||||
$_SESSION['preferences'] = $save_prefs;
|
||||
$_SESSION['preferences_time'] = time();
|
||||
$config->set_user_prefs($this->prefs);
|
||||
$this->data['preferences'] = $save_prefs;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique hash to identify this user whith
|
||||
*/
|
||||
function get_hash()
|
||||
{
|
||||
$prefs = $this->get_prefs();
|
||||
|
||||
// generate a random hash and store it in user prefs
|
||||
if (empty($prefs['client_hash'])) {
|
||||
$prefs['client_hash'] = rcube_utils::random_bytes(16);
|
||||
$this->save_prefs(array('client_hash' => $prefs['client_hash']));
|
||||
}
|
||||
|
||||
return $prefs['client_hash'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all user emails (from identities)
|
||||
*
|
||||
* @param bool Return only default identity
|
||||
*
|
||||
* @return array List of emails (identity_id, name, email)
|
||||
*/
|
||||
function list_emails($default = false)
|
||||
{
|
||||
if ($this->emails === null) {
|
||||
$this->emails = array();
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT `identity_id`, `name`, `email`"
|
||||
." FROM " . $this->db->table_name('identities', true)
|
||||
." WHERE `user_id` = ? AND `del` <> 1"
|
||||
." ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
|
||||
$this->ID);
|
||||
|
||||
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
$this->emails[] = $sql_arr;
|
||||
}
|
||||
}
|
||||
|
||||
return $default ? $this->emails[0] : $this->emails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default identity of this user
|
||||
*
|
||||
* @param int $id Identity ID. If empty, the default identity is returned
|
||||
* @return array Hash array with all cols of the identity record
|
||||
*/
|
||||
function get_identity($id = null)
|
||||
{
|
||||
$id = (int)$id;
|
||||
// cache identities for better performance
|
||||
if (!array_key_exists($id, $this->identities)) {
|
||||
$result = $this->list_identities($id ? "AND `identity_id` = $id" : '');
|
||||
$this->identities[$id] = $result[0];
|
||||
}
|
||||
|
||||
return $this->identities[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all identities linked with this user
|
||||
*
|
||||
* @param string $sql_add Optional WHERE clauses
|
||||
* @param bool $formatted Format identity email and name
|
||||
*
|
||||
* @return array List of identities
|
||||
*/
|
||||
function list_identities($sql_add = '', $formatted = false)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT * FROM ".$this->db->table_name('identities', true).
|
||||
" WHERE `del` <> 1 AND `user_id` = ?".
|
||||
($sql_add ? " ".$sql_add : "").
|
||||
" ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
|
||||
$this->ID);
|
||||
|
||||
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
if ($formatted) {
|
||||
$ascii_email = format_email($sql_arr['email']);
|
||||
$utf8_email = format_email(rcube_utils::idn_to_utf8($ascii_email));
|
||||
|
||||
$sql_arr['email_ascii'] = $ascii_email;
|
||||
$sql_arr['email'] = $utf8_email;
|
||||
$sql_arr['ident'] = format_email_recipient($ascii_email, $sql_arr['name']);
|
||||
}
|
||||
|
||||
$result[] = $sql_arr;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific identity record
|
||||
*
|
||||
* @param int $iid Identity ID
|
||||
* @param array $data Hash array with col->value pairs to save
|
||||
* @return boolean True if saved successfully, false if nothing changed
|
||||
*/
|
||||
function update_identity($iid, $data)
|
||||
{
|
||||
if (!$this->ID)
|
||||
return false;
|
||||
|
||||
$query_cols = $query_params = array();
|
||||
|
||||
foreach ((array)$data as $col => $value) {
|
||||
$query_cols[] = $this->db->quote_identifier($col) . ' = ?';
|
||||
$query_params[] = $value;
|
||||
}
|
||||
$query_params[] = $iid;
|
||||
$query_params[] = $this->ID;
|
||||
|
||||
$sql = "UPDATE ".$this->db->table_name('identities', true).
|
||||
" SET `changed` = ".$this->db->now().", ".join(', ', $query_cols).
|
||||
" WHERE `identity_id` = ?".
|
||||
" AND `user_id` = ?".
|
||||
" AND `del` <> 1";
|
||||
|
||||
call_user_func_array(array($this->db, 'query'),
|
||||
array_merge(array($sql), $query_params));
|
||||
|
||||
// clear the cache
|
||||
$this->identities = array();
|
||||
$this->emails = null;
|
||||
|
||||
return $this->db->affected_rows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new identity record linked with this user
|
||||
*
|
||||
* @param array $data Hash array with col->value pairs to save
|
||||
* @return int The inserted identity ID or false on error
|
||||
*/
|
||||
function insert_identity($data)
|
||||
{
|
||||
if (!$this->ID)
|
||||
return false;
|
||||
|
||||
unset($data['user_id']);
|
||||
|
||||
$insert_cols = $insert_values = array();
|
||||
foreach ((array)$data as $col => $value) {
|
||||
$insert_cols[] = $this->db->quote_identifier($col);
|
||||
$insert_values[] = $value;
|
||||
}
|
||||
$insert_cols[] = $this->db->quote_identifier('user_id');
|
||||
$insert_values[] = $this->ID;
|
||||
|
||||
$sql = "INSERT INTO ".$this->db->table_name('identities', true).
|
||||
" (`changed`, ".join(', ', $insert_cols).")".
|
||||
" VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
|
||||
|
||||
call_user_func_array(array($this->db, 'query'),
|
||||
array_merge(array($sql), $insert_values));
|
||||
|
||||
// clear the cache
|
||||
$this->identities = array();
|
||||
$this->emails = null;
|
||||
|
||||
return $this->db->insert_id('identities');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given identity as deleted
|
||||
*
|
||||
* @param int $iid Identity ID
|
||||
* @return boolean True if deleted successfully, false if nothing changed
|
||||
*/
|
||||
function delete_identity($iid)
|
||||
{
|
||||
if (!$this->ID)
|
||||
return false;
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities', true).
|
||||
" WHERE `user_id` = ? AND `del` <> 1",
|
||||
$this->ID);
|
||||
|
||||
$sql_arr = $this->db->fetch_assoc($sql_result);
|
||||
|
||||
// we'll not delete last identity
|
||||
if ($sql_arr['ident_count'] <= 1)
|
||||
return -1;
|
||||
|
||||
$this->db->query(
|
||||
"UPDATE ".$this->db->table_name('identities', true).
|
||||
" SET `del` = 1, `changed` = ".$this->db->now().
|
||||
" WHERE `user_id` = ?".
|
||||
" AND `identity_id` = ?",
|
||||
$this->ID,
|
||||
$iid);
|
||||
|
||||
// clear the cache
|
||||
$this->identities = array();
|
||||
$this->emails = null;
|
||||
|
||||
return $this->db->affected_rows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this identity the default one for this user
|
||||
*
|
||||
* @param int $iid The identity ID
|
||||
*/
|
||||
function set_default($iid)
|
||||
{
|
||||
if ($this->ID && $iid) {
|
||||
$this->db->query(
|
||||
"UPDATE ".$this->db->table_name('identities', true).
|
||||
" SET `standard` = '0'".
|
||||
" WHERE `user_id` = ? AND `identity_id` <> ?",
|
||||
$this->ID,
|
||||
$iid);
|
||||
|
||||
unset($this->identities[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's last_login timestamp
|
||||
*/
|
||||
function touch()
|
||||
{
|
||||
if ($this->ID) {
|
||||
$this->db->query(
|
||||
"UPDATE ".$this->db->table_name('users', true).
|
||||
" SET `last_login` = ".$this->db->now().
|
||||
" WHERE `user_id` = ?",
|
||||
$this->ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's failed_login timestamp and counter
|
||||
*/
|
||||
function failed_login()
|
||||
{
|
||||
if ($this->ID && ($rate = (int) $this->rc->config->get('login_rate_limit', 3))) {
|
||||
if (empty($this->data['failed_login'])) {
|
||||
$failed_login = new DateTime('now');
|
||||
$counter = 1;
|
||||
}
|
||||
else {
|
||||
$failed_login = new DateTime($this->data['failed_login']);
|
||||
$threshold = new DateTime('- 60 seconds');
|
||||
|
||||
if ($failed_login < $threshold) {
|
||||
$failed_login = new DateTime('now');
|
||||
$counter = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->query(
|
||||
"UPDATE " . $this->db->table_name('users', true)
|
||||
. " SET `failed_login` = " . $this->db->fromunixtime($failed_login->format('U'))
|
||||
. ", `failed_login_counter` = " . ($counter ?: "`failed_login_counter` + 1")
|
||||
. " WHERE `user_id` = ?",
|
||||
$this->ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the account is locked, e.g. as a result of brute-force prevention
|
||||
*/
|
||||
function is_locked()
|
||||
{
|
||||
if (empty($this->data['failed_login'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($rate = (int) $this->rc->config->get('login_rate_limit', 3)) {
|
||||
$last_failed = new DateTime($this->data['failed_login']);
|
||||
$threshold = new DateTime('- 60 seconds');
|
||||
|
||||
if ($last_failed > $threshold && $this->data['failed_login_counter'] >= $rate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the saved object state
|
||||
*/
|
||||
function reset()
|
||||
{
|
||||
$this->ID = null;
|
||||
$this->data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user record matching the given name and host
|
||||
*
|
||||
* @param string $user IMAP user name
|
||||
* @param string $host IMAP host name
|
||||
* @return rcube_user New user instance
|
||||
*/
|
||||
static function query($user, $host)
|
||||
{
|
||||
$dbh = rcube::get_instance()->get_dbh();
|
||||
$config = rcube::get_instance()->config;
|
||||
|
||||
// query for matching user name
|
||||
$sql_result = $dbh->query("SELECT * FROM " . $dbh->table_name('users', true)
|
||||
." WHERE `mail_host` = ? AND `username` = ?", $host, $user);
|
||||
|
||||
$sql_arr = $dbh->fetch_assoc($sql_result);
|
||||
|
||||
// username not found, try aliases from identities
|
||||
if (empty($sql_arr) && $config->get('user_aliases') && strpos($user, '@')) {
|
||||
$sql_result = $dbh->limitquery("SELECT u.*"
|
||||
." FROM " . $dbh->table_name('users', true) . " u"
|
||||
." JOIN " . $dbh->table_name('identities', true) . " i ON (i.`user_id` = u.`user_id`)"
|
||||
." WHERE `email` = ? AND `del` <> 1", 0, 1, $user);
|
||||
|
||||
$sql_arr = $dbh->fetch_assoc($sql_result);
|
||||
}
|
||||
|
||||
// user already registered -> overwrite username
|
||||
if ($sql_arr) {
|
||||
return new rcube_user($sql_arr['user_id'], $sql_arr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user record and return a rcube_user instance
|
||||
*
|
||||
* @param string $user IMAP user name
|
||||
* @param string $host IMAP host
|
||||
* @return rcube_user New user instance
|
||||
*/
|
||||
static function create($user, $host)
|
||||
{
|
||||
$user_name = '';
|
||||
$user_email = '';
|
||||
$rcube = rcube::get_instance();
|
||||
$dbh = $rcube->get_dbh();
|
||||
|
||||
// try to resolve user in virtuser table and file
|
||||
if ($email_list = self::user2email($user, false, true)) {
|
||||
$user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
|
||||
}
|
||||
|
||||
$data = $rcube->plugins->exec_hook('user_create', array(
|
||||
'host' => $host,
|
||||
'user' => $user,
|
||||
'user_name' => $user_name,
|
||||
'user_email' => $user_email,
|
||||
'email_list' => $email_list,
|
||||
'language' => $_SESSION['language'],
|
||||
));
|
||||
|
||||
// plugin aborted this operation
|
||||
if ($data['abort']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dbh->query(
|
||||
"INSERT INTO ".$dbh->table_name('users', true).
|
||||
" (`created`, `last_login`, `username`, `mail_host`, `language`)".
|
||||
" VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
|
||||
$data['user'],
|
||||
$data['host'],
|
||||
$data['language']);
|
||||
|
||||
if ($user_id = $dbh->insert_id('users')) {
|
||||
// create rcube_user instance to make plugin hooks work
|
||||
$user_instance = new rcube_user($user_id, array(
|
||||
'user_id' => $user_id,
|
||||
'username' => $data['user'],
|
||||
'mail_host' => $data['host'],
|
||||
'language' => $data['language'],
|
||||
));
|
||||
$rcube->user = $user_instance;
|
||||
$mail_domain = $rcube->config->mail_domain($data['host']);
|
||||
$user_name = $data['user_name'];
|
||||
$user_email = $data['user_email'];
|
||||
$email_list = $data['email_list'];
|
||||
|
||||
if (empty($email_list)) {
|
||||
if (empty($user_email)) {
|
||||
$user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
|
||||
}
|
||||
$email_list[] = $user_email;
|
||||
}
|
||||
// identities_level check
|
||||
else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
|
||||
$email_list = array($email_list[0]);
|
||||
}
|
||||
|
||||
if (empty($user_name)) {
|
||||
$user_name = $data['user'];
|
||||
}
|
||||
|
||||
// create new identities records
|
||||
$standard = 1;
|
||||
foreach ($email_list as $row) {
|
||||
$record = array();
|
||||
|
||||
if (is_array($row)) {
|
||||
if (empty($row['email'])) {
|
||||
continue;
|
||||
}
|
||||
$record = $row;
|
||||
}
|
||||
else {
|
||||
$record['email'] = $row;
|
||||
}
|
||||
|
||||
if (empty($record['name'])) {
|
||||
$record['name'] = $user_name != $record['email'] ? $user_name : '';
|
||||
}
|
||||
|
||||
$record['user_id'] = $user_id;
|
||||
$record['standard'] = $standard;
|
||||
|
||||
$plugin = $rcube->plugins->exec_hook('identity_create',
|
||||
array('login' => true, 'record' => $record));
|
||||
|
||||
if (!$plugin['abort'] && $plugin['record']['email']) {
|
||||
$rcube->user->insert_identity($plugin['record']);
|
||||
}
|
||||
$standard = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
rcube::raise_error(array(
|
||||
'code' => 500,
|
||||
'type' => 'php',
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Failed to create new user"), true, false);
|
||||
}
|
||||
|
||||
return $user_id ? $user_instance : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve username using a virtuser plugins
|
||||
*
|
||||
* @param string $email E-mail address to resolve
|
||||
* @return string Resolved IMAP username
|
||||
*/
|
||||
static function email2user($email)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$plugin = $rcube->plugins->exec_hook('email2user',
|
||||
array('email' => $email, 'user' => NULL));
|
||||
|
||||
return $plugin['user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve e-mail address from virtuser plugins
|
||||
*
|
||||
* @param string $user User name
|
||||
* @param boolean $first If true returns first found entry
|
||||
* @param boolean $extended If true returns email as array (email and name for identity)
|
||||
* @return mixed Resolved e-mail address string or array of strings
|
||||
*/
|
||||
static function user2email($user, $first=true, $extended=false)
|
||||
{
|
||||
$rcube = rcube::get_instance();
|
||||
$plugin = $rcube->plugins->exec_hook('user2email',
|
||||
array('email' => NULL, 'user' => $user,
|
||||
'first' => $first, 'extended' => $extended));
|
||||
|
||||
return empty($plugin['email']) ? NULL : $plugin['email'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of saved searches linked with this user
|
||||
*
|
||||
* @param int $type Search type
|
||||
*
|
||||
* @return array List of saved searches indexed by search ID
|
||||
*/
|
||||
function list_searches($type)
|
||||
{
|
||||
$plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
|
||||
|
||||
if ($plugin['abort']) {
|
||||
return (array) $plugin['result'];
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT `search_id` AS id, `name`"
|
||||
." FROM ".$this->db->table_name('searches', true)
|
||||
." WHERE `user_id` = ? AND `type` = ?"
|
||||
." ORDER BY `name`",
|
||||
(int) $this->ID, (int) $type);
|
||||
|
||||
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
$sql_arr['data'] = unserialize($sql_arr['data']);
|
||||
$result[$sql_arr['id']] = $sql_arr;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return saved search data.
|
||||
*
|
||||
* @param int $id Row identifier
|
||||
*
|
||||
* @return array Data
|
||||
*/
|
||||
function get_search($id)
|
||||
{
|
||||
$plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
|
||||
|
||||
if ($plugin['abort']) {
|
||||
return $plugin['result'];
|
||||
}
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT `name`, `data`, `type`"
|
||||
. " FROM ".$this->db->table_name('searches', true)
|
||||
. " WHERE `user_id` = ?"
|
||||
." AND `search_id` = ?",
|
||||
(int) $this->ID, (int) $id);
|
||||
|
||||
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
return array(
|
||||
'id' => $id,
|
||||
'name' => $sql_arr['name'],
|
||||
'type' => $sql_arr['type'],
|
||||
'data' => unserialize($sql_arr['data']),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes given saved search record
|
||||
*
|
||||
* @param int $sid Search ID
|
||||
*
|
||||
* @return boolean True if deleted successfully, false if nothing changed
|
||||
*/
|
||||
function delete_search($sid)
|
||||
{
|
||||
if (!$this->ID)
|
||||
return false;
|
||||
|
||||
$this->db->query(
|
||||
"DELETE FROM ".$this->db->table_name('searches', true)
|
||||
." WHERE `user_id` = ?"
|
||||
." AND `search_id` = ?",
|
||||
(int) $this->ID, $sid);
|
||||
|
||||
return $this->db->affected_rows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new saved search record linked with this user
|
||||
*
|
||||
* @param array $data Hash array with col->value pairs to save
|
||||
*
|
||||
* @return int The inserted search ID or false on error
|
||||
*/
|
||||
function insert_search($data)
|
||||
{
|
||||
if (!$this->ID)
|
||||
return false;
|
||||
|
||||
$insert_cols[] = 'user_id';
|
||||
$insert_values[] = (int) $this->ID;
|
||||
$insert_cols[] = $this->db->quote_identifier('type');
|
||||
$insert_values[] = (int) $data['type'];
|
||||
$insert_cols[] = $this->db->quote_identifier('name');
|
||||
$insert_values[] = $data['name'];
|
||||
$insert_cols[] = $this->db->quote_identifier('data');
|
||||
$insert_values[] = serialize($data['data']);
|
||||
|
||||
$sql = "INSERT INTO ".$this->db->table_name('searches', true)
|
||||
." (".join(', ', $insert_cols).")"
|
||||
." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
|
||||
|
||||
call_user_func_array(array($this->db, 'query'),
|
||||
array_merge(array($sql), $insert_values));
|
||||
|
||||
return $this->db->insert_id('searches');
|
||||
}
|
||||
}
|
||||
1295
data/web/rc/program/lib/Roundcube/rcube_utils.php
Normal file
1295
data/web/rc/program/lib/Roundcube/rcube_utils.php
Normal file
File diff suppressed because it is too large
Load Diff
904
data/web/rc/program/lib/Roundcube/rcube_vcard.php
Normal file
904
data/web/rc/program/lib/Roundcube/rcube_vcard.php
Normal file
@@ -0,0 +1,904 @@
|
||||
<?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: |
|
||||
| Logical representation of a vcard address record |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Logical representation of a vcard-based address record
|
||||
* Provides functions to parse and export vCard data format
|
||||
*
|
||||
* @package Framework
|
||||
* @subpackage Addressbook
|
||||
*/
|
||||
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',
|
||||
'groups' => 'CATEGORIES',
|
||||
);
|
||||
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($fieldmap)) {
|
||||
$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(self::cleanup($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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// fix broken vcards from Outlook that only supply ORG but not the required N or FN properties
|
||||
if (!strlen(trim($this->displayname . $this->surname . $this->firstname)) && strlen($this->organization)) {
|
||||
$this->displayname = $this->organization;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]] ?: 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] ?: $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) {
|
||||
$this->raw[$fn][] = array(0 => $val->format('Y-m-d'), '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] ?: $type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
unset($this->raw[$tag]);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$vcard_block = '';
|
||||
$in_vcard_block = false;
|
||||
|
||||
foreach (preg_split("/[\r\n]+/", $data) as $line) {
|
||||
if ($in_vcard_block && !empty($line)) {
|
||||
$vcard_block .= $line . "\n";
|
||||
}
|
||||
|
||||
$line = trim($line);
|
||||
|
||||
if (preg_match('/^END:VCARD$/i', $line)) {
|
||||
// parse vcard
|
||||
$obj = new rcube_vcard($vcard_block, $charset, true, self::$fieldmap);
|
||||
// FN and N is required by vCard format (RFC 2426)
|
||||
// on import we can be less restrictive, let's addressbook decide
|
||||
if (!empty($obj->displayname) || !empty($obj->surname) || !empty($obj->firstname) || !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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize vcard data for better parsing
|
||||
*
|
||||
* @param string vCard block
|
||||
*
|
||||
* @return string Cleaned vcard block
|
||||
*/
|
||||
public static function cleanup($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);
|
||||
|
||||
// Cleanup
|
||||
$vcard = preg_replace(array(
|
||||
// convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
|
||||
'/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./si',
|
||||
'/^item\d*\.X-AB.*$/mi', // remove cruft like item1.X-AB*
|
||||
'/^item\d*\./mi', // remove item1.ADR instead of ADR
|
||||
'/\n+/', // remove empty lines
|
||||
'/^(N:[^;\R]*)$/m', // if N doesn't have any semicolons, add some
|
||||
),
|
||||
array(
|
||||
'\2;type=\5\3:\4',
|
||||
'',
|
||||
'',
|
||||
"\n",
|
||||
'\1;;;;',
|
||||
), $vcard);
|
||||
|
||||
// convert X-WAB-GENDER to X-GENDER
|
||||
if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
|
||||
$value = $matches[1] == '2' ? 'male' : 'female';
|
||||
$vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $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++;
|
||||
}
|
||||
|
||||
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);
|
||||
$result = array();
|
||||
|
||||
for ($i=0; $i < count($lines); $i++) {
|
||||
if (!($pos = strpos($lines[$i], ':'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefix = substr($lines[$i], 0, $pos);
|
||||
$data = substr($lines[$i], $pos+1);
|
||||
|
||||
if (preg_match('/^(BEGIN|END)$/i', $prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
|
||||
if ($result['VERSION'][0] == "2.1"
|
||||
&& preg_match('/^([^;]+);([^:]+)/', $prefix, $regs2)
|
||||
&& !preg_match('/^TYPE=/i', $regs2[2])
|
||||
) {
|
||||
$prefix = $regs2[1];
|
||||
foreach (explode(';', $regs2[2]) as $prop) {
|
||||
$prefix .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all('/([^\\;]+);?/', $prefix, $regs2)) {
|
||||
$entry = array();
|
||||
$field = strtoupper($regs2[1][0]);
|
||||
$enc = null;
|
||||
|
||||
foreach($regs2[1] as $attrid => $attr) {
|
||||
$attr = preg_replace('/[\s\t\n\r\0\x0B]/', '', $attr);
|
||||
if ((list($key, $value) = explode('=', $attr)) && $value) {
|
||||
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])) {
|
||||
$data .= "\n" . $lines[++$i];
|
||||
}
|
||||
}
|
||||
$enc = $value == 'BASE64' ? 'B' : $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;
|
||||
}
|
||||
|
||||
$data = self::decode_value($data, $enc ?: 'base64');
|
||||
}
|
||||
else if ($field == 'PHOTO') {
|
||||
// vCard 4.0 data URI, "PHOTO:data:image/jpeg;base64,..."
|
||||
if (preg_match('/^data:[a-z\/_-]+;base64,/i', $data, $m)) {
|
||||
$entry['encoding'] = $enc = 'B';
|
||||
$data = substr($data, strlen($m[0]));
|
||||
$data = self::decode_value($data, 'base64');
|
||||
}
|
||||
}
|
||||
|
||||
if ($enc != 'B' && empty($entry['base64'])) {
|
||||
$data = self::vcard_unquote($data);
|
||||
}
|
||||
|
||||
$entry = array_merge($entry, (array) $data);
|
||||
$result[$field][] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
unset($result['VERSION']);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
else if (is_bool($attrvalues)) {
|
||||
// true means just a tag, not tag=value, as in PHOTO;BASE64:...
|
||||
if ($attrvalues) {
|
||||
// vCard v3 uses ENCODING=b (#1489183)
|
||||
if ($attrname == 'base64') {
|
||||
$attr .= ";ENCODING=b";
|
||||
}
|
||||
else {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
public 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', $sep => '\\'.$sep));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$s = trim(strtr($s, $rep2));
|
||||
}
|
||||
|
||||
// some implementations (GMail) use non-standard backslash before colon (#1489085)
|
||||
// we will handle properly any backslashed character - removing dummy backslahes
|
||||
// return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
|
||||
|
||||
$s = str_replace("\r", '', $s);
|
||||
$pos = 0;
|
||||
|
||||
while (($pos = strpos($s, '\\', $pos)) !== false) {
|
||||
$next = substr($s, $pos + 1, 1);
|
||||
if ($next == 'n' || $next == 'N') {
|
||||
$s = substr_replace($s, "\n", $pos, 2);
|
||||
}
|
||||
else {
|
||||
$s = substr_replace($s, '', $pos, 1);
|
||||
}
|
||||
|
||||
$pos += 1;
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
796
data/web/rc/program/lib/Roundcube/rcube_washtml.php
Normal file
796
data/web/rc/program/lib/Roundcube/rcube_washtml.php
Normal file
@@ -0,0 +1,796 @@
|
||||
<?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@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
| Author: Frederic Motte <fmotte@ubixis.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/*
|
||||
* Washtml, a HTML sanityzer.
|
||||
*
|
||||
* Copyright (c) 2007 Frederic Motte <fmotte@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
|
||||
* - SVG and MathML support
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'video', 'source',
|
||||
// form elements
|
||||
'button', 'input', 'textarea', 'select', 'option', 'optgroup',
|
||||
// SVG
|
||||
'svg', 'altglyph', 'altglyphdef', 'altglyphitem', 'animate',
|
||||
'animatecolor', 'animatetransform', 'circle', 'clippath', 'defs', 'desc',
|
||||
'ellipse', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line',
|
||||
'lineargradient', 'marker', 'mask', 'mpath', 'path', 'pattern',
|
||||
'polygon', 'polyline', 'radialgradient', 'rect', 'set', 'stop', 'switch', 'symbol',
|
||||
'text', 'textpath', 'tref', 'tspan', 'use', 'view', 'vkern', 'filter',
|
||||
// SVG Filters
|
||||
'feblend', 'fecolormatrix', 'fecomponenttransfer', 'fecomposite',
|
||||
'feconvolvematrix', 'fediffuselighting', 'fedisplacementmap',
|
||||
'feflood', 'fefunca', 'fefuncb', 'fefuncg', 'fefuncr', 'fegaussianblur',
|
||||
'feimage', 'femerge', 'femergenode', 'femorphology', 'feoffset',
|
||||
'fespecularlighting', 'fetile', 'feturbulence',
|
||||
// MathML
|
||||
'math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr',
|
||||
'mmuliscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow',
|
||||
'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd',
|
||||
'mtext', 'mtr', 'munder', 'munderover', 'maligngroup', 'malignmark',
|
||||
'mprescripts', 'semantics', 'annotation', 'annotation-xml', 'none',
|
||||
'infinity', 'matrix', 'matrixrow', 'ci', 'cn', 'sep', 'apply',
|
||||
'plus', 'minus', 'eq', 'power', 'times', 'divide', 'csymbol', 'root',
|
||||
'bvar', 'lowlimit', 'uplimit',
|
||||
);
|
||||
|
||||
/* 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',
|
||||
'background', 'src', 'poster', 'href',
|
||||
// attributes of form elements
|
||||
'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value',
|
||||
// SVG
|
||||
'accent-height', 'accumulate', 'additive', 'alignment-baseline', 'alphabetic',
|
||||
'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseprofile',
|
||||
'baseline-shift', 'begin', 'bias', 'by', 'clip', 'clip-path', 'clip-rule',
|
||||
'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile',
|
||||
'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction',
|
||||
'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity',
|
||||
'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size',
|
||||
'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'from',
|
||||
'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform',
|
||||
'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints',
|
||||
'keysplines', 'keytimes', 'lengthadjust', 'letter-spacing', 'kernelmatrix',
|
||||
'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid',
|
||||
'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits',
|
||||
'maskunits', 'max', 'mask', 'mode', 'min', 'numoctaves', 'offset', 'operator',
|
||||
'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order',
|
||||
'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits',
|
||||
'points', 'preservealpha', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount',
|
||||
'repeatdur', 'restart', 'rotate', 'scale', 'seed', 'shape-rendering', 'show', 'specularconstant',
|
||||
'specularexponent', 'spreadmethod', 'stddeviation', 'stitchtiles', 'stop-color',
|
||||
'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap',
|
||||
'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width',
|
||||
'surfacescale', 'targetx', 'targety', 'transform', 'text-anchor', 'text-decoration',
|
||||
'text-rendering', 'textlength', 'to', 'u1', 'u2', 'unicode', 'values', 'viewbox',
|
||||
'visibility', 'vert-adv-y', 'version', 'vert-origin-x', 'vert-origin-y', 'word-spacing',
|
||||
'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2',
|
||||
'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan',
|
||||
// MathML
|
||||
'accent', 'accentunder', 'bevelled', 'close', 'columnalign', 'columnlines',
|
||||
'columnspan', 'denomalign', 'depth', 'display', 'displaystyle', 'encoding', 'fence',
|
||||
'frame', 'largeop', 'length', 'linethickness', 'lspace', 'lquote',
|
||||
'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize',
|
||||
'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign',
|
||||
'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel',
|
||||
'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator',
|
||||
'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset',
|
||||
'fontsize', 'fontweight', 'fontstyle', 'fontfamily', 'groupalign', 'edge', 'side',
|
||||
);
|
||||
|
||||
/* Elements which could be empty and be returned in short form (<tag />) */
|
||||
static $void_elements = array('area', 'base', 'br', 'col', 'command', 'embed', 'hr',
|
||||
'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr',
|
||||
// MathML
|
||||
'sep', 'infinity', 'in', 'plus', 'eq', 'power', 'times', 'divide', 'root',
|
||||
'maligngroup', 'none', 'mprescripts',
|
||||
);
|
||||
|
||||
/* 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();
|
||||
|
||||
/* Elements which could be empty and be returned in short form (<tag />) */
|
||||
private $_void_elements = array();
|
||||
|
||||
/* Allowed HTML attributes */
|
||||
private $_html_attribs = array();
|
||||
|
||||
/* Max nesting level */
|
||||
private $max_nesting_level;
|
||||
|
||||
private $is_xml = false;
|
||||
|
||||
|
||||
/**
|
||||
* 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->_void_elements = array_flip((array)$p['void_elements']) + array_flip(self::$void_elements);
|
||||
|
||||
unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['void_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)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
// Remove unwanted white-space characters so regular expressions below work better
|
||||
$style = preg_replace('/[\n\r\s\t]+/', ' ', $style);
|
||||
|
||||
foreach (explode(';', $style) as $declaration) {
|
||||
if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
|
||||
$cssid = $match[1];
|
||||
$str = $match[2];
|
||||
$value = '';
|
||||
|
||||
foreach ($this->explode_style($str) as $val) {
|
||||
if (preg_match('/^url\(/i', $val)) {
|
||||
if (preg_match('/^url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
|
||||
if ($url = $this->wash_uri($match[1])) {
|
||||
$value .= ' url(' . htmlspecialchars($url, ENT_QUOTES) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!preg_match('/^(behavior|expression)/i', $val)) {
|
||||
// Set position:fixed to position:absolute for security (#5264)
|
||||
if (!strcasecmp($cssid, 'position') && !strcasecmp($val, 'fixed')) {
|
||||
$val = 'absolute';
|
||||
}
|
||||
|
||||
// whitelist ?
|
||||
$value .= ' ' . $val;
|
||||
|
||||
// #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]+$/', $val)
|
||||
) {
|
||||
$value .= 'px';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($value[0])) {
|
||||
$result[] = $cssid . ':' . $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode('; ', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a node and return allowed attributes and check values
|
||||
*/
|
||||
private function wash_attribs($node)
|
||||
{
|
||||
$result = '';
|
||||
$washed = array();
|
||||
|
||||
foreach ($node->attributes as $name => $attr) {
|
||||
$key = strtolower($name);
|
||||
$value = $attr->nodeValue;
|
||||
|
||||
if ($key == 'style' && ($style = $this->wash_style($value))) {
|
||||
// replace double quotes to prevent syntax error and XSS issues (#1490227)
|
||||
$result .= ' style="' . str_replace('"', '"', $style) . '"';
|
||||
}
|
||||
else if (isset($this->_html_attribs[$key])) {
|
||||
$value = trim($value);
|
||||
$out = null;
|
||||
|
||||
// in SVG to/from attribs may contain anything, including URIs
|
||||
if ($key == 'to' || $key == 'from') {
|
||||
$key = strtolower($node->getAttribute('attributeName'));
|
||||
if ($key && !isset($this->_html_attribs[$key])) {
|
||||
$key = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->is_image_attribute($node->tagName, $key)) {
|
||||
$out = $this->wash_uri($value, true);
|
||||
}
|
||||
else if ($this->is_link_attribute($node->tagName, $key)) {
|
||||
if (!preg_match('!^(javascript|vbscript|data:text)!i', $value)
|
||||
&& preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value)
|
||||
) {
|
||||
$out = $value;
|
||||
}
|
||||
}
|
||||
else if ($this->is_funciri_attribute($node->tagName, $key)) {
|
||||
if (preg_match('/^[a-z:]*url\(/i', $val)) {
|
||||
if (preg_match('/^([a-z:]*url)\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $value, $match)) {
|
||||
if ($url = $this->wash_uri($match[2])) {
|
||||
$result .= ' ' . $attr->nodeName . '="' . $match[1] . '(' . htmlspecialchars($url, ENT_QUOTES) . ')'
|
||||
. substr($val, strlen($match[0])) . '"';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$out = $value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$out = $value;
|
||||
}
|
||||
}
|
||||
else if ($key) {
|
||||
$out = $value;
|
||||
}
|
||||
|
||||
if ($out !== null && $out !== '') {
|
||||
$result .= ' ' . $attr->nodeName . '="' . htmlspecialchars($out, ENT_QUOTES) . '"';
|
||||
}
|
||||
else if ($value) {
|
||||
$washed[] = htmlspecialchars($attr->nodeName, ENT_QUOTES);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$washed[] = htmlspecialchars($attr->nodeName, ENT_QUOTES);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($washed) && $this->config['show_washed']) {
|
||||
$result .= ' x-washed="' . implode(' ', $washed) . '"';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wash URI value
|
||||
*/
|
||||
private function wash_uri($uri, $blocked_source = false)
|
||||
{
|
||||
if (($src = $this->config['cid_map'][$uri])
|
||||
|| ($src = $this->config['cid_map'][$this->config['base_url'].$uri])
|
||||
) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
// allow url(#id) used in SVG
|
||||
if ($uri[0] == '#') {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
if (preg_match('/^(http|https|ftp):.+/i', $uri)) {
|
||||
if ($this->config['allow_remote']) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$this->extlinks = true;
|
||||
if ($blocked_source && $this->config['blocked_src']) {
|
||||
return $this->config['blocked_src'];
|
||||
}
|
||||
}
|
||||
else if (preg_match('/^data:image.+/i', $uri)) { // RFC2397
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check it the tag/attribute may contain an URI
|
||||
*/
|
||||
private function is_link_attribute($tag, $attr)
|
||||
{
|
||||
return ($tag == 'a' || $tag == 'area') && $attr == 'href';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check it the tag/attribute may contain an image URI
|
||||
*/
|
||||
private function is_image_attribute($tag, $attr)
|
||||
{
|
||||
return $attr == 'background'
|
||||
|| $attr == 'color-profile' // SVG
|
||||
|| ($attr == 'poster' && $tag == 'video')
|
||||
|| ($attr == 'src' && preg_match('/^(img|source)$/i', $tag))
|
||||
|| ($tag == 'image' && $attr == 'href'); // SVG
|
||||
}
|
||||
|
||||
/**
|
||||
* Check it the tag/attribute may contain a FUNCIRI value
|
||||
*/
|
||||
private function is_funciri_attribute($tag, $attr)
|
||||
{
|
||||
return in_array($attr, array('fill', 'filter', 'stroke', 'marker-start',
|
||||
'marker-end', 'marker-mid', 'clip-path', 'mask', 'cursor'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The main loop that recurse on a node tree.
|
||||
* It output only allowed tags with allowed attributes and allowed inline styles
|
||||
*
|
||||
* @param DOMNode $node HTML element
|
||||
* @param int $level Recurrence level (safe initial value found empirically)
|
||||
*/
|
||||
private function dumpHtml($node, $level = 20)
|
||||
{
|
||||
if (!$node->hasChildNodes()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$level++;
|
||||
|
||||
if ($this->max_nesting_level > 0 && $level == $this->max_nesting_level - 1) {
|
||||
// log error message once
|
||||
if (!$this->max_nesting_level_error) {
|
||||
$this->max_nesting_level_error = true;
|
||||
rcube::raise_error(array('code' => 500, 'type' => 'php',
|
||||
'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Maximum nesting level exceeded (xdebug.max_nesting_level={$this->max_nesting_level})"),
|
||||
true, false);
|
||||
}
|
||||
return '<!-- ignored -->';
|
||||
}
|
||||
|
||||
$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, $level), $this);
|
||||
}
|
||||
else if (isset($this->_html_elements[$tagName])) {
|
||||
$content = $this->dumpHtml($node, $level);
|
||||
$dump .= '<' . $node->tagName;
|
||||
|
||||
if ($tagName == 'svg') {
|
||||
$xpath = new DOMXPath($node->ownerDocument);
|
||||
foreach ($xpath->query('namespace::*') as $ns) {
|
||||
if ($ns->nodeName != 'xmlns:xml') {
|
||||
$dump .= ' ' . $ns->nodeName . '="' . $ns->nodeValue . '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dump .= $this->wash_attribs($node);
|
||||
|
||||
if ($content === '' && ($this->is_xml || isset($this->_void_elements[$tagName]))) {
|
||||
$dump .= ' />';
|
||||
}
|
||||
else {
|
||||
$dump .= '>' . $content . '</' . $node->tagName . '>';
|
||||
}
|
||||
}
|
||||
else if (isset($this->_ignore_elements[$tagName])) {
|
||||
$dump .= '<!-- ' . htmlspecialchars($node->tagName, ENT_QUOTES) . ' not allowed -->';
|
||||
}
|
||||
else {
|
||||
$dump .= '<!-- ' . htmlspecialchars($node->tagName, ENT_QUOTES) . ' ignored -->';
|
||||
$dump .= $this->dumpHtml($node, $level); // 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, $level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
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'] = '';
|
||||
}
|
||||
|
||||
// Detect max nesting level (for dumpHTML) (#1489110)
|
||||
$this->max_nesting_level = (int) @ini_get('xdebug.max_nesting_level');
|
||||
|
||||
// SVG need to be parsed as XML
|
||||
$this->is_xml = stripos($html, '<html') === false && stripos($html, '<svg') !== false;
|
||||
$method = $this->is_xml ? 'loadXML' : 'loadHTML';
|
||||
$options = 0;
|
||||
|
||||
// Use optimizations if supported
|
||||
if (PHP_VERSION_ID >= 50400) {
|
||||
$options = LIBXML_PARSEHUGE | LIBXML_COMPACT | LIBXML_NONET;
|
||||
@$node->{$method}($html, $options);
|
||||
}
|
||||
else {
|
||||
@$node->{$method}($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)
|
||||
{
|
||||
$html = trim($html);
|
||||
|
||||
// special replacements (not properly handled by washtml class)
|
||||
$html_search = array(
|
||||
// space(s) between <NOBR>
|
||||
'/(<\/nobr>)(\s+)(<nobr>)/i',
|
||||
// PHP bug #32547 workaround: remove title tag
|
||||
'/<title[^>]*>[^<]*<\/title>/i',
|
||||
// remove <!doctype> before BOM (#1490291)
|
||||
'/<\!doctype[^>]+>[^<]*/im',
|
||||
// byte-order mark (only outlook?)
|
||||
'/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',
|
||||
// washtml/DOMDocument cannot handle xml namespaces
|
||||
'/<html\s[^>]+>/i',
|
||||
);
|
||||
|
||||
$html_replace = array(
|
||||
'\\1'.' '.'\\3',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'<html>',
|
||||
);
|
||||
|
||||
$html = preg_replace($html_search, $html_replace, trim($html));
|
||||
|
||||
// Replace all of those weird MS Word quotes and other high characters
|
||||
$badwordchars = array(
|
||||
"\xe2\x80\x98", // left single quote
|
||||
"\xe2\x80\x99", // right single quote
|
||||
"\xe2\x80\x9c", // left double quote
|
||||
"\xe2\x80\x9d", // right double quote
|
||||
"\xe2\x80\x94", // em dash
|
||||
"\xe2\x80\xa6" // elipses
|
||||
);
|
||||
|
||||
$fixedwordchars = array(
|
||||
"'",
|
||||
"'",
|
||||
'"',
|
||||
'"',
|
||||
'—',
|
||||
'...'
|
||||
);
|
||||
|
||||
$html = str_replace($badwordchars, $fixedwordchars, $html);
|
||||
|
||||
// PCRE errors handling (#1486856), should we use something like for every preg_* use?
|
||||
if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
|
||||
$errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
|
||||
|
||||
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
|
||||
// Don't remove MSOutlook (<!-->) conditional comments (#1489004)
|
||||
$html = preg_replace('/<!--[^-<>\[\n]+>/', '', $html);
|
||||
|
||||
// fix broken nested lists
|
||||
self::fix_broken_lists($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);
|
||||
|
||||
// fix invalid closing tags - remove any attributes (#1489446)
|
||||
if ($matches[1] == '</') {
|
||||
$matches[3] = '';
|
||||
}
|
||||
|
||||
return $matches[1] . $tagname . $matches[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix broken nested lists, they are not handled properly by DOMDocument (#1488768)
|
||||
*/
|
||||
public static function fix_broken_lists(&$html)
|
||||
{
|
||||
// do two rounds, one for <ol>, one for <ul>
|
||||
foreach (array('ol', 'ul') as $tag) {
|
||||
$pos = 0;
|
||||
while (($pos = stripos($html, '<' . $tag, $pos)) !== false) {
|
||||
$pos++;
|
||||
|
||||
// make sure this is an ol/ul tag
|
||||
if (!in_array($html[$pos+2], array(' ', '>'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$p = $pos;
|
||||
$in_li = false;
|
||||
$li_pos = 0;
|
||||
|
||||
while (($p = strpos($html, '<', $p)) !== false) {
|
||||
$tt = strtolower(substr($html, $p, 4));
|
||||
|
||||
// li open tag
|
||||
if ($tt == '<li>' || $tt == '<li ') {
|
||||
$in_li = true;
|
||||
$p += 4;
|
||||
}
|
||||
// li close tag
|
||||
else if ($tt == '</li' && in_array($html[$p+4], array(' ', '>'))) {
|
||||
$li_pos = $p;
|
||||
$p += 4;
|
||||
$in_li = false;
|
||||
}
|
||||
// ul/ol closing tag
|
||||
else if ($tt == '</' . $tag && in_array($html[$p+4], array(' ', '>'))) {
|
||||
break;
|
||||
}
|
||||
// nested ol/ul element out of li
|
||||
else if (!$in_li && $li_pos && ($tt == '<ol>' || $tt == '<ol ' || $tt == '<ul>' || $tt == '<ul ')) {
|
||||
// find closing tag of this ul/ol element
|
||||
$element = substr($tt, 1, 2);
|
||||
$cpos = $p;
|
||||
do {
|
||||
$tpos = stripos($html, '<' . $element, $cpos+1);
|
||||
$cpos = stripos($html, '</' . $element, $cpos+1);
|
||||
}
|
||||
while ($tpos !== false && $cpos !== false && $cpos > $tpos);
|
||||
|
||||
// not found, this is invalid HTML, skip it
|
||||
if ($cpos === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
// get element content
|
||||
$end = strpos($html, '>', $cpos);
|
||||
$len = $end - $p + 1;
|
||||
$element = substr($html, $p, $len);
|
||||
|
||||
// move element to the end of the last li
|
||||
$html = substr_replace($html, '', $p, $len);
|
||||
$html = substr_replace($html, $element, $li_pos, 0);
|
||||
|
||||
$p = $end;
|
||||
}
|
||||
else {
|
||||
$p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explode css style value
|
||||
*/
|
||||
protected function explode_style($style)
|
||||
{
|
||||
$pos = 0;
|
||||
|
||||
// first remove comments
|
||||
while (($pos = strpos($style, '/*', $pos)) !== false) {
|
||||
$end = strpos($style, '*/', $pos+2);
|
||||
|
||||
if ($end === false) {
|
||||
$style = substr($style, 0, $pos);
|
||||
}
|
||||
else {
|
||||
$style = substr_replace($style, '', $pos, $end - $pos + 2);
|
||||
}
|
||||
}
|
||||
|
||||
$style = trim($style);
|
||||
$strlen = strlen($style);
|
||||
$result = array();
|
||||
|
||||
// explode value
|
||||
for ($p=$i=0; $i < $strlen; $i++) {
|
||||
if (($style[$i] == "\"" || $style[$i] == "'") && $style[$i-1] != "\\") {
|
||||
if ($q == $style[$i]) {
|
||||
$q = false;
|
||||
}
|
||||
else if (!$q) {
|
||||
$q = $style[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$q && $style[$i] == ' ' && !preg_match('/[,\(]/', $style[$i-1])) {
|
||||
$result[] = substr($style, $p, $i - $p);
|
||||
$p = $i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = (string) substr($style, $p);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user